Skip to content

Instantly share code, notes, and snippets.

@neetsdkasu
Last active Jan 5, 2022
Embed
What would you like to do?
記録
module kiroku
go 1.17
<!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 -&gt; 13.633333 -&gt; 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>
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