{"id":13467721,"url":"https://github.com/jonaslu/ain","last_synced_at":"2025-05-15T10:05:27.280Z","repository":{"id":41232837,"uuid":"308876594","full_name":"jonaslu/ain","owner":"jonaslu","description":"A HTTP API client for the terminal","archived":false,"fork":false,"pushed_at":"2025-03-16T17:14:14.000Z","size":2165,"stargazers_count":610,"open_issues_count":1,"forks_count":13,"subscribers_count":10,"default_branch":"main","last_synced_at":"2025-04-14T16:55:35.470Z","etag":null,"topics":["api","api-client","api-rest","curl","go","http","httpie","insomnia","paw","postman","terminal"],"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/jonaslu.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":"2020-10-31T12:27:27.000Z","updated_at":"2025-04-09T02:52:16.000Z","dependencies_parsed_at":"2024-01-03T04:14:02.864Z","dependency_job_id":"545eb0a9-986c-4c6f-babd-79e871948248","html_url":"https://github.com/jonaslu/ain","commit_stats":{"total_commits":351,"total_committers":3,"mean_commits":117.0,"dds":0.005698005698005715,"last_synced_commit":"514a2fc3c1b17d9df2a409a87716df9f91d16b68"},"previous_names":[],"tags_count":10,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonaslu%2Fain","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonaslu%2Fain/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonaslu%2Fain/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonaslu%2Fain/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jonaslu","download_url":"https://codeload.github.com/jonaslu/ain/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254319718,"owners_count":22051072,"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":["api","api-client","api-rest","curl","go","http","httpie","insomnia","paw","postman","terminal"],"created_at":"2024-07-31T15:00:59.751Z","updated_at":"2025-05-15T10:05:22.137Z","avatar_url":"https://github.com/jonaslu.png","language":"Go","readme":"\u003cimg src=\"assets/logo.svg\" height=200 style=\"margin-bottom: 20px\"\u003e\n\n# Introduction\nAin is a terminal HTTP API client. It's an alternative to postman, paw or insomnia.\n\n![Show and tell](assets/show-and-tell.gif?raw=true)\n\n* Flexible organization of API:s using files and folders ([examples](https://github.com/jonaslu/ain/tree/main/examples)).\n* Use shell-scripts and executables for common tasks.\n* Put things that change in environment variables or .env-files.\n* Handles url-encoding.\n* Share the resulting [curl](https://curl.se/), [wget](https://www.gnu.org/software/wget/) or [httpie](https://httpie.io/) command-line.\n* Pipe the API output for further processing.\n* Tries hard to be helpful when there are errors.\n\nAin was built to enable scripting of input and further processing of output via pipes. It targets users who work with many API:s using a simple file format. It uses [curl](https://curl.se/), [wget](https://www.gnu.org/software/wget/) or [httpie](https://httpie.io/) to make the actual calls.\n\n⭐ Please leave a star if you find it useful! ⭐\n\n# Table of contents\n\u003c!-- npx doctoc --github --notitle --maxlevel=2 --\u003e\n\u003c!-- START doctoc generated TOC please keep comment here to allow auto update --\u003e\n\u003c!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --\u003e\n\n- [Pre-requisites](#pre-requisites)\n- [Installation](#installation)\n  - [If you have go installed](#if-you-have-go-installed)\n  - [Via homebrew](#via-homebrew)\n  - [Via scoop](#via-scoop)\n  - [Via the AUR (Arch Linux)](#via-the-aur-arch-linux)\n  - [Download binaries yourself](#download-binaries-yourself)\n- [Quick start](#quick-start)\n- [Important concepts](#important-concepts)\n- [Template files](#template-files)\n- [Running ain](#running-ain)\n- [Supported sections](#supported-sections)\n  - [[Host]](#host)\n  - [[Query]](#query)\n  - [[Headers]](#headers)\n  - [[Method]](#method)\n  - [[Body]](#body)\n  - [[Config]](#config)\n  - [[Backend]](#backend)\n  - [[BackendOptions]](#backendoptions)\n- [Variables](#variables)\n- [Executables](#executables)\n- [Fatals](#fatals)\n- [Quoting](#quoting)\n- [Escaping](#escaping)\n- [URL-encoding](#url-encoding)\n- [Sharing is caring](#sharing-is-caring)\n- [Handling line endings](#handling-line-endings)\n- [Troubleshooting](#troubleshooting)\n- [Ain in a bigger context](#ain-in-a-bigger-context)\n- [Contributing](#contributing)\n  - [Commit messages](#commit-messages)\n  - [Testing](#testing)\n\n\u003c!-- END doctoc generated TOC please keep comment here to allow auto update --\u003e\n\n# Pre-requisites\nYou need [curl](https://curl.se/), [wget](https://www.gnu.org/software/wget/) or [httpie](https://httpie.io/) installed and available on your `$PATH`. To test this run `ain -b`. This will generate a basic starter template listing what backends are available on your system in the [[Backend]](#backend) section. It will select one and leave the others commented out.\n\nYou can also check manually what backends you have installed by opening a shell and type `curl`, `wget` or `http` (add the suffix .exe to those commands if you're on windows). Any output from the command means it's installed.\n\nOn linux or mac one of the three is likely to already be installed. The others are available in your package manager or [homebrew](https://brew.sh).\n\nIf you're on windows curl.exe is installed if it's windows 10 build 17063 or higher. Otherwise you can get the binaries via [scoop](https://scoop.sh), [chocolatey](https://chocolatey.org/) or download them yourself. Ain uses curl.exe and cannot use the curl cmd-let powershell builtin.\n\n# Installation\n\n## If you have go installed\nYou need go 1.13 or higher. Using `go install`:\n```\ngo install github.com/jonaslu/ain/cmd/ain@latest\n```\n\n## Via homebrew\nUsing the package-manager [homebrew](https://brew.sh)\n```\nbrew install ain\n```\n\n## Via scoop\nUsing the windows package-manager [scoop](https://scoop.sh)\n```\nscoop bucket add jonaslu_tools https://github.com/jonaslu/scoop-tools.git\nscoop install ain\n```\n\n## Via the AUR (Arch Linux)\nFrom arch linux [AUR](https://aur.archlinux.org/) using [yay](https://github.com/Jguer/yay)\n```\nyay -S ain-bin\n```\n\n## Download binaries yourself\nInstall it so it's available on your `$PATH`:\n[https://github.com/jonaslu/ain/releases](https://github.com/jonaslu/ain/releases)\n\n# Quick start\nAin comes with a built in basic template that you can use as a starting point. Ain checks what backends (that's [curl](https://curl.se/), [wget](https://www.gnu.org/software/wget/) or [httpie](https://httpie.io/)) are available on your system and inserts them into the [[Backend]](#backend) section of the generated template. One will be selected and the rest commented out so the template is runnable directly.\n\nRun:\n```\nain -b basic-template.ain\n```\n\nThe command above will output a starter-template to the file `basic-template.ain`.\nThe basic template calls the / GET http endpoint on localhost with the `Content-Type: application/json`.\n\nTo run the template specify a `PORT` variable:\n```\nain basic-template.ain --vars PORT=8080\n```\n\nSee help for all options: `ain -h` and check out the [examples](https://github.com/jonaslu/ain/tree/main/examples).\n\n# Important concepts\n* Templates: Files containing what, how and where to make the API call. By convention has the file suffix `.ain`.\n* Sections: Label in a file grouping the API parameters.\n* Variables: Things that vary as inputs in a template file.\n* Executables: Enables using the output of a command in a template file.\n* Backends: The thing that makes the API call ([curl](https://curl.se/), [wget](https://www.gnu.org/software/wget/) or [httpie](https://httpie.io/)).\n* Fatals: Error in parsing the template files (it's your fault).\n\n# Template files\nAin assembles data in template files to build the API-call. Ain parses the data following labels called [sections](#supported-sections) in each template file. Here's a full example:\n```\n[Host]           # The URL. Appends across files. Mandatory\nhttp://localhost:${PORT}/api/blog/post\n\n[Query]          # Query parameters. Appends across files\nid=2e79870c-6504-4ac6-a2b7-01da7a6532f1\n\n[Headers]        # Headers for the API-call. Appends across files\nAuthorization: Bearer $(./get-jwt-token.sh)\nContent-Type: application/json\n\n[Method]         # HTTP method. Overwrites across files\nPOST\n\n[Body]           # Body for the API-call. Overwrites across files\n{\n  \"title\": \"Reaping death\",\n  \"content\": \"There is a place beyond the dreamworlds past the womb of night.\"\n}\n\n[Config]         # Ain specific config. Overwrites across files\nTimeout=10\n\n[Backend]        # How to make the API-call. Overwrites across files. Mandatory\ncurl\n\n[BackendOptions] # Options to the selected backends. Appends across files\n-sS              # Comments are ignored.\n```\nThe template files can be named anything but some unique ending-convention such as .ain is recommended so you can [find](https://man7.org/linux/man-pages/man1/find.1.html) them easily.\n\nAin understands eight [Sections] with each of the sections described in details [below](#supported-sections). The data in sections either appends or overwrites across template files passed to ain.\n\nAnything after a pound sign (#) is a comment and will be ignored.\n\n# Running ain\n`ain [OPTIONS] \u003ctemplate.ain\u003e [--vars VAR=VALUE ...]` \n\nAin accepts one or more template-file(s) as a mandatory argument. As sections appends or overwrite you can organize API-calls into hierarchical structures with increasing specificity using files and folders.\n\nYou can find examples of this in the [examples](https://github.com/jonaslu/ain/tree/main/examples) folder.\n\nAdding an exclamation-mark (!) at the end of a template file name makes ain open the file in your `$VISUAL` or `$EDITOR` editor. If none is set it falls back to vim in that order. Once opened you edit the template file for this run only.\n\nExample:\n```\nain templates/get-blog-post.ain!     # Lets you edit the get-blog-post.ain for this run\n```\n\nAin waits for the editor command to exit. Any terminal editor such as vim, emacs, nano etc will be fine. If your editor forks (as [vscode](https://code.visualstudio.com/) does by default) check if there's a flag stopping it from forking. To stop vscode from forking use the `--wait` [flag](https://code.visualstudio.com/docs/editor/command-line#_core-cli-options):\n\n```\nexport EDITOR=\"code --wait\"\n```\n\nIf ain is connected to a pipe it will read template file names from the pipe. This enables you to use [find](https://man7.org/linux/man-pages/man1/find.1.html) and a selector such as [fzf](https://github.com/junegunn/fzf) to keep track of the template-files:\n```\n$\u003e find . -name *.ain | fzf -m | ain\n```\n\nTemplate file names specified on the command line are read before names from a pipe. This means that `echo create-blog-post.ain | ain base.ain` is the same as `ain base.ain create-blog-post.ain`.\n\nWhen making the call ain mimics how data is returned by the backend. After printing any internal errors of it's own, ain echoes back output from the backend: first the standard error (stderr) and then the standard out (stdout). It then returns the exit code from the backend command as it's own unless there are error specific to ain in which it returns status 1.\n\n# Supported sections\nSections are case-insensitive and whitespace ignored but by convention uses CamelCase and are left indented. A section cannot be defined twice in a file. A section ends where the next begins or the file ends.\n\nSee [escaping](#escaping) If you need a literal section heading on a new line.\n\n## [Host]\nContains the URL to the API. This section appends lines from one template file to the next. This feature allows you to specify a base-url in one file (e g `base.ain`) as such: `http://localhost:3000` and in the next template file specify the endpoint path (e g `login.ain`): `/api/auth/login`.\n\nIt's recommended that you use the [[Query]](#Query) section below for query-parameters as it handles joining with delimiters and trimming whitespace. You can however put raw query-parameters in the [Host] section too.\n\nAny query-parameters added in the [[Query]](#Query) section are appended last to the URL. The whole URL is properly [url-encoded](#url-encoding) before passed to the backend. The [Host] section must combine to one and only one valid URL. Multiple URLs is not supported.\n\nAin performs no validation on the url (as backends differ on what a valid url looks like). If your call fails use `ain -p` as mentioned in [troubleshooting](#troubleshooting) to see the resulting command.\n\nThe [Host] section is mandatory and appends across template files.\n\n## [Query]\nAll lines in the [Query] section is appended last to the resulting URL. This means that you can specify query-parameters that apply to many endpoints in one file instead of having to include the same parameter in all endpoints.\n\nAn example is if an `API_KEY=\u003csecret\u003e` query-parameter applies to several endpoints. You can define this in a base-file and simply have the specific endpoint URL and possible extra query-parameters in their own file.\n\nExample - `base.ain`:\n```\n[Host]\nhttp://localhost:8080/api\n\n[Query]\nAPI_KEY=a922be9f-1aaf-47ef-b70b-b400a3aa386e\n```\n\n`get-post.ain`\n```\n[Host]\n/blog/post\n\n[Query]\nid=1\n```\n\nThis will result in the url:\n```\nhttp://localhost:8080/api/blog/post?API_KEY=a922be9f-1aaf-47ef-b70b-b400a3aa386e\u0026id=1\n```\n\nThe whitespace in a query key / value is only significant within the string.\n\nThis means that `page=3` and `page = 3` will become the same query parameter and `page = the next one` will become `page=the+next+one` when processed. If you need actual spaces between the equal-sign and the key / value strings you need to encode it yourself: e g `page+=+3` or put\nthe key-value in the [[Host]](#Host) section where space is significant.\n\nEach line under the [Query] section is appended with a delimiter. Ain defaults to the query-string delimiter `\u0026`. See the [[Config]](#Config) section for setting a custom delimiter.\n\nAll query-parameters are properly [url-encoded](#url-encoding).\n\nThe [Query] section appends across template files.\n\n## [Headers]\nHeaders to pass to the API. One header per line.\n\nExample:\n```\n[Headers]\nAuthorization: Bearer 888e90f2-319f-40a0-b422-d78bb95f229e\nContent-Type: application/json\n```\n\nThe [Headers] section appends across template files.\n\n## [Method]\nHttp method (e g GET, POST, PATCH). If omitted the backend default is used (GET in both curl, wget and httpie).\n\nExample:\n```\n[Method]\nPOST\n```\n\nThe [Method] section is overridden across template files.\n\n## [Body]\nIf the API call needs a body (as in the POST or PATCH http methods) the content of this section is passed as a file to the backend with formatting retained.\n\nThe file is removed after the API call unless you pass the `-l` flag. Ain places the file in the $TMPDIR directory (usually `/tmp` on your box). You can override this in your shell by explicitly setting the `$TMPDIR` environment variable.\n\nPassing the print command `-p` flag will cause ain to write out the file named ain-body\u003crandom-digits\u003e in the directory where ain is invoked and leave the file after completion. Leaving the body file makes the printed command shareable and runnable.\n\nThe [Body] section removes any leading and trailing whitespace lines, but keeps empty newlines between the first and last non-empty line.\n\nExample:\n```\n[Body]\n\n{\n  \"some\": \"json\",  # ain removes comments\n\n  \"more\": \"jayson\"\n}\n\n```\n\nIs passed as this in the temp-file:\n```\n{\n\n  \"some\": \"json\",\n\n  \"more\": \"jayson\"\n}\n```\n\nThe [Body] section overwrites across template files.\n\n## [Config]\nThis section contains config for ain. All config parameters are case-insensitive and any whitespace is ignored. Parameters for backends themselves are passed via the [[BackendOptions]](#BackendOptions) section.\n\nFull config example:\n```\n[Config]\nTimeout=3\nQueryDelim=;\n```\n\nThe [Config] sections overwrites across template files.\n\n### Timeout\nConfig format: `Timeout=\u003ctimeout in seconds\u003e`\n\nThe timeout is enforced during the whole execution of ain (both running executables and the actual API call). If omitted defaults to no timeout. This is the only section where [executables](#executables) cannot be used, since the timeout needs to be known before the executables are invoked.\n\n### Query delimiter\nConfig format: `QueryDelim=\u003ctext\u003e`\n\nThis is the delimiter used when concatenating the lines under the [[Query]](#Query) section. It can be any text that does not contain a space (including the empty string).\n\nDefaults to (`\u0026`).\n\n## [Backend]\nThe [Backend] specifies what command should be used to run the actual API call.\n\nValid options are [curl](https://curl.se/), [wget](https://www.gnu.org/software/wget/) or [httpie](https://httpie.io/).\n\nExample:\n```\n[Backend]\ncurl\n```\n\nThe [Backend] section is mandatory and overwrites across template files.\n\n## [BackendOptions]\nBackend specific options that are passed on to the [backend](#backend).\n\nExample:\n```\n[Backend]\ncurl\n\n[BackendOptions]\n-sS   # Makes curl disable its progress bar in a pipe\n```\n\nThe [BackendOptions] section appends across template files.\n\n# Variables\nVariables lets you specify things that vary such as ports, item ids etc. Ain supports variables via environment variables. Anything inside `${}` in a template is replaced with the value found in the environment. Example `${NODE_ENV}`. Environment variables can be set in your shell in various ways, or via the `--vars VAR1=value1 VAR2=value2` syntax passed after all template file names.\n\nThis will set the variable values in ain:s environment (and available via inheritance in any `$(commands)` spawned from the template [executables](#executables)). Variables set via `--vars` overrides any existing values in the environment, meaning `VAR=1 ain template.ain --vars VAR=2` will result in VAR having the value `2`.\n\nAin looks for any .env file in the folder where it's run for any default variable values. You can pass the path to a custom .env file via the `-e` flag.\n\nEnvironment variables are replaced before executables and can be used as input to the executable. Example `$(cat ${ENV}/token.json)`.\n\nAin uses [envparse](https://github.com/hashicorp/go-envparse) for parsing .env files.\n\n# Executables\nAn executable expression (example `$(command arg1 arg2)`) will be replaced by running the command with arguments and replacing the expression with the commands output (STDOUT). For example `$(echo 1)` will be replaced by `1`.\n\nA real world example is getting JWT tokens from a separate script and share that across templates:\n```\n[Headers]\nAuthorization: Bearer $(bash -c \"./get-login.sh | jq -r '.token'\")\n```\n\nIf shell features such as pipes are needed this can be done via a command string (e g [bash -c](https://man7.org/linux/man-pages/man1/bash.1.html#OPTIONS)) in bash. Note that quoting is needed if the argument contains whitespace as in the example above. See [quoting](#quoting).\n\nThe first word is an command on your $PATH and the rest are arguments to that command.\n\nSee [escaping](#escaping) for arguments containing closing-parentheses `)`.\n\nExecutables are replaced after environment-variables and only once (an executable returned from an executable will not be processed again).\n\n# Fatals\nAin has two types of errors: fatals and errors. Errors are things internal to ain (it's not your fault) such as not finding the backend-binary.\n\nFatals are errors in the template (it's your fault). Fatals include the template file name where the fatal occurred, the line-number and a small context of the template:\n```\n$ ain templates/example.ain\nFatal error in file: templates/example.ain\nCannot find value for variable PORT on line 2:\n1   [Host]\n2 \u003e http://localhost:${PORT}\n3\n```\n\nFatals can be hard to understand if [environment variables](#environment-variables) or [executables](#executables) are replaced in the template. If the line with the fatal contains any replaced value a separate expanded context is printed. It contains up to three lines with the resulting replacement and the row number into the original template:\n```\n$ TIMEOUT=-1 ain templates/example.ain \nFatal error in file: templates/example.ain\nTimeout interval must be greater than 0 on line 10:\n9   [Config]\n10 \u003e Timeout=${TIMEOUT}\n11   \nExpanded context:\n10 \u003e Timeout=-1\n```\n\n# Quoting\nThere are four places where quoting might be necessary: arguments to executables, backend options, invoking the $VISUAL or $EDITOR command and when passing template-names via a pipe. All for the same reasons as bash: a word is an argument to something and a whitespace is the delimiter to the next argument. If whitespace should be retained it must be quoted.\n\nThe canonical example of when quoting is needed is doing more complex things involving pipes. E g `$(sh -c 'find . | fzf -m | xargs echo')`.\n\nEscaping is kept simple, you can use `\\'` or `\\\"` respectively to insert a literal quote inside a quoted string of the same type. You can avoid this by selecting the other quote character (e g 'I need a \" inside this string') when possible.\n\n# Escaping\nTL;DR: To escape a comment `#` precede it with a backtick: `` `#``.\n\nThese symbols have special meaning to ain: \n```\nSymbol -\u003e meaning\n#      -\u003e comment\n${     -\u003e environment variable\n$(     -\u003e executable\n```\n\nIf you need these symbols literally in your output, escape with a backtick:\n```\nSymbol -\u003e output\n`#     -\u003e #\n`${    -\u003e ${\n`$(    -\u003e $(\n```\n\nIf you need a literal backtick just before a symbol, you escape the escaping with a slash:\n```\n\\`#\n\\`${\n\\`$(\n```\n\nIf you need a literal `}` in an environment variable you escape it with a backtick:\n```\nTemplate    -\u003e Environment variable\n${VA`}RZ}   -\u003e VA}RZ\n```\n\nIf you need a literal `)` in an executable, either escape it with a backtick or enclose it in quotes.\nThese two examples are equivalent and inserts the string Hi:\n```\n$(node -e console.log('Hi'`))\n$(node -e 'console.log(\"Hi\")')\n```\n\nIf you need a literal backtick right before closing the envvar or executable you escape the backtick with a slash:\n```\n$(echo \\`)\n${VAR\\`}\n```\n\nSince environment variables are only replaced once, `${` doesn't need escaping when returned from an environment variable. E g `VAR='${GOAT}'`, `${GOAT}` is passed literally to the output. Same for executables, any returned value containing `${` does not need escaping. E g `$(echo $(yo )`, `$(yo ` is passed literally to the output.\n\nPound sign (#) needs escaping if a comment was not intended when returned from both environment variables and executables.\n\nA section header (one of the eight listed under [supported sections](#supported-sections)) needs escaping if it's the only text a separate line. It is escaped with a backtick. Example:\n```\n[Body]\nI'm part of the\n`[Body]\nand included in the output.\n```\n\nIf you need a literal backtick followed by a valid section heading you escape that backtick with a slash. Example:\n```\n[Body]\nThis text is outputted as\n\\`[Body]\nbacktick [Body].\n```\n\n# URL-encoding\nBoth the path and the query-section of an url is scanned and any invalid characters are [URL-encoded](https://en.wikipedia.org/wiki/Percent-encoding)  while already legal encodings (format `%\u003chex\u003e\u003chex\u003e` and `+` for the query string) are kept as is.\n\nThis means that you can mix url-encoded text, half encoded text or unencoded text and ain will convert everything into a properly url-encoded URL.\n\nExample:\n```\n[Host]\nhttps://localhost:8080/download/file/dir with%20spaces # %20=\u003cspace\u003e\n\n[Query]\nfilename=filename with %24$ in it   # %24=$\n```\n\nWill result in the URL:\n```\nhttps://localhost:8080/download/file/dir%20with%20spaces?filename=filename+with+%24%24+in+it\n```\n\nThe only caveats is that ain cannot know if a plus sign (+) is an encoded space or an literal plus sign. In this case ain assumes a space and leave the plus sign as is.\n\nSecond ain cannot know if you meant the literal percent sign followed by two hex characters %\u003chex\u003e\u003chex\u003e instead of an encoded percent character. In this case ain assumes an escaped sequence and leaves the %\u003chex\u003e\u003chex\u003e as is.\n\nIn both cases you need to manually escape the plus (%2B) and percent sign (%25) in the url.\n\n# Sharing is caring\nAin can print out the command instead of running it via the `-p` flag. This enables you to inspect how the curl, wget or httpie API call would look like:\n```\nain -p base.ain create-blog-post.ain \u003e share-me.sh\n```\n\nThe output can then be shared (or for example run over an ssh connection).\n\nPiping it into bash is equivalent to running the command without `-p`.\n```\nain -p base.ain create-blog-post.ain | bash\n```\n\nAny content within the [[Body]](#Body) section when passing the flag `-p` will be written to a file in the current working directory where ain is invoked. The file is not removed after ain completes. See [[Body]](#body) for details.\n\n# Handling line endings\nAin uses line-feed (\\n) when printing it's output. If you're on windows and storing ain:s result to a file, this\nmay cause trouble. Instead of trying to guess what line ending we're on (WSL, docker, cygwin etc makes this a wild goose chase), you'll have to manually convert them if the receiving program complains.\n\nInstructions here: https://stackoverflow.com/a/19914445/1574968\n\n# Troubleshooting\nIf the templates are valid but the actual backend call fails, passing the `-p` flag will show you the command ain tries to run. Invoking this yourself in a terminal might give you more clues to what's wrong.\n\n# Ain in a bigger context\nBut wait! There's more!\n\nWith ain being terminal friendly there are few neat tricks in the [wiki](https://github.com/jonaslu/ain/wiki)\n\n# Contributing\nI'd love if you want to get your hands dirty and improve ain! \n\nBesides [go](https://go.dev/), ain comes with a [Taskfile](https://taskfile.dev/). Install it and run `task` in the root-folder for tasks relating to build, running and testing ain. \n\n## Commit messages\nCommit messages should describe why the change is needed, how the patch solves it and any other background information. Small focused commits are preferable to big blobs. All commits should include a [test plan](https://www.iamjonas.me/2021/04/the-test-plan.html) last in the message.\n\nBackground here: [atomic literate commits](https://www.iamjonas.me/2021/01/literate-atomic-commits.html)\n\n## Testing\nAny PR modifying code should include verification of changes using tests.\n\n### End to end tests\nAin comes with a battery of end-to-end tests which is the preferable way to verify. The tests reside in the folder or sub-folder of `test/e2e/templates`. The main end-to-end task runner is the `test/e2e/e2e_test.go`file. An end-to-end test case is a plain runnable .ain file. By convention ok-{test-name}.ain is used for successful tests and nok-{test-name}.ain for testing failures.\n\nYaml is added as comments last in the file, with at lest one empty row between the last section and the yaml, and used by the test runner to validate the output.\n\nCurrently supported yaml parameters are:\n```\nenv:       \u003c- (array) environment variables to set before the test\nargs:      \u003c- (array) arguments to pass to the test binary\nafterargs: \u003c- (array) arguments passed after the test file name (currently --vars)\nstderr:    \u003c- (string) compared with the stdout output of the test\nstdout:    \u003c- (string) compared with the stderr output of the test\nexitcode:  \u003c- (int) compared with the test binary exit code. Defaults to 0\n```\n\nFeel free to add more comments with explanation on the verification.\n\nWhen adding a test case check the coverage (`task test:cover`) and verify your patch has been touched by tests.\n\n### Unit tests\nIf the patch involves just one or a few methods and it's far easier to test it in isolation then add a unit-test in the same folder as the method under test.\n\n### Test plan\nThe third option is documenting any manual testing. Last in the commit message add a [test plan](https://www.iamjonas.me/2021/04/the-test-plan.html) with one bullet point for each test-case add: setup, execution and verification. Usage of coverage is encouraged so every part of the patch is properly tested.\n\nFor a TL;DR; do a `git log` and see the commit history.\n","funding_links":[],"categories":["Development","Go","CLI Tools","\u003ca name=\"webdev\"\u003e\u003c/a\u003eWeb development","Programming Languages","CLI"],"sub_categories":["HTTP Client","36. [ain](https://github.com/jonaslu/ain)","Bash/Shell"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjonaslu%2Fain","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjonaslu%2Fain","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjonaslu%2Fain/lists"}