{"id":28459734,"url":"https://github.com/cesanta/str","last_synced_at":"2025-09-25T09:51:05.055Z","repository":{"id":172502224,"uuid":"649370662","full_name":"cesanta/str","owner":"cesanta","description":"A single header string library for microcontrollers - printf, json, etc","archived":false,"fork":false,"pushed_at":"2025-01-02T18:01:44.000Z","size":129,"stargazers_count":32,"open_issues_count":0,"forks_count":5,"subscribers_count":6,"default_branch":"main","last_synced_at":"2025-07-02T12:48:22.808Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cesanta.png","metadata":{"files":{"readme":"README.md","changelog":null,"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-06-04T16:29:17.000Z","updated_at":"2025-05-19T02:30:53.000Z","dependencies_parsed_at":null,"dependency_job_id":"71670776-b133-4004-a5f5-699975a7980b","html_url":"https://github.com/cesanta/str","commit_stats":null,"previous_names":["cesanta/str"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/cesanta/str","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cesanta%2Fstr","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cesanta%2Fstr/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cesanta%2Fstr/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cesanta%2Fstr/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cesanta","download_url":"https://codeload.github.com/cesanta/str/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cesanta%2Fstr/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":276896553,"owners_count":25724047,"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","status":"online","status_checked_at":"2025-09-25T02:00:09.612Z","response_time":80,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":"2025-06-07T01:06:02.551Z","updated_at":"2025-09-25T09:51:05.037Z","avatar_url":"https://github.com/cesanta.png","language":"C","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Single header string library for microcontrollers\n\n[![License: AGPLv3/Commercial](https://img.shields.io/badge/License-AGPLv3%20or%20Commercial-green.svg)](https://www.gnu.org/licenses/agpl-3.0)\n[![Build Status](https://github.com/cesanta/str/workflows/test/badge.svg)](https://github.com/cesanta/str/actions)\n[![Code Coverage](https://codecov.io/gh/cesanta/str/branch/main/graph/badge.svg?token=B5LH9CRPQT)](https://codecov.io/gh/cesanta/str)\n\nSingle header string library for microcontrollers implements the\nfollowing routines:\n\n- `*printf()` - implements `printf()` family of functions that can print\n  standard specifiers (including floating point `%f` and `%g`) as well as\n  non-standard `%m` and `%M` specifiers that allow custom formatting like JSON,\n  hex, base64\n- `xmatch()` - glob pattern match with captures\n- `json_get()` - find element in a JSON string\n- `json_get_num()` - fetch numeric value from a JSON string\n- `json_get_bool()` - fetch boolean value from a JSON string\n- `json_get_str()` - fetch string value from a JSON string\n- `xhexdump()` - print hex dump of the given memory buffer\n\n## Features\n\n- Ideal for implementing serial protocols, debug logging, JSON data exchange\n- Source code is both ISO C and ISO C++ compliant\n- Tiny size: see the [Footprint](#footprint) section below for exact numbers\n- `*printf()` supports all major format specifiers, width, precition and alignment\n- `*printf()` supports floating point `%f` and `%g` specifiers by default\n- `*printf()` Supports non-standard `%M`,`%m` specifiers which allows for custom formats\n- Can print to anything: to a memory buffer, file, socket, UART, etc\n- Extensively [tested](/cesanta/str/actions) using VC98, VC22, ARM GCC, AVR GCC, x86 GCC, Clang\n\n## Usage example\n\nPrinting to a buffer: https://github.com/cesanta/str/blob/873b39dd14b074bf0779f5d06f5c5bfe3bcb416b/test/main.c#L174-L177\n\nPrint to the UART. Output JSON, and base64-encoded data: https://github.com/cesanta/str/blob/42f0571f8de45e1acd63ffb93526333bd15630c5/test/main.ino#L1-L14\n\nParse JSON: https://github.com/cesanta/str/blob/813e08acf4e389690830bfb5ff525c9e79bdb362/test/main.c#L183-L190\n\nParse and print floating point: https://github.com/cesanta/str/blob/23ebc5c6aafe1b9b5141f97563ee57c50c72093b/test/main.c#L155-L158\n\n## Code duplication\n\n`str.h` is divided in two parts: API declaration, and implementation.\nThe implementation part is wrapped into the preprocessor conditional:\n\n```c\n// API declarations\nsize_t xprintf(void (*)(char, void *), void *, const char *, ...);\n...\n\n#ifndef STR_API_ONLY\n// Implementation\n......\n#endif\n```\n\nIf more than one file includes `str.h`, then the build will result in\nduplicate symbols, because the implementation will end up duplicated in\nseveral object files.\nTo avoid this, only one file should include the full\n`str.h`, and the rest of the files should only include API declarations:\n\nfile1.c:\n```c\n#include \"str.h\"\n...\n```\n\nfile2.c, file3.c, ...:\n```c\n#define STR_API_ONLY\n#include \"str.h\"\n...\n```\n\n## API reference\n\n### xprintf(), vxprintf()\n```c\nsize_t vxprintf(void (*fn)(char, void *), void *arg, const char *fmt, va_list *);\nsize_t xprintf(void (*fn)(char, void *), void *arg, const char *fmt, ...);\n```\n\nPrint formatted string using an output function `fn()`. The output function\noutputs a single byte: `void fn(char ch, void *param) { ... }`. By using\ndifferent output functions, it is possible to print data to anywhere.\nParameters:\n- `fn` - an output function\n- `arg` - an parameter for the `fn()` output function\n- `fmt` - printf-like format string which supports the following specifiers:\n  - `%hhd`, `%hd`, `%d`, `%ld`, `%lld` - for `char`, `short`, `int`, `long`, `int64_t`\n  - `%hhu`, `%hu`, `%u`, `%lu`, `%llu` - same but for unsigned variants\n  - `%hhx`, `%hx`, `%x`, `%lx`, `%llx` - same, for unsigned hex output\n  - `%g`, `%f` - for `double`\n  - `%c` - for `char`\n  - `%s` - for `char *`\n  - `%%` - prints `%` character itself\n  - `%p` - for any pointer, prints `0x.....` hex value\n  - `%M` - (NON-STANDARD EXTENSION): prints using a custom format function\n  - `%m` - (NON-STANDARD EXTENSION): same as `%M` but double-quotes the result\n  - `%X.Y` - optional width and precision modifiers\n  - `%.*` - optional precision modifier specified as `int` argument\n\nReturn value: Number of bytes printed\n\nThe `%M` specifier expects a cusom format function that can grab any number of\npositional arguments. That format function should return the number of bytes\nit has printed. Here its signature:\n\n```c\nsize_t (*ff)(void (*fn)(char, void *), void *arg, va_list *ap);\n```\nParameters:\n- `fn` - an output function\n- `arg` - an parameter for the `fn()` output function\n- `ap` - a pointer for fetching positional arguments\n\nThis library ships with several pre-defined format functions described below.\nFor example, a `fmt_b64()` format function grabs two positional arguments:\n`int` and `void *`, and base64-encodes that memory location:\n\n```c\nchar buf[100];                                        // Base64-encode \"abc\"\nxsnprintf(buf, sizeof(buf), \"%M\", fmt_b64, 3, \"abc\"); // buf contains: YWJj\n```\n\n### xsnprintf(), xvsnprintf()\n```c\nsize_t xvsnprintf(char *buf, size_t len, const char *fmt, va_list *ap);\nsize_t xsnprintf(char *buf, size_t len, const char *fmt, ...);\n```\n\nPrint formatted string into a fixed-size buffer. Parameters:\n- `buf` - a buffer to print to. Can be NULL, in this case `len` must be 0\n- `len` - a size of the `buf`\n- `fmt` - a format string. Supports all specifiers mentioned above\n\nReturn value: number of bytes printed. The result is guaranteed to be NUL\nterminated.\n\n### json\\_get()\n```c\nint json_get(const char *buf, int len, const char *path, int *size);\n```\n\nParse JSON string `buf`, `len` and return the offset of the element\nspecified by the JSON `path`. The length of the element is stored in `size`.\n\nParameters:\n- `buf` - a pointer to a JSON string\n- `len` - a length of a JSON string\n- `path` - a JSON path. Must start with `$`, e.g. `$.user`, `$[12]`, `$`, etc\n- `size` - a pointer that receives element's length. Can be NULL\n\nReturn value: offset of the element, or negative value on error.\n\nUsage example:\n\n```c\n// JSON string buf, len contains { \"a\": 1, \"b\": [2, 3] }\nint size, ofs;\n\n// Lookup \"$\", which is the whole JSON. Can be used for validation\nofs = json_get(buf, len, \"$\", \u0026size);  // ofs == 0, size == 23\n\n// Lookup attribute \"a\". Point to value \"1\"\nofs = json_get(buf, len, \"$.a\", \u0026zize);  // ofs = 7, size = 1\n\n// Lookup attribute \"b\". Point to array [2, 3]\nofs = json_get(buf, len, \"$.b\", \u0026size);  // ofs = 15, size = 6\n\n// Lookup attribute \"b[1]\". Point to value \"3\"\nofs = json_get(buf, len, \"$.b[1]\", \u0026size); // ofs = 19, size = 1\n```\n\n### json\\_get\\_num()\n\n```c\nint json_get_num(const char *buf, int len, const char *path, double *val);\n```\n\nFetch numeric (double) value from the json string `buf`, `len` at JSON path\n`path` into a placeholder `val`. Return true if successful.\n\nParameters:\n- `buf` - a pointer to a JSON string\n- `len` - a length of a JSON string\n- `path` - a JSON path. Must start with `$`\n- `val` - a placeholder for value\n\nReturn value: 1 on success, 0 on error\n\nUsage example:\n\n```c\ndouble d = 0.0;\njson_get_num(\"[1,2,3]\", 7, \"$[1]\", \u0026d));        // d == 2\njson_get_num(\"{\\\"a\\\":1.23}\", 10, \"$.a\", \u0026d));   // d == 1.23\n```\n\n### json\\_get\\_bool()\n\n```c\nint json_get_bool(const char *buf, int len, const char *path, int *val);\n```\n\nFetch boolean (bool) value from the json string `buf` at JSON path\n`path` into a placeholder `v`. Return true if successful.\n\nParameters:\n- `buf` - a pointer to a JSON string\n- `len` - a length of a JSON string\n- `path` - a JSON path. Must start with `$`\n- `val` - a placeholder for value\n\nReturn value: 1 on success, 0 on error\n\nUsage example:\n\n```c\nint b = 0;\njson_get_bool(\"[123]\", 5, \"$[0]\", \u0026b));   // Error. b == 0\njson_get_bool(\"[true]\", 6, \"$[0]\", \u0026b));  // b == 1\n```\n\n### json\\_get\\_long()\n\n```c\nlong json_get_long(const char *buf, int len, const char *path, long default_val);\n```\n\nFetch integer numeric (long) value from the json string `buf`, `len` at JSON path\n`path`. Return it if found, or `default_val` if not found.\n\nParameters:\n- `buf` - a pointer to a JSON string\n- `len` - a length of a JSON string\n- `path` - a JSON path. Must start with `$`\n- `default_val` - a default value for the failure case\n\nReturn value: found value, or `default_val` value\n\nUsage example:\n\n```c\nlong a = json_get_long(\"[123]\", 5, \"$a\", -1));   // a == -1\nlong b = json_get_long(\"[123]\", 5, \"$[0]\", -1)); // b == 123\n```\n\n### json\\_get\\_str()\n\n```c\nint json_get_str(const char *buf, int len, const char *path, char *dst, size_t dstlen);\n```\n\nFetch string value from the json string `json` at JSON path\n`path`. If found, a string is allocated using `calloc()`,\nun-escaped, and returned to the caller. It is the caller's responsibility to\n`free()` the returned string.\n\nParameters:\n- `buf` - a pointer to a JSON string\n- `len` - a length of a JSON string\n- `path` - a JSON path. Must start with `$`\n- `dst` - a pointer to a buffer that holds the result\n- `dstlen` - a length of a result buffer\n\nReturn value: length of a decoded string. \u003e= 0 on success, \u003c 0 on error\n\nUsage example:\n\n```c\nchar dst[100];\njson_get_str(\"[1,2,\\\"hi\\\"]\", \"$[2]\", dst, sizeof(dst));  // dst contains \"hi\"\n```\n\n### xhexdump()\n\n```c\nvoid xhexdump(void (*fn)(char, void *), void *arg, const void *buf, size_t len);\n```\n\nPrint hex dump of the given memory buffer.\n\nParameters:\n- `fn` - an output function\n- `arg` - an parameter for the `fn()` output function\n- `buf` - a pointer to a buffer to print\n- `len` - a length of a buffer\n\nUsage example:\n\n```c\nxhexdump(xputchar, NULL, \"hi\", 2);\n```\n\n\n## Pre-defined `%M`, `%m` format functions\n\n```c\nsize_t fmt_*(void (*fn)(char, void *), void *arg, va_list *ap);\n```\n\nPre-defined helper functions for `%M` specifier:\n- `fmt_ip4` - print IPv4 address. Expect a pointer to 4-byte IPv4 address\n- `fmt_ip6` - print IPv6 address. Expect a pointer to 16-byte IPv6 address\n- `fmt_mac` - print MAC address. Expect a pointer to 6-byte MAC address\n- `fmt_b64` - print base64 encoded data. Expect `int`, `void *`\n- `fmt_esc` - print a string, escaping `\\n`, `\\t`, `\\r`, `\"`. Espects `int`, `char *`\n\nExamples:\n\n```\nuint32_t ip4 = 0x0100007f;                           // Print IPv4 address:\nxsnprintf(buf, sizeof(buf), \"%M\", fmt_ip4, \u0026ip4);    // 127.0.0.1\n\nuint8_t ip6[16] = {1, 100, 33};                      // Print IPv6 address:\nxsnprintf(buf, sizeof(buf), \"%M\", fmt_ip4, \u0026ip6);    // [164:2100:0:0:0:0:0:0]\n\nuint8_t mac[6] = {1, 2, 3, 4, 5, 6};                 // Print MAC address:\nxsnprintf(buf, sizeof(buf), \"%M\", fmt_mac, \u0026mac);    // 01:02:03:04:05:06\n\nconst char str[] = {'a', \\n, '\"', 0};                // Print escaped string:\nxsnprintf(buf, sizeof(buf), \"%M\", fmt_esc, 0, str);  // a\\n\\\"\n\nconst char *data = \"xyz\";                            // Print base64 data:\nxsnprintf(buf, sizeof(buf), \"%M\", fmt_b64, 3, data); // eHl6\n```\n\n## Custom `%M`, `%m` format functions\n\nIt is easy to create your own format functions to format data that is\nspecific to your application. For example, if you want to print your\ndata structure as JSON string, just create your custom formatting function:\n\n```c\nstruct foo { int a; double b; const char *c; };\n\nsize_t fmt_foo(void (*fn)(char, void *), void *arg, va_list *ap) {\n  struct foo *foo = va_arg(*ap, struct foo *);\n  return xxprintf(fn, arg, \"{\\\"a\\\":%d, \\\"b\\\":%g, \\\"c\\\":%m}\",\n                  foo-\u003ea, foo-\u003eb, ESC(c));\n}\n```\n\nAnd now, you can use that function:\n\n```\nstruct foo foo = {1, 2.34, \"hi\"};\nxsnprintf(buf, sizeof(buf), \"%M\", fmt_foo, \u0026foo);\n```\n\n### xmatch()\n\n```c\nstruct xstr {\n  char *buf;\n  size_t len;\n};\n\nbool xmatch(struct xstr str, struct xstr pattern, struct xstr *caps);\n```\n\nCheck if string `str` matches glob pattern `pattern`, and optionally capture\nwildcards into the provided array `caps`.\n\n\u003e NOTE: If `caps` is not NULL, then the `caps` array size must be at least the\n\u003e number of wildcard symbols in `pattern` plus 1. The last cap will be\n\u003e initialized to an empty string.\n\nThe glob pattern matching rules are as follows:\n\n- `?` matches any single character\n- `*` matches zero or more characters except `/`\n- `#` matches zero or more characters\n- any other character matches itself\n\nParameters:\n- `str` - a string to match\n- `pattern` - a pattern to match against\n- `caps` - an optional array of captures for wildcard symbols `?`, `*`, '#'\n\nReturn value: `true` if matches, `false` otherwise\n\nUsage example:\n\n```c\n// Assume that hm-\u003euri holds /foo/bar. Then we can match the requested URI:\nstruct xstr caps[3];  // Two wildcard symbols '*' plus 1\nstruct xstr str = xstr_s(\"/hello/world\");\nstruct xstr pattern = xstr_s{ \"/*/*\");\nif (xmatch(str, pattern, caps)) {\n  // caps[0] holds `hello`, caps[1] holds `world`.\n}\n```\n\n## Printing to dynamic memory\n\nThe `x*printf()` functions always return the total number of bytes that the\nresult string takes. Therefore it is possible to print to a `malloc()-ed`\nbuffer in two passes: in the first pass we calculate the length, and in the\nsecond pass we print:\n\n```c\nsize_t len = xsnprintf(NULL, 0, \"Hello, %s\", \"world\");\nchar *buf = malloc(len + 1);  // +1 is for the NUL terminator\nxsnprintf(buf, len + 1, \"Hello, %s\", \"world\");\n...\nfree(buf);\n```\n\n## Footprint\n\nThe following table contains footprint measurements for the ARM Cortex-M0 and\nARM Cortex-M7 builds of `xsnprintf()` compared to the standard `snprintf()`.\nThe compilation is done with `-Os` flag using ARM GCC 10.3.1.  See\n[test/footprint.c](test/footprint.c) and the corresponding\n[Makefile snippet](https://github.com/cesanta/str/blob/1f26b01c6277be329b264caa4597c6ad655ff135/test/Makefile#L47-L63)\nused for measurements.\n\n|                            | Cortex-M0 | Cortex-M7 |\n| -------------------------- | --------- | --------- |\n| `xsnprintf` (no float)    | 1844      | 2128      |\n| `xsnprintf`               | 10996     | 5592      |\n| Standard `snprintf`  (no float)  | 68368     | 68248     |\n| Standard `snprintf`        | 87476     | 81420     |\n\nNotes:\n- by default, standard snrpintf does not support float, and `x*printf` does\n- to enable float for ARM GCC (newlib), use `-u _printf_float`\n- to disable float for `x*printf`, use `-DNO_FLOAT`\n\n## Licensing\n\nThis library is licensed under the dual license:\n- Open source projects are covered by the GNU Affero 3.0 license\n- Commercial projects/products are covered by the commercial,\n  permanent, royalty free license:\n  - Buy a [single product license](https://buy.stripe.com/9AQaHI1Uud1U4msaEG) - covers a single company's product\n  - Buy a [company wide license](https://buy.stripe.com/3csaHIar09PI4ms8wx) - covers all company's products\n\n- For licensing questions, [contact us](https://mongoose.ws/contact/)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcesanta%2Fstr","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcesanta%2Fstr","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcesanta%2Fstr/lists"}