{"id":16443031,"url":"https://github.com/jsilvela/para","last_synced_at":"2026-05-15T06:39:23.073Z","repository":{"id":22706206,"uuid":"26050276","full_name":"jsilvela/para","owner":"jsilvela","description":"Paragraph compressor, line wrapper. For use with acme / sam","archived":false,"fork":false,"pushed_at":"2022-06-19T15:18:44.000Z","size":601,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-01-08T18:30:31.398Z","etag":null,"topics":["command-line","markdown","text","tool"],"latest_commit_sha":null,"homepage":"","language":"Go","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/jsilvela.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2014-11-01T12:40:55.000Z","updated_at":"2022-04-25T21:32:29.000Z","dependencies_parsed_at":"2022-08-21T00:00:38.463Z","dependency_job_id":null,"html_url":"https://github.com/jsilvela/para","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jsilvela%2Fpara","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jsilvela%2Fpara/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jsilvela%2Fpara/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jsilvela%2Fpara/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jsilvela","download_url":"https://codeload.github.com/jsilvela/para/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":240794917,"owners_count":19858719,"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":["command-line","markdown","text","tool"],"created_at":"2024-10-11T09:19:18.597Z","updated_at":"2026-05-15T06:39:18.040Z","avatar_url":"https://github.com/jsilvela.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# para\n\nA simple command-line utility to wrap text to a given number of columns. \\\nMeant for wrapping of text in *Sam* or *Acme*, or any other\neditors which don't do their own wrapping/compacting.\n\n*para* wraps text to a given column, filling each line as much as possible,\nto form compact paragraphs.\nWhen compacting, it respects full stops and paragraph breaks.\nIt also respects Markdown headers and list items.\n\n## Usage\n\n``` sh\npara [wrapColumn (optional)]\n```\n\nIf the optional number is not provided, the default width is 80.\n\n*para* takes input from `stdin`, and outputs to `stdout`; this is so that\nit can be used as a piped command in *Acme* or *Sam*.\n\n## Building / testing\n\nClone the repo, then.\n\n``` sh\ncd cloned-repo\ngo test\ngo build\ngo install\n```\n\nNote that depending on your Go environment, you may need to execute the\n`go install` with privileges.\n\n## How it works\n\n`para` fills two goals: wrapping and compacting.\n\nWrapping on its own is easy. We keep a running count of the length since\nthe last newline, and when we exceed the max width, we change the last\nwhitespace into a newline:\n\n``` text\nword1 word2 word3 … wordOverflows wordNext …\n```\n\nWould become:\n\n``` text\nword1 word2 word3 …\nwordOverflows wordNext …`\n```\n\nBut if we want to compact paragraphs, we need some interaction between lines\nof input.\n\nImagine that after wrapping a given input line, we get the following.\n\n``` text\nword word word word word word word word word word word word\nword word word word word word word word word word word word\nword\n```\n\nWe would want the next input line to continue filling the same paragraph.\n\n``` text\nword word word word word word word word word word word word\nword word word word word word word word word word word word\nword newLineWord newLineWord newLineWord newLineWord …\n```\n\nThis means that when wrapping a line of input:\n\n1. we don't necessarily want to \"close\" the paragraph with a newline\n1. we may want to append to an un-closed paragraph seen previously\n\nTo fill these needs, we keep track of the *carry* between lines of input.\nc.f. the signature of the central function:\n\n``` go\nwrapLine(line string, carry int) (wrapped string, newCarry int)\n```\n\nWith this signature in place, the code becomes clear. Special care needs to\nbe taken with:\n\n- paragraph breaks (i.e. empty lines)\n- full stops (i.e. lines of input ending with `\".\\n\"`)\n- Markdown sections\n- Markdown lists\n\nThese should be respected. Compaction should be skipped for them.\nWe achieve this by *flushing*  the un-closed running text:\n\n``` go\nflushRunningText := func() {\n    if carry \u003e 0 {\n        writer.WriteString(\"\\n\")\n        carry = 0\n    }\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjsilvela%2Fpara","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjsilvela%2Fpara","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjsilvela%2Fpara/lists"}