text:
# Patreon API
Patreon is developing an API of sorts. It's currently not used for a lot of actions on their website, other than for a few cleaned up features. It's also not documented yet. I'm doing some digging and am going to try my best to provide some documentation for it. I am not at all affiliated with Patreon and provide no warranty that this information will work in the future or even now. I'm just publishing it in the hope that it will be useful and will help people extend the Patreon platform in awesome ways.

## Notes

Some important information to look at before diving into this API.

### Making requests

Patreon is making use of the [JSON API spec](http://jsonapi.org) so some implementation details can be found by plumbing that documentation. The root of the API is at api.patreon.com

### API keys

Some requests require that you have an API key when you make them. This is done by appending "?api_key=YOUR API KEY" to the end of the URL. At present, it looks like there's only one API key shared by everyone: 1745177328c8a1d48100a9b14a1d38c1. Methods that require an api key are marked as such.

### Timestamps

Unless otherwise indicated, assume all timestamps are in this format: 1970-12-31T23:59:59

## Methods

### /login (POST)

Method for iniating a user session. Also retrieves a lot of information about the logged in user.

**Body data**

* email: user's patreon login email
* password: user's patreon login password

**Response**

* data: user object

In addition to returning a JSON object with information about the user, the server also responds with a Set-Cookie header that sets a cookie named "session_id" to a unique session ID on the domain "patreon.com". This cookie appears to expire a month later by default.

### /user (POST)

Creates a user, currently used for FB registrations

**Body data**

* fb\_access\_token: presumably an access token for Facebook's API?
* email: the email associated with the user's Facebook account?

**Response**

* data: user object

### /user/:userid (GET)

Retrieves data about a given user **[requires api key]**

**Response**

* data: user object

### /current_user (GET)

Retrieves information about the currently logged-in user. Appending "?include=cards" to the URL causes credit card information to be included as well.

**Response**

* data: user object
* If "?include=cards", card objects (see below) will be linked.

### /notifications

Unfinished endpoint for an unreleased feature

**Response**

* data: currently an empty array

### /campaign (GET)

Retrieves a list of campaigns. Only seems to work if "?filter=subbable" is appended to the URL

**Response**

* data: array of campaign objects

### /campaign/current_campaign (GET)

Unfinished endpoint for an unreleased feature **[requires api key]**

### /campaign/:campaignid (GET)

Retrieves information about a particular campaign **[requires api key]**

**Response**

* data: array of all category objects
* included: array of all reward objects and the user object for the creator of this campaign

### /campaign/:campaignid/drafts (GET)

Unfinished endpoint for an unreleased feature **[requires api key]**

### /categories (GET)

Gets a list of all the categories on the site (this is part of an unreleased feature) **[requires api key]**

**Response**

* data: array of all category objects

### /reward/:rewardid (GET)

Retrieves a given reward by its numeric ID **[requires api key]**

**Response**

* data: a reward object
* included: the user object of the creator

### /post (GET)

Creates a post??? Or maybe creates an ID for a post, which can then be modified with another call??? **[requires api key]**

### /post/:postid (GET)

Unfinished endpoint for an unreleased feature (but seems to be relatively functional at present).

Retrieves a given post by its numeric ID **[requires api key]**

**Response**

* data: a post object

### /post/:postid (POST)

Unfinished endpoint for an unreleased feature

Modifies an existing post **[requires api key]**

### /post/:postid (DELETE)

Unfinished endpoint for an unreleased feature

Deletes an existing post **[requires api key]**

### /post/:postid/undelete (GET?)

Unfinished endpoint for an unreleased feature

Undeletes a previously-deleted post **[requires api key]**

### /post/:postid/attachments (GET)

Unfinished endpoint for an unreleased feature

Retrieves the attachments for a given post **[requires api key]**

**Response**

* data: an array, presumably of attachments?

### /post/:postid/attachments (POST)

Unfinished endpoint for an unreleased feature

Uploads an attachment **[requires api key]**

### /post/:postid/attachments (DELETE)

Unfinished endpoint for an unreleased feature

Deletes an existing attachment **[requires api key]**

## Data types

These are the types of objects that may be included in requests or responses, either as the main "data" object, or as an object in the "linked" array.

### Campaign

Represents a campaign (i.e. a content creator's patreon page, essentially). Often, the 'included' array will include all the reward objects for this campaign and a user object for the creator of this campaign.

```
{
  "about": "<description of campaign>", // can contain HTML markup
  "created_at": "<timestamp>"
  "creation_name": "<name of campaign>",
  "creator": <number>, // seems to be a Subbable-only thing??
  "creator_name": "<name of creator>", //seems to be a Subbable-only thing??
  "id": <number>, // campaign ID number
  "image_small_url": "<url to 320x320 image>",
  "image_url": "<url to 714x402 image>",
  "is_monthly": <boolean>, // does this campaign charge per-month or per-creation?
  "is_nsfw": <boolean>,
  "links": {
    "rewards": {
      "linkage": [
        // array of reward objects
      ]
    }
  }
  "main_video_embed": "<html>", // an embed tag for the campaign's main video
  "main_video_url": "<url>", // an url to the campaign's main video
  "one_liner": "<short description of campaign>",
  "patron_count": <number>,
  "pay_per_name": "<pay per what?>" // what are supporters being charged for? (e.g. "month", "video", etc.)
  "pledge_sum": <number>, // pledged amount in cents
  "published_at": "<timestamp>",
  "summary": "<html markup>",
  "thanks_embed": "<html>", // embed tag for patron thanks video (null if no video),
  "thanks_msg": "<text>", // message shown to thank patrons
  "thanks_video_url": "<url>", // url for patron thanks video (null if no video),
  "type": "campaign",
  "url": "<url to patreon campaign>" // generally https://www.patreon.com/<username>
}
```

### Card

Represents a credit card or other payment method (i.e. PayPal)

```
{
  "card_type": "<name of payment method>", // known possibilities: "PayPal", "Visa"
  "expiration_date": "<expiration date of card in YYYY-MM-DD format>", // will be null for PayPal
  "id": "<unique identifier>",
  "links": {
    "user": {
      "id": "<user id>",
      "type": "user"
    }
  },
  "number": <last four digits of credit card>, // will be 0 for PayPal
  "type": "card"
}
```

### Category

Represents a category of campaign (e.g. Music, Comics, etc)

```
{
  "id": <numeric id>, // at time of writing, categories are ids 0-14, except the "All" category, which has id 99
  "name": <display name for category>,
  "type": "category"
}
```

### Comment

Represents a comment on a post

```
{
  "type": "<comment|like|success|other>",
  "body": "<body of the comment>",
  "links": {
    "parent": {
      "linkage": {
        "type": "<comment|like|success|other>",
        "id": "<id of parent comment>"
      }
    }
  }
}
```

### Post

Represents a post or creation on a campaign

```
{
  "campaign_id": "<id of associated campaign>",
  "category": <category if relevant>, // null if no comment set
  "cents_pledged_at_creation": <number>, // what was the total sum of all pledges at the time when this was posted?
  "comment_count": <comment count>,
  "content": "<body text>",
  "created_at": "<timestamp>", // presumably can't be null
  "current_user_has_liked": <boolean>, // has the logged in user liked this post?
  "deleted_at": <timestamp>, // null if not deleted
  "edited_at": <timestamp>, // null if not edited
  "embed": { // stuff for embedding videos. if it's not a video, all sub-fields will be set to null
      "description": null, // video description
      "domain": null, // web domain of platform where video is uploaded
      "html": null, // video embed code
      "subject": null, // video title
      "url": null // video url (i.e. on youtube or whatever other platform)
  },
  "id": "<id of this post>",
  "image_height": <height of large image>, // 0 if no image
  "image_large_url": "<url of full size image>", // null if no image
  "image_url": "<url of image shrunk to 320px wide>", // null if no image
  "image_width": <width of large image>, // 0 if no image
  "is_creation": <boolean>, // is this a creation or just a post?
  "is_paid": <boolean>, // true if this is an automated post after a month/content is completed, else false
  "like_count": <number of likes>,
  "min_cents_pledged_to_view": <number>, // minimum individual pledge necessary to view post
  "post_type": "<image|text_only|link>", // may be other unknown values
  "published_at": "<timestamp>", // null if unpublished
  "thumbnail_url": "<url of 75x75 image>", // null if no image
  "title": "<post title>",
  "type": "post",
  "url": "<url to this post>",
  "user_id": "<id of user who posted>"
}
```

### Reward

Represents a reward tier for a campaign. There are two reward objects, id:-1 "Everyone" and id:0 "Patrons Only", that seem to be global and constant for all campaigns.

```
{
  "amount": <number>, // pledge in cents required to access this reward (null if unrestricted)
  "created_at": <timestamp>, // null for global rewards
  "creator_id": null, // only appears for global rewards
  "description": "<html markup>", // description of what this
  "id": "<unique identifier>",
  "links": {
    "creator": {
      "linkage": {
          "id": "<user id of creator of this reward>",
          "type": "user"
      }
    }
  },
  "requires_shipping": <boolean>, // does patreon require a shipping address from users who enter at this level?
  "type": "reward",
  "user_limit": <number> // how many users are allowed to claim this reward? 0 if unlimited, null for global rewards
},
```

### User

A user object contains information about a Patreon user, usually the currently logged-in user.

```
{
  "about": "<about me>", // an optional bio, can be null
  "created": "<timestamp>",
  "email": "<user email>",
  "first_name": "<first name>",
  "full_name": "<first and last name>",
  "gender": <number>, // 0 if unset, 1 if male, 2 if female
  "id": "<user id>", // is always numeric afaik, but appears as a string nonetheless
  "image_url": "<url to 200x200 jpg>",
  "last_name": "<last name>",
  "status": 1, // not sure what this means, usually it's 1 but I have seen 0
  "thumb_url": "<url to 50x50 jpg>",
  "type": "user",
  "url": "<clean url to user profile>",
  "vanity": "<custom url username>" // null if no vanity username set
}
```


text:
# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

## [3.3.0] - 2021-05-02

### Added

- getoptions: Make the parser name optional
- Support installation via Homebrew

## [3.2.0] - 2021-04-30

### Added

- Added the feature to parse options directly by default parser name

## [3.1.0] - 2021-04-29

### Added

- gengetoptions: Added embed subcommand.

### Changed

- gengetoptions: Deprecate the `-d` (`--definition`) option.

## [3.0.0] - 2021-04-26

### Added

- Added support for `--with-*` and `--without-*` options.
  - #20: Thanks to Cem Keylan.
- Added new option parser generator `gengetoptions`.
- Added build system.
- Added support for handling of missing commands (`eval "$(getoptions ...) exit 1"`).

### Changed

- Changed Attribute `off` to `no`. [**breaking change**]
- Changed initial value `@off` to `@no`. [**breaking change**]
- Renamed `lin/getoptions.sh` to `lin/getoptions_base.sh`. [**breaking change**]
- Moved library generation feature of `getoptions` to `gengetoptions` [**breaking change**]

### Removed

- Removed incomplete scanning modes `=`, `#`. [**breaking change**]
- Removed `getoptions-cli` and replaced it with `gengetoptions`. [**breaking change**]

## [2.5.1] - 2021-01-10

### Changed

- Add SC2034 to shellcheck directive in generated code

### Fixed

- Fixed a bug that omitting the value of key-value would be an incorrect value.
- Fix handling of unknown short options in mode `=` and `#`.

## [2.5.0] - 2020-12-13

### Added

- Added some options for `/bin/getoptions`.
- Added scanning modes `=`, `#`, `@`.

## [2.4.0] - 2020-11-28

### Added

- Added new `/bin/getoptions` (external command version for `getoptions`).

### Changed

- Renamed previous `/bin/getoptions` to `/bin/getoptions-cli`.

## [2.3.0] - 2020-11-17

### Added

- Added getoptions CLI (generator).

### Fixed

- Fixed a bug that omitting the value of key-value would be an incorrect value.

## [2.2.0] - 2020-11-14

### Added

- Support for subcommands.

## [2.1.0] - 2020-11-03

### Added

- Support for abbreviating long options.

## [2.0.1] - 2020-10-30

### Fixed

- Add workaround for ksh88 (fixed only the test).

## [2.0.0] - 2020-10-29

### Added

- `setup` helper function.
  - Added `help` and `leading` attributes.
- `setup`, `flag`, `param` and `option` helper function.
  - Added `@export` as initial value.
- `flag`, `param`, `option`, `disp` and `msg` helper function.
  - Added `label` attribute.
- `option` helper function.
  - Added support `--no-option` syntax and the `off` attribute.
- Added extension features (`prehook` and `invoke`).

### Changed

- Improved the custom error handler. [**breaking change**]
  - The default error message is passed as the first argument, and changed the order of the arguments.
  - Adds `:<PATTERN>` to the validator name "pattern" for flexible customization of error message.
  - Adds `:<STATUS>` to the custom validator name for flexible customization of error message.
  - Changed the return value of custom error handler to be used as the exit status.
- Invoke validator before pattern matching. [**breaking change**]
- `option` helper function.
  - Changed the `default` attribute to the `on` attribute. [**breaking change**]
- Disable expansion variables in the help display. [**breaking change**]
- **Calling `getoptions_help` is no longer needed.** [**breaking change**]

### Removed

- `setup` helper function.
  - Remove `equal` attribute.

## [1.1.0] - 2020-10-21

### Added

- Added `@none` as initial value.

### Changed

- Unset `OPTARG` when the option parser ends normally.
  - #3: Thanks to Cem Keylan.
- Reset `OPTIND` to 1 when the option parser ends normally.

## [1.0.0] - 2020-08-20

### Added

- First release version

[Unreleased]: https://github.com/ko1nksm/getoptions/compare/v3.3.0...HEAD
[3.3.0]: https://github.com/ko1nksm/getoptions/compare/v3.2.0...v3.3.0
[3.2.0]: https://github.com/ko1nksm/getoptions/compare/v3.1.0...v3.2.0
[3.1.0]: https://github.com/ko1nksm/getoptions/compare/v3.0.0...v3.1.0
[3.0.0]: https://github.com/ko1nksm/getoptions/compare/v2.5.1...v3.0.0
[2.5.1]: https://github.com/ko1nksm/getoptions/compare/v2.5.0...v2.5.1
[2.5.0]: https://github.com/ko1nksm/getoptions/compare/v2.4.0...v2.5.0
[2.4.0]: https://github.com/ko1nksm/getoptions/compare/v2.3.0...v2.4.0
[2.3.0]: https://github.com/ko1nksm/getoptions/compare/v2.2.0...v2.3.0
[2.2.0]: https://github.com/ko1nksm/getoptions/compare/v2.1.0...v2.2.0
[2.1.0]: https://github.com/ko1nksm/getoptions/compare/v2.0.1...v2.1.0
[2.0.1]: https://github.com/ko1nksm/getoptions/compare/v2.0.0...v2.0.1
[2.0.0]: https://github.com/ko1nksm/getoptions/compare/v1.1.0...v2.0.0
[1.1.0]: https://github.com/ko1nksm/getoptions/compare/v1.0.0...v1.1.0
[1.0.0]: https://github.com/ko1nksm/getoptions/commits/v1.0.0

repo_language: Shell

mime_type: text/plain


text:
PREFIX ?= /usr/local
BINDIR := $(PREFIX)/bin
SHELL=bash

.PHONY: build clean check test testall coverage install uninstall

build: bin/getoptions bin/gengetoptions

clean:
	rm -f bin/getoptions bin/gengetoptions getoptions.tar.gz gengetoptions.tar.gz

check:
	shellcheck src/* lib/*.sh spec/*.sh examples/*.sh

test:
	shellspec

testall:
	shellspec -s sh
	shellspec -s bash
	shellspec -s 'busybox ash'
	shellspec -s ksh
	shellspec -s mksh
	shellspec -s posh
	shellspec -s yash
	shellspec -s zsh

coverage:
	shellspec -s bash --kcov --kcov-options "--coveralls-id=$(COVERALLS_REPO_TOKEN)"
	bash <(curl -s https://codecov.io/bash) -s coverage

dist: build
	tar -C bin -czf getoptions.tar.gz getoptions
	tar -C bin -czf gengetoptions.tar.gz gengetoptions

install: build
	mkdir -p $(BINDIR)
	install -m 755 bin/getoptions $(BINDIR)/getoptions
	install -m 755 bin/gengetoptions $(BINDIR)/gengetoptions

uninstall:
	rm -f $(BINDIR)/getoptions
	rm -f $(BINDIR)/gengetoptions

bin/getoptions: src/build.sh src/getoptions lib/*.sh
	src/build.sh < src/getoptions > bin/getoptions
	chmod +x bin/getoptions

bin/gengetoptions: src/build.sh src/gengetoptions
	src/build.sh < src/gengetoptions > bin/gengetoptions
	chmod +x bin/gengetoptions


text:
FROM alpine:edge
RUN apk add --no-cache shellcheck bash make
WORKDIR /root
COPY ./ ./
CMD [ "bash" ]


text:
# getoptions <!-- omit in toc -->

[![Test](https://github.com/ko1nksm/getoptions/workflows/Test/badge.svg)](https://github.com/ko1nksm/getoptions/actions)
[![codecov](https://codecov.io/gh/ko1nksm/getoptions/branch/master/graph/badge.svg)](https://codecov.io/gh/ko1nksm/getoptions)
[![CodeFactor](https://www.codefactor.io/repository/github/ko1nksm/getoptions/badge)](https://www.codefactor.io/repository/github/ko1nksm/getoptions)
[![GitHub top language](https://img.shields.io/github/languages/top/ko1nksm/getoptions.svg)](https://github.com/ko1nksm/getoptions/search?l=Shell)
[![License](https://img.shields.io/github/license/ko1nksm/getoptions.svg)](https://github.com/ko1nksm/getoptions/blob/master/LICENSE)

An elegant option parser for shell scripts (full support for all POSIX shells)

**getoptions** is a new option parser and generator written in POSIX-compliant shell script and released in august 2020.
It is for those who want to support the POSIX / GNU style option syntax in your shell scripts.
Most easy, simple, fast, small, extensible and portable. No more any loops and templates needed!

## TL; DR <!-- omit in toc -->

```sh
#!/bin/sh

VERSION="0.1"

parser_definition() {
  setup   REST help:usage -- "Usage: example.sh [options]... [arguments]..." ''
  msg -- 'Options:'
  flag    FLAG    -f --flag                 -- "takes no arguments"
  param   PARAM   -p --param                -- "takes one argument"
  option  OPTION  -o --option on:"default"  -- "takes one optional argument"
  disp    :usage  -h --help
  disp    VERSION    --version
}

eval "$(getoptions parser_definition) exit 1"

echo "FLAG: $FLAG, PARAM: $PARAM, OPTION: $OPTION"
printf '%s\n' "$@" # rest arguments
```

It generates a simple [option parser code](#how-to-see-the-option-parser-code) internally and parses the following arguments.

```sh
example.sh -f --flag -p VALUE --param VALUE -o --option -oVALUE --option=VALUE 1 2 3
```

```console
$ example.sh --help

Usage: example.sh [options]... [arguments]...

Options:
  -f, --flag                  takes no arguments
  -p, --param PARAM           takes one argument
  -o, --option[=OPTION]       takes one optional argument
  -h, --help
      --version
```

## Table of Contents <!-- omit in toc -->

- [Features](#features)
- [`getopt` vs `getopts` vs `getoptions`](#getopt-vs-getopts-vs-getoptions)
- [Requirements](#requirements)
- [Installation](#installation)
  - [Homebrew / Linuxbrew](#homebrew--linuxbrew)
- [Usage](#usage)
  - [Use as a command](#use-as-a-command)
  - [Use as a library](#use-as-a-library)
  - [Use as a generator](#use-as-a-generator)
  - [Embedding into a file](#embedding-into-a-file)
- [Benchmarks](#benchmarks)
- [How to see the option parser code](#how-to-see-the-option-parser-code)
  - [Arguments containing spaces and quotes](#arguments-containing-spaces-and-quotes)
  - [Why reuse `OPTARG` and `OPTIND` for different purposes?](#why-reuse-optarg-and-optind-for-different-purposes)
  - [About workarounds](#about-workarounds)
- [References](#references)
  - [Global functions](#global-functions)
  - [Helper functions](#helper-functions)
- [Examples](#examples)
  - [Basic](#basic)
  - [Advanced](#advanced)
    - [Custom error handler](#custom-error-handler)
    - [Custom helper functions](#custom-helper-functions)
  - [Subcommand](#subcommand)
  - [Prehook](#prehook)
  - [Extension](#extension)
  - [Practical example](#practical-example)
- [NOTE: 2.x breaking changes](#note-2x-breaking-changes)
- [NOTE: 3.x breaking changes](#note-3x-breaking-changes)
- [For developers](#for-developers)
  - [How to test getoptions](#how-to-test-getoptions)
- [Changelog](#changelog)
- [License](#license)

## Features

- **Full support for all POSIX shells**, no limitations, no bashisms
- High portability, supports all platforms (Linux, macOS, Windows, etc) where works POSIX shells
- Neither `getopt` nor `getopts` is used, and implemented with shell scripts only
- Provides DSL-like shell script way to define parsers for flexibility and extensibility
- No need for code generation from embedded special comments
- Can be used as an **option parser generator** to run without `getoptions`
- Support for POSIX [[1]][POSIX] and GNU [[2]][GNU1] [[3]][GNU2] compliant option syntax
- Support for **long options**
- Support for **subcommands**
- Support for **abbreviation option**
- Support for **automatic help generation**
- Support for options to call action function
- Support for validation and custom error handler
- Works fast with small overhead and small file size (5KB - 8KB) library
- No global variables are used (except the special variables `OPTARG` and `OPTIND`)
- Only a minimum of one (and a maximum of three) global functions are defined as a library
- No worry about license, it's public domain (Creative Commons Zero v1.0 Universal)

[POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html
[GNU1]: https://www.gnu.org/prep/standards/html_node/Command_002dLine-Interfaces.html
[GNU2]: https://www.gnu.org/software/libc/manual/html_node/Argument-Syntax.html

## `getopt` vs `getopts` vs `getoptions`

|                                 | getopt           | getopts               | getoptions            |
| ------------------------------- | ---------------- | --------------------- | --------------------- |
| Implementation                  | external command | shell builtin command | shell script          |
| Portability                     | No               | Yes                   | Yes                   |
| Short option beginning with `-` | ✔                | ✔                     | ✔                     |
| Short option beginning with `+` | ❌                | ⚠ zsh, ksh, mksh only | ✔                     |
| Combining short options         | ✔                | ✔                     | ✔                     |
| Long option beginning with `--` | ⚠ GNU only       | ❌                     | ✔                     |
| Long option beginning with `-`  | ⚠ GNU only       | ❌                     | ✔ limited             |
| Abbreviating long options       | ⚠ GNU only       | ❌                     | ✔                     |
| Optional argument               | ⚠ GNU only       | ❌                     | ✔                     |
| Option after arguments          | ⚠ GNU only       | ❌                     | ✔                     |
| Stop option parsing with `--`   | ✔                | ✔                     | ✔                     |
| Scanning modes                  | ⚠ GNU only       | ❌                     | ✔ `+` and enhancement |
| Subcommand                      | ❌                | ❌                     | ✔                     |
| Validation by pattern matching  | ❌                | ❌                     | ✔                     |
| Custom validation               | ❌                | ❌                     | ✔                     |
| Custom error handler            | ❌                | ✔                     | ✔ more flexible       |
| Automatic help generation       | ❌                | ❌                     | ✔                     |

## Requirements

**Almost no requirements.**

- Any POSIX shells
  - `dash` 0.5.4+, `bash` 2.03+, `ksh` 88+, `mksh` R28+, `zsh` 3.1.9+, `yash` 2.29+, busybox `ash` 1.1.3+, etc
- Only `cat` is used for help display, but it can be removed

## Installation

**Download prebuild shell scripts** from [releases](https://github.com/ko1nksm/getoptions/releases).

- getoptions: Option parser
- gengetoptions: Option parser generator

```sh
wget https://github.com/ko1nksm/getoptions/releases/latest/download/getoptions -O $HOME/bin/getoptions
chmod +x $HOME/bin/getoptions

# optional
wget https://github.com/ko1nksm/getoptions/releases/latest/download/gengetoptions -O $HOME/bin/gengetoptions
chmod +x $HOME/bin/gengetoptions
```

Or build and install it yourself.

```sh
git clone https://github.com/ko1nksm/getoptions.git
cd getoptions
make
make install PREFIX=$HOME
```

### Homebrew / Linuxbrew

```sh
brew tap ko1nksm/getoptions
brew install getoptions
```

## Usage

Support three ways of use. It is better to use it as a command at first,
and then use it as a library or generator as needed.

|      | command | library | generator |
| ---- | ------- | ------- | --------- |
| easy | ★★★     | ★★☆     | ★☆☆       |
| fast | ★☆☆     | ★★☆     | ★★★       |

### Use as a command

Use the `getoptions` command that you installed on your system.
This assumes that you have the `getoptions` command installed,
but it is the easiest to use and is suitable for personal scripts.

The execution speed is slightly slower than using it as a library. (Approx. 15ms overhead)

```sh
parser_definition() {
  setup REST help:usage -- "Usage: example.sh [options]... [arguments]..."
  ...
}

eval "$(getoptions parser_definition parse) exit 1"
parse "$@"
eval "set -- $REST"
```

The above code `exit 1` is the recommended option.
This allows you to exit if the `getoptions` command is not found.

If you omit the option parser name or use `-`, it will define the default option
parser and parse arguments immediately.

```sh
parser_definition() {
  setup REST help:usage -- "Usage: example.sh [options]... [arguments]..."
  ...
}

eval "$(getoptions parser_definition) exit 1"

# The above means the same as the following code.
# eval "$(getoptions parser_definition getoptions_parse) exit 1"
# getoptions_parse "$@"
# eval "set -- $REST"
```

HINT: Are you wondering why the external command can call a shell function?

The external command `getoptions` will output the shell function `getoptions`.
The external command `getoptions` will be hidden by the shell function `getoptions` that defined by `eval`,
and the `getoptions` will be called again, so it can be call the shell function `parser_definition`.

Try running the following command to see what is output.

```console
$ getoptions parser_definition parse
```

### Use as a library

The `getoptions` command is not recommended for use in distribution scripts
because it is not always installed on the system. This problem can be solved by
including getoptions as a shell script library in your shell scripts.

To use getoptions as a library, you need to generate a library using the `gengetoptions` command.
You can optionally adjust the indentation and other settings when generating the library.

```console
$ gengetoptions library > getoptions.sh
```

```sh
. ./getoptions.sh # Or include it here

parser_definition() {
  setup REST help:usage -- "Usage: example.sh [options]... [arguments]..."
  ...
}

eval "$(getoptions parser_definition parse)"
parse "$@"
eval "set -- $REST"
```

NOTE for 1.x and 2.x users: The previous version guided you to use `lib/*.sh`.
This is still available, but it is recommended to use `gengetoptions library`.

### Use as a generator

If you do not want to include getoptions in your shell scripts, you can pre-generate an option parser.
It also runs the fastest, so it suitable when you need a lot of options.

```console
$ gengetoptions parser -f examples/parser_definition.sh parser_definition parse prog > parser.sh
```

```sh
. ./parser.sh # Or include it here

parse "$@"
eval "set -- $REST"
```

### Embedding into a file

You can use `gengetoptions embed` to embed the generated code in a file,
which makes maintenance easier.

If you want to write the parser definition in the same file as
the shell script to execute, define it between `@getoptions` and `@end`.
The code contained here will be executed during code generation.

The generated code will be embedded between the `@gengetoptions` and `@end` directives.
The arguments of `@gengetoptions` are the same as the arguments of the `gengetoptions` command,
which allows you to embed the library as well as the parser.

Example

**example.sh**

```sh
#!/bin/sh

set -eu

# @getoptions
parser_definition() {
  setup   REST help:usage -- "Usage: example.sh [options]... [arguments]..." ''
  msg -- 'Options:'
  flag    FLAG    -f --flag                 -- "takes no arguments"
  param   PARAM   -p --param                -- "takes one argument"
  option  OPTION  -o --option on:"default"  -- "takes one optional argument"
  disp    :usage  -h --help
  disp    VERSION    --version
}
# @end

# @gengetoptions parser -i parser_definition parse
#
#     INSERTED HERE
#
# @end

parse "$@"
eval "set -- $REST"

echo "FLAG: $FLAG, PARAM: $PARAM, OPTION: $OPTION"
printf '%s\n' "$@" # rest arguments
```

```console
$ gengetoptions embed --overwrite example.sh
```

## Benchmarks

Ubuntu (dash) Core i7 3.4 Ghz

```ini
[command]
Benchmark #1: ./example.sh --flag --param param --option=option a b c
  Time (mean ± σ):       8.6 ms ±   0.3 ms    [User: 6.3 ms, System: 0.6 ms]
  Range (min … max):     7.7 ms …  10.1 ms    300 runs

[library]
Benchmark #1: ./example.sh --flag --param param --option=option a b c
  Time (mean ± σ):       8.3 ms ±   0.4 ms    [User: 5.2 ms, System: 0.5 ms]
  Range (min … max):     7.4 ms …  10.5 ms    322 runs

[generator]
Benchmark #1: ./example.sh --flag --param param --option=option a b c
  Time (mean ± σ):       4.7 ms ±   0.3 ms    [User: 1.4 ms, System: 0.1 ms]
  Range (min … max):     4.4 ms …   6.5 ms    510 runs
```

macOS (bash), Core i5 2.4 GHz

```ini
[command]
Benchmark #1: ./example.sh --flag --param param --option=option a b c
  Time (mean ± σ):      37.6 ms ±   3.2 ms    [User: 29.2 ms, System: 7.4 ms]
  Range (min … max):    33.4 ms …  47.5 ms    66 runs

[library]
Benchmark #1: ./example.sh --flag --param param --option=option a b c
  Time (mean ± σ):      31.0 ms ±   3.7 ms    [User: 26.0 ms, System: 4.4 ms]
  Range (min … max):    26.4 ms …  43.8 ms    77 runs

[generator]
Benchmark #1: ./example.sh --flag --param param --option=option a b c
  Time (mean ± σ):       5.6 ms ±   2.1 ms    [User: 2.8 ms, System: 2.0 ms]
  Range (min … max):     3.9 ms …  15.1 ms    277 runs
```

## How to see the option parser code

It is important to know what kind of code is being generated
when the option parser is not working as expected.

If you want to see the option parser code, rewrite it as follows.

```sh
# eval "$(getoptions parser_definition parse) exit 1"

# Preload the getoptions library
# (can be omitted when using getoptions as a library)
eval "$(getoptions -)"

# Output of the option parser
getoptions parser_definition parse
exit
```

The option parsing code generated by getoptions is very simple.

<details>
<summary>Example option parser code</summary>

```sh
FLAG=''
PARAM=''
OPTION=''
REST=''
parse() {
  OPTIND=$(($#+1))
  while OPTARG= && [ $# -gt 0 ]; do
    case $1 in
      --?*=*) OPTARG=$1; shift
        eval 'set -- "${OPTARG%%\=*}" "${OPTARG#*\=}"' ${1+'"$@"'}
        ;;
      --no-*|--without-*) unset OPTARG ;;
      -[po]?*) OPTARG=$1; shift
        eval 'set -- "${OPTARG%"${OPTARG#??}"}" "${OPTARG#??}"' ${1+'"$@"'}
        ;;
      -[fh]?*) OPTARG=$1; shift
        eval 'set -- "${OPTARG%"${OPTARG#??}"}" -"${OPTARG#??}"' ${1+'"$@"'}
        OPTARG= ;;
    esac
    case $1 in
      '-f'|'--flag')
        [ "${OPTARG:-}" ] && OPTARG=${OPTARG#*\=} && set "noarg" "$1" && break
        eval '[ ${OPTARG+x} ] &&:' && OPTARG='1' || OPTARG=''
        FLAG="$OPTARG"
        ;;
      '-p'|'--param')
        [ $# -le 1 ] && set "required" "$1" && break
        OPTARG=$2
        PARAM="$OPTARG"
        shift ;;
      '-o'|'--option')
        set -- "$1" "$@"
        [ ${OPTARG+x} ] && {
          case $1 in --no-*|--without-*) set "noarg" "${1%%\=*}"; break; esac
          [ "${OPTARG:-}" ] && { shift; OPTARG=$2; } || OPTARG='default'
        } || OPTARG=''
        OPTION="$OPTARG"
        shift ;;
      '-h'|'--help')
        usage
        exit 0 ;;
      '--version')
        echo "${VERSION}"
        exit 0 ;;
      --)
        shift
        while [ $# -gt 0 ]; do
          REST="${REST} \"\${$(($OPTIND-$#))}\""
          shift
        done
        break ;;
      [-]?*) set "unknown" "$1"; break ;;
      *)
        REST="${REST} \"\${$(($OPTIND-$#))}\""
    esac
    shift
  done
  [ $# -eq 0 ] && { OPTIND=1; unset OPTARG; return 0; }
  case $1 in
    unknown) set "Unrecognized option: $2" "$@" ;;
    noarg) set "Does not allow an argument: $2" "$@" ;;
    required) set "Requires an argument: $2" "$@" ;;
    pattern:*) set "Does not match the pattern (${1#*:}): $2" "$@" ;;
    notcmd) set "Not a command: $2" "$@" ;;
    *) set "Validation error ($1): $2" "$@"
  esac
  echo "$1" >&2
  exit 1
}
usage() {
cat<<'GETOPTIONSHERE'
Usage: example.sh [options]... [arguments]...

Options:
  -f, --flag                  takes no arguments
  -p, --param PARAM           takes one argument
  -o, --option[=OPTION]       takes one optional argument
  -h, --help
      --version
GETOPTIONSHERE
}
# Do not execute
```

</details>

### Arguments containing spaces and quotes

The getoptions correctly handles arguments containing spaces and quotes
without using arrays, which are not available in POSIX shells.

The magic is in the `REST` variable in the following code.

```sh
$ examples.sh --flag 1 --param value 2 -- 3

# examples.sh
...
eval "$(getoptions parser_definition parse "$0") exit 1"
parse "$@"
eval "set -- $REST"

echo "$REST" # => "${2}" "${5}" "${7}"
echo "$@" # => 1 2 3
...
```

### Why reuse `OPTARG` and `OPTIND` for different purposes?

This is to avoid using valuable global variables. The POSIX shell does not have local variables.
Instead of using long variable names to avoid conflicts, we reuse `OPTARG` and `OPTIND`.
This code has been tested to work without any problem with all POSIX shells (e.g. ksh88, bash 2.03).

If you use `getoptions` instead of `getopts` for option parsing, `OPTARG` and `OPTIND` are not needed.
In addition, you can also use `getopts`, since `OPTARG` and `OPTIND` will be correctly reset after use.

If you still don't like it, you can use the `--optarg` and `--optind` options of `gengetoptions` to change the variable name.
In addition, since the license of `getoptions` is CC0, you can modify it to use it as you like.

### About workarounds

The option parser code contains workarounds for some shell bugs.
If you want to know what that code means, please refer to [Workarounds.md](docs/Workarounds.md).

## References

For more information, see [References](docs/References.md).

### Global functions

When the `getoptions` is used as an external command, three global functions,
`getoptions`, `getoptions_help`, and `getoptions_abbr`, are defined in your shell script.

If you are using it as a library, only `getoptions` is required.
The other functions are needed when the corresponding features are used.

### Helper functions

Helper functions are  (`setup`, `flag`, `param`, etc) used to define option parsers,
and are defined only within the global functions described above.

## Examples

### Basic

[basic.sh](examples/basic.sh)

This is an example of basic usage. It should be enough for your personal script.

### Advanced

[advanced.sh](examples/advanced.sh)

Shell scripts distributed as utilities may require advanced features and validation.

#### Custom error handler

By defining the custom error handler, you can change the standard error messages,
respond to additional error messages, and change the exit status.

#### Custom helper functions

By defining your own helper functions, you can easily define advanced options.
For example, getoptions does not have a helper function to assign to the array,
but it can be easily implemented by a custom helper function.

### Subcommand

[subcmd.sh](examples/subcmd.sh)

Complex programs are often implemented using subcommands.
When using subcommands in getoptions, parse the arguments multiple times.
(For example, parse up to the subcommand, and then parse after it.
This design is useful for splitting shell scripts by each subcommand.

### Prehook

[prehook.sh](examples/prehook.sh)

If you define a `prehook` function in the parser definition,
it will be calledbefore helper functions is called.
This allows you to process the arguments before calling the helper function.

This feature was originally designed to handle variable names with prefixes
without complicating getoptions. Therefore, it may not be very flexible.

NOTE: The `prehook` function is not called in the help.

### Extension

TODO: ~~[extension.sh](examples/extension.sh)~~

Recall that the parser definition function is just a shell script.
You can extend the functionality by calling it from your function.
For example, you could add a `required` attribute that means nonsense required options.

### Practical example

getoptions was originally developed to improve the maintainability and testability for [ShellSpec][shellspec]
which has number of options. [ShellSpec optparser][optparser] is another good example of how to use getoptions.

[shellspec]: https://shellspec.info/
[optparser]: https://github.com/shellspec/shellspec/tree/master/lib/libexec/optparser

## NOTE: 2.x breaking changes

- Calling `getoptions_help` is no longer needed (see `help` attribute)
- Changed the `default` attribute of the `option` helper function to the `on` attribute
- Improved the custom error handler and changed the arguments
- Disable expansion variables in the help display

## NOTE: 3.x breaking changes

- Renamed `lin/getoptions.sh` to `lin/getoptions_base.sh`
- Renamed `getoptions-cli` to `gengetoptions`
- Moved library generation feature of `getoptions` to `gengetoptions`
- Removed scanning mode `=` and `#`
- Changed attribute `off` to `no`
- Changed initial value `@off` to `@no`

## For developers

### How to test getoptions

Tests are executed using [shellspec](https://github.com/shellspec/shellspec).

```sh
# Install shellspec (if not installed)
curl -fsSL https://git.io/shellspec | sh

# Run tests
shellspec

# Run tests with other shell
shellspec --shell bash
```

NOTE: Currently, only the option parser is being tested,
and the CLI utilities is not being tested.

## Changelog

[CHANGELOG.md](CHANGELOG.md)

## License

[Creative Commons Zero v1.0 Universal](https://github.com/ko1nksm/getoptions/blob/master/LICENSE)

All rights are relinquished and you can used as is or modified in your project.
No credit is also required, but I would appreciate it if you could credit me as the original author.


text:
# Workarounds

The generated option parser code contains workarounds for several shell bugs.
The reason why those codes are needed is not easy to understand and is explained here.
If you do not need these workarounds, you can remove them manually.

```sh
# e.g.
eval 'set -- "${OPTARG%%\=*}" "${OPTARG#*\=}"' ${1+'"$@"'}
eval '[ ${OPTARG+x} ] &&:' && OPTARG=1 || OPTARG=-1
$1*) OPTARG="$OPTARG --flag"
VERBOSE="$((${VERBOSE:-0}+$OPTARG))"
```

## 1. `${1+'"$@"'}`

```sh
eval 'set -- "${OPTARG%%\=*}" "${OPTARG#*\=}"' ${1+'"$@"'}

# Equivalent to the following
set -- "${OPTARG%%\=*}" "${OPTARG#*\=}" "$@"
```

This is a well-known workaround the problem of using `set -u` for some shells.
See [What does `${1+"$@"}` mean](https://www.in-ulm.de/~mascheck/various/bourne_args/).

> Oh wait, there's another meaning
> It's also a workaround for a problem in some shells if you want to use the flag u ("error upon using empty variables")
> and `"$@" `(or `$@`, `"$*"`, `$*`) is used without arguments. Example:
>
> ```sh
> $ shell -cu 'echo     "$@";  echo not reached'
> @: parameter not set
> $ shell -cu 'echo ${1+"$@"}; echo ok'
> ok
> ```
>
> At least these shells need that workaround:
>
> - bash-4.0.0 ... -4.0.27
> - dash-0.4.6 ... -0.4.18
> - all ksh88
> - ksh93 until release t+20090501
> - pdksh-5.1.3, -5.2.14
> - mksh before R39 (as pdksh descendant)
> - posh < 0.10 (as pdksh descendant)
> - NetBSD 2 ff. /bin/sh
> - all traditional Bourne shells

NOTE: In **NetBSD ksh on NetBSD 9.0** and **posh 0.14.1**, this problem still exists.

### `${1+"$@"}` vs `${1+'"$@"'}`

However, this workaround does not work properly with old zsh 3.x, so we are using a modified version.

```console
$ echo $ZSH_VERSION
3.1.9
$ set -- a b c; printf '[%s] ' ${1+"$@"}; echo
[a b c]
# correct: [a] [b] [c]
```

Modified version

```sh
eval 'set -- "${OPTARG%%\=*}" "${OPTARG#*\=}"' ${1+'"$@"'}

# if $1 is present, then
=> eval 'set -- "${OPTARG%%\=*}" "${OPTARG#*\=}"' '"$@"'
=> set -- "${OPTARG%%\=*}" "${OPTARG#*\=}" "$@"

# if $1 is not present, then
=> eval 'set -- "${OPTARG%%\=*}" "${OPTARG#*\=}"'
=> set -- "${OPTARG%%\=*}" "${OPTARG#*\=}"
```

## 2. `eval '[ ${OPTARG+x} ]'`

```sh
eval '[ ${OPTARG+x} ]' && OPTARG=1 || OPTARG=-1
```

This is a workaround for ksh 93u+. The code without the workaround is the following.

```sh
[ ${OPTARG+x} ] && OPTARG=1 || OPTARG=-1
```

The reproduction code is as follows. The `eval` avoids this problem.

```sh
#!/usr/bin/ksh
set -eu 1 2
VAR='var'
while [ $# -gt 0 ]; do
  [ $# -eq 1 ] && unset VAR  # unset VAR in the last loop
  if [ "${VAR+x}" ]; then    # if variable VAR is set

    # The last loop will be executed even though the variable VAR is not set
    echo "$VAR" # => [error] VAR: parameter not set

  fi
  shift
done
```

## 3. `eval '[ ... ] &&:'`

```sh
eval '[ ${OPTARG+x} ] &&:' && OPTARG=1 || OPTARG=-1
```

This `&&:` is a workaround for OpenBSD ksh.

The reproduction code is as follows.

```sh
set -eu
eval "[ ]"     && v=1 || v=2 # it should not exit with failure, but it does
eval "[ ] &&:" && v=1 || v=2 # it works as expected
```

## 4. `$1*)`

The `$1` in this code should be enclosed in double quotes. If `$1` contains a pattern string, it will cause incorrect behavior.

```sh
set -- "???"
case foo in
  "$1"*) echo "it doesn't match" ;; # correct
  $1*) echo "it matches" ;; # wrong
esac
```

However, posh 0.10.2, 0.12.6 doesn't work properly when variables are
enclosed in double quotes.

```sh
#!/usr/bin/posh
set -- "foo"
case foo in
  "$1"*) echo "ok" ;;
  *) echo "not ok" >&2; exit 1 ;; # posh 0.10.2, 0.12.6
esac
```

Therefore, it cannot be enclosed in double quotes, but it would be in trouble
if `$1` contains a pattern, so we check it beforehand.

```sh
case $1 in (*[!a-zA-Z0-9_-]*) break; esac
```

This workaround has a very limited number of affected shells and limits the characters that can be used as long options, so it may be removed in the future.

## 5. `$((${VERBOSE:-0}+$OPTARG))`

There is no need to use `$` to refer to simple variables in arithmetic expressions.

```sh
VERBOSE="$((${VERBOSE:-0}+OPTARG))"
```

However, older shells (e.g. dash 0.5.2) require `$`. The affected shells are
limited and quite old, but we use `$` because it doesn't cause any problems.


text:
# References

- [Global functions](#global-functions)
  - [`getoptions` - Generate a function for option parsing](#getoptions---generate-a-function-for-option-parsing)
  - [`getoptions_abbr` - Handle abbreviated long options](#getoptions_abbr---handle-abbreviated-long-options)
  - [`getoptions_help` - Generate a function to display help](#getoptions_help---generate-a-function-to-display-help)
  - [`getoptions_parse` - Default option parser name](#getoptions_parse---default-option-parser-name)
- [Option parser](#option-parser)
  - [Option parser definition function](#option-parser-definition-function)
  - [Custom error handler](#custom-error-handler)
  - [Prehook](#prehook)
- [Helper functions](#helper-functions)
  - [Data types & Initial values](#data-types--initial-values)
  - [`setup` - Setup global settings (mandatory)](#setup---setup-global-settings-mandatory)
    - [Scanning modes](#scanning-modes)
  - [`flag` - Define an option that take no argument](#flag---define-an-option-that-take-no-argument)
  - [`param` - Define an option that take an argument](#param---define-an-option-that-take-an-argument)
  - [`option` - Define an option that take an optional argument](#option---define-an-option-that-take-an-optional-argument)
  - [`disp` - Define an option that display only](#disp---define-an-option-that-display-only)
  - [`msg` - Display message in help](#msg---display-message-in-help)
  - [`cmd` - Define a subcommand](#cmd---define-a-subcommand)
  - [Attributes related to the help display](#attributes-related-to-the-help-display)

## Global functions

### `getoptions` - Generate a function for option parsing

This function is, to be exact, an option parser generator.
An option parser is defined by `eval` the generated code.

`getoptions <definition> [<parser|-> [arguments]...]`

- parser_definition - Option parser definition
- parser_name - Function name for option parser
- arguments - Passed to the parser definition function

```sh
parser_definition() {
  setup REST ...
  ...
}

# Define the parse function for option parsing
eval "$(getoptions parser_definition parse "$0")"
parse "$@"          # Option parsing
eval "set -- $REST" # Exclude options from arguments
```

If you omit the option parser name or use `-`, it will define the default option
parser and parse arguments immediately.

```sh
parser_definition() {
  setup REST ...
  ...
}

eval "$(getoptions parser_definition - "$0") exit 1"

# The above means the same as the following code.
# eval "$(getoptions parser_definition getoptions_parse "$0") exit 1"
# getoptions_parse "$@"
# eval "set -- $REST"
```

### `getoptions_abbr` - Handle abbreviated long options

This function is called automatically by `getoptions` with the `abbr` attribute,
Do not call it manually.

### `getoptions_help` - Generate a function to display help

This function generates a function for displaying help. Normally, this function is called
automatically by the `help` attribute of `getoptions`, but you can also call it manually.

The function to display help does not necessarily need to be generated by `getoptions_help`.
Since it is just a function, you may define your own shell function.

`getoptions_help <definition> <help> [arguments...]`

- parser_definition - Option parser definition
- help_name - Function name for help display
- arguments - Passed to the parser definition function

```sh
parser_definition() {
  ...
}

eval "$(getoptions_help parser_definition usage)"
usage
```

### `getoptions_parse` - Default option parser name

This is the default option parser name defined when `-` is specified as the option parser name.

## Option parser

This is a function for parsing options, which is generated by `getoptions`
based on the parser definition function.

The option parser reuses the shell special variables `OPTIND` and `OPTARG`
for a different purpose than `getopts`. When the option parsing is successfully
completed, the `OPTIND` is reset to 1 and the `OPTARG` is unset.
When option parsing fails, the `OPTARG` is set to the value of the failed option.

### Option parser definition function

This is the function for defining the option parser.

The option parser definition functions are called by `getoptions` 2 times
(or 3 times when using automatic help generation).

Since this function is called within a subshell, defining variables and
functions within this function does not pollute the global. Of course,
helper functions are also defined in the subshell.

### Custom error handler

You can modify the standard error messages and define additional error messages
by defining a custom error handler. It is also possible to change the exit status.

Example

```sh
parser_definition() {
  setup   REST error:error
  param   NUMBER --number validate:number
  param   RANGE  --range validate:'range 10 100' -- '10 - 100'
}

number() {
  case $OPTARG in (*[!0-9]*)
    return 1
  esac
}

range() {
  number || return 1
  [ "$1" -le "$OPTARG" ] && [ "$OPTARG" -le "$2" ] && return 0
  return 2
}

# Custom error handler
#   $1: Default error message
#   $2: Error name
#     - `unknown` (Unrecognized option)
#     - `noarg` (Does not allow an argument)
#     - `required` (Requires an argument)
#     - `pattern:<PATTERN>` (Does not match the pattern)
#     - `validator_name:<STATUS>` (Validation error)
#     - `ambiguous` (Ambiguous option)
#   $3: Option
#   $4-: Validator name and arguments (if $2 is validator_name)
#   $4-: Candidate options (if $2 is ambiguous)
#   return: exit status
error() {
  case $2 in
    unknown) echo "Unrecognized option: $3" ;;
    number:*) echo "Not a number: $3" ;;
    range:1) echo "Not a number: $3" ;;
    range:2) echo "Out of range ($5 - $6): $3"; return 2 ;;
    *) return 0 ;; # Display default error
  esac
  return 1
}
```

The value of the option that caused the error is stored in the `OPTARG` variable.
If the cause of the error is `ambiguous`, the `OPTARG` is stored candidate
options splited by `, `.

### Prehook

If you define a `prehook` function in the parser definition,
the `prehook` function will be called before helper functions is called.
Use `invoke` to call the original function from within a `prehook` function.

NOTE: The `prehook` function is not called in the help.

```sh
extension() {
  # The prehook is called before helper functions is called.
  prehook() {
    helper=$1 varname_or_action=$2
    shift 2

    # Do something

    invoke "$helper" "$varname_or_action" "$@"
  }
}

parser_definition() {
  extension
  setup   REST
  flag    FLAG -f  --flag
}
```

## Helper functions

Helper functions are not defined globally.
They are available only in the `getoptions` and `getoptions_help` functions.

### Data types & Initial values

| name       | description                                                                                |
| ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| SWITCH     | `-?`, `+?`, `--*`, `--no-*`, `--with-*`, `--without-*`,
`--{no-}*` (expand to `--flag` and `--no-flag`),
`--with{out}-*` (expand to `--with-flag` and `--without-flag`) |
| BOOLEAN    | Boolean (true: not zero-length string, false: **zero-length string**)                                                                                |
| STRING     | String                                                                                |
| NUMBER     | Number                                                                                |
| STATEMENT  | Function name (arguments can be added) - e.g. `foo`, `foo 1 2 3`                                                                                |
| CODE       | Statement or multiple statements - e.g `foo; bar`                                                                                |
| KEY-VALUE  | `key:value` style arguments - If `:value` is omitted, it is the same as `key:key`                                                                                |
| INIT-VALUE | Initial value (`@on`, `@no`, `@unset`, `@none`, `@export`)                                                                                |

| name      | description                                                                  |
| --------- | ---------------------------------------------------------------------------- |
| `@on`     | Set the variable to a positive value. [default: `1`]                         |
| `@no`     | Set the variable to a negative value. [default: empty]                       |
| `@unset`  | Unset the variable                                                           |
| `@none`   | Do not initialization (Use the current value as it is)                       |
| `@export` | Add only export flag without initialization (Use the current value as it is) |

### `setup` - Setup global settings (mandatory)

`setup <restargs> [settings...] [default attributes...] [-- [messages...]]`

- resrargs (VARIABLE)
  - The variable name for getting rest arguments
  - Specify `-` if you want to omit the restargs
- settings (KEY-VALUE)
  - `abbr`:BOOLEAN - Handle abbreviated long options (requires `getoptions_abbr`)
  - `alt`:BOOLEAN - allow long options starting with single `-` (alternative)
    - Unlike `getopt`, the syntax `-abc` and `-sVALUE` cannot be used when enabled
  - `error`:STATEMENT - Custom error handler
  - `help`:STATEMENT - Define help function (requires `getoptions_help`)
  - `leading`:STRING - Leading characters in the option part of the help [default: `"  "` (two spaces)]
  - `mode`:STRING - Scanning modes (See below or `man getopt`) [default: empty]
  - `plus`:BOOLEAN - Those start with `+` are treated as options [default: auto]
  - `width`:NUMBER - The width of the option or subcommand part of the help [default: `"30,12"`]
- default attributes (KEY-VALUE)
  - `export`:BOOLEAN - Export variables [default: empty]
  - `hidden`:BOOLEAN - Do not display in help [default: empty]
  - `init`:[@INIT-VALUE | =STRING | CODE] - Initial value
  - `on`:STRING - The positive value [default: `1`]
  - `no`:STRING - The negative value [default: empty]
- message (STRING)
  - Help messages

#### Scanning modes

| mode | `getopt`      | `getoptions`                                                |
| ---- | ------------- | ----------------------------------------------------------- |
| `+`  | Supported     | Stop parsing when an argument (not an option) is found      |
| `-`  | Supported     | Not supported (No point in supporting it)                   |
| `@`  | Not supported | Similar to mode `+` but leaves `--` (AKA, subcommands mode) |

### `flag` - Define an option that take no argument

`flag <varname | :action> [switches...] [attributes...] [-- [messages...]]`

- varname (VARIABLE) or action (STATEMENT)
  - Variable name or Action function
- switches (SWITCH)
  - Options
- attributes (KEY-VALUE)
  - `abbr`:BOOLEAN - Set empty to disable individually [default: `1` if abbreviation option is enabled]
  - `counter`:BOOLEAN - Counts the number of flags
  - `export`:BOOLEAN - Export variables
  - `hidden`:BOOLEAN - Do not display in help
  - `init`:[@INIT-VALUE | =STRING | CODE] - Initial value
  - `label`:STRING - The option part of the help
  - `on`:STRING - The positive value
  - `no`:STRING - The negative value
  - `pattern`:PATTERN - Pattern to accept
  - `validate`:STATEMENT - Code for value validation
- message (STRING)
  - Help messages

### `param` - Define an option that take an argument

`param <varname | :action> [switches...] [attributes...] [-- [messages...]]`

- varname (VARIABLE) or action (STATEMENT)
  - Variable name or Action function
- switches (SWITCH)
  - Options
- attributes (KEY-VALUE)
  - `abbr`:BOOLEAN - Set empty to disable individually [default: `1` if abbreviation option is enabled]
  - `export`:BOOLEAN - Export variables
  - `hidden`:BOOLEAN - Do not display in help
  - `init`:[@INIT-VALUE | =STRING | CODE] - Initial value
  - `label`:STRING - Option part of help message
  - `pattern`:PATTERN - Pattern to accept
  - `validate`:STATEMENT - Code for value validation
  - `var`:STRING - Variable name displayed in help
- message (STRING)
  - Help messages

### `option` - Define an option that take an optional argument

`option <varname | :action> [switches...] [attributes...] [-- [messages...]]`

- varname (VARIABLE) or action (STATEMENT)
  - Variable name or Action function
- switches (SWITCH)
  - Options
- attributes (KEY-VALUE)
  - `abbr`:BOOLEAN - Set empty to disable individually [default: `1` if abbreviation option is enabled]
  - `export`:BOOLEAN - Export variables
  - `hidden`:BOOLEAN - Do not display in help
  - `init`:[@INIT-VALUE | =STRING | CODE] - Initial value
  - `label`:STRING - Option part of help message
  - `on`:STRING - The positive value
  - `no`:STRING - The negative value
  - `pattern`:PATTERN - Pattern to accept
  - `validate`:STATEMENT - Code for value validation
  - `var`:STRING - Variable name displayed in help
- message (STRING)
  - Help messages

### `disp` - Define an option that display only

`disp <varname | :action> [switches...] [attributes...] [-- [messages...]]`

- varname (VARIABLE) or action (STATEMENT)
  - Variable name or Action function
- switches (SWITCH)
  - Options
- attributes (KEY-VALUE)
  - `abbr`:BOOLEAN - Set empty to disable individually [default: `1` if abbreviation option is enabled]
  - `hidden`:BOOLEAN - Do not display in help
  - `label`:STRING - Option part of help message
- message (STRING)
  - Help messages

### `msg` - Display message in help

`msg [attributes...] [-- [messages...]]`

- attributes (KEY-VALUE)
  - `hidden`:BOOLEAN - Do not display in help
  - `label`:STRING - Option part of help message
- message (STRING)
  - Help messages

### `cmd` - Define a subcommand

`cmd [subcommand] [-- [messages...]]`

- attributes (KEY-VALUE)
  - `hidden`:BOOLEAN - Do not display in help
  - `label`:STRING - Command part of help message
- message (STRING)
  - Help messages

NOTE: Defining a subcommand will change the scanning mode to `@` (subcommands mode).

### Attributes related to the help display

```console
$ example.sh ---help

Options:
+----------------------------- message -----------------------------+
:                                                                   :
+----------- label -----------+                                     :
:   width [default: 30]       :                                     :
:                             :                                     :
  -f, +f, --flag              message for --flag
  -l, +l, --long-long-option-name
                              message for --long-long-option-name
  -o, +o, --option VAR        message for --option
|     |            |
|     |            +-- var
|     +-- plus (visible if specified)
+-- leading [default: "  " (two spaces)]

Commands:
+---------------- message ----------------+
:                                         :
+-- label --+                             :
: width [12]:                             :
:           :                             :
  cmd1      subcommand1
  cmd2      subcommand2
  cmd3      subcommand3
|
+-- leading [default: "  " (two spaces)]
```


text:
name: Test

on: [push]

jobs:
  test:
    runs-on: ubuntu-20.04
    steps:
    - uses: actions/checkout@v1
    - name: Install packages
      run: |
        sudo apt-get update
        sudo apt-get install -y bash busybox ksh mksh posh yash zsh kcov
    - name: Install shellspec
      run: |
        echo "${HOME}/.local/bin" >> "$GITHUB_PATH"
        curl -fsSL https://git.io/shellspec | sh -s master -y
        echo "--color" > $HOME/.shellspec
    - name: Run tests
      env:
        COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }}
        CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
      run: |
        make testall coverage


text:
name: Release

on:
  push:
    tags:
      - 'v[0-9]*'

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v1
    - name: Make dist archive
      run: make dist
    - name: Create release
      id: create_release
      uses: actions/create-release@v1.0.0
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      with:
        tag_name: ${{ github.ref }}
        release_name: ${{ github.ref }}
        draft: false
        prerelease: ${{ contains(github.ref, '-') }}
    - name: Upload getoptions
      uses: actions/upload-release-asset@v1
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      with:
        upload_url: ${{ steps.create_release.outputs.upload_url }}
        asset_path: bin/getoptions
        asset_name: getoptions
        asset_content_type: application/octet-stream
    - name: Upload gengetoptions
      uses: actions/upload-release-asset@v1
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      with:
        upload_url: ${{ steps.create_release.outputs.upload_url }}
        asset_path: bin/gengetoptions
        asset_name: gengetoptions
        asset_content_type: application/octet-stream
    - name: Upload getoptions.tar.gz
      uses: actions/upload-release-asset@v1
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      with:
        upload_url: ${{ steps.create_release.outputs.upload_url }}
        asset_path: getoptions.tar.gz
        asset_name: getoptions.tar.gz
        asset_content_type: application/gzip
    - name: Upload gengetoptions.tar.gz
      uses: actions/upload-release-asset@v1
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      with:
        upload_url: ${{ steps.create_release.outputs.upload_url }}
        asset_path: gengetoptions.tar.gz
        asset_name: gengetoptions.tar.gz
        asset_content_type: application/gzip


text:
#!/bin/sh
# gengetoptions is released under CC0
# https://creativecommons.org/publicdomain/zero/1.0/

set -euf

VERSION="VERSION" # @VAR
URL="URL" # @VAR
LICENSE="LICENSE" # @VAR

SHELLCHECK="shell=sh disable=SC2004,SC2034,SC2145,SC2194"

[ "${ZSH_VERSION:-}" ] && setopt shwordsplit

parser_definition_setup() {
	setup REST help:usage abbr:true width:25 error -- \
		"Usage: ${1##*/} $2" '' 'Options:'
}

parser_definition() {
	parser_definition_setup "$2" \
		"[options]... <command> [arguments]..."

	disp :info --info -- 'Show detected getoptions info'
	disp :usage -h --help -- "Display this help and exit"
	disp VERSION -v --version -- "Display the version and exit"

	msg -- '' 'Commands:'

	cmd library -- 'Generate custom library'
	cmd parser -- 'Generate option parser'
	cmd embed -- 'Embed the generated library or parser'
	cmd example -- 'Generate parser definition example'
}

parser_definition_library() {
	parser_definition_setup "$2" \
		"library [options]..."

	flag SHELLCHECK --shellcheck -- \
		"Embed the shellcheck directives"

	flag BASE --no-base init:=getoptions -- \
		"Do not include base library"

	flag ABBR --no-abbr init:=getoptions_abbr -- \
		"Do not include abbr library"

	flag HELP --no-help init:=getoptions_help -- \
		"Do not include help library"

	parser_definition_style

	disp :usage -h --help -- "Display this help and exit"
}

parser_definition_parser() {
	parser_definition_setup "$2" \
		"parser [options]... <definition> <parser> [arguments]..."

	param FILE -f --file -- \
		"Read from a file instead of stdin"

	param DEFINITION -d --definition var:NAME -- \
		"Specify the parser definition name (deprecated)"

	option SHELLCHECK --shellcheck var:DIRECTIVES on:"$SHELLCHECK" -- \
		"Embed the shellcheck directives" "  [default: '$SHELLCHECK']"

	parser_definition_style

	disp :usage -h --help -- "Display this help and exit"
}

parser_definition_style() {
	option INDENT -i --indent on:2 validate:number var:N -- \
		"Use N spaces instead of tabs for indentation [default: 2]"

	flag COMMENT --no-comments init:@on -- \
		"Do not embed comments"

	param ARG --optarg -- \
		"Use ARG instead of the variable name OPTARG"

	param IND --optind -- \
		"Use IND instead of the variable name OPTIND"
}

parser_definition_embed() {
	parser_definition_setup "$2" \
		"embed [options]... <file>"

	flag OVERWRITE -w --overwrite -- \
		"Overwrite the specified file"

	flag ERASE -e --erase -- \
		"Erase the embedded code"

	disp :usage -h --help -- "Display this help and exit"
}

number() {
	case $OPTARG in (*[!0-9]*)
		return 1
	esac
	return 0
}

abort() {
	echo "gengetoptions:" "$@" >&2
	exit 1
}

error() {
	case $2 in
		number:*) echo "Not a number: $3" ;;
		*) return 0 ;;
	esac
	return 1
}

parser_definition_not_found() {
	abort "The parser definition '$1' not found"
}

is_valid_identifier() {
	case $1 in (*[!a-zA-Z0-9_]*) return 1; esac
	return 0
}

parse_shellcheck_directives() {
	shellcheck_shell='' shellcheck_disable=''
	# shellcheck disable=SC2086
	set -- $1
	while [ $# -gt 0 ]; do
		case $1 in
			shell=*) shellcheck_shell=${1#*\=} ;;
			disable=*) shellcheck_disable=${1#*\=} ;;
		esac
		shift
	done
}

comment() {
	[ "$COMMENT" ] || return 0
	echo "#" "$@"
}

shellcheck() {
	[ "$2" ] || return 0
	printf '# shellcheck %s=%s\n' "$1" "$2"
}

replace_all() {
	if [ $# -lt 4 ]; then
		eval "set -- \"\$1\" \"\${$1}\" \"\$2\" \"\$3\" \"\""
	else
		set -- "$1" "$2" "$3" "$4" ""
	fi
	while :; do
		case $2 in
			*"$3"*) set -- "$1" "${2#*"$3"}" "$3" "$4" "$5${2%%"$3"*}$4" ;;
			*) break ;;
		esac
	done
	eval "$1=\$5\$2"
}

chvarname() {
	while IFS= read -r line; do
		[ "$ARG" ] && replace_all line OPTARG "$ARG"
		[ "$IND" ] && replace_all line OPTIND "$IND"
		printf '%s\n' "$line"
	done
}

# shellcheck disable=SC2004
tab2space() {
	[ ! "${1:-}" ] && cat && return 0
	TAB=$(printf '\t')

	prefix='' i=0
	while [ "$i" -lt "$1" ] && i=$(($i + 1)); do
		prefix="$prefix "
	done
	i=0

	while IFS= read -r line; do
		while [ "$line" ]; do
			case $line in ([!${TAB}]*) break; esac
			i=$(($i + 1)) line=${line#?}
		done
		while [ "$i" -gt 0 ] && i=$(($i - 1)); do
			line="${prefix}${line}"
		done
		printf '%s\n' "$line"
	done
}

embed() {
	args=''
	while IFS= read -r line; do
		case $line in
			\#\ @end)
				if [ "$args" ]; then
					[ "$ERASE" ] || eval "fetch < \"\$1\" | gengetoptions $args"
					args=''
				fi
				;;
			\#\ @gengetoptions\ *)
				[ "$args" ] && abort 'Missing @end directive'
				args=${line#*\ *\ }
				;;
			*) [ "$args" ] && continue
		esac
		printf '%s\n' "$line"
	done
	[ "$args" ] && abort 'Missing @end directive'
	printf '%s' "$line"
}

fetch() {
	in_section=''
	while IFS= read -r line; do
		case $line in
			\#\ @getoptions) in_section=1 && continue ;;
			\#\ @end) in_section='' && continue ;;
		esac
		if [ "$in_section" ]; then
			printf '%s\n' "$line"
		fi
	done
}

do_library() {
	eval "$(getoptions parser_definition_library parse "$0")"
	parse "$@"
	eval "set -- $REST"

	lib=$("$GETOPTIONS" -)
	printf '%s\n' "$lib" | {
		# shellcheck disable=SC2030
		funcname='' shellcheck_disable=""
		while IFS= read -r line; do
			case $line in
				\#\ shellcheck*)
					shellcheck_disable=""
					[ "$SHELLCHECK" ] || continue
					case $line in (*disable*)
						shellcheck_disable=${line#*disable=}
					esac
					continue ;;
				\#*) continue ;;
				getoptions*)
					funcname=${line%%\(\)*}
					case $funcname in ("$BASE" | "$ABBR" | "$HELP")
						comment "[$funcname] License: $LICENSE"
						comment "$URL ($VERSION)"
						shellcheck disable "$shellcheck_disable"
					esac
			esac
			case $funcname in ("$BASE" | "$ABBR" | "$HELP")
				printf '%s\n' "$line"
			esac
		done
	 } | tab2space "$INDENT" | chvarname
}

do_parser() {
	eval "$(getoptions parser_definition_parser parse "$0")"
	parse "$@"
	eval "set -- $REST"
	[ $# -lt 2 ] && usage && exit 1

	if [ "$DEFINITION" ] || { [ ! "$FILE" ] && [ -e "$1" ]; } then
		echo "Deprecated. Use the --file option instead." >&2
		if [ ! "$DEFINITION" ]; then
			DEFINITION=${1##*/} && DEFINITION=${DEFINITION%%.*}
		fi
		FILE=$1 parser=$2 definition=$DEFINITION
		shift
		set -- "$definition" "$@"
	else
		parser=$1 definition=$2
	fi

	if [ "$definition" != "-" ]; then
		if is_valid_identifier "$definition"; then
			eval "$definition() { parser_definition_not_found \"\$definition\"; }"
		else
			abort "The parser definition name '$definition' is not a valid name"
		fi
	fi
	if ! is_valid_identifier "$parser"; then
		abort "The parser name '$parser' is not a valid name"
	fi

	# shellcheck disable=SC1090
	case $FILE in
		'' | - ) eval "$(cat)" ;;
		*) . "$FILE" ;;
	esac
	generated=$(getoptions "$@")
	printf '%s\n' "$generated" | {
		parse_shellcheck_directives "$SHELLCHECK"
		shellcheck shell "$shellcheck_shell"
		comment "Generated by getoptions (BEGIN)"
		comment "URL: $URL ($VERSION)"
		while IFS= read -r line; do
			[ "$line" = "# Do not execute" ] && continue
			if [ "$line" = "$parser() {" ]; then
				# shellcheck disable=SC2031
				shellcheck disable "$shellcheck_disable"
			fi
			printf '%s\n' "$line"
		done
		comment "Generated by getoptions (END)"
	} | tab2space "$INDENT" | chvarname
}

do_embed() {
	eval "$(getoptions parser_definition_embed parse "$0")"
	parse "$@"
	eval "set -- $REST"
	[ $# -lt 1 ] && usage && exit 1

	[ -e "$1" ] || abort "No such file: $1"

	if [ "$OVERWRITE" ]; then
		# shellcheck disable=SC2094
		contents=$(embed "$1" < "$1")
		printf '%s\n' "$contents" > "$1"
	else
		# shellcheck disable=SC2094
		embed "$1" < "$1"
	fi
}

do_example() {
cat<<'HERE'
parser_definition() {
	setup   REST plus:true help:usage abbr:true \
		-- "Usage: ${2##*/} [options...] [arguments...]" ''
	msg -- 'Options:'
	flag    FLAG    -f +f --{no-}flag
	flag    VERBOSE -v    --verbose   counter:true init:=0
	param   PARAM   -p    --param     pattern:"foo | bar"
	option  OPTION  -o    --option    on:"default" validate:number
	disp    :usage  -h    --help
	disp    VERSION       --version
}
eval "$(getoptions parser_definition parse "$0") exit 1"
parse "$@"
eval "set -- $REST"
HERE
}

info() {
	"$GETOPTIONS"
	echo "Path: $GETOPTIONS"
}

if [ -e "${0%/*}/getoptions" ]; then
	GETOPTIONS="$(cd "${0%/*}" && pwd)/getoptions"
elif which getoptions >/dev/null 2>&1; then
	GETOPTIONS=$(which getoptions)
else
	abort "getoptions not found"
fi
getoptions() { "$GETOPTIONS" "$@"; }

eval "$(getoptions parser_definition parse "$0")"
parse "$@"
eval "set -- $REST"

[ "${1:---}" = "--" ] && usage && exit 1
command=$1
shift
"do_$command" "$@"


text:
#!/bin/sh
# shellcheck disable=SC2016,SC2039
# getoptions is released under CC0
# https://creativecommons.org/publicdomain/zero/1.0/

set -eu

VERSION="VERSION" # @VAR
URL="URL" # @VAR
LICENSE="LICENSE" # @VAR

outlibs() {
	# @OUTPUT-BEGIN
	cat lib/getoptions_base.sh # @INCLUDE-FILE
	cat lib/getoptions_abbr.sh # @INCLUDE-FILE
	cat lib/getoptions_help.sh # @INCLUDE-FILE
	# @OUTPUT-END
}

quote() {
	q="$2'" r=''
	while [ "$q" ]; do r="$r${q%%\'*}'\''" && q=${q#*\'}; done
	q="'${r%????}'" && q=${q#\'\'} && q=${q%\'\'}
	eval "$1=\${q:-\"''\"}"
}

usage() {
	echo 'Usage: eval "$(getoptions <definition> [<parser|-> [arguments]...]) exit 1"'
}

info() {
	echo "getoptions $VERSION ($URL)"
	echo "License: $LICENSE"
}

case ${1:-} in
	'') [ -t 1 ] && info && exit 0;;
	-v | --version) echo "$VERSION" && exit 0 ;;
	-h | --help) usage && exit 0 ;;
	-?*) info && usage && exit 1 ;;
esac

if [ "${KSH_VERSION:-}" ] || [ "${ZSH_VERSION:-}" ]; then
	outlibs 'print' '-r' '--'
else
	outlibs 'printf' '%s\n'
fi
[ "${1:-}" = - ] && exit 0
for i; do quote i "$i"; set -- "$@" "$i"; shift; done
echo "eval \"\$(getoptions $*)\""


text:
#!/bin/sh
# shellcheck disable=SC2016,SC2034

set -eu

VERSION="v3.3.0"
URL="https://github.com/ko1nksm/getoptions"
LICENSE="Creative Commons Zero v1.0 Universal"

while IFS= read -r line; do
	case $line in
		*"# @OUTPUT-BEGIN") echo "\"\$@\" '" ;;
		*"# @OUTPUT-END") echo "'" ;;
		*"# @INCLUDE-FILE") eval "$line" | sed "s/'/'\\\\''/g" ;;
		*"# @VAR")
			var=${line%%\=*} value=""
			eval "value=\$$var"
			printf '%s="%s"\n' "$var" "$value"
			;;
		*) printf '%s\n' "$line" ;;
	esac
done


text:
# shellcheck shell=sh disable=SC2016,SC2154
# [getoptions_abbr] License: Creative Commons Zero v1.0 Universal
getoptions_abbr() {
	abbr() {
		_3 "case '$1' in"
		_4 '"$1") OPTARG=; break ;;'
		_4 '$1*) OPTARG="$OPTARG '"$1"'"'
		_3 'esac'
	}
	args() {
		abbr=1
		shift
		for i; do
			case $i in
				--) break ;;
				[!-+]*) eval "${i%%:*}=\${i#*:}"
			esac
		done
		[ "$abbr" ] || return 0

		for i; do
			case $i in
				--) break ;;
				--\{no-\}*) abbr "--${i#--\{no-\}}"; abbr "--no-${i#--\{no-\}}" ;;
				--*) abbr "$i"
			esac
		done
	}
	setup() { :; }
	for i in flag param option disp; do
		eval "_$i()" '{ args "$@"; }'
	done
	msg() { :; }
	_2 'set -- "${1%%\=*}" "${1#*\=}" "$@"'
	[ "$_alt" ] && _2 'case $1 in -[!-]?*) set -- "-$@"; esac'
	_2 'while [ ${#1} -gt 2 ]; do'
	_3 'case $1 in (*[!a-zA-Z0-9_-]*) break; esac'
	"$@"
	_3 'break'
	_2 'done'
	_2 'case ${OPTARG# } in'
	_3 '*\ *)'
	_4 'eval "set -- $OPTARG $1 $OPTARG"'
	_4 'OPTIND=$((($#+1)/2)) OPTARG=$1; shift'
	_4 'while [ $# -gt "$OPTIND" ]; do OPTARG="$OPTARG, $1"; shift; done'
	_4 'set "Ambiguous option: $1 (could be $OPTARG)" ambiguous "$@"'
	[ "$_error" ] && _4 "$_error" '"$@" >&2 || exit $?'
	_4 'echo "$1" >&2'
	_4 'exit 1 ;;'
	_3 '?*)'
	_4 '[ "$2" = "$3" ] || OPTARG="$OPTARG=$2"'
	_4 "shift 3; eval 'set -- \"\${OPTARG# }\"' \${1+'\"\$@\"'}; OPTARG= ;;"
	_3 '*) shift 2'
	_2 'esac'
}


text:
# shellcheck shell=sh
# [getoptions_help] License: Creative Commons Zero v1.0 Universal
getoptions_help() {
	_width='30,12' _plus='' _leading='  '

	pad() { p=$2; while [ ${#p} -lt "$3" ]; do p="$p "; done; eval "$1=\$p"; }
	kv() { eval "${2-}${1%%:*}=\${1#*:}"; }
	sw() { pad sw "$sw${sw:+, }" "$1"; sw="$sw$2"; }

	args() {
		_type=$1 var=${2%% *} sw='' label='' hidden='' && shift 2
		while [ $# -gt 0 ] && i=$1 && shift && [ "$i" != -- ]; do
			case $i in
				--*) sw $((${_plus:+4}+4)) "$i" ;;
				-?) sw 0 "$i" ;;
				+?) [ ! "$_plus" ] || sw 4 "$i" ;;
				*) [ "$_type" = setup ] && kv "$i" _; kv "$i"
			esac
		done
		[ "$hidden" ] && return 0 || len=${_width%,*}

		[ "$label" ] || case $_type in
			setup | msg) label='' len=0 ;;
			flag | disp) label="$sw " ;;
			param) label="$sw $var " ;;
			option) label="${sw}[=$var] "
		esac
		[ "$_type" = cmd ] && label=${label:-$var } len=${_width#*,}
		pad label "${label:+$_leading}$label" "$len"
		[ ${#label} -le "$len" ] && [ $# -gt 0 ] && label="$label$1" && shift
		echo "$label"
		pad label '' "$len"
		for i; do echo "$label$i"; done
	}

	for i in setup flag param option disp 'msg -' cmd; do
		eval "${i% *}() { args $i \"\$@\"; }"
	done

	echo "$2() {"
	echo "cat<<'GETOPTIONSHERE'"
	"$@"
	echo "GETOPTIONSHERE"
	echo "}"
}


text:
# shellcheck shell=sh disable=SC2016
# [getoptions] License: Creative Commons Zero v1.0 Universal
getoptions() {
	_error='' _on=1 _no='' _export='' _plus='' _mode='' _alt='' _rest='' _def=''
	_flags='' _nflags='' _opts='' _help='' _abbr='' _cmds='' _init=@empty IFS=' '
	[ $# -lt 2 ] && set -- "${1:?No parser definition}" -
	[ "$2" = - ] && _def=getoptions_parse

	i='					'
	while eval "_${#i}() { echo \"$i\$@\"; }"; [ "$i" ]; do i=${i#?}; done

	quote() {
		q="$2'" r=''
		while [ "$q" ]; do r="$r${q%%\'*}'\''" && q=${q#*\'}; done
		q="'${r%????}'" && q=${q#\'\'} && q=${q%\'\'}
		eval "$1=\${q:-\"''\"}"
	}
	code() {
		[ "${1#:}" = "$1" ] && c=3 || c=4
		eval "[ ! \${$c:+x} ] || $2 \"\$$c\""
	}
	sw() { sw="$sw${sw:+|}$1"; }
	kv() { eval "${2-}${1%%:*}=\${1#*:}"; }
	loop() { [ $# -gt 1 ] && [ "$2" != -- ]; }

	invoke() { eval '"_$@"'; }
	prehook() { invoke "$@"; }
	for i in setup flag param option disp msg; do
		eval "$i() { prehook $i \"\$@\"; }"
	done

	args() {
		on=$_on no=$_no export=$_export init=$_init _hasarg=$1 && shift
		while loop "$@" && shift; do
			case $1 in
				-?) [ "$_hasarg" ] && _opts="$_opts${1#-}" || _flags="$_flags${1#-}" ;;
				+?) _plus=1 _nflags="$_nflags${1#+}" ;;
				[!-+]*) kv "$1"
			esac
		done
	}
	defvar() {
		case $init in
			@none) : ;;
			@export) code "$1" _0 "export $1" ;;
			@empty) code "$1" _0 "${export:+export }$1=''" ;;
			@unset) code "$1" _0 "unset $1 ||:" "unset OPTARG ||:; ${1#:}" ;;
			*)
				case $init in @*) eval "init=\"=\${${init#@}}\""; esac
				case $init in [!=]*) _0 "$init"; return 0; esac
				quote init "${init#=}"
				code "$1" _0 "${export:+export }$1=$init" "OPTARG=$init; ${1#:}"
		esac
	}
	_setup() {
		[ "${1#-}" ] && _rest=$1
		while loop "$@" && shift; do kv "$1" _; done
	}
	_flag() { args '' "$@"; defvar "$@"; }
	_param() { args 1 "$@"; defvar "$@"; }
	_option() { args 1 "$@"; defvar "$@"; }
	_disp() { args '' "$@"; }
	_msg() { args '' _ "$@"; }

	cmd() { _mode=@ _cmds="$_cmds${_cmds:+|}'$1'"; }
	"$@"
	cmd() { :; }
	_0 "${_rest:?}=''"

	_0 "${_def:-$2}() {"
	_1 'OPTIND=$(($#+1))'
	_1 'while OPTARG= && [ $# -gt 0 ]; do'
	[ "$_abbr" ] && getoptions_abbr "$@"

	args() {
		sw='' validate='' pattern='' counter='' on=$_on no=$_no export=$_export
		while loop "$@" && shift; do
			case $1 in
				--\{no-\}*) i=${1#--?no-?}; sw "'--$i'|'--no-$i'" ;;
				--with\{out\}-*) i=${1#--*-}; sw "'--with-$i'|'--without-$i'" ;;
				[-+]? | --*) sw "'$1'" ;;
				*) kv "$1"
			esac
		done
		quote on "$on"
		quote no "$no"
	}
	setup() { :; }
	_flag() {
		args "$@"
		[ "$counter" ] && on=1 no=-1 v="\$((\${$1:-0}+\$OPTARG))" || v=''
		_3 "$sw)"
		_4 '[ "${OPTARG:-}" ] && OPTARG=${OPTARG#*\=} && set "noarg" "$1" && break'
		_4 "eval '[ \${OPTARG+x} ] &&:' && OPTARG=$on || OPTARG=$no"
		valid "$1" "${v:-\$OPTARG}"
		_4 ';;'
	}
	_param() {
		args "$@"
		_3 "$sw)"
		_4 '[ $# -le 1 ] && set "required" "$1" && break'
		_4 'OPTARG=$2'
		valid "$1" '$OPTARG'
		_4 'shift ;;'
	}
	_option() {
		args "$@"
		_3 "$sw)"
		_4 'set -- "$1" "$@"'
		_4 '[ ${OPTARG+x} ] && {'
		_5 'case $1 in --no-*|--without-*) set "noarg" "${1%%\=*}"; break; esac'
		_5 '[ "${OPTARG:-}" ] && { shift; OPTARG=$2; } ||' "OPTARG=$on"
		_4 "} || OPTARG=$no"
		valid "$1" '$OPTARG'
		_4 'shift ;;'
	}
	valid() {
		set -- "$validate" "$pattern" "$1" "$2"
		[ "$1" ] && _4 "$1 || { set -- ${1%% *}:\$? \"\$1\" $1; break; }"
		[ "$2" ] && {
			_4 "case \$OPTARG in $2) ;;"
			_5 '*) set "pattern:'"$2"'" "$1"; break'
			_4 "esac"
		}
		code "$3" _4 "${export:+export }$3=\"$4\"" "${3#:}"
	}
	_disp() {
		args "$@"
		_3 "$sw)"
		code "$1" _4 "echo \"\${$1}\"" "${1#:}"
		_4 'exit 0 ;;'
	}
	_msg() { :; }

	[ "$_alt" ] && _2 'case $1 in -[!-]?*) set -- "-$@"; esac'
	_2 'case $1 in'
	_wa() { _4 "eval 'set -- $1' \${1+'\"\$@\"'}"; }
	_op() {
		_3 "$1) OPTARG=\$1; shift"
		_wa '"${OPTARG%"${OPTARG#??}"}" '"$2"'"${OPTARG#??}"'
		_4 "$3"
	}
	_3 '--?*=*) OPTARG=$1; shift'
	_wa '"${OPTARG%%\=*}" "${OPTARG#*\=}"'
	_4 ';;'
	_3 '--no-*|--without-*) unset OPTARG ;;'
	[ "$_alt" ] || {
		[ "$_opts" ] && _op "-[$_opts]?*" '' ';;'
		[ ! "$_flags" ] || _op "-[$_flags]?*" - 'OPTARG= ;;'
	}
	[ "$_plus" ] && {
		[ "$_nflags" ] && _op "+[$_nflags]?*" + 'unset OPTARG ;;'
		_3 '+*) unset OPTARG ;;'
	}
	_2 'esac'
	_2 'case $1 in'
	"$@"
	rest() {
		_4 'while [ $# -gt 0 ]; do'
		_5 "$_rest=\"\${$_rest}" '\"\${$(($OPTIND-$#))}\""'
		_5 'shift'
		_4 'done'
		_4 'break ;;'
	}
	_3 '--)'
	[ "$_mode" = @ ] || _4 'shift'
	rest
	_3 "[-${_plus:++}]?*)" 'set "unknown" "$1"; break ;;'
	_3 '*)'
	case $_mode in
		@)
			_4 "case \$1 in ${_cmds:-*}) ;;"
			_5 '*) set "notcmd" "$1"; break'
			_4 'esac'
			rest ;;
		+) rest ;;
		*) _4 "$_rest=\"\${$_rest}" '\"\${$(($OPTIND-$#))}\""'
	esac
	_2 'esac'
	_2 'shift'
	_1 'done'
	_1 '[ $# -eq 0 ] && { OPTIND=1; unset OPTARG; return 0; }'
	_1 'case $1 in'
	_2 'unknown) set "Unrecognized option: $2" "$@" ;;'
	_2 'noarg) set "Does not allow an argument: $2" "$@" ;;'
	_2 'required) set "Requires an argument: $2" "$@" ;;'
	_2 'pattern:*) set "Does not match the pattern (${1#*:}): $2" "$@" ;;'
	_2 'notcmd) set "Not a command: $2" "$@" ;;'
	_2 '*) set "Validation error ($1): $2" "$@"'
	_1 'esac'
	[ "$_error" ] && _1 "$_error" '"$@" >&2 || exit $?'
	_1 'echo "$1" >&2'
	_1 'exit 1'
	_0 '}'

	[ "$_help" ] && eval "shift 2; getoptions_help $1 $_help" ${3+'"$@"'}
	[ "$_def" ] && _0 "eval $_def \${1+'\"\$@\"'}; eval set -- \"\${$_rest}\""
	_0 '# Do not execute' # exit 1
}


text:
#!/bin/sh

set -eu

VERSION=0.1

# shellcheck disable=SC1083
parser_definition() {
	setup   REST plus:true help:usage abbr:true -- \
		"Usage: ${2##*/} [options...] [arguments...]" ''
	msg -- 'getoptions basic example' ''
	msg -- 'Options:'
	flag    FLAG_A  -a                                        -- "message a"
	flag    FLAG_B  -b                                        -- "message b"
	flag    FLAG_F  -f +f --{no-}flag                         -- "expands to --flag and --no-flag"
	flag    FLAG_W        --with{out}-flag                    -- "expands to ---with-flag and --without-flag"
	flag    VERBOSE -v    --verbose   counter:true init:=0    -- "e.g. -vvv is verbose level 3"
	param   PARAM   -p    --param     pattern:"foo | bar"     -- "accepts --param value / --param=value"
	param   NUMBER  -n    --number    validate:number         -- "accepts only a number value"
	option  OPTION  -o    --option    on:"default"            -- "accepts -ovalue / --option=value"
	disp    :usage  -h    --help
	disp    VERSION       --version
}

number() { case $OPTARG in (*[!0-9]*) return 1; esac; }

eval "$(getoptions parser_definition - "$0") exit 1"

echo "FLAG_A: $FLAG_A"
echo "FLAG_B: $FLAG_B"
echo "FLAG_F: $FLAG_F"
echo "FLAG_W: $FLAG_W"
echo "VERBOSE: $VERBOSE"
echo "PARAM: $PARAM"
echo "NUMBER: $NUMBER"
echo "OPTION: $OPTION"
echo "VERSION: $VERSION"
i=0
while [ $# -gt 0 ] && i=$((i + 1)); do
	echo "$i: $1"
	shift
done


text:
#!/bin/bash
# shellcheck disable=SC2153

set -eu

VERSION=0.1
BLOOD_TYPES='A | B | O | AB'

: "${LANG:=C}"

# shellcheck disable=SC1083,SC2016,SC2145
parser_definition() {
	# custom helper functions
	array() { param ":append_array $@"; }
	parray() { param ":append_array_posix $@"; } # posix version

	setup   REST error:error on:1 no: export:true plus:true width:35 help:usage abbr:true -- \
		"Usage: ${2##*/} [options...] [arguments...]" '' \
		'getoptions advanced example' ''
	msg     label:"OPTION" -- "DESCRIPTION"
	flag    FLAG_A    -a --flag-a on:1 no: init:= export:
	flag    FLAG_B    -b +b --{no-}flag-b on:ON no:NO init:@no
	flag    FLAG_C    -c +c --{no-}flag-c on:1 no:0 init:@unset
	flag    VERBOSE   -v +v --{no-}verbose counter:true init:=0
	param   PARAM     -p    --param init:="default"
	param   LANG            --lang init:@none # or init:="$LANG" for using current value
	param   NUMBER          --number validate:number
	param   RANGE           --range validate:'range 10 100' \
		-- '10 - 100'
	param   PATTERN         --pattern pattern:'foo | bar' \
		-- 'foo | bar'
	param   BLOOD_TYPE      --blood-type validate:blood_type pattern:"$BLOOD_TYPES" \
		-- "$BLOOD_TYPES"
	param   REGEX           --regex validate:'regex "^[1-9][0-9]*$"' \
		-- '^[1-9][0-9]*$'
	param   :multiple       --multiple init:'MULTIPLE=""' var:MULTIPLE
	array   ARRAY           --array init:'ARRAY=()' var:VALUE
	parray 	PARRAY          --array-posix init:'PARRAY=""' var:VALUE
	param   :'action "$1" p1 p2' --act1 --act2 var:param
	option  OPTION    -o +o --{no-}option on:"on value" no:"no value"
	disp    :"getoptions parser_definition parse ''" --generate \
		-- 'Display parser code'
	disp    :usage -h --help
	disp    VERSION --version
}

error() {
	case $2 in
		unknown) echo "$1" ;;
		number:*) echo "Not a number: $3" ;;
		range:1) echo "Not a number: $3" ;;
		range:2) echo "Out of range ($5 - $6): $3"; return 2 ;;
		pattern:"$BLOOD_TYPES") echo "Invalid blood type: $3"; return 2 ;;
		regex:*) echo "Not match regex ($4): $3" ;;
		*) return 0 ;; # Display default error
	esac
	return 1
}

number() {
	case $OPTARG in (*[!0-9]*) return 1; esac
}

blood_type() {
	# Normalization only
	case $OPTARG in
		a) OPTARG="A" ;;
		b) OPTARG="B" ;;
		[aA][bB]) OPTARG="AB" ;;
		o) OPTARG="O" ;;
	esac
}

range() {
	number || return 1
	[ "$1" -le "$OPTARG" ] && [ "$OPTARG" -le "$2" ] && return 0
	return 2
}

regex() {
	awk -v s="$OPTARG" -v r="$1" 'BEGIN{exit match(s, r)==0}'
}

multiple() {
	MULTIPLE="${MULTIPLE}${MULTIPLE:+,}${OPTARG}"
}

append_array() {
	eval "$1+=(\"\$OPTARG\")"
}

append_array_posix() {
	# Store multiple (escaped) values in one variable in a POSIX compliant way
	set -- "$1" "$OPTARG"
	until [ "${2#*\'}" = "$2" ] && eval "$1=\"\$$1 '\${3:-}\$2'\""; do
		set -- "$1" "${2#*\'}" "${2%%\'*}'\"'\"'"
	done
}

action() {
	# Example of passing options and parameters
	echo "Do action: option => [$1], param=>[$2, $3], arg => [$OPTARG]"
	exit
}

disp_array() {
	echo "$1:"
	shift
	i=0
	while [ $# -gt 0 ] && i=$((i + 1)); do
		echo "  $i: $1"
		shift
	done
}

eval "$(getoptions parser_definition parse "$0") exit 1"
parse "$@"
eval "set -- $REST"

echo "FLAG_A: $FLAG_A"
echo "FLAG_B: $FLAG_B"
if [ ${FLAG_C+x} ]; then
	echo "FLAG_C: $FLAG_C"
else
	echo "FLAG_C: <unset>"
fi
echo "VERBOSE: $VERBOSE"
echo "PARAM: $PARAM"
echo "LANG: $LANG"
echo "NUMBER: $NUMBER"
echo "RANGE: $RANGE"
echo "PATTERN: $PATTERN"
echo "BLOOD_TYPE: $BLOOD_TYPE"
echo "REGEX: $REGEX"
echo "MULTIPLE: $MULTIPLE"
if [ ${#ARRAY[@]} -eq 0 ]; then
	disp_array ARRAY
else
	disp_array ARRAY "${ARRAY[@]}"
fi
eval disp_array PARRAY "$PARRAY"
echo "OPTION: $OPTION"
echo "VERSION: $VERSION"
disp_array arguments "$@"


text:
#!/bin/sh

set -eu

# shellcheck disable=SC2034
VERSION="0.1"

parser_definition() {
  setup   REST help:usage -- "Usage: example.sh [options]... [arguments]..." ''
  msg -- 'Options:'
  flag    FLAG    -f --flag                 -- "takes no arguments"
  param   PARAM   -p --param                -- "takes one argument"
  option  OPTION  -o --option on:"default"  -- "takes one optional argument"
  disp    :usage  -h --help
  disp    VERSION    --version
}

[ "${GETOPTIONS:-}" ] && return 0

case ${MODE:-command} in
  command | library)
    if [ "${MODE:-}" = "library" ]; then
      # shellcheck disable=SC1091
      . ./getoptions-library.sh
    fi
    eval "$(getoptions parser_definition -) exit 1" ;;
  parser)
    # shellcheck disable=SC1091
    . ./getoptions-parser.sh ;;
esac

echo "FLAG: $FLAG, PARAM: $PARAM, OPTION: $OPTION"
printf '%s\n' "$@"


text:
#!/bin/sh

set -eu

type hyperfine || exit 1

cd "${0%/*}"

# warmup
i=0; while [ "$i" -lt 100000 ]; do i=$((i+1)); done

bench() {
  echo "[$1]"
  export MODE="$1"
  set -- ./example.sh --flag --param param --option=option a b c
  hyperfine --warmup 1 "$*"
}

bench "command"

lib="./getoptions-library.sh"
gengetoptions library > "$lib"
bench "library"
rm "$lib"

lib="./getoptions-parser.sh"
gengetoptions parser -f ./example.sh parser_definition - > "$lib"
bench "parser"
rm "$lib"


text:
#!/bin/sh

set -eu

PREFIX_VERSION=0.1

# shellcheck disable=SC2145
parser_definition() {
	# Append prefix to variable name using prehook
	prefix=$3
	prehook() {
		helper=$1 subject=$2
		shift 2
		case $helper in (flag | param | option)
			case $subject in
				:*) subject="$subject $prefix" ;;
				*) subject="${prefix}_${subject}" ;;
			esac
		esac
		invoke "$helper" "$subject" "$@"
	}

	multi() { param ":multiple $@"; } # custom helper function

	setup   REST help:usage -- \
		"Usage: ${2##*/} [options...] [arguments...]" '' \
		'getoptions extension example' ''
	flag    FLAG      -f  --flag
	param   PARAM     -p  --param
	multi   MULTIPLE  -m  --multiple init:"${3}_MULTIPLE=''"
	disp    :usage    -h  --help
	disp    VERSION   -v  --version
}

multiple() {
	eval "$2_$1=\${$2_$1}\${$2_$1:+,}\${OPTARG}"
}

eval "$(getoptions parser_definition parse "$0" PREFIX) exit 1"
parse "$@"
eval "set -- $REST"

echo "FLAG: $PREFIX_FLAG"
echo "PARAM: $PREFIX_PARAM"
echo "MULTIPLE: $PREFIX_MULTIPLE"
echo "VERSION: $PREFIX_VERSION"

i=0
while [ $# -gt 0 ] && i=$((i + 1)); do
	echo "$i: $1"
	shift
done


text:
#!/bin/sh

set -eu

# shellcheck disable=SC2034
VERSION=0.1

# shellcheck disable=SC1083
parser_definition() {
	setup   REST help:usage abbr:true -- \
		"Usage: ${2##*/} [global options...] [command] [options...] [arguments...]"
	msg -- '' 'getoptions subcommand example' ''
	msg -- 'Options:'
	flag    GLOBAL  -g --global    -- "global flag"
	disp    :usage  -h --help
	disp    VERSION    --version

	msg -- '' 'Commands:'
	cmd cmd1 -- "subcommand 1"
	cmd cmd2 -- "subcommand 2"
	cmd cmd3 -- "subcommand 3"
}

# shellcheck disable=SC1083
parser_definition_cmd1() {
	setup   REST help:usage abbr:true -- \
		"Usage: ${2##*/} cmd1 [options...] [arguments...]"
	msg -- '' 'getoptions subcommand example' ''
	msg -- 'Options:'
	flag    FLAG_A  -a --flag-a
	disp    :usage  -h --help
}

# shellcheck disable=SC1083
parser_definition_cmd2() {
	setup   REST help:usage abbr:true -- \
		"Usage: ${2##*/} cmd2 [options...] [arguments...]"
	msg -- '' 'getoptions subcommand example' ''
	msg -- 'Options:'
	flag    FLAG_B  -b --flag-b
	disp    :usage  -h --help
}

# shellcheck disable=SC1083
parser_definition_cmd3() {
	setup   REST help:usage abbr:true -- \
		"Usage: ${2##*/} cmd3 [options...] [arguments...]"
	msg -- '' 'getoptions subcommand example' ''
	msg -- 'Options:'
	flag    FLAG_C  -c --flag-c
	disp    :usage  -h --help
}

eval "$(getoptions parser_definition parse "$0") exit 1"
parse "$@"
eval "set -- $REST"

if [ $# -gt 0 ]; then
	cmd=$1
	shift
	case $cmd in
		cmd1)
			eval "$(getoptions parser_definition_cmd1 parse "$0")"
			parse "$@"
			eval "set -- $REST"
			echo "FLAG_A: $FLAG_A"
			;;
		cmd2)
			eval "$(getoptions parser_definition_cmd2 parse "$0")"
			parse "$@"
			eval "set -- $REST"
			echo "FLAG_B: $FLAG_B"
			;;
		cmd3)
			eval "$(getoptions parser_definition_cmd3 parse "$0")"
			parse "$@"
			eval "set -- $REST"
			echo "FLAG_C: $FLAG_C"
			;;
		--) # no subcommand, arguments only
	esac
fi

echo "GLOBAL: $GLOBAL"
i=0
while [ $# -gt 0 ] && i=$((i + 1)); do
	echo "$i: $1"
	shift
done


text:
# shellcheck shell=sh disable=SC1083

parser_definition() {
	prog=${2:?The program name is not set}
	setup   REST plus:true help:usage abbr:true error alt:true -- \
		"Usage: ${prog##*/} [options...] [arguments...]" ''
	msg -- 'getoptions parser_definition example' ''
	msg -- 'Options:'
	flag    FLAG    -f +f --{no-}flag                         -- "expands to --flag and --no-flag"
	flag    VERBOSE -v    --verbose   counter:true init:=0    -- "e.g. -vvv is verbose level 3"
	param   PARAM   -p    --param     pattern:"foo | bar"     -- "accepts --param value / --param=value"
	option  OPTION  -o    --option    on:"default"            -- "accepts -ovalue / --option=value"
	disp    :usage  -h    --help
	disp    VERSION       --version
}


text:
# shellcheck shell=sh disable=SC1083,SC2004,SC2016

Describe "getoptions_help()"
	Include ./lib/getoptions_help.sh

	usage() {
		eval "$(getoptions_help parser_definition _usage)"
		case $# in
			0) _usage ;;
			*) _usage "$@" ;;
		esac
	}

	It "generates usage"
		parser_definition() { echo 'usage'; }
		When call usage
		The output should eq "usage"
		The status should be success
	End

	It "displays usage"
		parser_definition() {
			setup - width:20 plus:true -- 'usage'
			msg -- "header"
			msg label:option -- "description"
			flag FLAG_A -a +a --{no-}flag-a -- "flag a"
			setup - width:25 hidden
			param PARAM_P -p -- "param p"
			option OPTION_W --with{out}-flag-w -- "option w"
			option OPTION_O -o -- "option o"
			msg -- "footer"
		}
		When call usage
		The line 1 should eq "usage"
		The line 2 should eq "header"
		The line 3 should eq "  option            description"
		The line 4 should eq "  -a, +a, --{no-}flag-a "
		The line 5 should eq "                    flag a"
		The line 6 should eq "  -p PARAM_P             param p"
		The line 7 should eq "          --with{out}-flag-w[=OPTION_W] "
		The line 8 should eq "                         option w"
		The line 9 should eq "  -o[=OPTION_O]          option o"
		The line 10 should eq "footer"
	End
End


text:
# shellcheck shell=sh disable=SC1083,SC2004,SC2016

Describe "getoptions_abbr()"
	Include ./lib/getoptions_base.sh
	Include ./lib/getoptions_abbr.sh

	parse() {
		eval "$(getoptions parser_definition _parse)"
		case $# in
			0) _parse ;;
			*) _parse "$@" ;;
		esac
	}

	It "generates option parser with abbreviation module"
		parser_definition() { setup ARGS abbr:true; }
		getoptions_abbr() { echo 'getoptions_abbr called' >&2; }
		When call parse
		The stderr should eq 'getoptions_abbr called'
		The status should be success
	End

	Describe 'Default error handler'
		Context "when specified an ambiguous option"
			It "displays error"
				parser_definition() {
					setup ARGS abbr:true
					flag FLAG --flag-a
					flag FLAG --flag-b
				}
				When run parse --flag
				The stderr should eq "Ambiguous option: --flag (could be --flag-a, --flag-b)"
				The status should be failure
			End
		End
	End

	Describe 'custom error handler'
		myerror() {
			case $2 in
				ambiguous)
					echo "message: $1"
					echo "error name: $2"
					echo "option: $3"
					echo "candidate $(($# - 3)): $4 $5"
					echo "$OPTARG"
					return 12
			esac
			return 0
		}

		Context "when specified an ambiguous option"
			It "displays error"
				parser_definition() {
					setup ARGS abbr:true error:myerror
					flag FLAG --flag-a
					flag FLAG --flag-b
				}
				When run parse --flag
				The line 1 of stderr should eq "message: Ambiguous option: --flag (could be --flag-a, --flag-b)"
				The line 2 of stderr should eq "error name: ambiguous"
				The line 3 of stderr should eq "option: --flag"
				The line 4 of stderr should eq "candidate 2: --flag-a --flag-b"
				The line 5 of stderr should eq "--flag-a, --flag-b"
				The status should eq 12
			End
		End
	End

	Context "when abbreviation attribute specified"
		parser_definition() {
			setup  ARGS abbr:true
			flag   FLAG --flag
			param  PARAM --param
			option OPTION --{no-}option
		}
		It "treats an abbreviation option"
			When call parse --p=value
			The variable PARAM should eq "value"
		End

		It "treats an abbreviation option"
			When call parse --o
			The variable OPTION should eq 1
		End

		It "treats an abbreviation option"
			When call parse --n
			The variable OPTION should eq ''
		End
	End

	Context "when abbr attribute not specified"
		parser_definition() {
			setup ARGS abbr:true
			flag  FLAG --flag -- abbr:
			param PARAM --param abbr:
		}

		It "does not treat as an abbreviation option"
			When run parse --p value
			The stderr should eq "Unrecognized option: --p"
			The status should be failure
		End
	End

	Context "when the specified option contains metacharacters"
		parser_definition() {
			setup ARGS abbr:true
			flag  FLAG_A --flag-a
			flag  FLAG_B --flag-b
		}

		It "does not match an abbreviation option"
			When run parse '--*'
			The stderr should eq "Unrecognized option: --*"
			The status should be failure
		End
	End
End