{"id":21585435,"url":"https://github.com/objecthub/swift-clformat","last_synced_at":"2025-07-09T04:06:47.103Z","repository":{"id":153192955,"uuid":"614114380","full_name":"objecthub/swift-clformat","owner":"objecthub","description":"Implementation of Common Lisp's `format` procedure from scratch in Swift 5 as a more powerful and complete replacement of `printf`.","archived":false,"fork":false,"pushed_at":"2025-03-27T23:58:28.000Z","size":339,"stargazers_count":8,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-06-12T16:09:42.330Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/objecthub.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","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,"zenodo":null}},"created_at":"2023-03-14T23:22:34.000Z","updated_at":"2025-03-27T23:56:48.000Z","dependencies_parsed_at":null,"dependency_job_id":"2592d41a-5017-44fa-ab07-ea4ad45f6c27","html_url":"https://github.com/objecthub/swift-clformat","commit_stats":null,"previous_names":[],"tags_count":13,"template":false,"template_full_name":null,"purl":"pkg:github/objecthub/swift-clformat","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/objecthub%2Fswift-clformat","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/objecthub%2Fswift-clformat/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/objecthub%2Fswift-clformat/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/objecthub%2Fswift-clformat/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/objecthub","download_url":"https://codeload.github.com/objecthub/swift-clformat/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/objecthub%2Fswift-clformat/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":264390710,"owners_count":23600563,"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-11-24T15:10:40.750Z","updated_at":"2025-07-09T04:06:47.079Z","avatar_url":"https://github.com/objecthub.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Swift CLFormat\n\n[![Platforms: macOS, iOS, Linux](https://img.shields.io/badge/Platforms-macOS,%20iOS,%20Linux-blue.svg?style=flat)](https://developer.apple.com/osx/) [![Language: Swift 6](https://img.shields.io/badge/Language-Swift%206-green.svg?style=flat)](https://developer.apple.com/swift/) [![IDE: Xcode 16](https://img.shields.io/badge/IDE-Xcode%2014-orange.svg?style=flat)](https://developer.apple.com/xcode/) [![Package managers: SwiftPM, Carthage](https://img.shields.io/badge/Package%20managers-SwiftPM,%20Carthage-8E64B0.svg?style=flat)](https://github.com/Carthage/Carthage) [![License: Apache](http://img.shields.io/badge/License-Apache-lightgrey.svg?style=flat)](https://raw.githubusercontent.com/objecthub/swift-numberkit/master/LICENSE)\n\nThis framework implements\n[Common Lisp's `format` procedure](https://www.cs.cmu.edu/Groups/AI/html/cltl/clm/node200.html#SECTION002633000000000000000)\nfrom scratch in Swift. `format` is a procedure that produces formatted text using a\nformat string similar to `printf`. The formatting formalism is significantly more expressive\ncompared to `printf`. It allows users to display numbers in various formats (e.g. hex, binary,\noctal, roman numerals, natural language), apply conditional formatting, output text in a\ntabular format, iterate over data structures, and even apply `format` recursively to handle\ndata that includes their own preferred formatting strings.\n\nThe documentation of this framework includes:\n\n  - The [\u003ctt\u003eclformat\u003c/tt\u003e API](#API),\n  - A description of the [formatting language](#formatting-language),\n  - A comprehensive list of the [supported formatting directives](DIRECTIVES.md),\n  - A short introduction of the [command-line tool](#command-line-tool), and\n  - [Technical requirements](#requirements) for using this framework.\n\nHere are a few examples to get a quick impression of the usage of \u003ctt\u003eclformat\u003c/tt\u003e:\n\n```swift\nclformat(\"~D message~:P received. Average latency: ~,2Fms.\", args: 17, 4.2567)\n⇒ \"17 messages received. Average latency: 4.26ms.\"\nclformat(\"~D file~:P ~A. Average latency: ~,2Fms.\", args: 1, \"stored\", 68.1)\n⇒ \"1 file stored. Average latency: 68.10ms.\"\n```\n\n## API\n\n### clformat and clprintf\n\nThe primary formatting procedure provided by framework _CLFormat_ is `clformat`. It has\nthe following signature:\n\n```swift\nfunc clformat(_ control: String,\n              config: CLControlParserConfig? = CLControlParserConfig.default,\n              locale: Locale? = nil,\n              tabsize: Int = 4,\n              linewidth: Int = 80,\n              args: Any?...) throws -\u003e String\n```\n\n`control` is the formatting string. It is using the formatting language described in the\nnext section to define how the output will be formatted. `config` refers to the\n[format configuration](https://github.com/objecthub/swift-clformat/blob/main/Sources/CLFormat/CLFormatConfig.swift)\nwhich determines how the control string and the arguments get parsed and interpreted. This\nparameter gets usually omitted, unless a user wants to define their own control formatting\nlanguage. `locale` refers to a `Locale` object which is used for executing\nlocale-specific directives. `tabsize` defines the maximum number of space characters that\ncorrespond to a single tab character. `linewidth` specifies the number of characters per\nline (this is used by the justification directive only). Finally, `args` refers to the\nsequence of arguments provided for inclusion in the formatting procedure. The control\nstring determines how these arguments will be injected into the final output that\nfunction `clformat` returns. Here is an example:\n\n```swift\ntry clformat(\"~A is ~D year~:P old.\", args: \"John\", 32)\n⇒ \"John is 32 years old.\"\ntry clformat(\"~A is ~D year~:P old.\", args: \"Vicky\", 1)\n⇒ \"Vicky is 1 year old.\"\n```\n\nThere is also an [overloaded variant](https://github.com/objecthub/swift-clformat/blob/7b1b0ab894180449101330c867e740463e5e38d5/Sources/CLFormat/CLFormat.swift#L131)\nof `clformat` which supports arguments provided as\nan array. It is otherwise equivalent to the first variant.\n\n```swift\nfunc clformat(_ control: String,\n              config: CLControlParserConfig? = CLControlParserConfig.default,\n              locale: Locale? = nil,\n              tabsize: Int = 4,\n              linewidth: Int = 80,\n              arguments: [Any?]) throws -\u003e String\n```\n\nFinally, there are is an [overloaded function](https://github.com/objecthub/swift-clformat/blob/7b1b0ab894180449101330c867e740463e5e38d5/Sources/CLFormat/CLFormat.swift#L157)\n`clprintf` which prints out the formatted\nstring directly to the standard output port. Via the `terminator` argument it is possible\nto control whether a newline character is added automatically.\n\n```swift\nfunc clprintf(_ control: String,\n              config: CLControlParserConfig? = CLControlParserConfig.default,\n              locale: Locale? = nil,\n              tabsize: Int = 4,\n              linewidth: Int = 80,\n              args: Any?...,\n              terminator: String = \"\\n\") throws\nfunc clprintf(_ control: String,\n              config: CLControlParserConfig? = CLControlParserConfig.default,\n              locale: Locale? = nil,\n              tabsize: Int = 4,\n              linewidth: Int = 80,\n              arguments: [Any?],\n              terminator: String = \"\\n\") throws\n```\n\nNote that, by default, both `clformat` and `clprintf` use the formatting directives as\nspecified by [`CLControlParserConfig.default`](https://github.com/objecthub/swift-clformat/blob/7b1b0ab894180449101330c867e740463e5e38d5/Sources/CLFormat/CLControlParserConfig.swift#L239).\nThis is a mutable parser configuration that\ncan be used to influence all invocations of `clformat` and `clprintf` which don't provide\ntheir own parser configuration.\n\n### String extensions\n\nSimilar to how `printf` is integrated into Swift's `String` API, framework _CLFormat_\nprovides two new [`String` initializers](https://github.com/objecthub/swift-clformat/blob/7b1b0ab894180449101330c867e740463e5e38d5/Sources/CLFormat/CLFormat.swift#L87)\nwhich make use of the _CLFormat_ formatting mechanism.\nThey can be used interchangably with `clformat` to allow for a more object-oriented style as\nopposed to the procedural nature of `clformat`.\n\n```swift\nextension String {\n  init(control: String,\n       config: CLControlParserConfig? = nil,\n       locale: Locale? = nil,\n       tabsize: Int = 4,\n       linewidth: Int = 80,\n       args: Any?...) throws\n  init(control: String,\n       config: CLControlParserConfig? = nil,\n       locale: Locale? = nil,\n       tabsize: Int = 4,\n       linewidth: Int = 80,\n       arguments: [Any?]) throws\n}\n```\n\n### Optimizing repeated formatting\n\nEvery single time `clformat` is being invoked, a control language parser is converting\nthe control string into an easier to process intermediate format. If a control string is\nbeing used over and over again by a program, it makes sense to convert the control string\nonly once into its intermediate format and reuse it whenever a new list of arguments is\napplied. The following code shows how to do that. Values of struct `CLControl` represent\nthe intermediate format of a given control string.\n\n```swift\nlet control = try CLControl(string: \"~A = ~,2F (time: ~4,1,,,'0Fms)\")\nlet values: [[Any?]] = [[\"Stage 1\", 317.452, 12.7],\n                        [\"Stage 2\", 570.159, 41.2],\n                        [\"Stage 3\", 123.745, 9.4]]\nfor args in values {\n  print(try control.format(arguments: args))\n}\n```\n\nThis is the generated output:\n\n```\nStage 1 = 317.45 (time: 12.7ms)\nStage 2 = 570.16 (time: 41.2ms)\nStage 3 = 123.74 (time: 09.4ms)\n```\n\n## Formatting language\n\nThe control string used by `clformat` and related functions and constructors consists of\ncharacters that are copied verbatim into the output as well as\n[_formatting directives_](DIRECTIVES.md). All formatting directives start with a\ntilde (`~`) and end with a single character identifying the type of the directive.\nDirectives may take prefix _parameters_ written immediately after the tilde\ncharacter, separated by comma. Both integers and characters are allowed as parameters.\nThey may be followed by formatting _modifiers_ `:`, `@`, and `+`. This is the general\nformat of a formatting directive:\n\n```ebnf\n~param1,param2,...mX\n\nwhere m = (potentially empty) sequence of modifier characters \":\", \"@\", and \"+\"\n      X = character identifying the directive type\n```\n\nThis grammar describes the syntax of directives formally in BNF:\n\n```ebnf\n\u003cdirective\u003e  ::= \"~\" \u003cmodifiers\u003e \u003cchar\u003e\n               | \"~\" \u003cparameters\u003e \u003cmodifiers\u003e \u003cchar\u003e\n\u003cmodifiers\u003e  ::= \u003cempty\u003e\n               | \":\" \u003cmodifiers\u003e\n               | \"@\" \u003cmodifiers\u003e\n               | \"+\" \u003cmodifiers\u003e\n\u003cparameters\u003e ::= \u003cparameter\u003e\n               | \u003cparameter\u003e \",\" \u003cparameters\u003e\n\u003cparameter\u003e  ::= \u003cempty\u003e\n               | \"#\"\n               | \"v\"\n               | \u003cnumber\u003e\n               | \"-\" \u003cnumber\u003e\n               | \u003ccharacter\u003e\n\u003cnumber\u003e     ::= \u003cdigit\u003e\n               | \u003cdigit\u003e \u003cnumber\u003e\n\u003cdigit\u003e      ::= \"0\" | \"1\" | \"2\" | \"3\" | \"4\" | \"5\" | \"6\" | \"7\" | \"8\" | \"9\"\n\u003ccharacter\u003e  ::= \"'\" \u003cchar\u003e\n```\n\nThe following sections introduce a few directives and explain how directives are\ncombined to build control strings that define expressive formatting instructions.\n\n### Simple Directives\n\nHere is a simple control string which injects a readable description of an argument via\nthe directive `~A`:\n\n```\n\"I received ~A as a response\"\n```\n\nDirective `~A` refers to a the _next argument_ provided to `clformat` when compiling the\nformatted output:\n\n```swift\nclformat(\"I received ~A as a response\", args: \"nothing\")\n⇒ \"I received nothing as a response\"\nclformat(\"I received ~A as a response\", args: \"a long email\")\n⇒ \"I received a long email as a response\"\n```\n\nDirective `~A` may be given parameters to influence the formatted output. The first\nparameter of `~A`-directives defines the minimal length. If the length of the textual\nrepresentation of the next argument is smaller than the minimal length, padding characters\nare inserted:\n\n```swift\nclformat(\"|Name: ~10A|Location: ~13A|\", args: \"Smith\", \"New York\")\n⇒ \"|Name: Smith     |Location: New York     |\"\nclformat(\"|Name: ~10A|Location: ~13A|\", args: \"Williams\", \"San Francisco\")\n⇒ \"|Name: Williams  |Location: San Francisco|\"\nclformat(\"|Name: ~10,,,'_@A|Location: ~13,,,'-A|\", args: \"Garcia\", \"Los Angeles\")\n⇒ \"|Name: ____Garcia|Location: Los Angeles--|\"\n```\n\nThe third example above utilizes more than one parameter and, in one case, includes a\n`@` modifier. The directive `~13,,,'-A` defines the first and the fourth parameter. The\nsecond and third parameter are omitted and thus defaults are used. The fourth parameter\ndefines the padding character. If character literals are used in the parameter list,\nthey are prefixed with a quote `'`. The directive `~10,,,'_@A` includes an `@` modifier\nwhich will result in padding of the output on the left.\n\nIt is possible to inject a parameter from the list of arguments. The following examples\nshow how parameter `v` is used to do this for formatting a floating-point number with\na configurable number of fractional digits.\n\n```swift\nclformat(\"length = ~,vF\", args: 2, Double.pi)\n⇒ \"length = 3.14\"\nclformat(\"length = ~,vF\", args: 4, Double.pi)\n⇒ \"length = 3.1416\"\n```\n\nHere `v` is used as the second parameter of the fixed floating-point directive `~F`,\nindicating the number of fractional digits. It refers to the next provided argument (which is\neither 2 or 4 in the examples above).\n\n### Composite Directives\n\nThe next example shows how one can refer to the total number of arguments that are not\nyet consumed in the formatting process by using `#` as a parameter value.\n\n```swift\nclformat(\"~A left for formatting: ~#[none~;one~;two~:;many~].\",\n         args: \"Arguments\", \"eins\", 2)\n⇒ \"Arguments left for formatting: two.\"\nclformat(\"~A left for formatting: ~#[none~;one~;two~:;many~].\",\n         args: \"Arguments\")\n⇒ \"Arguments left for formatting: none.\"\nclformat(\"~A left for formatting: ~#[none~;one~;two~:;many~].\",\n         args: \"Arguments\", \"eins\", 2, \"drei\", \"vier\")\n⇒ \"Arguments left for formatting: many.\"\n```\n\nIn these examples, the _conditional directive_ `~[` is used. It is followed by _clauses_\nseparared by directive `~;` until `~]` is reached. Thus, there are four clauses in the example\nabove: `none`, `one`, `two`, and `many`. The parameter in front of the `~[` directive\ndetermines which of the clauses is being output. All other clauses will be discarded.\nFor instance, `~1[zero~;one~;two~:;many~]` will output `one` as clause 1 is chosen (which\nis the second one, given that numbering starts with zero). The last clause is special because\nit is prefixed with the `~;` directive using a `:` modifier: this is a _default clause_ which is\nchosen when none of the others are applicable. Thus, `~8[zero~;one~;two~:;many~]` outputs\n`many`. This also explains how the example above works: here `#` refers to the number of\narguments that are still available and this number drives what is being returned in this\ndirective: `~#[...~]`.\n\nAnother powerful composite directive is the _iteration directive_ `~{`. With this\ndirective it is possible to iterate over all elements of a sequence. The control string\nbetween `~{` and `~}` gets repeated as long as there are still elements left in the\nsequence which is provided as an argument. For instance, `Numbers:~{ ~A~}` applied to\nargument `[\"one\", \"two\", \"three\"]` results in the output `Numbers: one two three`.\nThe control string between `~{` and `~}` can also consume more than one element of the\nsequence. Thus, `Numbers:~{ ~A=\u003e~A~}` applied to argument `[\"one\", 1, \"two\", 2]`\noutputs `Numbers: one=\u003e1 two=\u003e2`.\n\nOf course, it is also possible to nest arbitrary composite directives. Here is an example\nfor a control string that uses a combination of iteration and conditional directives to\noutput the elements of a sequence separated by a comma: `(~{~#[~;~A~:;~A, ~]~})`. When this\ncontrol string is used with the argument `[\"one\", \"two\", \"three\"]`, the following formatted\noutput is generated: `(one, two, three)`.\n\n## Formatting directive reference\n\nThe [formatting directives supported by the _CLFormat_ framework](DIRECTIVES.md)\nare based on the directives specified in\n[Common Lisp the Language, 2nd Edition](https://www.cs.cmu.edu/Groups/AI/html/cltl/clm/node1.html)\nby Guy L. Steele Jr. Some directives have been extended to meet today's formatting requirements\n(e.g. to support localization) and to achieve a natural embedding in Swift. All extensions were\nintroduced in a way to not impact backward compatibility. \n\n## Command-line tool\n\nThe \u003ctt\u003eswift-clformat\u003c/tt\u003e framework also includes a command-line tool for experimenting\nwith \u003ctt\u003eclformat\u003c/tt\u003e control strings. It prompts users to first enter a control string\nfollowed by a list of arguments. The syntax of control strings is described above. The\nargument list is entered in a syntax that is close to the syntax literals have in Swift.\nHere is an example for an interaction with the command-line tool:\n\n```swift\n═════════╪═══════════════════════════\n  CONTROL│ Done.~^ ~D warning~:P.~^ ~D error~:P.\nARGUMENTS│ 1, 5\n─────────┤\n   RESULT│ Done. 1 warning. 5 errors.\n═════════╪═══════════════════════════\n  CONTROL│ ~%;; ~{~\u003c~%;; ~1,30:; ~S~\u003e~^,~}.~%\nARGUMENTS│ [\"first line\", \"second\", \"a long third line\", \"fourth\"]\n─────────┤\n   RESULT│ \n         │ ;;  \"first line\", \"second\",\n         │ ;;  \"a long third line\",\n         │ ;;  \"fourth\".\n         │ \n═════════╪═══════════════════════════\n  CONTROL│ ~:{/~S~^ ...~}\nARGUMENTS│ [[\"hot\", \"dog\"], [\"hamburger\"], [\"ice\", \"cream\"]]\n─────────┤\n   RESULT│ /\"hot\" .../\"hamburger\"/\"ice\" ...\n═════════╪═══════════════════════════\n```\n\n## Requirements\n\nThe following technologies are needed to build the _CLFormat_ framework. The library\nand the command-line tool can both be built either using _Xcode_ or the _Swift Package Manager_.\n\n- [Xcode 16](https://developer.apple.com/xcode/)\n- [Swift 6](https://developer.apple.com/swift/)\n- [Swift Package Manager](https://swift.org/package-manager/)\n- [MarkdownKit](http://github.com/objecthub/swift-markdownkit)\n\n## Copyright\n\nAuthor: Matthias Zenger (\u003cmatthias@objecthub.com\u003e)  \nCopyright © 2023-2025 Matthias Zenger. All rights reserved.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fobjecthub%2Fswift-clformat","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fobjecthub%2Fswift-clformat","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fobjecthub%2Fswift-clformat/lists"}