ledger-ts:基于 TypeScript 的开源记账 DSL - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
爱意满满的作品展示区。
hamsterbase
V2EX    分享创造

ledger-ts:基于 TypeScript 的开源记账 DSL

  •  
      hamsterbase
    carytrivett 2024-02-19 22:07:12 +08:00 3233 次点击
    这是一个创建于 601 天前的主题,其中的信息可能已经有所发展或是发生改变。

    项目开源地址为 https://github.com/hamsterbase/ledger-ts/blob/main/src/example/beancount.ts

    使用之前推荐先学习一下 beancount 语法。

    在日常使用 beancount 记账时,我遇到了一些困扰。这些问题激发了我自行创造 DSL 的想法。简而言之,我的目标是开发一种更高级的记账语言,而将 beancount 当作底层的“汇编语言”。

    我选择基于 TypeScript 开发,通过一些工具函数来实现账单的记录。在 typescript 代码里定义所有的货币、账户和交易记录。借助这种方法,无需自己开发编译器。 可以充分利用 Visual Studio Code 强大的代码补全功能,而且还可以在 TypeScript 的类型系统中进行进阶操作和验证。

    此外,对于一些常见的账目模式,可以开发工具函数来进一步简化和自动化账单的处理

    这个是原始的 beancount 账单

    1970-10-01 commodity USD 1970-10-01 commodity CNY 1970-01-01 open Assets:CN:Cash CNY 1970-01-01 open Assets:Cash USD 1970-01-01 open Assets:UTrade:Account:AAPL USD 1970-01-01 open Assets:UTrade:Account:EWJ USD 1970-01-01 open Expenses:Food:Groceries USD 1970-01-01 open Expenses:Food:Alcool USD 1970-01-01 * "Distribution of cash expenses" Assets:Cash -300 USD Expenses:Food:Alcool 300 USD 1970-01-01 * "CN to usd" Assets:CN:Cash -700 CNY @@ 100 USD Assets:Cash 100 USD 

    这个是我发明的 DSL

    import { EAccountType, Ledger, utils } from "../index.js"; // 声明货币 const { USD, CNY } = utils.createCurrencies({ defaultDate: "1970-10-01" }, [ "USD", "CNY", ] as const); // 声明 Assets 账户 const Assets = utils.buildAccountHierarchy(USD, EAccountType.Assets, { CN: { Cash: utils.createAccountNodeConfig({ open: "1970-01-01", currency: CNY }), }, Cash: utils.createAccountNodeConfig({ open: "1970-01-01" }), UTrade: { Account: { AAPL: utils.createAccountNodeConfig({ open: "1970-01-01" }), EWJ: utils.createAccountNodeConfig({ open: "1970-01-01" }), }, }, }); // 声明消费 Expenses 账户 const Expenses = utils.buildAccountHierarchy(USD, EAccountType.Expenses, { Food: { Groceries: utils.createAccountNodeConfig({ open: "1970-01-01" }), Alcool: utils.createAccountNodeConfig({ open: "1970-01-01" }), }, }); const ledger = new Ledger( [ ...utils.flattenAccountHierarchy(Assets), ...utils.flattenAccountHierarchy(Expenses), ], [USD, CNY] ); const { tr } = utils.transactionBuilder(ledger); // 记录账单 tr( "1970-01-01", "Distribution of cash expenses", Assets.Cash.posting(-300), Expenses.Food.Alcool.posting(300) ); tr( "1970-01-01", "CN to usd", Assets.CN.Cash.posting(-700).asCost(100, USD), Assets.Cash.posting(100) ); console.log(utils.beanCount.serializationLedger(ledger)); 

    基于 typescript ,可以很方便的编写复杂逻辑。

    比如 ledger 就内置了 prepaid 分帐这个函数,可以很方便的将年付的订阅服务,均摊到每一个月。

     ledger.transaction( ...prepaid({ date: "2021-01-03", start: "2021-01-01", from: assets.CN.Bank.Card.USTC, to: expenses.XGP, amount: -100, prepaid: assets.Prepaid, parts: 12, payee: "xgp", narration: "xgp prepaid", }) ); 
    2021-01-03 * "xgp" "xgp prepaid" Assets:CN:Bank:Card:USTC -100 CNY Assets:Prepaid 100 CNY 2021-01-01 * "xgp" "xgp prepaid" remain: 11 Assets:Prepaid -8.33 CNY Expenses:XGP 8.33 CNY 2021-02-01 * "xgp" "xgp prepaid" remain: 10 Assets:Prepaid -8.33 CNY Expenses:XGP 8.33 CNY 2021-03-01 * "xgp" "xgp prepaid" remain: 9 Assets:Prepaid -8.33 CNY Expenses:XGP 8.33 CNY 2021-04-01 * "xgp" "xgp prepaid" remain: 8 Assets:Prepaid -8.33 CNY Expenses:XGP 8.33 CNY 2021-05-01 * "xgp" "xgp prepaid" remain: 7 Assets:Prepaid -8.33 CNY Expenses:XGP 8.33 CNY 2021-06-01 * "xgp" "xgp prepaid" remain: 6 Assets:Prepaid -8.33 CNY Expenses:XGP 8.33 CNY 2021-07-01 * "xgp" "xgp prepaid" remain: 5 Assets:Prepaid -8.33 CNY Expenses:XGP 8.33 CNY 2021-08-01 * "xgp" "xgp prepaid" remain: 4 Assets:Prepaid -8.33 CNY Expenses:XGP 8.33 CNY 2021-09-01 * "xgp" "xgp prepaid" remain: 3 Assets:Prepaid -8.33 CNY Expenses:XGP 8.33 CNY 2021-10-01 * "xgp" "xgp prepaid" remain: 2 Assets:Prepaid -8.33 CNY Expenses:XGP 8.33 CNY 2021-11-01 * "xgp" "xgp prepaid" remain: 1 Assets:Prepaid -8.33 CNY Expenses:XGP 8.33 CNY 2021-12-01 * "xgp" "xgp prepaid" remain: 0 Assets:Prepaid -8.37 CNY Expenses:XGP 8.37 CNY 
    6 条回复    2024-02-21 10:43:03 +08:00
    iyeatse
        1
    iyeatse  
       2024-02-19 22:19:40 +08:00 via iPhone   2
    Beancount 本来的优势是纯文本,用 ts 封装之后看上去变得更复杂了,一个账本文件中有一半都是语法关键字而不是内容本身,还是挺头疼的
    hamsterbase
        2
    hamsterbase  
    OP
       2024-02-19 22:32:08 +08:00
    @iyeatse

    货币与账户只需要定义一次。 后续的账本是很简单的。 可以直接 Assets.xxx 来使用账单

    ```
    tr(
    "1970-01-01",
    "Distribution of cash expenses",
    Assets.Cash.posting(-300),
    Expenses.Food.Alcool.posting(300)
    );

    tr(
    "1970-01-01",
    "CN to usd",
    Assets.CN.Cash.posting(-700).asCost(100, USD),
    Assets.Cash.posting(100)
    );
    ``

    我还会封装一些常用的账户,只需要记录消费多少钱就行了, 其他都是自动生成的


    ```
    recentAccount.zs 招商信用卡(
    Expenses.Food.Alcool.posting(10),
    "2021-01-01",
    "买酒"
    );
    ```
    ufo5260987423
        3
    ufo5260987423  
       2024-02-20 01:37:38 +08:00
    有一种 00 年代买家用电脑附赠记账工具的感觉。
    R4rvZ6agNVWr56V0
        4
    R4rvZ6agNVWr56V0  
       2024-02-20 01:52:36 +08:00
    DSL 已经不适合当今社会了,利用 LLM 模型使用自然语言进行非结构化/半结构化数据处理才是王道。
    param
        5
    param  
       2024-02-20 20:45:03 +08:00 via Android
    @GeekGao DSL 手写不适合了,但是生成了然后只读还是有用的。毕竟自然语言没有严格的逻辑,很多时候表达一些逻辑还要靠伪代码呢。
    skies457
        6
    skies457  
       2024-02-21 10:43:03 +08:00
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     868 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 30ms UTC 19:23 PVG 03:23 LAX 12:23 JFK 15:23
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86