{"id":16339011,"url":"https://github.com/phadej/cabal-fields","last_synced_at":"2025-04-11T02:30:37.263Z","repository":{"id":241605825,"uuid":"645509312","full_name":"phadej/cabal-fields","owner":"phadej","description":"An alternative parser for cabal-like files","archived":false,"fork":false,"pushed_at":"2024-05-29T10:39:59.000Z","size":41,"stargazers_count":4,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-03-25T07:51:08.474Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Haskell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/phadej.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-05-25T20:20:47.000Z","updated_at":"2024-06-18T15:41:19.000Z","dependencies_parsed_at":"2024-11-07T03:44:52.225Z","dependency_job_id":"6e072b34-6931-44a8-a98f-1721948c8455","html_url":"https://github.com/phadej/cabal-fields","commit_stats":null,"previous_names":["phadej/cabal-fields"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phadej%2Fcabal-fields","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phadej%2Fcabal-fields/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phadej%2Fcabal-fields/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phadej%2Fcabal-fields/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/phadej","download_url":"https://codeload.github.com/phadej/cabal-fields/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248329500,"owners_count":21085543,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-10T23:53:18.508Z","updated_at":"2025-04-11T02:30:36.858Z","avatar_url":"https://github.com/phadej.png","language":"Haskell","funding_links":[],"categories":[],"sub_categories":[],"readme":"# cabal-fields\n\n[`cabal-fields`](https://github.com/phadej/cabal-fields) is not an answer to [Migrate from the .cabal format to a widely supported format](https://github.com/haskell/cabal/issues/7548) issue.\n\nEnvelope grammar vs. specific format grammar\n--------------------------------------------\n\nIt is important to separate the envelope format (whether it's JSON, YAML, TOML, or cabal-like) from the actual file format (`package.json`, `stack.yaml`, `Cargo.toml`, or `pkg.cabal` for various package description formats).\n\nAn envelope format provides the common syntax.\nOften it has special support for enumerations i.e. lists.\ncabal-like format doesn't. All fields are just opaque text.\nDepending on how you look at it, that's the good or bad thing.\n\nSurely, specifying build dependencies like:\n\n```yaml\ndependencies:\n  - base \u003e= 4.13 \u0026\u0026 \u003c 5\n  - bytestring\n```\n\nmakes the list structure clear for consumers.\nHowever, e.g. [`hpack`](https://github.com/sol/hpack) doesn't use\nlist syntax uniformly: `ghc-options`, which is a list-typed field,\nis still an opaque text field in `hpack` package description.\n\nAnd individual package dependencies are also just opaque text fields,\nthere isn't even a split between a package name and the version range.\n\nOn the other hand, in cabal-like envelope there simply aren't \nany built-in \"types\": no lists, no numbers, no booleans.\nAs a actual file format designer you need to choose how to represent them,\nallowing you to pick the format best suited for the domain.\nFor example, in `.cabal` files we don't need to write versions in quotes,\neven if we have single digit versions!\n\nFor the purpose of automatic \"exact-print\" editing, it would be best\nif envelope format supported as much of needed structure as possible\n(e.g. there would be package name and version range split).\n[For example in Cargo](https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html)\n\n```toml\n[dependencies]\ntime = \"0.1.12\"\n```\n\nthe separation is there.\n\nOTOH, there is a gotcha:\n\n\u003cblockquote\u003e\nLeaving off the caret is a simplified equivalent syntax to using caret requirements. While caret requirements are the default, it is recommended to use the simplified syntax when possible.\n\u003c/blockquote\u003e\n\nI'm quite sure that a lot of ad-hoc tools work only with simplified syntax.\n\nHaving simple envelope format is then probably the second best.\nIf some file-format specific parsing has to be written anyway\n(e.g. to parse version ranges), dealing with a bit more complex stuff\n(like lists in `.cabal` `build-depends`) shouldn't be considerably more effort.\n\nGreibach lexing-parsing\n---------------------------\n\nIn formal language theory, a context-free grammar is in [Greibach normal form (GNF)](https://en.wikipedia.org/wiki/Greibach_normal_form) if the right-hand sides of all production rules start with a terminal symbol, optionally followed by some variables:\n\n```text\nA → xBC\nA → yDE\n```\n\nThis suggests a representation for parsing procedure output, which looks like\ntoken stream (can be lazily constructed and consumed), but does represent\na complete parse result, not just the result of lexing.\n\nThe idea is to have a continuation parameter for each production,\n`A` may start with `X` (`A1` constructor) and then continue with `B`, which continues with `C` and then eventually with `k`.\n\n```haskell\ndata A e k\n  = A1 X (B e (C e k))\n  | A2 Y (D e (E e k))\n  | A_Err e\n```\n\nAdditionally have an error constructor so the possible parse errors\nare embedded somewhere later in t he stream. So there is `A = ... | A_Err`, `B = ... | B_Err` etc.\n\nThis may sound complicated, but it isn't.\nFor simple grammars, the tokens stream type isn't that complicated.\nSee for example `aeson`'s [`Tokens`](https://hackage.haskell.org/package/aeson-2.2.2.0/docs/Data-Aeson-Decoding-Tokens.html).\nFor JSON value, the `Tokens` looks almost like the [`Value`](https://hackage.haskell.org/package/aeson-2.2.2.0/docs/Data-Aeson-Types.html#t:Value) type,\nbut it does preserve more of the grammar. For example, the key order in maps is \"as written\", etc.\nThis is sometimes important distinction: do you want a syntax representation or a value representation.\n\nA cabal-like envelope format is also a simple grammar,\nwhich can be parsed into similar token stream.\nIn `cabal-fields` it looks like\n\n```haskell\ndata AnnByteString ann = ABS ann {-# UNPACK #-} !ByteString\n  deriving (Show, Eq, Functor, Foldable, Traversable)\n\ndata Tokens ann k e\n    = TkSection !(AnnByteString ann) !(AnnByteString ann) (Tokens ann (Tokens ann k e) e)\n    | TkField !(AnnByteString ann) !ann (TkFieldLines ann (Tokens ann k e) e)\n    | TkComment !(AnnByteString ann) (Tokens ann k e)\n    | TkEnd k\n    | TkErr e\n  deriving (Show, Eq)\n\ndata TkFieldLines ann k e\n    = TkFieldLine !(AnnByteString ann) (TkFieldLines ann k e)\n    | TkFieldComment !(AnnByteString ann) (TkFieldLines ann k e)\n    | TkFieldEnd k\n    | TkFieldErr e\n  deriving (Show, Eq, Functor, Foldable, Traversable)\n```\n\ncompare this to [the `Field` type](https://hackage.haskell.org/package/Cabal-syntax-3.12.0.0/docs/Distribution-Fields-Field.html#t:Field) in `Cabal-syntax`;\nnot considerably more complicated.\n\nA benefit of Greibach-parsing is that it's relatively easy to write FFI-able parsers in C.\nWe don't need to create AST, we can have lexer-like interface, leaving the handling of AST creation\nto the host language.\nThe parser implementation can be embedded into e.g. Haskell or Python.\n\nCabal and braces\n----------------\n\nI must admit I like cabal-like format a lot. It's simplicity and free-formness make it good fit for almost any kind of configuration.\n\nBut there is a feature that I very much dislike.\n\nWhile `.cabal` files are perceived to have white-space layout,\nthere's actually a curly-braces option.\nWith curly-braces you can write the whole `.cabal` file on a single line!\n\nIf you look (but I don't recommend) [into grammar for cabal envelope, the handling of curly-braces](https://github.com/haskell/cabal/blob/88c81c92751c0beb36fb7c508ccf06c682a4f9a2/Cabal-syntax/src/Distribution/Fields/Parser.hs#L178),\nand especially how it interacts with whitespace layout, you'll see some horrific stuff.\n\nYou can write\n\n```cabal\ntest-suite hkd-example\n  default-language: Haskell2010\n  type: { exitcode-stdio-1.0 } main-is:HKD.hs \n  hs-source-dirs:   test\n  build-depends:\n      base\n    , some\n```\n\nbut I kindly ask you, please don't. :P\n\nIn short, for a feature used (or known) as little, it adds quite a lot of complexity!\nAnd I'd argue that the syntax is not natural.\n\nSo for now, I simply don't support it. `Cabal-syntax` must support all the stuff and warts, `cabal-fields` doesn't.\n\nCabal and section arguments\n---------------------------\n\nAnother gotcha in cabal envelope format is that while the field contents are opaque,\nthe section arguments (e.g. the `test-suite` name, or expression in `if`)\nis actually lexed. It's non an opaque string, i.e. cannot be arbitrary.\n\nThe only case where it makes a difference i can think of top my head, is to allow end-of-line comments.\nE.g. you can today write (and some did / do on Hackage):\n\n```cabal\nflag tagged -- toggle me\n  default: True\n  manual: True\n```\n\nbut I wouldn't recommend.\n\nThis is *the only* case where you can have a comment on otherwise non-whitespace line.\nE.g. if you write\n\n```cabal\n  build-depends: base \u003c5 -- i feel lucky\n```\n\nthat won't work, the `-- i feel lucky` will be considered as part of the field content.\n\nDoing it so makes the envelope format simpler: there are no escaping on the envelope level. If you escape something in e.g. `description:` field, it's handled by only by haddock.\nThat avoids double escaping head-aches.\n\nSo `cabal-fields` treats section arguments as an opaque text.\nIf you have a end-of-line comment on that line, it will be included.\n\nC interface\n-----------\n\nThe `cabal-fields` library was first prototyped in Haskell and has safe interface.\nHowever, C doesn't have sum types, nor polymorphic recursion nor many safety features at all.\nSo the C version looks an ordinary lexer interface would.\nBut there is a guarantee that only valid cabal-like files will be recognised,\nso the token stream will be well-formed; or an error token will be returned.\n\nPython interface\n----------------\n\nI tested the pure Haskell version against Haskell-using-C-FFI version.\nThey behave the same (against the most of Hackage).\n\nThe goal however was to parse `.cabal` files with Python.\nPeople do complain that they cannot modify the `.cabal` files with Python.\nWhy I don't understand why you'd use Python, if you can use Haskell,\nbut not you \"can\" use Python too.\n\nThe `cabalfields-demo.py` by default will do an exact print of the input files:\n\n```text\n% python3 cabalfields-demo.py ../cabal-fields/cabal-fields.cabal \n../cabal-fields/cabal-fields.cabal\n??? same: True\ncabal-version: 2.4\nname:          cabal-fields\nversion:       0.1\nsynopsis:      An alternative parser for .cabal like files\ndescription:   An alternative parser for @.cabal@ like files.\n...\n```\n\nwith intermediate types which look like:\n\n```python\nclass Field:\n    def __init__(self, name, name_pos, colon_pos, contents):\n        self.name = name\n        self.name_pos = name_pos\n        self.colon_pos = colon_pos\n        self.contents = contents\n\nclass Section:\n    def __init__(self, name, name_pos, args, contents):\n        self.name = name\n        self.name_pos = name_pos\n        self.args = args\n        self.contents = contents\n\nclass FieldLine:\n    def __init__(self, contents, pos):\n        self.contents = contents\n        self.pos = pos\n\nclass Comment:\n    def __init__(self, contents, pos):\n        self.contents = contents\n        self.pos = pos\n```\n\nI haven't yet added any modification or consistency functionality.\n\nIt would be simpler to edit the structure if instead of absolute positions, there would be differences;\nand the pretty-printer would check that differences are consistent (i.e. there are needed newlines, enough indentation etc).\nThat shouldn't be too difficult of an exercise to do.\nIt might be easier to do on Haskell version first (types do help).\n\nPerfectly, the C library would also contain a builder.\nBut it needs a prototype first.\n\nAlso it's easier to write parsers in C, we don't need to think of memory allocation: the tokens returned are splices of the input byte array.\nInn printing we need to have some kind of a builder abstraction: we would like to have\nan interface which can be used to produce both continuous strict byte-array for Python (using custom allocators),\nbut also lazy `ByteString` in Haskell.\n\nConclusion\n----------\n\n`cabal-fields` is a library for parsing `.cabal` like files.\nIt is using Greibach lexing-parsing approach.\nIt doesn't support curly braces, and slightly differs in how it handles section arguments.\nThere is also a C implementation.\nWith a Python module using it.\nAnd small demo of exact-printing `.cabal` like files from Python.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fphadej%2Fcabal-fields","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fphadej%2Fcabal-fields","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fphadej%2Fcabal-fields/lists"}