<commit_before>package main

import (
	"database/sql"
	"encoding/json"
	"flag"
	"fmt"
	"html/template"
	"net/http"
	"os"
	"regexp"
	"time"

	_ "github.com/mattn/go-sqlite3"
)

var settings struct {
	DailyTarget int
	Database    string
}

func init() {
	flag.IntVar(&settings.DailyTarget, "target", 900, "The number of words to write daily")
	flag.StringVar(&settings.Database, "db", "diary.db", "The name of the database file")
}

func main() {
	flag.Parse()

	db, err := sql.Open("sqlite3", settings.Database)
	if err != nil {
		panic(err)
	}

	_, err = db.Exec("CREATE TABLE IF NOT EXISTS entries (date TEXT PRIMARY KEY, text TEXT, words INTEGER)")
	if err != nil {
		panic(err)
	}

	http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
		now := time.Now()
		days := daysOfMonth(now)
		annotatedDays, err := annotateDays(db, days)
		if err != nil {
			fmt.Fprintf(os.Stderr, "Error: %s\n", err)
		}

		text := ""
		words := 0
		rows, err := db.Query("SELECT text, words FROM entries WHERE date = ?", now.Format("2006-01-02"))
		if err != nil {
			fmt.Fprintf(os.Stderr, "Error: %s\n", err)
		}
		defer rows.Close()

		if rows.Next() {
			err = rows.Scan(&text, &words)
			if err != nil {
				fmt.Fprintf(os.Stderr, "Error: %s\n", err)
			}
		}

		err = indexTmpl.Execute(w, map[string]interface{}{
			"Title": fmt.Sprintf("%d words", settings.DailyTarget),

			"Now":  now,
			"Days": annotatedDays,

			"Text":  text,
			"Words": words,
		})
		if err != nil {
			fmt.Fprintf(os.Stderr, "Error: %s\n", err)
		}
	})

	http.HandleFunc("/save", func(w http.ResponseWriter, req *http.Request) {
		var entry map[string]string
		dec := json.NewDecoder(req.Body)
		err := dec.Decode(&entry)
		if err != nil {
			respondWithError(w, http.StatusBadRequest, err)
			return
		}

		text, ok := entry["text"]
		if !ok {
			respondWithError(w, http.StatusBadRequest, fmt.Errorf("Missing field 'text'"))
			return
		}

		words := countWords(text)
		now := time.Now()
		_, err = db.Exec("INSERT OR REPLACE INTO entries VALUES (?, ?, ?)", now.Format("2006-01-02"), text, words)
		if err != nil {
			respondWithError(w, http.StatusInternalServerError, fmt.Errorf("%s", http.StatusText(http.StatusInternalServerError)))
			return
		}

		enc := json.NewEncoder(w)
		err = enc.Encode(map[string]string{"message": "saved post", "time": now.Format(time.RFC3339)})
		if err != nil {
			fmt.Fprintf(os.Stderr, "Error: %s\n", err)
		}
	})

	http.ListenAndServe("localhost:12345", nil)
}

func respondWithError(w http.ResponseWriter, status int, err error) {
	res := map[string]string{"error": err.Error()}
	out, err := json.Marshal(res)
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		fmt.Fprintf(w, "{\"error\": %q}", http.StatusText(http.StatusInternalServerError))
		return
	}
	w.WriteHeader(status)
	w.Write(out)
}

var indexTmpl = template.Must(template.New("index").Parse(`<!doctype html>
<html>
	<head>
		<meta charset="utf-8" />
		<title>{{ .Title }}</title>

		<style>
		#content {
			display: flex;
			flex-direction: column;
			align-items: center;
		}

		#days {
			list-style-type: none;
			padding: 0;
			display: flex;
			width: 80vw;
			justify-content: space-around;
		}

		#days li {
			width: 1.5em;
			height: 1.5em;
			text-align: center;
			border: 1px solid;
			border-radius: 100%;
		}<commit_msg>Add a command to import posts<commit_after>package main

import (
	"database/sql"
	"encoding/json"
	"flag"
	"fmt"
	"html/template"
	"io/ioutil"
	"net/http"
	"os"
	"regexp"
	"time"

	_ "github.com/mattn/go-sqlite3"
)

var settings struct {
	DailyTarget int
	Database    string
}

func init() {
	flag.IntVar(&settings.DailyTarget, "target", 900, "The number of words to write daily")
	flag.StringVar(&settings.Database, "db", "diary.db", "The name of the database file")
}

func main() {
	flag.Parse()

	db, err := sql.Open("sqlite3", settings.Database)
	if err != nil {
		panic(err)
	}

	_, err = db.Exec("CREATE TABLE IF NOT EXISTS entries (date TEXT PRIMARY KEY, text TEXT, words INTEGER)")
	if err != nil {
		panic(err)
	}

	if flag.NArg() >= 1 {
		cmd := flag.Arg(0)
		if cmd == "import" {
			importEntry(db)
			return
		}
	}

	http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
		now := time.Now()
		days := daysOfMonth(now)
		annotatedDays, err := annotateDays(db, days)
		if err != nil {
			fmt.Fprintf(os.Stderr, "Error: %s\n", err)
		}

		text := ""
		words := 0
		rows, err := db.Query("SELECT text, words FROM entries WHERE date = ?", now.Format("2006-01-02"))
		if err != nil {
			fmt.Fprintf(os.Stderr, "Error: %s\n", err)
		}
		defer rows.Close()

		if rows.Next() {
			err = rows.Scan(&text, &words)
			if err != nil {
				fmt.Fprintf(os.Stderr, "Error: %s\n", err)
			}
		}

		err = indexTmpl.Execute(w, map[string]interface{}{
			"Title": fmt.Sprintf("%d words", settings.DailyTarget),

			"Now":  now,
			"Days": annotatedDays,

			"Text":  text,
			"Words": words,
		})
		if err != nil {
			fmt.Fprintf(os.Stderr, "Error: %s\n", err)
		}
	})

	http.HandleFunc("/save", func(w http.ResponseWriter, req *http.Request) {
		var entry map[string]string
		dec := json.NewDecoder(req.Body)
		err := dec.Decode(&entry)
		if err != nil {
			respondWithError(w, http.StatusBadRequest, err)
			return
		}

		text, ok := entry["text"]
		if !ok {
			respondWithError(w, http.StatusBadRequest, fmt.Errorf("Missing field 'text'"))
			return
		}

		now := time.Now()
		err = saveEntry(db, now, text)
		if err != nil {
			respondWithError(w, http.StatusInternalServerError, fmt.Errorf("%s", http.StatusText(http.StatusInternalServerError)))
			return
		}

		enc := json.NewEncoder(w)
		err = enc.Encode(map[string]string{"message": "saved post", "time": now.Format(time.RFC3339)})
		if err != nil {
			fmt.Fprintf(os.Stderr, "Error: %s\n", err)
		}
	})

	http.ListenAndServe("localhost:12345", nil)
}

func respondWithError(w http.ResponseWriter, status int, err error) {
	res := map[string]string{"error": err.Error()}
	out, err := json.Marshal(res)
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		fmt.Fprintf(w, "{\"error\": %q}", http.StatusText(http.StatusInternalServerError))
		return
	}
	w.WriteHeader(status)
	w.Write(out)
}

func saveEntry(db *sql.DB, date time.Time, text string) error {
	_, err := db.Exec("INSERT OR REPLACE INTO entries VALUES (?, ?, ?)", date.Format("2006-01-02"), text, countWords(text))
	return err
}

func importEntry(db *sql.DB) {
	if flag.NArg() < 2 {
		fmt.Fprintf(os.Stderr, "Usage: %s import <date>\n", os.Args[0])
		os.Exit(1)
	}

	rawDate := flag.Arg(1)
	date, err := time.Parse("2006-01-02", rawDate)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Error: invalid date '%s': %s\n", rawDate, err)
		os.Exit(1)
	}

	f := os.Stdin
	if flag.NArg() >= 3 {
		f, err = os.Open(flag.Arg(2))
		if err != nil {
			fmt.Fprintf(os.Stderr, "Error: %s\n", err)
			os.Exit(1)
		}
	}
	defer f.Close()

	text, err := ioutil.ReadAll(f)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Error: reading %s: %s\n", f.Name(), err)
		os.Exit(1)
	}

	err = saveEntry(db, date, string(text))
	if err != nil {
		fmt.Fprintf(os.Stderr, "Error: saving entry: %s\n", err)
		os.Exit(1)
	}
}

var indexTmpl = template.Must(template.New("index").Parse(`<!doctype html>
<html>
	<head>
		<meta charset="utf-8" />
		<title>{{ .Title }}</title>

		<style>
		#content {
			display: flex;
			flex-direction: column;
			align-items: center;
		}

		#days {
			list-style-type: none;
			padding: 0;
			display: flex;
			width: 80vw;
			justify-content: space-around;
		}

		#days li {
			width: 1.5em;
			height: 1.5em;
			text-align: center;
			border: 1px solid;
			border-radius: 100%;
		}
{'fraction_non_alphanumeric': 0.17707788813382122, 'fraction_numerical': 0.013852587558808156, 'mean_word_length': 3.5284023668639053, 'pattern_counts': {'":': 18, '<': 19, '<?xml version=': 0, '>': 20, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1096404', 'n_tokens_mistral': 3308, 'n_tokens_neox': 2906, 'n_words': 671}