del code related to last n lines
parent
2141e030ef
commit
f41386b3b4
|
|
@ -72,7 +72,7 @@ func (router Router) API(w http.ResponseWriter, r *http.Request) {
|
|||
case "/api/transactions":
|
||||
router.APITransactions(w, r)
|
||||
case "/api/amend":
|
||||
router.APILastNLines(w, r)
|
||||
router.APIAmend(w, r)
|
||||
case "/api/reg":
|
||||
router.APIReg(w, r)
|
||||
default:
|
||||
|
|
@ -80,6 +80,29 @@ func (router Router) API(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
}
|
||||
|
||||
func (router Router) APITransactions(w http.ResponseWriter, r *http.Request) {
|
||||
bpis, err := router.bpis()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
deltas, err := router.files.Deltas()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
json.NewEncoder(w).Encode(map[string]any{
|
||||
"deltas": deltas.Like(ledger.LikeAfter(time.Now().Add(-1 * time.Hour * 24 * 365 / 2).Format("2006-01"))),
|
||||
"balances": deltas.Balances().
|
||||
Like("^(Bel:Asset|Zach:Asset|HouseyMcHouseface:Debts:Credit)").
|
||||
Group(`^[^:]*`).
|
||||
WithBPIs(bpis),
|
||||
})
|
||||
}
|
||||
|
||||
func (router Router) APIAmend(w http.ResponseWriter, r *http.Request) {
|
||||
http.Error(w, http.StatusText(http.StatusNotImplemented), http.StatusNotImplemented)
|
||||
}
|
||||
|
||||
func (router Router) APIReg(w http.ResponseWriter, r *http.Request) {
|
||||
deltas, err := router.files.Deltas()
|
||||
if err != nil {
|
||||
|
|
@ -312,34 +335,6 @@ func (router Router) APIReg(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
}
|
||||
|
||||
func (router Router) APITransactions(w http.ResponseWriter, r *http.Request) {
|
||||
bpis, err := router.bpis()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
lastNLines, err := router.files.TempGetLastNLines(20)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
deltas, err := router.files.Deltas()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
json.NewEncoder(w).Encode(map[string]any{
|
||||
"deltas": deltas.Like(ledger.LikeAfter(time.Now().Add(-1 * time.Hour * 24 * 365 / 2).Format("2006-01"))),
|
||||
"balances": deltas.Balances().
|
||||
Like("^(Bel:Asset|Zach:Asset|HouseyMcHouseface:Debts:Credit)").
|
||||
Group(`^[^:]*`).
|
||||
WithBPIs(bpis),
|
||||
"lastNLines": lastNLines,
|
||||
})
|
||||
}
|
||||
|
||||
func (router Router) APILastNLines(w http.ResponseWriter, r *http.Request) {
|
||||
http.Error(w, http.StatusText(http.StatusNotImplemented), http.StatusNotImplemented)
|
||||
}
|
||||
|
||||
func (r Router) bpis() (ledger.BPIs, error) {
|
||||
if r.bpiPath == "" {
|
||||
return make(ledger.BPIs), nil
|
||||
|
|
|
|||
|
|
@ -1,17 +1,13 @@
|
|||
package ledger
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
|
|
@ -65,89 +61,6 @@ func (files Files) Amend(old, now Delta) error {
|
|||
return files.Add(transaction.payee, []Delta{old, now}...)
|
||||
}
|
||||
|
||||
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]
|
||||
|
||||
newFile, err := func() (string, error) {
|
||||
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 {
|
||||
if len(strings.TrimSpace(lines[i])) == 0 {
|
||||
continue
|
||||
}
|
||||
if _, err := fmt.Fprintln(w, lines[i]); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
if err := w.Close(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return w.Name(), nil
|
||||
}()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r, err := os.Open(newFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer r.Close()
|
||||
|
||||
w, err := os.Create(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer w.Close()
|
||||
|
||||
_, err = io.Copy(w, r)
|
||||
return err
|
||||
}
|
||||
|
||||
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(bytes.TrimSpace(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 {
|
||||
result := make([]string, 0, len(files))
|
||||
for i := range files {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,6 @@
|
|||
package ledger
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
|
|
@ -450,148 +447,3 @@ func TestFilesOfDir(t *testing.T) {
|
|||
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())))
|
||||
realp := p + ".real"
|
||||
|
||||
os.WriteFile(realp, []byte(c.given), os.ModePerm)
|
||||
if err := os.Symlink(realp, p); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if stat, err := os.Lstat(p); err != nil {
|
||||
t.Error(err)
|
||||
} else if stat.Mode().IsRegular() {
|
||||
t.Error("p is already a regular file")
|
||||
}
|
||||
|
||||
files := Files([]string{p})
|
||||
if err := files.TempSetLastNLines(c.n, c.input); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
got, _ := os.ReadFile(p)
|
||||
if string(got) != c.want {
|
||||
t.Errorf("want\n%s, got\n%s", c.want, got)
|
||||
}
|
||||
|
||||
realb, _ := os.ReadFile(realp)
|
||||
b, _ := os.ReadFile(realp)
|
||||
if !bytes.Equal(b, realb) {
|
||||
t.Errorf("%s no longer links to %s", p, realp)
|
||||
}
|
||||
|
||||
if stat, err := os.Lstat(p); err != nil {
|
||||
t.Error(err)
|
||||
} else if stat.Mode().IsRegular() {
|
||||
t.Error("p is now a regular file")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue