Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
40308f2
feat: define and parse specs format
ascandone Jun 19, 2025
782292b
quick and dirty prototype
ascandone Jun 19, 2025
4b6f9bd
prototype v2
ascandone Jun 27, 2025
3e9beba
colored diff!
ascandone Jun 27, 2025
4264a63
minor
ascandone Jun 27, 2025
8e7ea3d
proto
ascandone Jun 27, 2025
db11997
impl better UI for balances
ascandone Jun 27, 2025
9fa0cad
improve prettyprint function
ascandone Jul 1, 2025
8a9abff
improve pprint
ascandone Jul 1, 2025
d4218a8
improve ui
ascandone Jul 1, 2025
434e8ec
improve run cmd ui
ascandone Jul 1, 2025
9d324b7
improve UI
ascandone Jul 1, 2025
a2c4f96
refactor
ascandone Jul 1, 2025
f6983ae
edit col
ascandone Jul 1, 2025
0e625ab
show files stats
ascandone Jul 1, 2025
9d5df76
improve schema
ascandone Jul 2, 2025
b6631bd
change field name
ascandone Jul 2, 2025
7ab8d47
add col
ascandone Jul 2, 2025
6e26269
add util
ascandone Jul 2, 2025
ab69c69
add interactive mode mvp
ascandone Jul 2, 2025
59c01eb
allow feature flags
ascandone Jul 2, 2025
81c6eb2
refactor assertions
ascandone Jul 7, 2025
c6e87af
show failed assertion's name
ascandone Jul 8, 2025
7f1c691
better handling of panic
ascandone Jul 8, 2025
b6d0624
refactor
ascandone Jul 8, 2025
f862e77
improve err
ascandone Jul 8, 2025
ef9fcf1
test cmd flags
ascandone Jul 8, 2025
a620797
removed comments
ascandone Jul 8, 2025
9dec055
refactor
ascandone Jul 8, 2025
a8ba47b
WIP broken
ascandone Jul 8, 2025
c6d8d56
fix assertions
ascandone Jul 8, 2025
a3dfec2
fix newline
ascandone Jul 10, 2025
dc28320
defined schema for specs format
ascandone Jul 10, 2025
420fd5c
improve description
ascandone Jul 10, 2025
33ee124
change fields name
ascandone Jul 10, 2025
6394369
change color
ascandone Jul 10, 2025
779d6c3
fix schema
ascandone Jul 10, 2025
2262251
add required field to schema
ascandone Jul 12, 2025
d695d09
update schema
ascandone Jul 12, 2025
be21b89
minor
ascandone Jul 14, 2025
1d9901f
polish errors
ascandone Jul 14, 2025
8bc2ded
fix
ascandone Jul 16, 2025
f099c1c
linter fix
ascandone Jul 16, 2025
a888a9d
improve coverage
ascandone Jul 16, 2025
d905a87
improved coverage
ascandone Jul 16, 2025
a7a2285
more tests
ascandone Jul 16, 2025
da404ed
removed comment
ascandone Jul 16, 2025
46ab23d
removed dead code
ascandone Jul 16, 2025
1618b8b
edit gitignore
ascandone Jul 16, 2025
856a89b
more tests
ascandone Jul 16, 2025
db16df7
fix lint errs
ascandone Jul 16, 2025
32d6e05
removed unused code
ascandone Jul 16, 2025
6f9f25c
fix typo
ascandone Jul 16, 2025
29a351f
refactor: moved files in the specs_format module
ascandone Jul 17, 2025
1302479
feat: make it work recursively
ascandone Jul 18, 2025
16d12a8
feat: also allow single file
ascandone Jul 18, 2025
5829de1
fix
ascandone Jul 18, 2025
4eead8a
refactor: moved function in different module
ascandone Jul 18, 2025
6be6937
feat: renamed fields
ascandone Jul 18, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@
dist/

coverage.*

.direnv
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@ go 1.22.1
require (
github.com/antlr4-go/antlr/v4 v4.13.1
github.com/gkampitakis/go-snaps v0.5.13
github.com/sergi/go-diff v1.0.0
)

require gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect

require github.com/goccy/go-yaml v1.18.0 // indirect

require (
Expand Down
5 changes: 4 additions & 1 deletion go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/f
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
Expand All @@ -59,7 +61,8 @@ golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
79 changes: 76 additions & 3 deletions internal/ansi/ansi.go
Original file line number Diff line number Diff line change
@@ -1,22 +1,95 @@
package ansi

import "fmt"
import (
"fmt"
"strings"
)

const resetCol = "\033[0m"

func Compose(cols ...func(string) string) func(string) string {
return func(s string) string {
for _, mod := range cols {
s = mod(s)
}
return s
}
}

func replaceLast(s, oldStr, newStr string) string {
lastIndex := strings.LastIndex(s, oldStr)
if lastIndex == -1 {
return s
}
return s[:lastIndex] + newStr + s[lastIndex+len(oldStr):]
}

func col(s string, code int) string {
c := fmt.Sprintf("\033[%dm", code)
return c + s + resetCol
colorCode := fmt.Sprintf("\033[%dm", code)
// This trick should allow to stack colors (TODO test)
s = replaceLast(s, resetCol, resetCol+colorCode)
return colorCode + s + resetCol
}

func ColorRed(s string) string {
return col(s, 31)
}

func ColorWhite(s string) string {
return col(s, 37)
}

func ColorGreen(s string) string {
return col(s, 32)
}

func ColorYellow(s string) string {
return col(s, 33)
}

func ColorCyan(s string) string {
return col(s, 36)
}

func ColorLight(s string) string {
return col(s, 97) // Bright white → light
}

func ColorBrightBlack(s string) string {
return col(s, 90)
}

func ColorBrightRed(s string) string {
return col(s, 91)
}

func ColorBrightGreen(s string) string {
return col(s, 92)
}

func ColorBrightYellow(s string) string {
return col(s, 93)
}

// BG
func BgDark(s string) string {
return col(s, 100)
}

func BgRed(s string) string {
return col(s, 41)
}

func BgGreen(s string) string {
return col(s, 42)
}

// modifiers

func Bold(s string) string {
return col(s, 1)
}

func Underline(s string) string {
return col(s, 4)
}
1 change: 1 addition & 0 deletions internal/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ func Execute(options CliOptions) {

rootCmd.AddCommand(lspCmd)
rootCmd.AddCommand(checkCmd)
rootCmd.AddCommand(getTestCmd())
rootCmd.AddCommand(getRunCmd())

if err := rootCmd.Execute(); err != nil {
Expand Down
32 changes: 12 additions & 20 deletions internal/cmd/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (
"os"
"strings"

"github.com/formancehq/numscript/internal/ansi"
"github.com/formancehq/numscript/internal/flags"
"github.com/formancehq/numscript/internal/interpreter"
"github.com/formancehq/numscript/internal/parser"
Expand All @@ -21,7 +20,7 @@ const (
OutputFormatJson = "json"
)

type Args struct {
type runArgs struct {
VariablesOpt string
BalancesOpt string
MetaOpt string
Expand All @@ -38,7 +37,7 @@ type inputOpts struct {
Balances interpreter.Balances `json:"balances"`
}

func (o *inputOpts) fromRaw(opts Args) error {
func (o *inputOpts) fromRaw(opts runArgs) error {
if opts.RawOpt == "" {
return nil
}
Expand All @@ -50,7 +49,7 @@ func (o *inputOpts) fromRaw(opts Args) error {
return nil
}

func (o *inputOpts) fromStdin(opts Args) error {
func (o *inputOpts) fromStdin(opts runArgs) error {
if !opts.StdinFlag {
return nil
}
Expand All @@ -67,7 +66,7 @@ func (o *inputOpts) fromStdin(opts Args) error {
return nil
}

func (o *inputOpts) fromOptions(path string, opts Args) error {
func (o *inputOpts) fromOptions(path string, opts runArgs) error {
if path != "" {
numscriptContent, err := os.ReadFile(path)
if err != nil {
Expand Down Expand Up @@ -108,7 +107,7 @@ func (o *inputOpts) fromOptions(path string, opts Args) error {
return nil
}

func run(path string, opts Args) error {
func run(path string, opts runArgs) error {
opt := inputOpts{
Variables: make(map[string]string),
Meta: make(interpreter.AccountsMetadata),
Expand Down Expand Up @@ -172,26 +171,19 @@ func showJson(result *interpreter.ExecutionResult) error {
}

func showPretty(result *interpreter.ExecutionResult) error {
fmt.Println(ansi.ColorCyan("Postings:"))
postingsJson, err := json.MarshalIndent(result.Postings, "", " ")
if err != nil {
return fmt.Errorf("error marshaling postings: %w", err)
}
fmt.Println(string(postingsJson))

fmt.Println()
fmt.Println("Postings:")
fmt.Println(interpreter.PrettyPrintPostings(result.Postings))

fmt.Println(ansi.ColorCyan("Meta:"))
txMetaJson, err := json.MarshalIndent(result.Metadata, "", " ")
if err != nil {
return fmt.Errorf("error marshaling metadata: %w", err)
if len(result.Metadata) != 0 {
fmt.Println("Meta:")
fmt.Println(interpreter.PrettyPrintMeta(result.Metadata))
}
fmt.Println(string(txMetaJson))

return nil
}

func getRunCmd() *cobra.Command {
opts := Args{}
opts := runArgs{}

cmd := cobra.Command{
Use: "run",
Expand Down
51 changes: 51 additions & 0 deletions internal/cmd/test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package cmd

import (
"os"

"github.com/formancehq/numscript/internal/specs_format"
"github.com/spf13/cobra"
)

type testArgs struct {
paths []string
}

var opts = testArgs{}

func runTestCmd() {
files, err := specs_format.ReadSpecsFiles(opts.paths)
if err != nil {
_, _ = os.Stderr.Write([]byte(err.Error()))
os.Exit(1)
return
}

pass := specs_format.RunSpecs(os.Stdout, os.Stderr, files)
if !pass {
os.Exit(1)
}
}

func getTestCmd() *cobra.Command {

cmd := &cobra.Command{
Use: "test folder...",
Short: "Test numscript file using the numscript specs format",
Long: `Searches for any <file>.num.specs files in the given directory (or directories),
and tests the corresponding <file>.num file (if any).
Defaults to "." if there are no given paths`,
Args: cobra.MatchAll(),
Run: func(cmd *cobra.Command, paths []string) {

if len(paths) == 0 {
paths = []string{"."}
}

opts.paths = paths
runTestCmd()
},
}

return cmd
}
7 changes: 7 additions & 0 deletions internal/interpreter/__snapshots__/balances_test.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@

[TestPrettyPrintBalance - 1]
| Account | Asset | Balance |
| alice | EUR/2 | 1 |
| alice | USD/1234 | 999999 |
| bob | BTC | 3 |
---
50 changes: 50 additions & 0 deletions internal/interpreter/accounts_metadata.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package interpreter

import (
"github.com/formancehq/numscript/internal/utils"
)

func (m AccountsMetadata) fetchAccountMetadata(account string) AccountMetadata {
return utils.MapGetOrPutDefault(m, account, func() AccountMetadata {
return AccountMetadata{}
})
}

func (m AccountsMetadata) DeepClone() AccountsMetadata {
cloned := make(AccountsMetadata)
for account, accountBalances := range m {
for asset, metadataValue := range accountBalances {
clonedAccountBalances := cloned.fetchAccountMetadata(account)
utils.MapGetOrPutDefault(clonedAccountBalances, asset, func() string {
return metadataValue
})
}
}
return cloned
}

func (m AccountsMetadata) Merge(update AccountsMetadata) {
for acc, accBalances := range update {
cachedAcc := utils.MapGetOrPutDefault(m, acc, func() AccountMetadata {
return AccountMetadata{}
})

for curr, amt := range accBalances {
cachedAcc[curr] = amt
}
}
}

func (m AccountsMetadata) PrettyPrint() string {
header := []string{"Account", "Name", "Value"}

var rows [][]string
for account, accMetadata := range m {
for name, value := range accMetadata {
row := []string{account, name, value}
rows = append(rows, row)
}
}

return utils.CsvPretty(header, rows, true)
}
Loading
Loading