演習a5
日時とタイトルと本文を保存できるメモアプリケーションを作成せよ.
(1)
- タイトル,日付,内容を格納するデータ型Memoと,Memo型を要素とするスライスMemoSliceを宣言せよ.
(2)
- Memosをファイルに格納する関数Saveとファイルから読み込む関数LoadMemoを作成せよ.
- ただし,メモファイルのファイル名は memo.txtとし,ファイルの1行目には"Memo File メモ数"という文字列があるものとする.また,ファイルの2行目から,タイトル行,日時行,内容行が,新しい順に並ぶものとする.
(3)
- (1)(2)を使ってメモアプリを作成せよ.
解答例(1)
解答例(1)
ソースコード
- package main
- package main
- import (
- "text/template"
- "net/http"
- "time"
- "fmt"
- "bufio"
- "strconv"
- "strings"
- "os"
- "log"
- )
- type Memo struct {
- Title string
- Date string
- Body string
- }
- type MemoSlice []Memo
- const EXPAND = 5
- func LoadMemo() (memoArray MemoSlice, num int, err error) {
- f, err := os.Open("memo.txt")
- if err != nil { log.Fatal(err) }
- defer func () {
- err := f.Close()
- if err != nil { log.Fatal(err) }
- }()
- input := bufio.NewReader(f)
- line, _ := input.ReadString('\n')
- if line[0:10] != "Memo File " { log.Fatal("LoadMemo: memo.txt の形式が違います.") }
- num, _ = strconv.Atoi(line[10:len(line)-1])
- memoArray = make([]Memo, num+EXPAND)
- memos := memoArray[EXPAND:]
- for i:=0; i < num; i++ {
- line, err = input.ReadString('\n')
- memos[i].Title = line[:len(line)-1]
- line, err = input.ReadString('\n')
- memos[i].Date = line[:len(line)-1]
- line, err = input.ReadString('\n')
- memos[i].Body = line[:len(line)-1]
- }
- return
- }
- func (memos MemoSlice) Save() error {
- f, err := os.OpenFile("memo.txt",os.O_WRONLY,0)
- if err != nil { log.Fatal(err) }
- defer func() {
- err = f.Close()
- if err != nil { log.Fatal(err) }
- }()
- output := bufio.NewWriter(f)
- _, err = output.WriteString("Memo File "+strconv.Itoa(len(memos))+"\n")
- for i :=0; i < len(memos); i++ {
- _, err = output.WriteString(memos[i].Title+"\n")
- _, err = output.WriteString(memos[i].Date+"\n")
- _, err = output.WriteString(memos[i].Body+"\n")
- }
- output.Flush()
- return err
- }
- func main() {
- fmt.Println("メモサーバ スタート")
- memoArray, num, err := LoadMemo()
- if err != nil { log.Fatal(err) }
- memos := memoArray[len(memoArray)-num:]
- fmt.Println("メモファイル読み込み完了")
- http.HandleFunc("/", func (w http.ResponseWriter, r *http.Request) {
- w.Header().Set("Content-Type", "text/html")
- t := template.Must(template.ParseFiles("memo.html"))
- err := t.Execute(w,memos)
- if err != nil {
- fmt.Println(err.Error())
- }
- })
- http.HandleFunc("/memo", func (w http.ResponseWriter, r *http.Request) {
- fmt.Println("メモした")
- title := r.FormValue("title")
- date := time.Now().Format(time.RFC3339)
- body := strings.Replace(r.FormValue("body"),"\n","<br />", -1)
- memos = memoArray[len(memoArray)-len(memos)-1:]
- memos[0] = Memo{title, date, body}
- if len(memoArray) == len(memos) {
- newMemoArray := make([]Memo,len(memoArray)+EXPAND)
- copy(newMemoArray[EXPAND:], memos)
- memoArray = newMemoArray
- err = memos.Save()
- if err != nil { log.Fatal(err) }
- fmt.Println("セーブした")
- }
- http.Redirect(w, r, "/", http.StatusFound)
- })
- http.HandleFunc("/quit", func (w http.ResponseWriter, r *http.Request) {
- err = memos.Save()
- if err != nil { log.Fatal(err) }
- fmt.Println("セーブした")
- fmt.Println("サービス終了")
- os.Exit(0)
- })
- fmt.Println("サービス開始")
- http.ListenAndServe(":10080",nil)
- }
HTMLテンプレート
- <!DOCTYPE htm>
- <head>
- <meta charset="utf-8" />
- <title>memo</title>
- </head>
- <body>
- <h1>メモ</h1>
- <form action="/memo" method="POST">
- <div>タイトル<br><textarea name="title" rows="1" cols="40"></textarea></div>
- <div>内容<br><textarea name="body" rows="3" cols="40"></textarea></div>
- <div><input type="submit" value="メモする"></div>
- </form>
- {{range .}}
- <hr>
- <p>タイトル:{{.Title}}</p>
- <p>日時:{{.Date}}</p>
- <p>内容:</p>
- {{.Body}}
- {{end}}
- </body>
- </html>
解説
解説
(1)
- 14行目から18行目:データ型Memoの宣言
- 20行目:データ型MemoSliceの宣言
(2)
LoadMemo
- 25行目から30行目:ファイルのオープンとクローズ.
- 33行目:ファイルから1行読み込み.ReadString('\n')で'\n'まで読み込む.
- 34行目:ファイルの最初の11バイトが"Memo File "か否かで,メモファイルか否かを判定.
- 35行目:数を読み込み,strconv.Atoiで整数に変換.これにより,numにメモ数が代入される.
- 37行目から38行目:メモ数+EXPANDの長さのスライスを作成し,スライスの後方に(EXPAND以降に右詰めで)データを読み込むため,その部分のスライスをmemosとする.
- 39行目から46行目:タイトル行,日時行,内容行の順に,ファイルからメモ数分だけメモをmemosに読み込む
Save
- 51行目から56行目:ファイルのオープンとクローズ.
- 59行目:1行目に"Memo File "とメモ数を出力.strconv.Itoaで整数を文字列に変換.
- 60行目から64行目:メモ数分だけ,メモを出力.
- 65行目:出力バッファをフラッシュ.
(3)
main
- 71行目:メモファイルからメモをmemoArrayに読み込む.この時点で,memoArrayにはEXPAND個(0からEXPAND-1)の空きがある.
- 73行目:memoArrayのメモが入っている部分(スライス)をmemosとする.
- 76行目以降で,ハンドルを登録している.ハンドルから,memosにアクセスするために,関数リテラルを用いる.
- 76行目から83行目:パスが"/"のとき,メモの一覧(memos)を表示する.
- 85行目から101行目:パスが"/memo"のとき,テンプレートのフォームに入力されたメモデータを,その時の日時とともに,memosに追加する.
- 92行目から99行目:memoArrayがいっぱいになったら,大きなスライスを用意し,そちらにデータをコピーする.また,メモデータをセーブする.
- 100行目:"/"にリダイレクトする(メモリストを表示する)
- 103行目から109行目:"/quit"にアクセスしたら,メモデータをセーブして,サーバを停止する.
- 112行目:サービス開始.
- メモファイルが大きくなるとsaveに時間がかかる,Ctl-Cで終わらせると最大5つのメモが消えてしまう,websocketを使ってデータのやり取りを減らした方がいい,などなど,改良の余地は多数あります.
テンプレート
- 8行目から12行目:メモの入力フォーム.
- 13行目から19行目:メモのリストの表示.
- 13行目の{{range .}}により,ソースコードの79行目で指定したmemosというスライスの要素に順にアクセスし,要素数だけ14行目から18行目を繰り返し表示する.
- スライスの要素はMemo型であるので,そのフィールドには,.Title,.Date,.Bodyでアクセスする.