-
-
Save neetsdkasu/9582f60ad0d95aad375519053a4ab461 to your computer and use it in GitHub Desktop.
記録
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module kiroku | |
go 1.17 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="ja"> | |
<head> | |
<meta charset="utf-8" /> | |
<title>kiroku</title> | |
<style> | |
body { padding: 1rem 5rem 10rem 5rem; } | |
div { | |
display: grid; | |
grid-template-columns: 2fr 5fr; | |
margin-bottom: 5px; | |
} | |
</style> | |
</head> | |
<body> | |
<h3>kiroku</h3> | |
<hr /> | |
<div><span>パス:</span><span>{{.Path}}</span></div> | |
<div><span>情報:</span><span>{{.Info}}</span></div> | |
<hr /> | |
<form method="post" action="."> | |
<input name="setvalue" id="setvalue" type="hidden" value="1" /> | |
<div><input type="reset" /></div> | |
<div><label for="date">日付:</label><input name="date" id="date" type="date" value="{{.Date}}" /></div> | |
<div><label for="weight">体重:</label><input name="weight" id="weight" type="number" step="0.1" placeholder="{{.Weight}}" /></div> | |
<div><label for="wakeup">起床時刻:</label><input name="wakeup" id="wakeup" type="number" step="0.01" placeholder="{{.Wakeup}}" /></div> | |
<div><label for="tobed">就寝時刻:</label><input name="tobed" id="tobed" type="number" step="0.01" placeholder="{{.Tobed}}" /></div> | |
<div><label for="sleep">睡眠時間:</label><input name="sleep" id="sleep" type="number" step="0.01" placeholder="{{.Sleep}}" /></div> | |
<div><label for="interrupt">睡眠中断回数:</label><input name="interrupt" id="interrupt" type="number" step="1" placeholder="{{.Interrupt}}" /></div> | |
<div><label for="earlytemp">起床時体温(報知):</label><input name="earlytemp" id="earlytemp" type="number" step="0.1" placeholder="{{.Earlytemp}}" /></div> | |
<div><label for="normaltemp">起床時体温(平衡):</label><input name="normaltemp" id="normaltemp" type="number" step="0.1" placeholder="{{.Normaltemp}}" /></div> | |
<div><label for="evening">夕方体温(平衡):</label><input name="evening" id="evening" type="number" step="0.1" placeholder="{{.Evening}}" /></div> | |
<div><input type="submit" /></div> | |
</form> | |
<ul> | |
<li>※0 空欄は更新なし</li> | |
<li>※1 時刻・時間の"分"は100/60にして小数(第3位切捨て)にする ( 13:38 -> 13.633333 -> 13.63 )</li> | |
</uL> | |
<hr /> | |
<h3>Show CSV</h3> | |
<form method="post" action="."> | |
<input name="showcsv" id="showcsv" type="hidden" value="1" /> | |
<div><label for="show">日付:</label><input name="show" id="show" type="date" value="{{.Show}}" /></div> | |
<div><label for="kind">種類:</label><select name="kind" id="kind"> | |
<option value="weight" {{if (eq .Kind "weight")}}selected{{end}}>体重</option> | |
<option value="wakeup" {{if (eq .Kind "wakeup")}}selected{{end}}>起床時刻</option> | |
<option value="tobed" {{if (eq .Kind "tobed")}}selected{{end}}>就寝時刻</option> | |
<option value="sleep" {{if (eq .Kind "sleep")}}selected{{end}}>睡眠時間</option> | |
<option value="interrupt" {{if (eq .Kind "interrupt")}}selected{{end}}>睡眠中断回数</option> | |
<option value="earlytemp" {{if (eq .Kind "earlytemp")}}selected{{end}}>起床時体温(報知)</option> | |
<option value="normaltemp" {{if (eq .Kind "normaltemp")}}selected{{end}}>起床時体温(平衡)</option> | |
<option value="evening" {{if (eq .Kind "evening")}}selected{{end}}>夕方体温(平衡)</option> | |
</select></div> | |
<div><input type="submit" value="表示" /></div> | |
</form> | |
<hr /> | |
<form method="post" action="."> | |
<input name="editcsv" id="editcsv" type="hidden" value="1" /> | |
<div><label for="file">ファイル名:</label><input name="file" id="file" type="text" readonly value="{{.File}}" /></div> | |
<textarea name="csv" id="csv" cols="60" rows="15">{{.Csv}}</textarea> | |
<div><input type="submit" value="更新"/><label><input name="update" id="update" type="checkbox">更新する場合チェック入れる</label></div> | |
</form> | |
<hr /> | |
<div><a href=".">リロード</a></div> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"bytes" | |
_ "embed" | |
"encoding/csv" | |
"flag" | |
"fmt" | |
"html/template" | |
"log" | |
"net/http" | |
"net/url" | |
"os" | |
"path/filepath" | |
"sort" | |
"strings" | |
"time" | |
) | |
const version = "v1.0.0" | |
func main() { | |
log.Println("kiroku", version) | |
var ip, port, dir string | |
flag.StringVar(&ip, "a", "127.0.0.1", "localhost IP address") | |
flag.StringVar(&port, "p", "8080", "port number") | |
flag.StringVar(&dir, "d", "", "CSV files directory path [REQUIRE]") | |
flag.Parse() | |
if dir == "" { | |
flag.Usage() | |
log.Fatal("REQUIRE -d flag") | |
return | |
} | |
if finfo, err := os.Stat(dir); err != nil { | |
log.Fatal(err) | |
return | |
} else if !finfo.IsDir() { | |
log.Fatal(dir + " is not found") | |
return | |
} | |
log.Println("DIRECTORY", dir) | |
addr := ip + ":" + port | |
log.Println("START", addr) | |
http.Handle("/", &myHandler{dir}) | |
log.Fatal(http.ListenAndServe(addr, nil)) | |
} | |
//go:embed index.html | |
var indexHtml string | |
var templateHtml = template.Must(template.New("index").Parse(indexHtml)) | |
type Data struct { | |
Path string | |
Info string | |
Date string | |
Weight string | |
Wakeup string | |
Tobed string | |
Sleep string | |
Interrupt string | |
Earlytemp string | |
Normaltemp string | |
Evening string | |
Show string | |
Kind string | |
File string | |
Csv string | |
} | |
const ( | |
dateFormat = "2006-01-02" | |
fileDateFormat = "200601" | |
) | |
type myHandler struct { | |
dir string | |
} | |
func (h *myHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { | |
if err := r.ParseForm(); err != nil { | |
http.Error(w, err.Error(), http.StatusBadRequest) | |
return | |
} | |
now := time.Now().Format(dateFormat) | |
var d Data | |
d.Path = h.dir | |
if !d.setValue(r.PostForm) { | |
d.Date = now | |
} | |
if !d.showCsv(r.PostForm) { | |
d.Show = now | |
d.Kind = "weight" | |
} | |
d.editCsv(r.PostForm) | |
templateHtml.Execute(w, &d) | |
} | |
func (d *Data) setValue(form url.Values) bool { | |
var b strings.Builder | |
if !form.Has("setvalue") { | |
return false | |
} | |
d.Date = form.Get("date") | |
d.Weight = form.Get("weight") | |
d.Wakeup = form.Get("wakeup") | |
d.Tobed = form.Get("tobed") | |
d.Sleep = form.Get("sleep") | |
d.Interrupt = form.Get("interrupt") | |
d.Earlytemp = form.Get("earlytemp") | |
d.Normaltemp = form.Get("normaltemp") | |
d.Evening = form.Get("evening") | |
t, err := time.Parse(dateFormat, d.Date) | |
if err != nil { | |
d.Info = "日付が設定されていません(SetValue) " + err.Error() | |
return false | |
} | |
suffix := t.Format(fileDateFormat) + ".csv" | |
fmt.Fprint(&b, "対象", d.Date, "。") | |
if d.Weight != "" { | |
fmt.Fprint(&b, "体重", d.Weight) | |
if err = d.addRow("weight"+suffix, "%+4.1f", d.Weight); err != nil { | |
fmt.Fprint(&b, err.Error()) | |
d.Info = b.String() | |
return true | |
} | |
fmt.Fprint(&b, "。") | |
} | |
if d.Wakeup != "" { | |
fmt.Fprint(&b, "起床時刻", d.Wakeup) | |
if err = d.addRow("wakeup"+suffix, "%+5.2f", d.Wakeup); err != nil { | |
fmt.Fprint(&b, err.Error()) | |
d.Info = b.String() | |
return true | |
} | |
fmt.Fprint(&b, "。") | |
} | |
if d.Tobed != "" { | |
fmt.Fprint(&b, "就寝時刻", d.Tobed) | |
if err = d.addRow("tobed"+suffix, "%+5.2f", d.Tobed); err != nil { | |
fmt.Fprint(&b, err.Error()) | |
d.Info = b.String() | |
return true | |
} | |
fmt.Fprint(&b, "。") | |
} | |
if d.Sleep != "" { | |
fmt.Fprint(&b, "睡眠時間", d.Sleep) | |
if err = d.addRow("sleep"+suffix, "%+5.2f", d.Sleep); err != nil { | |
fmt.Fprint(&b, err.Error()) | |
d.Info = b.String() | |
return true | |
} | |
fmt.Fprint(&b, "。") | |
} | |
if d.Interrupt != "" { | |
fmt.Fprint(&b, "睡眠中断回数", d.Interrupt) | |
if err = d.addRow("interrupt"+suffix, "%+g", d.Interrupt); err != nil { | |
fmt.Fprint(&b, err.Error()) | |
d.Info = b.String() | |
return true | |
} | |
fmt.Fprint(&b, "。") | |
} | |
if d.Earlytemp != "" { | |
fmt.Fprint(&b, "起床時体温(報知)", d.Earlytemp) | |
if err = d.addRow("earlytemp"+suffix, "%+4.1f", d.Earlytemp); err != nil { | |
fmt.Fprint(&b, err.Error()) | |
d.Info = b.String() | |
return true | |
} | |
fmt.Fprint(&b, "。") | |
} | |
if d.Normaltemp != "" { | |
fmt.Fprint(&b, "起床時体温(平衡)", d.Normaltemp) | |
if err = d.addRow("normaltemp"+suffix, "%+4.1f", d.Normaltemp); err != nil { | |
fmt.Fprint(&b, err.Error()) | |
d.Info = b.String() | |
return true | |
} | |
fmt.Fprint(&b, "。") | |
} | |
if d.Evening != "" { | |
fmt.Fprint(&b, "夕方体温(平衡)", d.Evening) | |
if err = d.addRow("evening"+suffix, "%+4.1f", d.Evening); err != nil { | |
fmt.Fprint(&b, err.Error()) | |
d.Info = b.String() | |
return true | |
} | |
fmt.Fprint(&b, "。") | |
} | |
d.Info = b.String() | |
return true | |
} | |
func (d *Data) addRow(filename, format, value string) error { | |
var v float64 | |
if _, err := fmt.Sscan(value, &v); err != nil { | |
return err | |
} | |
value = fmt.Sprintf(format, v) | |
fp := filepath.Join(d.Path, filename) | |
blob, err := os.ReadFile(fp) | |
if err != nil { | |
if os.IsNotExist(err) { | |
blob = []byte("Date,Value\n") | |
} else { | |
return err | |
} | |
} | |
rows, err := csv.NewReader(bytes.NewReader(blob)).ReadAll() | |
if err != nil { | |
return err | |
} | |
pos := len(rows) | |
for i, row := range rows[1:] { | |
cmp := strings.Compare(row[0], d.Date) | |
if cmp < 0 { | |
continue | |
} | |
if cmp == 0 { | |
row[1] = value | |
pos = -1 | |
} else { | |
pos = i + 1 | |
} | |
break | |
} | |
if pos >= 0 { | |
newRows := make([][]string, len(rows)+1) | |
copy(newRows[:pos], rows[:pos]) | |
newRows[pos] = []string{d.Date, value} | |
copy(newRows[pos+1:], rows[pos:]) | |
rows = newRows | |
} | |
var b bytes.Buffer | |
writer := csv.NewWriter(&b) | |
if err = writer.WriteAll(rows); err != nil { | |
return err | |
} | |
if err = os.WriteFile(fp, b.Bytes(), 0666); err != nil { | |
return err | |
} | |
log.Println("UPDATE", filename) | |
return nil | |
} | |
func (d *Data) showCsv(form url.Values) bool { | |
if !form.Has("showcsv") { | |
return false | |
} | |
d.Show = form.Get("show") | |
d.Kind = form.Get("kind") | |
if d.Show == "" { | |
d.Info = "日付が設定されていません(ShowCSV)" | |
return false | |
} | |
t, err := time.Parse(dateFormat, d.Show) | |
if err != nil { | |
d.Info = err.Error() | |
return true | |
} | |
switch d.Kind { | |
case "weight": | |
case "wakeup": | |
case "tobed": | |
case "sleep": | |
case "interrupt": | |
case "earlytemp": | |
case "normaltemp": | |
case "evening": | |
default: | |
d.Info = "種類が設定されていません(ShowCSV)" | |
return true | |
} | |
filename := d.Kind + t.Format(fileDateFormat) + ".csv" | |
blob, err := os.ReadFile(filepath.Join(d.Path, filename)) | |
if err != nil { | |
if os.IsNotExist(err) { | |
d.File = filename | |
d.Csv = "Date,Value\n" | |
d.Info = "仮の" + filename + "を用意しました(ShowCSV)" | |
} else { | |
d.Info = err.Error() | |
} | |
return true | |
} | |
var sb strings.Builder | |
if _, err = sb.Write(blob); err != nil { | |
d.Info = err.Error() | |
return true | |
} | |
d.File = filename | |
d.Csv = sb.String() | |
d.Info = filename + "を読み込みました(ShowCSV)" | |
return true | |
} | |
func (d *Data) editCsv(form url.Values) { | |
if !form.Has("editcsv") { | |
return | |
} | |
d.File = form.Get("file") | |
d.Csv = form.Get("csv") | |
if !form.Has("update") { | |
d.Info = "更新のチェックを入れてください(EditCSV)" | |
return | |
} | |
if d.File == "" { | |
d.Info = "ファイル名が指定されてません(EditCSV)" | |
return | |
} | |
if d.Csv == "" { | |
d.Info = "CSVが空です(EditCSV)" | |
return | |
} | |
rows, err := csv.NewReader(strings.NewReader(d.Csv)).ReadAll() | |
if err != nil { | |
d.Info = err.Error() | |
return | |
} | |
if s := checkCsv(d.File, rows); s != "" { | |
d.Info = s | |
return | |
} | |
var b bytes.Buffer | |
writer := csv.NewWriter(&b) | |
if err = writer.WriteAll(rows); err != nil { | |
d.Info = err.Error() | |
return | |
} | |
fp := filepath.Join(d.Path, d.File) | |
if err = os.WriteFile(fp, b.Bytes(), 0666); err != nil { | |
d.Info = err.Error() | |
return | |
} | |
d.Csv = b.String() | |
d.Info = d.File + "を保存しました(EditCSV)" | |
log.Println("UPDATE", d.File) | |
} | |
func checkCsv(filename string, rows [][]string) string { | |
if rows[0][0] != "Date" { | |
return "ヘッダのDateが無い!" | |
} | |
if rows[0][1] != "Value" { | |
return "ヘッダのValueが無い!" | |
} | |
dateset := map[string]bool{} | |
var f float64 | |
for i, row := range rows[1:] { | |
t, err := time.Parse(dateFormat, row[0]) | |
if err != nil { | |
return fmt.Sprint(i, "行目に日付フォーマット違反!", row[0], ",", row[1]) | |
} | |
if dateset[row[0]] { | |
return fmt.Sprint(i, "行目に日付重複違反!", row[0], ",", row[1]) | |
} | |
if !strings.Contains(filename, t.Format(fileDateFormat)) { | |
return fmt.Sprint(i, "行目に日付の間違い!", row[0], ",", row[1]) | |
} | |
dateset[row[0]] = true | |
if _, err = fmt.Sscan(row[1], &f); err != nil { | |
return fmt.Sprint(i, "行目に数値フォーマット違反!", row[0], ",", row[1]) | |
} | |
} | |
sort.Slice(rows[1:], func(i, j int) bool { | |
return rows[i+1][0] < rows[j+1][0] | |
}) | |
return "" | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment