Compare commits
10 Commits
c75bd74823
...
b2d233bb82
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b2d233bb82 | ||
|
|
253bff9f1d | ||
|
|
09f3baee4d | ||
|
|
4de3b4a822 | ||
|
|
2f21a23a33 | ||
|
|
227de17951 | ||
|
|
a623dcc195 | ||
|
|
569e50b162 | ||
|
|
622c173264 | ||
|
|
ce5bf71f6b |
@@ -27,10 +27,12 @@ import (
|
|||||||
|
|
||||||
//go:embed public/*
|
//go:embed public/*
|
||||||
var _staticFileDir embed.FS
|
var _staticFileDir embed.FS
|
||||||
var staticFileDir = func() embed.FS {
|
var publicHandler = func() http.Handler {
|
||||||
panic("if dev mode then return live")
|
if os.Getenv("DEBUG") != "" {
|
||||||
return _staticFileDir
|
return http.FileServer(http.Dir("./http"))
|
||||||
}
|
}
|
||||||
|
return http.FileServer(http.FS(_staticFileDir))
|
||||||
|
}()
|
||||||
|
|
||||||
func Main() {
|
func Main() {
|
||||||
foo := flag.String("foo", "bal", "bal or reg")
|
foo := flag.String("foo", "bal", "bal or reg")
|
||||||
@@ -65,29 +67,49 @@ func Main() {
|
|||||||
if *httpOutput != "" {
|
if *httpOutput != "" {
|
||||||
foo := func(w http.ResponseWriter, r *http.Request) {
|
foo := func(w http.ResponseWriter, r *http.Request) {
|
||||||
if !strings.HasPrefix(r.URL.Path, "/api") {
|
if !strings.HasPrefix(r.URL.Path, "/api") {
|
||||||
http.FileServer(http.FS(staticFileDir)).ServeHTTP(w, r)
|
publicHandler.ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reqF := f
|
||||||
|
if queryF := r.URL.Query().Get("f"); queryF != "" {
|
||||||
|
queryF = path.Join("http", queryF)
|
||||||
|
reqF, err = ledger.NewFiles(queryF)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
switch r.URL.Path {
|
switch r.URL.Path {
|
||||||
case "/api/transactions":
|
case "/api/transactions":
|
||||||
reqF := f
|
lastNLines, err := reqF.TempGetLastNLines(20)
|
||||||
if queryF := r.URL.Query().Get("f"); queryF != "" {
|
if err != nil {
|
||||||
queryF = path.Join("http", queryF)
|
panic(err)
|
||||||
reqF, err = ledger.NewFiles(queryF)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
deltas, err := reqF.Deltas()
|
deltas, err := reqF.Deltas()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
json.NewEncoder(w).Encode(map[string]any{
|
json.NewEncoder(w).Encode(map[string]any{
|
||||||
"deltas": deltas.Like(ledger.LikeAfter(time.Now().Add(-1 * time.Hour * 24 * 365 / 2).Format("2006-01"))),
|
"deltas": deltas.Like(ledger.LikeAfter(time.Now().Add(-1 * time.Hour * 24 * 365 / 2).Format("2006-01"))),
|
||||||
"balances": deltas.Balances().Like("^AssetAccount:").WithBPIs(bpis),
|
"balances": deltas.Balances().Like("^AssetAccount:").WithBPIs(bpis),
|
||||||
|
"lastNLines": lastNLines,
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
|
case "/api/lastnlines":
|
||||||
|
if r.Method != http.MethodPut {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var lines []string
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&lines); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if err := reqF.TempSetLastNLines(20, lines); err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
w.WriteHeader(http.StatusResetContent)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
deltas, err := f.Deltas()
|
deltas, err := f.Deltas()
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
../../../../../../Sync/Core/tmp/moolah.dat
|
|
||||||
3090
cmd/http/moolah.dat
Normal file
3090
cmd/http/moolah.dat
Normal file
File diff suppressed because it is too large
Load Diff
@@ -37,6 +37,7 @@
|
|||||||
var d = JSON.parse(body)
|
var d = JSON.parse(body)
|
||||||
loadBalances(d.balances)
|
loadBalances(d.balances)
|
||||||
loadDeltas(d.deltas)
|
loadDeltas(d.deltas)
|
||||||
|
loadLastNLines(d.lastNLines)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
function loadBalances(balances) {
|
function loadBalances(balances) {
|
||||||
@@ -68,6 +69,26 @@
|
|||||||
result += `</table>`
|
result += `</table>`
|
||||||
document.getElementById("reg").innerHTML = result
|
document.getElementById("reg").innerHTML = result
|
||||||
}
|
}
|
||||||
|
function setLastNLines(form) {
|
||||||
|
http("PUT", "/api/lastnlines?f="+f, (body, status) => {
|
||||||
|
if (status == 205) {
|
||||||
|
init()
|
||||||
|
}
|
||||||
|
document.getElementById("lastNLinesStatus").innerHTML = `(${status}) ${body}`
|
||||||
|
}, JSON.stringify(form.elements["lastNLines"].value.split("\n")))
|
||||||
|
}
|
||||||
|
function loadLastNLines(lastNLines) {
|
||||||
|
var result = `<form onsubmit="setLastNLines(this); return false;" action="#">`
|
||||||
|
result += ` <div>${f}</div>`
|
||||||
|
result += ` <textarea name="lastNLines">`
|
||||||
|
for (var k in lastNLines) {
|
||||||
|
result += lastNLines[k] + "\n"
|
||||||
|
}
|
||||||
|
result += ` </textarea>`
|
||||||
|
result += ` <input type="submit">`
|
||||||
|
result += `</form>`
|
||||||
|
document.getElementById("lastNLines").innerHTML = result
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
</header>
|
</header>
|
||||||
<body onload="init();" style="min-width: 1024px;">
|
<body onload="init();" style="min-width: 1024px;">
|
||||||
@@ -82,6 +103,13 @@
|
|||||||
</details>
|
</details>
|
||||||
</details>
|
</details>
|
||||||
<details open>
|
<details open>
|
||||||
|
<summary>Edit</summary>
|
||||||
|
<div id="lastNLinesStatus">
|
||||||
|
</div>
|
||||||
|
<div id="lastNLines">
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
<details>
|
||||||
<summary>Register</summary>
|
<summary>Register</summary>
|
||||||
<div id="reg">
|
<div id="reg">
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
package ledger
|
package ledger
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
"unicode"
|
"unicode"
|
||||||
@@ -20,6 +24,61 @@ func NewFiles(p string, q ...string) (Files, error) {
|
|||||||
return f, err
|
return f, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (files Files) TempGetLastNLines(n int) ([]string, error) {
|
||||||
|
p := files.paths()[0]
|
||||||
|
f, err := os.Open(p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
return peekLastNLines(io.Discard, bufio.NewReader(f), n)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (files Files) TempSetLastNLines(n int, lines []string) error {
|
||||||
|
p := files.paths()[0]
|
||||||
|
w, err := ioutil.TempFile(os.TempDir(), path.Base(p))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer w.Close()
|
||||||
|
|
||||||
|
r, err := os.Open(p)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer r.Close()
|
||||||
|
|
||||||
|
if _, err := peekLastNLines(w, bufio.NewReader(r), n); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for i := range lines {
|
||||||
|
fmt.Fprintln(w, lines[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
return os.Rename(w.Name(), p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func peekLastNLines(w io.Writer, r *bufio.Reader, n int) ([]string, error) {
|
||||||
|
lastNLines := make([]string, 0, n)
|
||||||
|
for {
|
||||||
|
line, err := r.ReadBytes('\n')
|
||||||
|
if len(line) > 0 {
|
||||||
|
lastNLines = append(lastNLines, string(bytes.TrimRight(line, "\n")))
|
||||||
|
for i := 0; i < len(lastNLines)-n; i++ {
|
||||||
|
fmt.Fprintln(w, lastNLines[i])
|
||||||
|
}
|
||||||
|
lastNLines = lastNLines[max(0, len(lastNLines)-n):]
|
||||||
|
}
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return lastNLines, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (files Files) paths() []string {
|
func (files Files) paths() []string {
|
||||||
result := make([]string, 0, len(files))
|
result := make([]string, 0, len(files))
|
||||||
for i := range files {
|
for i := range files {
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package ledger
|
package ledger
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -290,3 +292,131 @@ func TestFilesOfDir(t *testing.T) {
|
|||||||
t.Error(paths)
|
t.Error(paths)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFilesTempGetLastNLines(t *testing.T) {
|
||||||
|
cases := map[string]struct {
|
||||||
|
input string
|
||||||
|
n int
|
||||||
|
want []string
|
||||||
|
}{
|
||||||
|
"empty": {},
|
||||||
|
"get n lines from empty file": {
|
||||||
|
input: "",
|
||||||
|
n: 5,
|
||||||
|
want: []string{},
|
||||||
|
},
|
||||||
|
"get 0 lines from file": {
|
||||||
|
input: "#a\n#b",
|
||||||
|
n: 0,
|
||||||
|
want: []string{},
|
||||||
|
},
|
||||||
|
"get 3 lines from 2 line file": {
|
||||||
|
input: "#a\n#b",
|
||||||
|
n: 3,
|
||||||
|
want: []string{"#a", "#b"},
|
||||||
|
},
|
||||||
|
"get 2 lines from 3 line file": {
|
||||||
|
input: "#a\n#b\n#c",
|
||||||
|
n: 2,
|
||||||
|
want: []string{"#b", "#c"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, d := range cases {
|
||||||
|
c := d
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
p := path.Join(t.TempDir(), base64.URLEncoding.EncodeToString([]byte(t.Name())))
|
||||||
|
os.WriteFile(p, []byte(c.input), os.ModePerm)
|
||||||
|
files, err := NewFiles(p)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if got, err := files.TempGetLastNLines(c.n); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else if fmt.Sprint(got) != fmt.Sprint(c.want) {
|
||||||
|
for i := range c.want {
|
||||||
|
t.Logf("want[%d] = %q", i, c.want[i])
|
||||||
|
}
|
||||||
|
for i := range got {
|
||||||
|
t.Logf(" got[%d] = %q", i, got[i])
|
||||||
|
}
|
||||||
|
t.Errorf("wanted\n\t%+v, got\n\t%+v", c.want, got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFilesTempSetLastNLines(t *testing.T) {
|
||||||
|
cases := map[string]struct {
|
||||||
|
given string
|
||||||
|
input []string
|
||||||
|
n int
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
"empty": {},
|
||||||
|
"append to empty": {
|
||||||
|
input: []string{"hello", "world"},
|
||||||
|
n: 100,
|
||||||
|
want: "hello\nworld\n",
|
||||||
|
},
|
||||||
|
"replace last 10 of 1 lines with 2": {
|
||||||
|
given: "ohno",
|
||||||
|
input: []string{"hello", "world"},
|
||||||
|
n: 10,
|
||||||
|
want: "hello\nworld\n",
|
||||||
|
},
|
||||||
|
"replace last 1 of 1 lines with 2": {
|
||||||
|
given: "ohno",
|
||||||
|
input: []string{"hello", "world"},
|
||||||
|
n: 1,
|
||||||
|
want: "hello\nworld\n",
|
||||||
|
},
|
||||||
|
"replace last 0 of 1 lines with 2": {
|
||||||
|
given: "ohno",
|
||||||
|
input: []string{"hello", "world"},
|
||||||
|
n: 0,
|
||||||
|
want: "ohno\nhello\nworld\n",
|
||||||
|
},
|
||||||
|
"replace last 1 of 1 lines with 0": {
|
||||||
|
given: "ohno",
|
||||||
|
input: []string{},
|
||||||
|
n: 1,
|
||||||
|
want: "",
|
||||||
|
},
|
||||||
|
"replace last 1 of 2 lines with 1": {
|
||||||
|
given: "ohno\nhaha",
|
||||||
|
input: []string{"replaced"},
|
||||||
|
n: 1,
|
||||||
|
want: "ohno\nreplaced\n",
|
||||||
|
},
|
||||||
|
"replace last 1 of 2 lines with 2": {
|
||||||
|
given: "ohno\nhaha",
|
||||||
|
input: []string{"replac", "ed"},
|
||||||
|
n: 1,
|
||||||
|
want: "ohno\nreplac\ned\n",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, d := range cases {
|
||||||
|
c := d
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
p := path.Join(t.TempDir(), base64.URLEncoding.EncodeToString([]byte(t.Name())))
|
||||||
|
os.WriteFile(p, []byte(c.given), os.ModePerm)
|
||||||
|
files := Files([]string{p})
|
||||||
|
if err := files.TempSetLastNLines(c.n, c.input); err != nil {
|
||||||
|
s := err.Error()
|
||||||
|
if _, err := os.Stat(s); err == nil {
|
||||||
|
got, _ := os.ReadFile(s)
|
||||||
|
if string(got) != c.want {
|
||||||
|
t.Errorf("want\n%s, got\n%s", c.want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
got, _ := os.ReadFile(p)
|
||||||
|
if string(got) != c.want {
|
||||||
|
t.Errorf("want\n%s, got\n%s", c.want, got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user