From a2c99c710ee2c73967b8a437abd5bd26438e8029 Mon Sep 17 00:00:00 2001 From: Justin Lin Date: Fri, 25 Jul 2025 12:23:47 +1000 Subject: [PATCH] feat: add inline and block runner. Improve execution error handling --- examples/ledger.j | 72 ++++++++++++++++++++++++ examples/reports_2011-01.md | 109 ++++++++++++++++++++++++++++++++++++ monthly.md | 45 --------------- parse.go | 2 +- render.go | 19 +------ types.go | 34 +++++++++++ 6 files changed, 218 insertions(+), 63 deletions(-) create mode 100644 examples/ledger.j create mode 100644 examples/reports_2011-01.md delete mode 100644 monthly.md diff --git a/examples/ledger.j b/examples/ledger.j new file mode 100644 index 0000000..9fd375b --- /dev/null +++ b/examples/ledger.j @@ -0,0 +1,72 @@ +; -*- ledger -*- +; This example file retrived from ledger-cli.org example journal file +; https://ledger-cli.org/doc/ledger3.html#Example-Journal-File + += /^Income/ + (Liabilities:Tithe) 0.12 + +~ Monthly + Assets:Checking $500.00 + Income:Salary + +~ Monthly + Expenses:Food $100 + Assets + +2010/12/01 * Checking balance + Assets:Checking $1,000.00 + Equity:Opening Balances + +2010/12/20 * Organic Co-op + Expenses:Food:Groceries $ 37.50 ; [=2011/01/01] + Expenses:Food:Groceries $ 37.50 ; [=2011/02/01] + Expenses:Food:Groceries $ 37.50 ; [=2011/03/01] + Expenses:Food:Groceries $ 37.50 ; [=2011/04/01] + Expenses:Food:Groceries $ 37.50 ; [=2011/05/01] + Expenses:Food:Groceries $ 37.50 ; [=2011/06/01] + Assets:Checking $ -225.00 + +2010/12/28=2011/01/01 Acme Mortgage + Liabilities:Mortgage:Principal $ 200.00 + Expenses:Interest:Mortgage $ 500.00 + Expenses:Escrow $ 300.00 + Assets:Checking $ -1000.00 + +2011/01/02 Grocery Store + Expenses:Food:Groceries $ 65.00 + Assets:Checking + +2011/01/05 Employer + Assets:Checking $ 2000.00 + Income:Salary + +2011/01/14 Bank + ; Regular monthly savings transfer + Assets:Savings $ 300.00 + Assets:Checking + +2011/01/19 Grocery Store + Expenses:Food:Groceries $ 44.00 ; hastag: not block + Assets:Checking + +2011/01/25 Bank + ; Transfer to cover car purchase + Assets:Checking $ 5,500.00 + Assets:Savings + ; :nobudget: + +apply tag hastag: true +apply tag nestedtag: true +2011/01/25 Tom's Used Cars + Expenses:Auto $ 5,500.00 + ; :nobudget: + Assets:Checking + +2011/01/27 Book Store + Expenses:Books $20.00 + Liabilities:MasterCard +end tag +2011/12/01 Sale + Assets:Checking:Business $ 30.00 + Income:Sales +end tag diff --git a/examples/reports_2011-01.md b/examples/reports_2011-01.md new file mode 100644 index 0000000..a8ac285 --- /dev/null +++ b/examples/reports_2011-01.md @@ -0,0 +1,109 @@ +# Finance Report from 2011-01 to 2011-02 + +## Previous status: + + +``` +Balance Sheet 2010-12-31 + + || 2010-12-31 +================================++============ + Assets || +--------------------------------++------------ + Assets:Checking || $-225.00 +--------------------------------++------------ + || $-225.00 +================================++============ + Liabilities || +--------------------------------++------------ + Liabilities:Mortgage:Principal || $-200.00 +--------------------------------++------------ + || $-200.00 +================================++============ + Net: || $-25.00 + +``` + + +## Expenses: + +### Expense Summary by Category + + +``` +Budget performance in 2011-01: + + || Jan +===============++============================== + Expenses || $5,629.00 + Expenses:Food || $109.00 [ 109% of $100.00] +---------------++------------------------------ + || $5,629.00 [5629% of $100.00] + +``` + + + +``` + $5,500.00 Expenses:Auto + $109.00 Expenses:Food + $20.00 Expenses:Books +-------------------- + $5,629.00 + +``` + + +## Net Income: + + +``` +Income Statement 2011-01 + + || Jan +=========================++============ + Revenues || +-------------------------++------------ + Income:Salary || $2,000.00 +-------------------------++------------ + || $2,000.00 +=========================++============ + Expenses || +-------------------------++------------ + Expenses:Auto || $5,500.00 + Expenses:Books || $20.00 + Expenses:Food:Groceries || $109.00 +-------------------------++------------ + || $5,629.00 +=========================++============ + Net: || $-3,629.00 + +``` + + +## Ending balance: + + +``` +Balance Sheet 2011-01-31 + + || 2011-01-31 +================================++============ + Assets || +--------------------------------++------------ + Assets:Checking || $1,366.00 + Assets:Savings || $-5,200.00 +--------------------------------++------------ + || $-3,834.00 +================================++============ + Liabilities || +--------------------------------++------------ + Liabilities:MasterCard || $20.00 + Liabilities:Mortgage:Principal || $-200.00 +--------------------------------++------------ + || $-180.00 +================================++============ + Net: || $-3,654.00 + +``` + diff --git a/monthly.md b/monthly.md deleted file mode 100644 index ce95f25..0000000 --- a/monthly.md +++ /dev/null @@ -1,45 +0,0 @@ ---- -output: "reports_{{.b}}.md" -params: - b: - required: true - usage: | - Required. - The begin time of report. - Examples: - 2025 - 2025-07 - 2025-07-15 - Jul - e: - required: true - usage: | - Required. - The end time of report. - Same format as -b - f: - default: ~/.hledger.journal - usage: The ledger file to parse ---- - -# Finance Report from {{.b}} to {{.e}} - -## Previous status: - -{{ sh "hledger -f $f balancesheet -e $b" }} - -## Expenses: - -### Expense Summary by Category - -{{ sh "hledger -f $f bal expenses -b $b -e $e --depth 3 --sort amount" }} - -{{ sh "hledger -f $f bal expenses -b $b -e $e --depth 2 --sort amount" }} - -## Net Income: - -{{sh "hledger -f $f income -b $b -e $e --monthly"}} - -## Ending balance: - -{{sh "hledger -f $f balancesheet -e $e"}} diff --git a/parse.go b/parse.go index 12fbd70..95c6699 100644 --- a/parse.go +++ b/parse.go @@ -23,7 +23,7 @@ func parseDocument(doc string) (frontmatter string, body string, err error) { err = fmt.Errorf("Invalid document: frontmatter required.\n%s", doc) return } - parts := strings.Split(doc, sep) + parts := strings.SplitN(doc, sep, 3) if len(parts) < 3 { err = fmt.Errorf("Invalid document: frontmatter required.\n%s", doc) return diff --git a/render.go b/render.go index 18ed0e3..88f51d8 100644 --- a/render.go +++ b/render.go @@ -2,28 +2,13 @@ package main import ( "bytes" - "fmt" - "os/exec" "text/template" ) -func sh(ctx TemplateContext) func(string) string { - return func(command string) string { - cmd := exec.Command("sh", "-c", command) - for k, v := range ctx { - cmd.Env = append(cmd.Env, k+"="+v) - } - out, err := cmd.Output() - if err != nil { - return fmt.Sprintf("> Failed to execute the command: %s\n> %s\n", cmd.Args, err.Error()) - } - return fmt.Sprintf("\n```\n%s\n```\n", string(out)) - } -} - func render(tmpl string, ctx TemplateContext) []byte { funcMap := template.FuncMap{ - "sh": sh(ctx), + "sh": ctx.inline, + "shell": ctx.codeBlock, } t, err := template.New("foo").Funcs(funcMap).Parse(tmpl) if err != nil { diff --git a/types.go b/types.go index 780a6ac..81abd7f 100644 --- a/types.go +++ b/types.go @@ -3,7 +3,10 @@ package main import ( "bytes" "encoding/json" + "errors" "fmt" + "os/exec" + "strings" "text/template" ) @@ -40,3 +43,34 @@ type ParamSpec struct { } type TemplateContext map[string]string + +func (ctx TemplateContext) run(command string) (string, error) { + cmd := exec.Command("sh", "-c", command) + var stdout bytes.Buffer + cmd.Stdout = &stdout + cmd.Stderr = &stdout + for k, v := range ctx { + cmd.Env = append(cmd.Env, k+"="+v) + } + err := cmd.Run() + if err != nil { + return "", errors.New(stdout.String()) + } + return stdout.String(), nil +} + +func (ctx TemplateContext) codeBlock(command string) string { + out, err := ctx.run(command) + if err != nil { + return fmt.Sprintf("> Failed to execute the command:\n> `%s`\n> %s\n", command, err.Error()) + } + return fmt.Sprintf("\n```\n%s\n```\n", out) +} + +func (ctx TemplateContext) inline(command string) string { + out, err := ctx.run(command) + if err != nil { + return fmt.Sprintf("`Failed to execute the command: '%s'. %s`", command, err.Error()) + } + return fmt.Sprintf("`%s`", strings.Trim(out, "\n ")) +}