The module, command, binary, window title and docs are now 012, and the native file extension is .012 to match.
- commit
- 7e221183c15d0512b5ca8d84f79510fc196515eb
- parent
- 66b0e8df33fac0fb85375a51e656f953a52df740
- author
- Dami <dami.etoile@gmail.com>
@@ -1,4 +1,4 @@
1-# one23 builds as pure Go. Only the e2e tests need cgo and libghostty-vt,1+# 012 builds as pure Go. Only the e2e tests need cgo and libghostty-vt,22 # which is built from source with Zig into .deps/.33 44 GHOSTTY_COMMIT := 27e8b3fa85d9cf8c7cd5ae2ced348bcb0a4fba9c@@ -10,10 +10,10 @@ GHOSTTY_STAMP := $(GHOSTTY_OUT)/.built-$(GHOSTTY_COMMIT)
1010 1111 1212 build:13- CGO_ENABLED=0 go build -o bin/one23 ./cmd/one2313+ CGO_ENABLED=0 go build -o bin/012 ./cmd/0121414 1515 run: build16- ./bin/one2316+ ./bin/0121717 1818 test:1919 go test ./...@@ -1,16 +1,16 @@
1-# one231+# 01222 33 A spreadsheet for the terminal with a Lotus 1-2-3 look and Google Sheets44 behavior, built on Bubble Tea v2.55 66 ```sh77 make run # build (pure Go, CGO_ENABLED=0) and start8-./bin/one23 budget.o238+./bin/012 budget.01299 ```1010 1111 ## Keys1212 13-Inside the grid, one23 works like Google Sheets.13+Inside the grid, 012 works like Google Sheets.1414 1515 | Key | Action |1616 |---|---|@@ -49,7 +49,7 @@ context line (prompts, key hints, formula errors).
4949 ## Layout5050 5151 ```52-cmd/one23 entry point52+cmd/012 entry point5353 internal/sheet engine: addresses, Pratt parser, evaluator, recalc, file format5454 internal/ui Bubble Tea model: modes, menu, prompts, rendering5555 e2e/ end-to-end tests: real binary on a pty, rendered by libghostty-vt@@ -1,6 +1,6 @@
11 # Roadmap: past 1-2-322 3-Principles: inside the grid one23 behaves like Google Sheets; 1-2-3 is the3+Principles: inside the grid 012 behaves like Google Sheets; 1-2-3 is the44 visual identity around it. The main binary stays pure Go (`CGO_ENABLED=0`), and every feature ships with unit55 tests, a libghostty e2e test and reviewed golden screens. Visual and UX66 quality keep pace with features: see [docs/UX.md](docs/UX.md).@@ -11,9 +11,9 @@ quality keep pace with features: see [docs/UX.md](docs/UX.md).
1111 |---|---|---|1212 | 1 | The engine does what Sheets users reach for daily, and every change can be undone | Undo/redo (Ctrl+Z/Ctrl+Y) covers every mutation; copy, cut and paste (Ctrl+C/X/V, paste values) and insert/delete rows and columns adjust relative and `$absolute` references; number formats (Format menu, auto-detected `$` and `%` on entry) and named ranges work and persist; Sheets' everyday functions, dates and finance functions match excelize on a differential test suite |1313 | 2 | Finding and using features is faster than in any terminal spreadsheet | A Sheets-structured menu bar (File Edit View Insert Format Data Help) and a searchable command palette reach every command with its key shown; formulas autocomplete with signature hints; precedents and dependents are visible and jumpable; rows and columns freeze; find/replace and filter work; fill down (Ctrl+D) and series fill work; an optional vim keymap exists |14-| 3 | one23 uses the terminal it's running in to the fullest, and degrades gracefully | Copy and paste ranges through the system clipboard; charts render as images in Ghostty/kitty and as text elsewhere; URLs are clickable; error cells are marked beyond color; cursor shape reflects the mode; the theme follows light and dark |15-| 4 | Data moves in and out of one23 without friction or cgo | CSV/TSV, XLSX, SQLite, Parquet and Lotus .wk1 import, with export where it makes sense; large imports show progress; the binary still builds with `CGO_ENABLED=0` |16-| 5 | one23 goes where 1-2-3 couldn't | Multiple sheets with cross-sheet references; derived frequency and pivot sheets; recorded and Starlark macros; an SSH server mode; opt-in decimal arithmetic; VHS demo tapes in CI |14+| 3 | 012 uses the terminal it's running in to the fullest, and degrades gracefully | Copy and paste ranges through the system clipboard; charts render as images in Ghostty/kitty and as text elsewhere; URLs are clickable; error cells are marked beyond color; cursor shape reflects the mode; the theme follows light and dark |15+| 4 | Data moves in and out of 012 without friction or cgo | CSV/TSV, XLSX, SQLite, Parquet and Lotus .wk1 import, with export where it makes sense; large imports show progress; the binary still builds with `CGO_ENABLED=0` |16+| 5 | 012 goes where 1-2-3 couldn't | Multiple sheets with cross-sheet references; derived frequency and pivot sheets; recorded and Starlark macros; an SSH server mode; opt-in decimal arithmetic; VHS demo tapes in CI |1717 1818 ## Phase 1: engine foundations (unblocks everything else)1919 @@ -1,4 +1,4 @@
1-// Command one23 is a Lotus 1-2-3 style spreadsheet for the terminal.1+// Command 012 is a Lotus 1-2-3 style spreadsheet for the terminal.22 package main33 44 import (@@ -9,20 +9,20 @@ import (
99 1010 tea "charm.land/bubbletea/v2"1111 12- "one23/internal/sheet"13- "one23/internal/ui"12+ "012/internal/sheet"13+ "012/internal/ui"1414 )1515 1616 func main() {1717 if err := run(os.Args[1:]); err != nil {18- fmt.Fprintln(os.Stderr, "one23:", err)18+ fmt.Fprintln(os.Stderr, "012:", err)1919 os.Exit(1)2020 }2121 }2222 2323 func run(args []string) error {2424 if len(args) > 1 {25- return errors.New("usage: one23 [file" + sheet.FileExt + "]")25+ return errors.New("usage: 012 [file" + sheet.FileExt + "]")2626 }2727 s, name := sheet.New(), ""2828 if len(args) == 1 {@@ -121,8 +121,8 @@ func TestSaveQuitReopen(t *testing.T) {
121121 s := start(t, dir)122122 s.keys("Budget", "<enter>", "1200", "<enter>", "=A2*12", "<enter>")123123 s.keys("<ctrl+s>", "budget", "<enter>")124- s.eventually("title update", func() bool { return s.title() == "one23 - budget.o23" })125- if _, err := os.Stat(filepath.Join(dir, "budget.o23")); err != nil {124+ s.eventually("title update", func() bool { return s.title() == "012 - budget.012" })125+ if _, err := os.Stat(filepath.Join(dir, "budget.012")); err != nil {126126 t.Fatal(err)127127 }128128 @@ -133,9 +133,9 @@ func TestSaveQuitReopen(t *testing.T) {
133133 t.Errorf("active screen after exit = %v, want primary", scr)134134 }135135 136- r := start(t, dir, "budget.o23")136+ r := start(t, dir, "budget.012")137137 r.waitForLine(gridRow1+2, numRow(3, "14400"))138- if !strings.Contains(r.screen(), "budget.o23") {138+ if !strings.Contains(r.screen(), "budget.012") {139139 t.Errorf("status line missing file name:\n%s", r.screen())140140 }141141 }@@ -35,11 +35,11 @@ func TestFormatsSurviveSaveAndReopen(t *testing.T) {
3535 s.keys("1450", "<enter>", "<up>", "<ctrl+shift+1>", "<ctrl+i>")3636 s.waitForLine(gridRow1, numRow(1, "1,450.00"))3737 s.keys("<ctrl+s>", "formats", "<enter>")38- s.eventually("saved", func() bool { return s.title() == "one23 - formats.o23" })38+ s.eventually("saved", func() bool { return s.title() == "012 - formats.012" })3939 s.keys("<ctrl+q>")4040 s.waitExit()4141 42- r := start(t, dir, "formats.o23")42+ r := start(t, dir, "formats.012")4343 r.waitForLine(gridRow1, numRow(1, "1,450.00"))4444 if !strings.Contains(r.html(), "font-style:italic") {4545 t.Errorf("italic lost after reopening:\n%s", r.html())@@ -1,4 +1,4 @@
1-// Package e2e runs the real one23 binary on a pseudo-terminal and renders1+// Package e2e runs the real 012 binary on a pseudo-terminal and renders22 // its output with libghostty-vt, Ghostty's terminal emulator core. Tests33 // assert on what a user would actually see: screen text, cursor position,44 // window title and screen mode. Keystrokes are encoded by libghostty from@@ -22,16 +22,16 @@ import (
2222 var binPath string2323 2424 func TestMain(m *testing.M) {25- dir, err := os.MkdirTemp("", "one23-e2e")25+ dir, err := os.MkdirTemp("", "012-e2e")2626 if err != nil {2727 panic(err)2828 }29- binPath = filepath.Join(dir, "one23")30- build := exec.Command("go", "build", "-o", binPath, "./cmd/one23")29+ binPath = filepath.Join(dir, "012")30+ build := exec.Command("go", "build", "-o", binPath, "./cmd/012")3131 build.Dir = ".."3232 build.Env = append(os.Environ(), "CGO_ENABLED=0")3333 if out, err := build.CombinedOutput(); err != nil {34- fmt.Fprintf(os.Stderr, "building one23: %v\n%s", err, out)34+ fmt.Fprintf(os.Stderr, "building 012: %v\n%s", err, out)3535 os.Exit(1)3636 }3737 code := m.Run()@@ -41,7 +41,7 @@ func TestMain(m *testing.M) {
4141 4242 const waitTimeout = 5 * time.Second4343 44-// session is one running instance of one23 attached to a virtual terminal.44+// session is one running instance of 012 attached to a virtual terminal.4545 type session struct {4646 t *testing.T4747 dir string@@ -68,7 +68,7 @@ type options struct {
6868 light bool // use the light reference palette6969 }7070 71-// start launches one23 in dir (a fresh temp dir if empty) with args.71+// start launches 012 in dir (a fresh temp dir if empty) with args.7272 func start(t *testing.T, dir string, args ...string) *session {7373 t.Helper()7474 return startWith(t, options{dir: dir}, args...)@@ -123,7 +123,7 @@ var screens = []screen{
123123 {name: "menu-narrow", opts: options{cols: 60, rows: 16}, setup: func(s *session) {124124 budget(s)125125 s.keys("<alt+h>")126- s.waitFor("│ About one23")126+ s.waitFor("│ About 012")127127 }},128128 {name: "functions", setup: func(s *session) {129129 budget(s)@@ -276,7 +276,7 @@ func (s *session) stableHTML() string {
276276 // with each reference palette.277277 func writeGallery(dir string) error {278278 var b strings.Builder279- b.WriteString(`<!doctype html><meta charset="utf-8"><title>one23 screens</title>279+ b.WriteString(`<!doctype html><meta charset="utf-8"><title>012 screens</title>280280 <style>281281 body{margin:0;padding:32px;background:#0f1012;color:#ddd;font:14px system-ui,sans-serif}282282 h1{font-weight:600;margin:0 0 24px}@@ -298,7 +298,7 @@ h2{font:500 13px ui-monospace,monospace;color:#aaa;margin:0 0 8px}
298298 }299299 b.WriteString("}\n")300300 }301- b.WriteString("</style><h1>one23 screens</h1><div class=grid>\n")301+ b.WriteString("</style><h1>012 screens</h1><div class=grid>\n")302302 for _, sc := range screens {303303 body, err := os.ReadFile(filepath.Join(dir, sc.name+".html"))304304 if err != nil {@@ -20,10 +20,10 @@ func TestAltFOpensFileMenuAndSaves(t *testing.T) {
2020 s.waitFor("MENU")2121 // "s" highlights Save (Save as also starts with s); Enter runs it.2222 s.keys("s", "<enter>")23- s.waitFor("Save as: SHEET1.o23")23+ s.waitFor("Save as: SHEET1.012")2424 s.keys("<enter>")2525 s.eventually("saved file", func() bool {26- _, err := os.Stat(filepath.Join(dir, "SHEET1.o23"))26+ _, err := os.Stat(filepath.Join(dir, "SHEET1.012"))2727 return err == nil2828 })2929 s.waitFor("READY")@@ -1,6 +1,6 @@
11 <pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">MENU</span><span style="background:var(--vt-palette-6)"> </span>22 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">A1</span><span style="background:var(--vt-palette-8)"> </span>3-one23 devel: Lotus 1-2-3 looks, Google Sheets keys. <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Esc</span><span style="background:var(--vt-palette-8)"> </span> Close3+012 devel: Lotus 1-2-3 looks, Google Sheets keys. <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Esc</span><span style="background:var(--vt-palette-8)"> </span> Close44 <span style="background:var(--vt-palette-8)"> </span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">A</span><span style="background:var(--vt-palette-6)"> </span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">B</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">C</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">D</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">E</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">F</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">G</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">H</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">I</span><span style="background:var(--vt-palette-8)"> </span>55 <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">1</span><span style="background:var(--vt-palette-6)"> </span>66 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">2</span><span style="background:var(--vt-palette-8)"> </span>@@ -4,7 +4,7 @@
44 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">A</span><span style="background:var(--vt-palette-8)"> </span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">B</span><span style="background:var(--vt-palette-6)"> </span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-8)">│</span> Keyboard shortcuts <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+/</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>55 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">1</span><span style="background:var(--vt-palette-8)"> </span> Household budget 202<span style="color:var(--vt-palette-8)">│</span> Function list <span style="color:var(--vt-palette-8)">│</span>66 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">2</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">├───────────────────────────────┤</span>7-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">3</span><span style="background:var(--vt-palette-8)"> </span> Rent 1450 <span style="color:var(--vt-palette-8)">│</span> About one23 <span style="color:var(--vt-palette-8)">│</span>7+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">3</span><span style="background:var(--vt-palette-8)"> </span> Rent 1450 <span style="color:var(--vt-palette-8)">│</span> About 012 <span style="color:var(--vt-palette-8)">│</span>88 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">4</span><span style="background:var(--vt-palette-8)"> </span> Groceries 612.4 <span style="color:var(--vt-palette-8)">└───────────────────────────────┘</span>99 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">5</span><span style="background:var(--vt-palette-8)"> </span> Transit 961010 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">6</span><span style="background:var(--vt-palette-8)"> </span> Savings r <span style="color:var(--vt-palette-1)">#DIV/0!</span>@@ -7,7 +7,7 @@ import (
77 88 tea "charm.land/bubbletea/v2"99 10- "one23/internal/sheet"10+ "012/internal/sheet"1111 )1212 1313 // command is a user-facing action. Every action is registered once here@@ -144,7 +144,7 @@ func init() {
144144 &command{id: "file.save", title: "Save", desc: "Save the sheet", run: (*Model).save},145145 &command{id: "file.saveas", title: "Save as", desc: "Save the sheet under a new name", run: (*Model).openSave},146146 &command{id: "file.open", title: "Open", desc: "Open a sheet, replacing this one", run: (*Model).openRetrieve},147- &command{id: "quit", title: "Quit", desc: "Close one23", run: (*Model).quit},147+ &command{id: "quit", title: "Quit", desc: "Close 012", run: (*Model).quit},148148 )149149 }150150 @@ -10,7 +10,7 @@ import (
1010 tea "charm.land/bubbletea/v2"1111 "github.com/charmbracelet/x/ansi"1212 13- "one23/internal/sheet"13+ "012/internal/sheet"1414 )1515 1616 func init() {@@ -25,9 +25,9 @@ func init() {
2525 m.openOverlay(p)2626 return nil2727 }},28- &command{id: "help.about", title: "About one23", desc: "Show the version", run: func(m *Model) tea.Cmd {28+ &command{id: "help.about", title: "About 012", desc: "Show the version", run: func(m *Model) tea.Cmd {2929 m.openOverlay(&choiceBar{30- msg: "one23 " + version() + ": Lotus 1-2-3 looks, Google Sheets keys.",30+ msg: "012 " + version() + ": Lotus 1-2-3 looks, Google Sheets keys.",3131 choices: []choice{3232 {key: "esc", label: "Close", run: func(*Model) tea.Cmd { return nil }},3333 },@@ -39,7 +39,7 @@ func init() {
3939 keymap["ctrl+/"] = "help"4040 }4141 42-// version is the release one23 was built from, or "devel" for a source42+// version is the release 012 was built from, or "devel" for a source4343 // build (including Go's pseudo-versions such as v0.0.0-2026...+dirty).4444 func version() string {4545 if bi, ok := debug.ReadBuildInfo(); ok && strings.HasPrefix(bi.Main.Version, "v") && !strings.ContainsAny(bi.Main.Version, "-+") {@@ -114,7 +114,7 @@ func TestOverlaysOnTinyScreens(t *testing.T) {
114114 func TestAbout(t *testing.T) {115115 m := newModel()116116 m.runCommand("help.about")117- if l := line(m, contextLine); !strings.HasPrefix(l, "one23 devel") || !strings.Contains(l, "Esc Close") {117+ if l := line(m, contextLine); !strings.HasPrefix(l, "012 devel") || !strings.Contains(l, "Esc Close") {118118 t.Errorf("about %q", l)119119 }120120 press(t, m, "<esc>")@@ -10,7 +10,7 @@ import (
1010 tea "charm.land/bubbletea/v2"1111 "github.com/charmbracelet/x/ansi"1212 13- "one23/internal/sheet"13+ "012/internal/sheet"1414 )1515 1616 var named = map[string]tea.Key{@@ -367,10 +367,10 @@ func TestSaveAndOpen(t *testing.T) {
367367 m := newModel()368368 press(t, m, "Hello", "<enter>", "42", "<enter>")369369 press(t, m, "<ctrl+s>", "budget", "<enter>")370- if m.filename != "budget.o23" || m.changed {370+ if m.filename != "budget.012" || m.changed {371371 t.Fatalf("filename %q changed %v err %q", m.filename, m.changed, m.errMsg)372372 }373- if _, err := os.Stat("budget.o23"); err != nil {373+ if _, err := os.Stat("budget.012"); err != nil {374374 t.Fatal(err)375375 }376376 // Ctrl+S again saves without asking.@@ -381,11 +381,11 @@ func TestSaveAndOpen(t *testing.T) {
381381 382382 m2 := newModel()383383 press(t, m2, "<ctrl+o>")384- if !strings.Contains(line(m2, 2), "budget.o23") {384+ if !strings.Contains(line(m2, 2), "budget.012") {385385 t.Errorf("file list %q", line(m2, 2))386386 }387387 press(t, m2, "budget", "<enter>")388- if m2.sheet.Value(addr("A2")).Num != 42 || m2.filename != "budget.o23" {388+ if m2.sheet.Value(addr("A2")).Num != 42 || m2.filename != "budget.012" {389389 t.Errorf("opened A2 %+v file %q", m2.sheet.Value(addr("A2")), m2.filename)390390 }391391 press(t, m2, "<ctrl+o>", "missing", "<enter>")@@ -10,7 +10,7 @@ import (
1010 tea "charm.land/bubbletea/v2"1111 "github.com/charmbracelet/x/ansi"1212 13- "one23/internal/sheet"13+ "012/internal/sheet"1414 )1515 1616 // The control panel is three lines: the menu bar with the mode indicator,@@ -35,7 +35,7 @@ func (m *Model) View() tea.View {
3535 v := tea.NewView(content)3636 v.AltScreen = true3737 v.MouseMode = tea.MouseModeAllMotion // hover feedback; see mouse.go38- v.WindowTitle = "one23 - " + m.displayName()38+ v.WindowTitle = "012 - " + m.displayName()3939 if m.changed {4040 v.WindowTitle += " (modified)"4141 }@@ -137,7 +137,7 @@ func (m *Model) spread(left, right string) string {
137137 return left + strings.Repeat(" ", gap) + right138138 }139139 140-// promptLine is an open prompt, e.g. "Save as: budget.o23", and the keys140+// promptLine is an open prompt, e.g. "Save as: budget.012", and the keys141141 // or choices that go with it.142142 func (m *Model) promptLine() (left, right string) {143143 switch {@@ -116,7 +116,7 @@ func TestMenuBarKeyboard(t *testing.T) {
116116 if highlighted(t, m) != "Quit" {117117 t.Errorf("up highlighted %q", highlighted(t, m))118118 }119- if !strings.Contains(line(m, m.height-1), "Close one23") {119+ if !strings.Contains(line(m, m.height-1), "Close 012") {120120 t.Errorf("status line should describe the item: %q", line(m, m.height-1))121121 }122122 press(t, m, "<right>")@@ -7,7 +7,7 @@ import (
77 88 "github.com/xuri/excelize/v2"99 10- "one23/internal/sheet"10+ "012/internal/sheet"1111 )1212 1313 // patterns are number formats: Sheets' Format > Number presets and@@ -21,10 +21,10 @@ var patterns = []string{
2121 2222 var values = []float64{0, 1, -1, 0.5, 1234.5678, -1234.5678, 0.125, 1e6, 46291, 46291.6041666667, 1.0423611111}2323 24-// skippedFormats are pattern and value pairs where one23 deliberately24+// skippedFormats are pattern and value pairs where 012 deliberately2525 // differs, or excelize departs from Excel.2626 var skippedFormats = map[string]string{27- "#,##0, -1": "one23 drops the sign of a number that rounds to zero; excelize shows -0",27+ "#,##0, -1": "012 drops the sign of a number that rounds to zero; excelize shows -0",2828 "0.0# 46291.6041666667": "excelize keeps a trailing zero under #",2929 "[h]:mm:ss 1e+06": "excelize overflows",3030 }@@ -35,7 +35,7 @@ func skipReason(pat string, v float64) string {
3535 case isDateOrTime(pat) && v < 0:3636 return "dates before 1899-12-30 are negative serials in Sheets; Excel has none"3737 case isDate(pat) && v < 61:38- return "Excel counts a fictitious 1900-02-29, so its dates before March 1900 are a day off; Sheets and one23 don't"38+ return "Excel counts a fictitious 1900-02-29, so its dates before March 1900 are a day off; Sheets and 012 don't"3939 }4040 return skippedFormats[fmt.Sprintf("%s %v", pat, v)]4141 }@@ -62,10 +62,10 @@ func TestFormatsAgainstExcelize(t *testing.T) {
6262 t.Fatal(err)6363 }6464 ours := sheet.FormatPattern(v, pat)65- // one23 shows AM and PM in capitals, as Sheets' Time format does;65+ // 012 shows AM and PM in capitals, as Sheets' Time format does;6666 // excelize copies the case of the pattern's am/pm.6767 if ours != theirs && !(strings.Contains(pat, "am/pm") && strings.EqualFold(ours, theirs)) {68- t.Errorf("%q of %v: one23 %q, excelize %q", pat, v, ours, theirs)68+ t.Errorf("%q of %v: 012 %q, excelize %q", pat, v, ours, theirs)6969 continue7070 }7171 matched++@@ -1,4 +1,4 @@
1-// Package oracle checks one23's formula engine against excelize's1+// Package oracle checks 012's formula engine against excelize's22 // calculation engine: the same data and formulas go into both, and the33 // results must agree. It is a separate module so the main binary never44 // depends on excelize. Run it with make oracle.@@ -12,7 +12,7 @@ import (
1212 1313 "github.com/xuri/excelize/v2"1414 15- "one23/internal/sheet"15+ "012/internal/sheet"1616 )1717 1818 // fixture is the data both engines read, typed as a user would.@@ -107,10 +107,10 @@ var formulas = []string{
107107 `=RATE(48, -200, 8000)`, `=NPV(0.1, H1:H4)`, `=IRR(H1:H4)`, `=IRR(C1:C5)`,108108 }109109 110-// skipped are formulas where Google Sheets, which one23 follows, and110+// skipped are formulas where Google Sheets, which 012 follows, and111111 // Excel disagree, or where excelize departs from Excel. Each says why.112112 var skipped = map[string]string{113- // Sheets and Excel disagree; one23 follows Sheets.113+ // Sheets and Excel disagree; 012 follows Sheets.114114 `=ROUND(-2.5)`: "Sheets makes ROUND's places optional; Excel requires them",115115 116116 // excelize departs from Excel (and Sheets) here.@@ -140,7 +140,7 @@ var skipped = map[string]string{
140140 `=NPV(0.1, H1:H4)`: "excelize reads only the first cell of a range",141141 }142142 143-// toExcel translates a one23 formula to Excel syntax: 1-2-3 style @SUM143+// toExcel translates a 012 formula to Excel syntax: 1-2-3 style @SUM144144 // and A1..B2 ranges become SUM and A1:B2, and the leading = goes.145145 func toExcel(f string) string {146146 f = strings.TrimPrefix(f, "=")@@ -177,7 +177,7 @@ func TestToExcel(t *testing.T) {
177177 }178178 }179179 180-// build loads the fixture into one23, then copies the values one23180+// build loads the fixture into 012, then copies the values 012181181 // computed into an excelize workbook, so both start from the same data.182182 func build(t *testing.T) (*sheet.Sheet, *excelize.File) {183183 t.Helper()@@ -225,7 +225,7 @@ func TestAgainstExcelize(t *testing.T) {
225225 continue226226 }227227 if err := s.Set(addr(cell), f); err != nil {228- t.Errorf("one23 rejects %s: %v", f, err)228+ t.Errorf("012 rejects %s: %v", f, err)229229 continue230230 }231231 ours := s.Value(addr(cell))@@ -240,12 +240,12 @@ func TestAgainstExcelize(t *testing.T) {
240240 matched++241241 continue242242 }243- t.Errorf("%s: one23 %s, excelize %q", f, show(ours), theirs)243+ t.Errorf("%s: 012 %s, excelize %q", f, show(ours), theirs)244244 }245245 t.Logf("%d of %d formulas match excelize, %d skipped", matched, len(formulas)-len(skipped), len(skipped))246246 }247247 248-// agree compares a one23 value with excelize's text result: numbers to248+// agree compares a 012 value with excelize's text result: numbers to249249 // about 12 significant digits, booleans as TRUE/FALSE, errors by code.250250 func agree(v sheet.Value, x string) bool {251251 switch v.Kind {