{"id":13893914,"url":"https://github.com/RobTillaart/printHelpers","last_synced_at":"2025-07-17T08:31:36.643Z","repository":{"id":42046110,"uuid":"276051597","full_name":"RobTillaart/printHelpers","owner":"RobTillaart","description":"Arduino library to help formatting data for printing","archived":false,"fork":false,"pushed_at":"2024-04-13T09:17:01.000Z","size":43,"stargazers_count":11,"open_issues_count":0,"forks_count":3,"subscribers_count":3,"default_branch":"master","last_synced_at":"2024-05-02T05:56:47.648Z","etag":null,"topics":["arduino","engineering","formatting","int64","notation","scientific"],"latest_commit_sha":null,"homepage":"","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/RobTillaart.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","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},"funding":{"github":"RobTillaart","custom":"https://www.paypal.me/robtillaart"}},"created_at":"2020-06-30T09:11:47.000Z","updated_at":"2024-07-26T04:00:05.669Z","dependencies_parsed_at":"2023-02-15T22:16:00.504Z","dependency_job_id":"690016f3-a983-4d57-aa15-d1d3f5480dec","html_url":"https://github.com/RobTillaart/printHelpers","commit_stats":{"total_commits":26,"total_committers":2,"mean_commits":13.0,"dds":"0.038461538461538436","last_synced_commit":"9f33beb915e03ffefe85f8abdd46527d78c8ded9"},"previous_names":[],"tags_count":16,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RobTillaart%2FprintHelpers","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RobTillaart%2FprintHelpers/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RobTillaart%2FprintHelpers/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RobTillaart%2FprintHelpers/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/RobTillaart","download_url":"https://codeload.github.com/RobTillaart/printHelpers/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":226243893,"owners_count":17594452,"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":["arduino","engineering","formatting","int64","notation","scientific"],"created_at":"2024-08-06T18:01:19.747Z","updated_at":"2025-07-17T08:31:36.636Z","avatar_url":"https://github.com/RobTillaart.png","language":"C++","funding_links":["https://github.com/sponsors/RobTillaart","https://www.paypal.me/robtillaart"],"categories":["C++"],"sub_categories":[],"readme":"\n[![Arduino CI](https://github.com/RobTillaart/printHelpers/workflows/Arduino%20CI/badge.svg)](https://github.com/marketplace/actions/arduino_ci)\n[![Arduino-lint](https://github.com/RobTillaart/printHelpers/actions/workflows/arduino-lint.yml/badge.svg)](https://github.com/RobTillaart/printHelpers/actions/workflows/arduino-lint.yml)\n[![JSON check](https://github.com/RobTillaart/printHelpers/actions/workflows/jsoncheck.yml/badge.svg)](https://github.com/RobTillaart/printHelpers/actions/workflows/jsoncheck.yml)\n[![GitHub issues](https://img.shields.io/github/issues/RobTillaart/printHelpers.svg)](https://github.com/RobTillaart/printHelpers/issues)\n\n[![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](https://github.com/RobTillaart/printHelpers/blob/master/LICENSE)\n[![GitHub release](https://img.shields.io/github/release/RobTillaart/printHelpers.svg?maxAge=3600)](https://github.com/RobTillaart/printHelpers/releases)\n[![PlatformIO Registry](https://badges.registry.platformio.org/packages/robtillaart/library/printHelpers.svg)](https://registry.platformio.org/libraries/robtillaart/printHelpers)\n\n\n# printHelpers\n\nArduino library to help formatting data for printing.\n\n\n## Description\n\nThe printHelpers library contains a number of functions that help to print\ndata in a way not supported in the standard print library of the Arduino.\n\n- **char \\* print64()** returns a string for **uint64_t** and **int64_t**.\n- **char \\* sci()** returns a string in scientific format - exponent has step 1.\n- **char \\* eng()** returns a string in engineering format - exponent has step 3.\n- **char \\* scieng()** returns a string in exponential format - exponent has step 1 to 9.\n- **char \\* toBytes()** returns a string in KB MB GB etc.\n- **char \\* hex()** returns hexadecimal output with **leading zeros** up to **uint64_t**.\n- **char \\* bin()** returns binary output with **leading zeros** up to **uint64_t**.\n- **char \\* toRoman()** returns a ROMAN representation of a number.\n- **char \\* printInch(float inch, uint16_t step)** returns a string e.g. 5 7/8\".\n- **char \\* printFeet(float feet)** returns a string e.g. 7\"4'\n- **char \\* csi()** returns a comma separated integer for readability e.g. 3,254,152.\n- **char \\* fraction()** returns a fraction representation of a double/float e.g. 355/113.\n\nFor the details, see sections below.\n\nAs always, feedback is welcome.\nIf a (generic) print format is missing, please open an issue.\n\n\n### PRINTBUFFERSIZE\n\nThe functions of this library share the same internal char array / buffer\nto hold the generated data.\nThe reason for a shared buffer is that the output still exists after the function call. \nGiving every function a static buffer would increase footprint (RAM).\n**NOTE: This is not thread safe!**\nSee section about thread safety below.\n\nThe library defines **PRINTBUFFERSIZE** = 66 for this in the printHelpers.h file.\nWith this size one is able to print a 64 bit integer in base 2.\n\nOne can reduce the footprint by using a smaller **PRINTBUFFERSIZE** e.g. 22,\nhowever it would limit especially the base used in **print64()**.\nMaking the buffer even smaller limits e.g. the number of decimals shown\nwhich might be completely valid as it depends on your requirements.\n\nNote that **sci()** and **eng()** (imho most important functions) use the same buffer.\nThese functions need about 10 bytes plus one bytes for every decimal used.\nSo for floats one need 15-20 bytes max, for doubles one need up to 30 bytes max.\nIn practice a size of 22 will work for most applications.\n\n\n#### Table PRINTBUFFERSIZE vs base print64()\n\n| PRINTBUFFERSIZE | BASE SUPPORTED | nr. decimals | Notes     |\n|:---------------:|:--------------:|:------------:|:----------|\n|     66          |    02 - 36     |    0 - 50    | (default) |\n|     34          |    04 - 36     |    0 - 20    |\n|     24          |    08 - 36     |    0 - 14    |\n|     22          |    10 - 36     |    0 - 12    |\n|     18          |    16 - 36     |    0 - 07    |\n\nWhen functions are added, the recommended minimum size might increase.\n\n\n### 0.5.0 Thread safe\n\nAs stated above, the functions share the same internal char array / buffer.\nSo **printHelpers.h** is definitely **not** thread safe.\nTherefore one should copy / print the data (returned pointer) as fast as possible,\nor at least before another function from this library is called.\n\nSince 0.5.0 a thread-safe version of this library is under development.\nTo use the threadsafe version one need to include **printHelpersMT.h**.\nThe implementation is quite different but the core code is identical.\nMore details see sections below and in the code.\n\nThe thread safe version is minimally tested, feedback is welcome.\n\n\n### Related\n\n- https://github.com/RobTillaart/Fraction\n- https://github.com/RobTillaart/lineFormatter for tabular formatting.\n- https://github.com/RobTillaart/PrintCharArray captures data in a char array buffer.\n- https://github.com/RobTillaart/PrintSize counts length of a number of print commands. (right alignment)\n- https://github.com/RobTillaart/PrintString captures data in a String.\n\n\n## Interface printHelpers.h\n\n```cpp\n#include \"printHelpers.h\"\n```\n\nThe following functions are implemented:\n\n\n### print64()\n\n- **char \\* print64(int64_t value, uint8_t base = 10)** converts a 64 bit integer\nnumber to a char array.\nThe plus sign is not printed, neither are leading zero's.\nBase 10 (DEC) and 16 (HEX) are supported and other bases to 36 can be used.\nDefault base == 10 == decimal.\nNote that negative numbers will always get a minus sign for any base.\nCast the number to uint64_t to suppress the sign.\n\n- **char \\* print64(uint64_t value, uint8_t base = 10)** converts a unsigned 64 bit\ninteger number to a char array.\nNo sign is printed, neither are leading zero's.\nBase 10 (DEC) and 16 (HEX) are supported and bases up to 36 can be used.\nDefault base == 10 == decimal.\n\n\n### sci() eng()\n\nThe existing Arduino print library only supports printing of floats and\ndoubles up to about 4E9 while the range of floats goes up to ~1E38.\nThe smallest float values will often be printed as 0.00 while floats\nsupport down to about 1E-38 (subnormal even to 1E-45).\nThe existing (AVR) library function **dtostrf()** has no scientific notation\nand **dtostre()** is limited to 7 decimals. These latter two are faster.\n\n- **char \\* sci(double value, uint8_t decimals)** converts a float or double to a\nchar array.\nE.g. **print(sci(f, 4))** ==\u003e results in **\"6.7407E+21\"**.\nValues printed with **sci()** do look often pretty in column output.\n\n- **size_t sci(Stream \u0026str, double value, uint8_t decimals)** as above.\nPrints directly to a stream, returning bytes printed.\n\n- **char \\* eng(double value, uint8_t decimals)** converts a float or double to a\nchar array.\nE.g. print(eng(f, 4)) ==\u003e results in \"6.7407E+21\".\nNote the exponent created by **eng()** is always a multiple of 3.\nValues printed with **eng()** do not always look pretty in column output.\nThis is due to the exponent power of 3. However its output translates easy to\nthousands, millions, billions, and millis, micros, nano etc. which are powers of 3.\n\n- **char \\* scieng(double value, uint8_t decimals, uint8_t exponentMultiple)** converts a\nfloat or double to a char array.\n**sci()** and **eng()** use the same underlying function called **scieng()**\nas the initial code for converting is almost identical.\nAlthough not intended to be used directly, feel free to use it.\nThe last parameter **exponentMultiple** defines where the exponent is a multiple of.\nFor the **sci()** function this is 1, for the **eng()** function this is 3.\nThe **scieng()** function works for multiples from 1..9 for the exponent.\nThe usability of other values than 1 and 3 are not known.\nPersonally I like the multiple of 2 as I get 2 orders of magnitude in the\nmantissa. This is e.g. useful for temperature Celsius or percentages.\n\nNote: if the parameter **exponentMultiple** is set to zero, \nthe function **scieng()** will print e.g. x.xxxxxE+00, with the exponent set to zero.\nAlthough this is technically a bug it could be a feature for someone.\nSo I decided to leave this behaviour in the code.\n\n\n### toBytes()\n\n- **char \\* toBytes(double value, uint8_t decimals = 2)** converts a big number\nrepresenting an amount of bytes to a shorter string usable for displaying.\nThe string uses official extensions.\n\nThe number of decimals is max 3, example: 3.292.528 ==\u003e \"3.140 MB\"\n\nValue ranges supported are in steps of powers of 1024.\nThese abbreviations will all be shown in UPPERCASE so KB, MB etc.\n\n|  Unit       |  abbrev.  |  size    |  Unit        |  abbrev.  |  size    |\n|:-----------:|:---------:|:--------:|:------------:|:---------:|:--------:|\n|  bytes      |     B     |  1024^0  |              |           |          |\n|  kilobytes  |    KB     |  1024^1  |  zettabytes  |    ZB     |  1024^7  |\n|  megabytes  |    MB     |  1024^2  |  yottabytes  |    YB     |  1024^8  |\n|  gigabytes  |    GB     |  1024^3  |  xonaytes    |    XB     |  1024^9  |\n|  terabytes  |    TB     |  1024^4  |  wekabytes   |    WB     |  1024^10 |\n|  petabytes  |    PB     |  1024^5  |  vundabytes  |    VB     |  1024^11 |\n|  exabytes   |    EB     |  1024^6  |  udabytes    |    UB     |  1024^12 |\n\nMost applications will only use the first 5 entries, so there is some room\nto optimize the code-size of this function.\n\n\n#### Bigger\n\nTreda Byte is officially shortened as \"TDB\" and uses 2 chars to indicate the magnitude.\nThat would take extra memory or slightly more complex code.\nAs it is very seldom used, \"official\" support stops with UDA.\nShould be big enough for some time.\n\nNote: max uint64_t == 2^64 is in the order of exa or zetta bytes. \nAnd a 4 byte float can represent up to ~100 udabytes range (1024^12 == 10^36)\n\nTo have some support for the really big sizes the code uses lowercase for the next 8 levels:\nTo enable this patch the function in the **printHelpers.cpp** file.\n\n|  Unit       |  abbrev.  |  size     |  Unit       |  abbrev.  |  size     |\n|:-----------:|:---------:|:---------:|:-----------:|:---------:|:---------:|\n|  tredabytes |    tB     |  1024^13  |  ochabytes  |    oB     |  1024^18  |\n|  sortabytes |    sB     |  1024^14  |  nenabytes  |    nB     |  1024^19  |\n|  rintabytes |    rB     |  1024^15  |  mingabytes |    mB     |  1024^20  |\n|  quexabytes |    qB     |  1024^16  |  lumabytes  |    lB     |  1024^21  |\n|  peptabytes |    pB     |  1024^17  |             |           |           |\n\nNote that from the ZETTA prefix all higher prefixes are starting with the\nprevious letter of the alphabet ZYXWVUtsrqponml\n\n\n### hex() bin()\n\nThe default print() function of Arduino does not have leading zero's\nfor **HEX** and **BIN**.\nThis often causes a \"broken\" layout especially if one wants to print\nthe output in columns.\n\nTo solve this the following functions are added that will generate a\nconstant length char array.\n\n- **char \\* hex(uint64_t value, uint8_t digits = 16)**\n- **char \\* hex(uint32_t value, uint8_t digits = 8)**\n- **char \\* hex(uint16_t value, uint8_t digits = 4)**\n- **char \\* hex(uint8_t value, uint8_t digits = 2)**\n- **char \\* bin(uint64_t value, uint8_t digits = 64)**\n- **char \\* bin(uint32_t value, uint8_t digits = 32)**\n- **char \\* bin(uint16_t value, uint8_t digits = 16)**\n- **char \\* bin(uint8_t value, uint8_t digits = 8)**\n\nNote: Data types not supported, must be cast to an supported type.\n\nNote: There is overlap between **hex(value)** and **print64(value, HEX)**.\nThe latter does not produce the leading zero's or fixed length output.\n\nNote: **hex()** does not print hex indicator like \"0x\" or \"H\".\n\nNote: **bin()** does not print bin indicator like \"0b\" or \"B\".\n\n\n### toRoman()\n\nhttps://en.wikipedia.org/wiki/Roman_numerals\n\nA less used but well known print format are the Roman digits.\nThe library function **toRoman()** will convert any number from 0..100 million into a Roman number.\nThe numbers 1..5000 (\"official\" range) are the well known UPPER case characters.\n\n- **char \\* toRoman(int32_t value)** returns Roman string.\n\n|  char  |  unit  |  notes      | |  char  |  unit    |\n|:------:|-------:|:------------|-|:------:|---------:|\n|   M    |  1000  |  M = Mille  | |   m    |  10^7    |\n|   D    |  500   |             | |   d    |  5.10^6  |\n|   C    |  100   |  C = Cent   | |   c    |  10^6    |\n|   L    |  50    |             | |   l    |  5.10^5  |\n|   X    |  10    |             | |   x    |  10^5    |\n|   V    |  5     |             | |   v    |  5.10^4  |\n|   I    |  1     |             | |   i    |  10^4    |\n|   N    |  0     |  extension  | |        |          |\n\n\nNote: The maximum length returned is 16 characters in the \"official\" supported range.\n\nexample:\n- 4888 == MMMMDCCCLXXXVIII.\n- 12345678 == mccxxxivMMMMMDCLXXVIII (unofficial).\n\nDeviations from the standard:\n- value == 0 =\u003e N is not part of the \"official\" numbers but useful.\n- values between 5K-10K are extended with extra M chars.\n- values 10K-100M are represented with lower case characters.\n  This is not a standard, but it sort of works well.\n- values \u003e 100M return OVF == overflow.\n- There is no special 'subtract code' for 9000 to have a clear distinction between\n  \"official\" and extended numbers.\n- The number 4 is often written as IIII on clocks with Roman digits,\n  although IV would be (more?) correct and therefore IV is generated.\n  The reason for IIII in a clock face is that it is opposite of VIII giving a visual balance.\n- Since 0.4.6 negative numbers will have a - sign in front.\n\nThere is no **roman2intger()** function (yet).\n\n\n### Distance feet inch\n\nNote that both inch and feet should be positive or zero.\nA negative value will return an error (\"E-NEG\").\n\n- **char \\* printInch(float inch, uint16_t step = 16)** prints a float inch distance \ndefault in sixteenth ```a b/16```.\nThe parameter step must be a power of 2 == 2, 4, 8, 16, 32, 64 .. 32768.\n\n- **char \\* printFeet(float feet)** prints a float feet distance as\n``` a'b\"``` e.g. 4.5 feet prints as ```4'6\"```.\n\n\n### Comma Separated Integer\n\nExperimental 0.4.3\n\nWhen you are working with large numbers, more than lets say 6 digits, readability becomes an issue.\nWith these numbers it is often difficult to see if it is 2 million something or 20 million something.\nA proven way to solve this is to print those large numbers in (fixed) groups of 3 digits separated by comma's.\nThis improves the readability a lot, and yes, the price is more room needed on a display.\n\nThe comma is chosen as default thousands separator, however since 0.5.0 \none can set the separator to any character e.g. to underscore or a point.\n\nThis function can work with both signed and unsigned up to 64 bit numbers.\nLike all printHelper functions it uses the shared print buffer.\n\nExample **csi(192837465)** becomes \"192,837,465\".\n\nsigned:\n- **char \\* csi(int64_t value, char separator = ',')**\n- **char \\* csi(int32_t value, char separator = ',')**\n- **char \\* csi(int16_t value, char separator = ',')**\n- **char \\* csi(int8_t value, char separator = ',')**\n\nunsigned:\n- **char \\* csi(uint64_t value, char separator = ',')**\n- **char \\* csi(uint32_t value, char separator = ',')**\n- **char \\* csi(uint16_t value, char separator = ',')**\n- **char \\* csi(uint8_t value, char separator = ',')**\n\n\n### Fraction\n\nExperimental 0.4.5, based upon Fraction class - https://github.com/RobTillaart/Fraction\n\nThe precision is hard set to absolute 1e-6.\nThe fraction will have a numerator and denumerator in the range 1..99999.\nNote that as floats only have 7 significant digits the precision varies\nespecially for numbers above 1 (as decimal part eats up significant digits).\n\nThe algorithm is primary meant for values between 0 and 1 however any float \nwill be processed. The algorithm does not always come up with the best \npossible fraction (balance performance quality).\n\nTime is not constant, e.g. **fraction(PI)** takes about 620 us on an Arduino UNO 16 MHz.\n\n- **char \\* fraction(double value)** approach the value with a fraction like n / d.\n- **char \\* fraction(double value, uint16_t denominator)** choose the denominator.\nNote it will be reduced if possible e.g. 6/8 =\u003e 3/4\n\nIf you have a faster or more accurate algorithm or both please let me know\nand open an issue.\n\n----\n\n## Interface printHelpersMT.h\n\n**EXPERIMENTAL 0.5.0**\n\nThis MT ( == MultiThreading) version of the library implements classes for the printHelper functions.\nThese are short living classes that do not share a buffer, so they should be thread safe.\n\nWarning: the internal char buffer does not exist any more when the objects go out of scope.\nSo one must copy its contents before it goes out of scope.\n\nThe description is identical to functions above, however there might be unknown behaviour \ndetails that differ. So use with care.\n\nThis version needs more testing / verification e.g. in RTOS.\n\n```cpp\n#include \"printHelpersMT.h\"\n```\n\n### print64()\n\n- **print64(int64_t value, uint8_t base = 10)**\n- **print64(uint64_t value, uint8_t base = 10)**\n\n### sci() eng()\n\n- **scieng(double value, uint8_t decimals, uint8_t em = 1)**\n- **sci(double value, uint8_t decimals)**\n- **eng(double value, uint8_t decimals)**\n\n### toBytes()\n\n- **toBytes(double value, uint8_t decimals = 2)**\n\n### hex() bin()\n\n- **hex(uint64_t value, uint8_t digits = 16)**\n- **hex(uint32_t value, uint8_t digits = 8)**\n- **hex(uint16_t value, uint8_t digits = 4)**\n- **hex(uint8_t value, uint8_t digits = 2)**\n- **bin(uint64_t value, uint8_t digits = 64)**\n- **bin(uint32_t value, uint8_t digits = 32)**\n- **bin(uint16_t value, uint8_t digits = 16)**\n- **bin(uint8_t value, uint8_t digits = 8)**\n\n### toRoman()\n\n- **toRoman(int32_t value)**\n\n### Distance feet inch\n\n- **printInch(float inch, uint16_t step = 16)**\n- **printFeet(float feet)**\n\n### Comma Separated Integer\n\n- **csi(int64_t value, char separator = ',')**\n- **csi(int32_t value, char separator = ',')**\n- **csi(int16_t value, char separator = ',')**\n- **csi(int8_t value, char separator = ',')**\n- **csi(uint64_t value, char separator = ',')**\n- **csi(uint32_t value, char separator = ',')**\n- **csi(uint16_t value, char separator = ',')**\n- **csi(uint8_t value, char separator = ',')**\n\n### Fraction\n\n- **fraction(double value)**\n- **fraction(double value, uint32_t denominator)**\n\n----\n\n## Future\n\n#### Must\n\n- check TODO's in the code / documentation\n- improve documentation\n\n#### Should\n\n- test and verify printHelpersMT\n- implement printHelpersMT.h unit tests\n  - rewrite of current needed\n- improve readability of the code\n  - em ==\u003e exponentFactor?\n\n#### Could\n\n- investigate **bin(float)** to dump floats?\n  - \"sign, mantissa, exponent bits\"\n  - like this \"s0 m0111010 e100010\" (right length)\n- investigate separators in **hex()**\n  - space per 8, 4 or 2\n- optimize **char \\* hex(uint8_t / uint16_t ...)**\n- base64 representation\n  - base64(float)\n  - base64(double)\n  - base64(any type) \n  - type debase64(type, string);\n  - needs investigation\n- rename sci() to **scientific()** for readability?\n- rename eng() to **engineering()** for readability?\n- implement **roman2intger()**?\n\n#### Wont\n\n- is there need for Scientific or Engineering integers?\n  - this just works! (OK some loss of precision.\n- add **oct()** along BIN, HEX\n  - rarely used.\n- add **float()** as Arduino limits floats to \"MAXLONG\" by code.\n  - use dtostrf() - is that portable?\n  - use sci() or eng()\n- add **base(value, digits, base)** for any base \u003e 1.\n  - only upon request.\n- investigate separators in **bin()**\n  - point or space, per 8 or 4 or 2\n  - ==\u003e printBuffer too small for bin(64) ==\u003e need 75-100 bytes.\n- Investigate performance and accuracy\n  - **sci()** and **eng()**.\n  - investigate sci() version based upon use of log()\n  - done =\u003e see examples.\n- investigate group size in csi() ?\n  - not or very very rarely needed\n\n## Support\n\nIf you appreciate my libraries, you can support the development and maintenance.\nImprove the quality of the libraries by providing issues and Pull Requests, or\ndonate through PayPal or GitHub sponsors.\n\nThank you,\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FRobTillaart%2FprintHelpers","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FRobTillaart%2FprintHelpers","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FRobTillaart%2FprintHelpers/lists"}