Committing before help rework

This commit is contained in:
2026-09-05 13:19:55 -05:00
parent 4cce5cc45c
commit 3fa77f5707
105 changed files with 22505 additions and 684 deletions
+295 -12
View File
@@ -62,11 +62,6 @@
<ul>
<li class=" " data-id="CliGenerator/CliGen/AdditionalDefaultFlag" data-name="cligen::additionaldefaultflag">
<a href="CliGen/AdditionalDefaultFlag.html">AdditionalDefaultFlag</a>
</li>
<li class=" " data-id="CliGenerator/CliGen/App" data-name="cligen::app">
<a href="CliGen/App.html">App</a>
@@ -77,16 +72,31 @@
</li>
<li class=" " data-id="CliGenerator/CliGen/ArgReprocessedError" data-name="cligen::argreprocessederror">
<a href="CliGen/ArgReprocessedError.html">ArgReprocessedError</a>
</li>
<li class=" " data-id="CliGenerator/CliGen/Argument" data-name="cligen::argument">
<a href="CliGen/Argument.html">Argument</a>
</li>
<li class=" " data-id="CliGenerator/CliGen/BaseCommandNode" data-name="cligen::basecommandnode">
<a href="CliGen/BaseCommandNode.html">BaseCommandNode</a>
</li>
<li class=" " data-id="CliGenerator/CliGen/BaseFlag" data-name="cligen::baseflag">
<a href="CliGen/BaseFlag.html">BaseFlag</a>
</li>
<li class=" " data-id="CliGenerator/CliGen/Coercable" data-name="cligen::coercable">
<a href="CliGen/Coercable.html">Coercable</a>
</li>
<li class=" " data-id="CliGenerator/CliGen/Command" data-name="cligen::command">
<a href="CliGen/Command.html">Command</a>
@@ -97,13 +107,33 @@
</li>
<li class=" " data-id="CliGenerator/CliGen/CommandNode" data-name="cligen::commandnode">
<li class=" " data-id="CliGenerator/CliGen/CommandMeta" data-name="cligen::commandmeta">
<a href="CliGen/CommandMeta.html">CommandMeta</a>
</li>
<li class=" " data-id="CliGenerator/CliGen/CommandNode" data-name="cligen::commandnode(t)">
<a href="CliGen/CommandNode.html">CommandNode</a>
</li>
<li class=" " data-id="CliGenerator/CliGen/DefaultFlag" data-name="cligen::defaultflag">
<a href="CliGen/DefaultFlag.html">DefaultFlag</a>
<li class=" " data-id="CliGenerator/CliGen/ConfigurationError" data-name="cligen::configurationerror">
<a href="CliGen/ConfigurationError.html">ConfigurationError</a>
</li>
<li class=" " data-id="CliGenerator/CliGen/DuplicateCommandError" data-name="cligen::duplicatecommanderror">
<a href="CliGen/DuplicateCommandError.html">DuplicateCommandError</a>
</li>
<li class=" " data-id="CliGenerator/CliGen/DuplicateFlagError" data-name="cligen::duplicateflagerror">
<a href="CliGen/DuplicateFlagError.html">DuplicateFlagError</a>
</li>
<li class=" " data-id="CliGenerator/CliGen/Error" data-name="cligen::error">
<a href="CliGen/Error.html">Error</a>
</li>
@@ -112,16 +142,86 @@
</li>
<li class=" " data-id="CliGenerator/CliGen/FlagArgumentError" data-name="cligen::flagargumenterror">
<a href="CliGen/FlagArgumentError.html">FlagArgumentError</a>
</li>
<li class=" " data-id="CliGenerator/CliGen/FlagBundleError" data-name="cligen::flagbundleerror">
<a href="CliGen/FlagBundleError.html">FlagBundleError</a>
</li>
<li class=" " data-id="CliGenerator/CliGen/FlagMeta" data-name="cligen::flagmeta">
<a href="CliGen/FlagMeta.html">FlagMeta</a>
</li>
<li class=" " data-id="CliGenerator/CliGen/FlagMissingArgumentError" data-name="cligen::flagmissingargumenterror">
<a href="CliGen/FlagMissingArgumentError.html">FlagMissingArgumentError</a>
</li>
<li class=" " data-id="CliGenerator/CliGen/FlagNotFoundError" data-name="cligen::flagnotfounderror">
<a href="CliGen/FlagNotFoundError.html">FlagNotFoundError</a>
</li>
<li class=" " data-id="CliGenerator/CliGen/Format" data-name="cligen::format">
<a href="CliGen/Format.html">Format</a>
</li>
<li class=" " data-id="CliGenerator/CliGen/HelpRequestedError" data-name="cligen::helprequestederror">
<a href="CliGen/HelpRequestedError.html">HelpRequestedError</a>
</li>
<li class=" " data-id="CliGenerator/CliGen/InternalError" data-name="cligen::internalerror">
<a href="CliGen/InternalError.html">InternalError</a>
</li>
<li class=" " data-id="CliGenerator/CliGen/InvalidFlagValueError" data-name="cligen::invalidflagvalueerror">
<a href="CliGen/InvalidFlagValueError.html">InvalidFlagValueError</a>
</li>
<li class=" " data-id="CliGenerator/CliGen/InvalidOptionError" data-name="cligen::invalidoptionerror">
<a href="CliGen/InvalidOptionError.html">InvalidOptionError</a>
</li>
<li class=" " data-id="CliGenerator/CliGen/MatchType" data-name="cligen::matchtype">
<a href="CliGen/MatchType.html">MatchType</a>
</li>
<li class=" " data-id="CliGenerator/CliGen/MissingDispatchError" data-name="cligen::missingdispatcherror">
<a href="CliGen/MissingDispatchError.html">MissingDispatchError</a>
</li>
<li class=" " data-id="CliGenerator/CliGen/MissingRequiredFlagError" data-name="cligen::missingrequiredflagerror">
<a href="CliGen/MissingRequiredFlagError.html">MissingRequiredFlagError</a>
</li>
<li class=" " data-id="CliGenerator/CliGen/Parsable" data-name="cligen::parsable">
<a href="CliGen/Parsable.html">Parsable</a>
</li>
<li class=" " data-id="CliGenerator/CliGen/ParseableInvariantError" data-name="cligen::parseableinvarianterror">
<a href="CliGen/ParseableInvariantError.html">ParseableInvariantError</a>
</li>
<li class=" " data-id="CliGenerator/CliGen/PreRunCommand" data-name="cligen::preruncommand">
<a href="CliGen/PreRunCommand.html">PreRunCommand</a>
</li>
<li class=" " data-id="CliGenerator/CliGen/ProxyCommand" data-name="cligen::proxycommand">
<a href="CliGen/ProxyCommand.html">ProxyCommand</a>
@@ -132,6 +232,26 @@
</li>
<li class=" " data-id="CliGenerator/CliGen/RegexInvariantError" data-name="cligen::regexinvarianterror">
<a href="CliGen/RegexInvariantError.html">RegexInvariantError</a>
</li>
<li class=" " data-id="CliGenerator/CliGen/ReservedFlagError" data-name="cligen::reservedflagerror">
<a href="CliGen/ReservedFlagError.html">ReservedFlagError</a>
</li>
<li class=" " data-id="CliGenerator/CliGen/RunCommand" data-name="cligen::runcommand">
<a href="CliGen/RunCommand.html">RunCommand</a>
</li>
<li class=" " data-id="CliGenerator/CliGen/RuntimeError" data-name="cligen::runtimeerror">
<a href="CliGen/RuntimeError.html">RuntimeError</a>
</li>
<li class=" " data-id="CliGenerator/CliGen/Selection" data-name="cligen::selection">
<a href="CliGen/Selection.html">Selection</a>
@@ -142,11 +262,56 @@
</li>
<li class=" " data-id="CliGenerator/CliGen/SubCommandInfo" data-name="cligen::subcommandinfo">
<a href="CliGen/SubCommandInfo.html">SubCommandInfo</a>
</li>
<li class="parent " data-id="CliGenerator/CliGen/Timeparse" data-name="cligen::timeparse">
<a href="CliGen/Timeparse.html">Timeparse</a>
<ul>
<li class=" " data-id="CliGenerator/CliGen/Timeparse/OperationUnit" data-name="cligen::timeparse::operationunit">
<a href="CliGen/Timeparse/OperationUnit.html">OperationUnit</a>
</li>
<li class=" " data-id="CliGenerator/CliGen/Timeparse/RelativeOperation" data-name="cligen::timeparse::relativeoperation">
<a href="CliGen/Timeparse/RelativeOperation.html">RelativeOperation</a>
</li>
</ul>
</li>
<li class=" " data-id="CliGenerator/CliGen/TimeParseError" data-name="cligen::timeparseerror">
<a href="CliGen/TimeParseError.html">TimeParseError</a>
</li>
<li class=" " data-id="CliGenerator/CliGen/Trigger" data-name="cligen::trigger">
<a href="CliGen/Trigger.html">Trigger</a>
</li>
<li class=" " data-id="CliGenerator/CliGen/UnknownCommandNodeError" data-name="cligen::unknowncommandnodeerror">
<a href="CliGen/UnknownCommandNodeError.html">UnknownCommandNodeError</a>
</li>
<li class=" " data-id="CliGenerator/CliGen/UnknownFlagError" data-name="cligen::unknownflagerror">
<a href="CliGen/UnknownFlagError.html">UnknownFlagError</a>
</li>
<li class=" " data-id="CliGenerator/CliGen/ValidationError" data-name="cligen::validationerror">
<a href="CliGen/ValidationError.html">ValidationError</a>
</li>
</ul>
@@ -159,11 +324,17 @@
<div class="main-content">
<h1><a id="cli-generator" class="anchor" href="#cli-generator"> <svg class="octicon-link" aria-hidden="true">
<h1><a id="cligen" class="anchor" href="#cligen"> <svg class="octicon-link" aria-hidden="true">
<use href="#octicon-link"/>
</svg>
</a>CliGenerator</h1>
<p>This is a crystal project to manage setting up OptionParser objects based around &quot;Command&quot; objects and arguments you define inside them.</p>
</a>cligen</h1>
<p>A Crystal shard that generates CLI parsers from class definitions using annotations and macros. Define your commands as classes; cligen builds the runtime parse tree.</p>
<h2><a id="how-it-works" class="anchor" href="#how-it-works">
<svg class="octicon-link" aria-hidden="true">
<use href="#octicon-link"/>
</svg>
</a>How It Works</h2>
<p>Subclass <code><a href="CliGen/Command.html">CliGen::Command</a></code>, annotate your instance variables with <code>@[<a href="CliGen/Argument.html">CliGen::Argument</a>]</code> or <code>@[<a href="CliGen/Selection.html">CliGen::Selection</a>]</code>, and register the command with an <code><a href="CliGen/App.html">CliGen::App</a></code>. At compile time, macros inspect the annotations and generate typed <code>Flag(T)</code> objects; at runtime, <code>CliGen::App.process</code> walks the <code>CommandNode</code> tree to route arguments, populate your command instance, and dispatch to the right method.</p>
<h2><a id="installation" class="anchor" href="#installation">
<svg class="octicon-link" aria-hidden="true">
<use href="#octicon-link"/>
@@ -185,7 +356,119 @@
<use href="#octicon-link"/>
</svg>
</a>Usage</h2>
<pre><code class="language-crystal"><span class="k">require</span> <span class="s">&quot;cligen&quot;</span></code></pre>
<pre><code class="language-crystal"><span class="k">require</span> <span class="s">&quot;cligen&quot;</span>
@[<span class="t">CliGen</span><span class="t">::</span><span class="t">CommandInfo</span>(description: <span class="s">&quot;Greet someone&quot;</span>)]
<span class="k">class</span> <span class="t">Greet</span> <span class="o">&lt;</span> <span class="t">CliGen</span><span class="t">::</span><span class="t">Command</span>
@[<span class="t">CliGen</span><span class="t">::</span><span class="t">Argument</span>(short: <span class="s">&quot;-n&quot;</span>, long: <span class="s">&quot;--name VALUE&quot;</span>, description: <span class="s">&quot;Name to greet&quot;</span>)]
@name : <span class="t">String</span> <span class="o">=</span> <span class="s">&quot;world&quot;</span>
argument(otherval : <span class="t">String</span> <span class="o">=</span> <span class="s">&quot;abc&quot;</span>,
long: <span class="s">&quot;--other&quot;</span>,
short: <span class="s">&quot;-o&quot;</span>,
options: <span class="s">%w[ abc def ghi ]</span>,
description: <span class="s">&quot;This provides a way of setting the second string taht is printed&quot;</span>
)
argument(myvars : <span class="t">Array</span>(<span class="t">String</span>) <span class="o">=</span> [ <span class="s">&quot;a&quot;</span> ],
short: <span class="s">&quot;-m&quot;</span>,
options: <span class="s">%w[ a b c ]</span>,
description: <span class="s">&quot;Provide multiple things to be printed out in the main function&quot;</span>
)
<span class="k">def</span> <span class="m">main</span>
puts <span class="s">&quot;1) Hello, </span><span class="i">#{</span>@name<span class="i">}</span><span class="s">!&quot;</span>
puts <span class="s">&quot;2) </span><span class="i">#{</span>@otherval<span class="i">}</span><span class="s">&quot;</span>
@myvars.each_with_index <span class="k">do</span> <span class="o">|</span>var, index<span class="o">|</span>
puts <span class="s">&quot;%d) %s&quot;</span> <span class="o">%</span> [ <span class="n">3</span> <span class="o">+</span> index, var ]
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="t">CliGen</span><span class="t">::</span><span class="t">App</span>.process</code></pre>
<pre><code class="language-crystal">$ myapp greet --name Alice -m a a -m a,a,b
1) Hello, Alice!
2) abc
3) a
4) a
5) a
6) a
7) b</code></pre>
<p>Full API documentation and design notes are in <a href="design.adoc"><code>design.adoc</code></a>.</p>
<h2><a id="architecture" class="anchor" href="#architecture">
<svg class="octicon-link" aria-hidden="true">
<use href="#octicon-link"/>
</svg>
</a>Architecture</h2>
<p>As a short overview, this projects makes HEAVY use of Crystal macros to learn the shape of your project &amp; command subclasses.</p>
<p>Subclassing to <code><a href="CliGen/Command.html">CliGen::Command</a></code> injects macros into your class that provides you user friendly DSLs/Macros for defining arguments/flags &amp; subcommands. This is later used in the library <code>src/cligen/app/generate.cr</code> to generate a object graph of your commands and all annotated &quot;arguments/flags&quot; and stores them in a tree from the App object itself.</p>
<p>This allows the project to &quot;learn&quot; your project &amp; generate a command tree from the defined data.</p>
<p>The MAJORITY of stdlib types (Int*, Float*, String, Bool &amp; Time) are all supported in-place (as these are the primary data-types you might try to comsume from the CLI. However, custom data types are supported provided you extend the class's metaclass with <code><a href="CliGen/Coercable.html">CliGen::Coercable</a></code> &amp;&amp; <code><a href="CliGen/Parsable.html">CliGen::Parsable</a></code> modules and define the <code>self.parse_args(args : Array(CliGen::Arg)</code> and <code>self.coerce(arg : String)</code> class methods.</p>
<p>EX:</p>
<pre><code class="language-crystal"><span class="k">module</span> <span class="t">MyModule</span>
<span class="k">class</span> <span class="t">MyData</span>
<span class="k">extend</span> <span class="t">CliGen</span><span class="t">::</span><span class="t">Parsable</span>
<span class="k">extend</span> <span class="t">CliGen</span><span class="t">::</span><span class="t">Coercable</span>
@value : <span class="t">Int32</span>
<span class="k">def</span> <span class="m">initialize</span>(value : <span class="t">String</span>)
@value <span class="o">=</span> value.to_i32
<span class="k">end</span>
<span class="k">def</span> <span class="m">self</span>.parse_args(args : <span class="t">Array</span>(<span class="t">CliGen</span><span class="t">::</span><span class="t">Arg</span>))
arg <span class="o">=</span> args.first
<span class="c"># Mark the argument as processed</span>
arg.processed
new(arg.value)
<span class="k">end</span>
<span class="k">def</span> <span class="m">self</span>.coerce(arg : <span class="t">String</span>)
new(arg)
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span></code></pre>
<h2><a id="coercable-method" class="anchor" href="#coercable-method">
<svg class="octicon-link" aria-hidden="true">
<use href="#octicon-link"/>
</svg>
</a>Coercable Method:</h2>
<pre><code class="language-crystal"> <span class="k">def</span> <span class="m">self</span>.coerce(arg : <span class="t">String</span>)
new(arg)
<span class="k">end</span></code></pre>
<p>The coerce method provides you the ability to parse a single string value into your class/datatype. This is generally only used when parsing from ENV VAR and when being used by parsing from an Array(T) type.</p>
<p>This should only be used in the case you need a simple datatype that can be learned from a single string.</p>
<h2><a id="parsable-method" class="anchor" href="#parsable-method">
<svg class="octicon-link" aria-hidden="true">
<use href="#octicon-link"/>
</svg>
</a>Parsable Method:</h2>
<pre><code class="language-crystal"> <span class="k">def</span> <span class="m">self</span>.parse_args(args : <span class="t">Array</span>(<span class="t">CliGen</span><span class="t">::</span><span class="t">Arg</span>))
arg <span class="o">=</span> args.first
<span class="c"># Mark the argument as processed (required)</span>
arg.processed
new(arg.value)
<span class="k">end</span></code></pre>
<p>This method is used the most and is used for when using the bare class as the generic type in the Flag(T). With this the Flag(T) will collect all provided arguments (cli arguments that weren't determined to be flags or subcommands) and pass them to your parse_args method so that you can parse them how you see fit and determine if the args provided by the user are enough and to be able to raise if data is not provided correctly/in the right format.</p>
<p>This gives the framework a way to allow you to extend the parser in your own custom way to allow for a &quot;custom&quot; format to be procesed. However, it's very strict and you MUST properly mark the arguments as processed so that the mainloop won't double-process arguments passed to your custom parser. However, this won't happen as in the Flag(T) I am doing a check to ensure that args were processed after passing it to your code.</p>
<h2><a id="development" class="anchor" href="#development">
<svg class="octicon-link" aria-hidden="true">
<use href="#octicon-link"/>
</svg>
</a>Development</h2>
<pre><code class="language-bash"># Type-check without running
crystal build src/cligen.cr --no-codegen
# Run specs
crystal spec</code></pre>
<h2><a id="ai-assistance-disclosure" class="anchor" href="#ai-assistance-disclosure">
<svg class="octicon-link" aria-hidden="true">
<use href="#octicon-link"/>
</svg>
</a>AI Assistance Disclosure</h2>
<p>This project uses <a href="https://claude.ai/code">Claude Code</a> as a development aid — specifically for catching bugs, spotting typos, reviewing implementations, and talking through design decisions. All architecture decisions, code, and design are written by the author. Claude is used the way one might use a second pair of eyes on a diff, not as a code generator.</p>
<p><code>CLAUDE.md</code> at the repo root documents the project structure for Claude's context. <code>.claude/</code> holds project-level Claude Code settings.</p>
<h2><a id="contributors" class="anchor" href="#contributors">
<svg class="octicon-link" aria-hidden="true">
<use href="#octicon-link"/>