Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/bug_report.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ body:
attributes:
label: Alya Version
description: Run `alya --version` and paste the output here.
placeholder: "alya 0.0.19"
placeholder: "alya 0.0.20"
validations:
required: true
- type: dropdown
Expand Down
2 changes: 1 addition & 1 deletion .github/scripts/generate_release_notes.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ def main():
pkg_name = Path.cwd().name

description = pkg_meta.get("description", "A modern package for the Alya programming language")
alya_version = pkg_meta.get("alya-version", "0.0.19")
alya_version = pkg_meta.get("alya-version", "0.0.20")

# Resolve Repository Slug (e.g. alya-lang/toml)
repo = os.environ.get("GITHUB_REPOSITORY", "")
Expand Down
6 changes: 6 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,12 @@ jobs:
run: |
alya test

- name: Run package test suite without default features
env:
ALYA_TEST_TIMEOUT_SECS: '600'
run: |
alya test --no-default-features

- name: Run micro-benchmarks smoke test
if: hashFiles('benches/bench_basic.alya') != ''
run: |
Expand Down
336 changes: 218 additions & 118 deletions README.md

Large diffs are not rendered by default.

28 changes: 20 additions & 8 deletions alya.toml
Original file line number Diff line number Diff line change
@@ -1,17 +1,33 @@
[package]
name = "cli"
version = "0.1.0"
alya-version = "0.0.19"
version = "0.2.0"
alya-version = "0.0.21"
entry = "src/lib.alya"
description = "Modern command-line interface, argument parsing, flag handling, and subcommand router for Alya"
description = "Modern command-line toolkit for Alya with typed flags, options, subcommand routing, generated help screens, and structured parse errors"
authors = ["Alya Language Contributors <https://github.com/alya-lang>"]
license = "MIT"
homepage = "https://github.com/alya-lang/alya"
repository = "https://github.com/alya-lang/cli"
keywords = ["alya", "alya-lang", "package", "cli", "argparse", "flags", "subcommands", "terminal"]
keywords = ["alya", "alya-lang", "package", "cli", "argparse", "flags", "subcommands", "help", "terminal"]

[dependencies]

# Optional: feature-gated API slices. Members name another feature or an
# optional dependency; gate the using code with `@cfg(feature = "...")`
# and provide a `@cfg(not(feature = "..."))` fallback stub in `src/lib.alya`
# so callers get a clear descriptive error instead of an undefined reference.
# `help` renders the usage screens and version banner
# (`src/core/formatter.alya`), `strict` adds the throwing and recovering
# parse entry points (`src/core/errors.alya`), and `env` lets options fall
# back to environment variables (`std/os`). `default` is active unless the
# caller passes `--no-default-features`; select features explicitly via
# `alya build/run/test/bench/install --features <a,b>`.
[features]
default = ["help", "strict", "env"]
help = []
strict = []
env = []

# [lint]
# disabled_rules = []
# exclude = []
Expand All @@ -24,7 +40,3 @@ keywords = ["alya", "alya-lang", "package", "cli", "argparse", "flags", "subcomm

# [bench]
# exclude = []

[features]
default = ["help"]
help = []
79 changes: 39 additions & 40 deletions benches/bench_basic.alya
Original file line number Diff line number Diff line change
@@ -1,72 +1,71 @@
## Benchmark suite for the cli package
## Part of the Alya Language package ecosystem (https://github.com/alya-lang)
##
## Measures the three hot paths of the package: building a specification,
## parsing an argument array, and rendering a usage screen.

import "std/bench"
import "std/test"
import "../src/lib.alya" as cli

function main()
let runner = bench_runner("Alya CLI Micro-Benchmarks")

# 1. App initialization and option definition
# ═══════════════════════════════════════════════════════════
# 1. Specification building
# ═══════════════════════════════════════════════════════════
bench_start(runner, 2000)
let i = 0
while i < 2000
let app = cli::new("bench-cli", "Benchmark CLI tool", "1.0.0")
cli::app_add_flag(app, "-v, --verbose", "Enable verbose output")
cli::app_add_flag(app, "-d, --debug", "Enable debug output")
cli::app_add_option(app, "-o, --output", "dist", "Output target directory")
cli::app_add_option(app, "-f, --format", "json", "Serialization format")
let cmd = cli::command("build", "Build project artifacts")
cli::cmd_add_flag(cmd, "-r, --release", "Build in release mode")
cli::app_add_command(app, cmd)
let app = cli::new_app("bench-cli", "Benchmark CLI tool", "1.0.0")
app.flag("-v, --verbose", "Enable verbose output")
app.flag("-d, --debug", "Enable debug output")
app.option("-o, --output", "dist", "Output target directory")
app.option("-f, --format", "json", "Serialization format")
let cmd = cli::new_command("build", "Build project artifacts")
cmd.flag("-r, --release", "Build in release mode")
app.command(cmd)
i += 1
end
bench_stop(runner, "cli::new + flags/options/commands (2k runs)")
bench_stop(runner, "specification building (2k runs)")

# ═══════════════════════════════════════════════════════════
# 2. Argument parsing
let app = cli::new("bench-cli", "Benchmark CLI tool", "1.0.0")
cli::app_add_flag(app, "-v, --verbose", "Enable verbose output")
cli::app_add_flag(app, "-d, --debug", "Enable debug output")
cli::app_add_option(app, "-o, --output", "dist", "Output target directory")
cli::app_add_option(app, "-f, --format", "json", "Serialization format")
let cmd = cli::command("build", "Build project artifacts")
cli::cmd_add_flag(cmd, "-r, --release", "Build in release mode")
cli::app_add_command(app, cmd)
# ═══════════════════════════════════════════════════════════
let app = cli::new_app("bench-cli", "Benchmark CLI tool", "1.0.0")
app.flag("-v, --verbose", "Enable verbose output")
app.flag("-d, --debug", "Enable debug output")
app.option("-o, --output", "dist", "Output target directory")
app.option("-f, --format", "json", "Serialization format")
let cmd = cli::new_command("build", "Build project artifacts")
cmd.flag("-r, --release", "Build in release mode")
app.command(cmd)

let test_args = ["build", "-v", "--release", "--output=target/bin", "src/main.alya"]

bench_start(runner, 2000)
let j = 0
while j < 2000
let ctx = cli::parse(app, test_args)
cli::ctx_get_flag(ctx, "verbose")
cli::ctx_get_option(ctx, "output", "")
cli::ctx_get_command(ctx)
ctx.get_flag("verbose")
ctx.get_option("output", "")
ctx.get_command()
j += 1
end
bench_stop(runner, "cli::parse + query lookups (2k runs)")
bench_stop(runner, "argument parsing + lookups (2k runs)")

# 3. Formatter benchmark
bench_help_formatter(app, runner)

bench_summary(runner)
end

@cfg(feature = "help")
function bench_help_formatter(app, runner)
bench_start(runner, 20)
# ═══════════════════════════════════════════════════════════
# 3. Usage screen rendering
# ═══════════════════════════════════════════════════════════
@cfg(feature = "help")
bench_start(runner, 200)
let k = 0
while k < 20
cli::help(app)
while k < 200
cli::format_help(app)
k += 1
end
bench_stop(runner, "cli::help automated screen format (20 runs)")
end
bench_stop(runner, "usage screen rendering (200 runs)")

@cfg(not(feature = "help"))
# alya-lint: disable unused-param
function bench_help_formatter(app, runner)
bench_summary(runner)
end
# alya-lint: enable unused-param

main()
133 changes: 94 additions & 39 deletions examples/demo.alya
Original file line number Diff line number Diff line change
@@ -1,54 +1,109 @@
## Demo of the cli package
## Part of the Alya Language package ecosystem (https://github.com/alya-lang)
##
## Builds a realistic command line tool, renders its usage screen, and
## parses a simulated invocation.

import "../src/lib.alya" as cli

function main()
# 1. Initialize application
let app = cli::new("forge", "Alya high-performance build and test tool", "1.0.0")
cli::app_set_author(app, "Alya Language Contributors")

# 2. Configure global flags and options
cli::app_add_flag(app, "-v, --verbose", "Enable verbose diagnostic logs")
cli::app_add_flag(app, "-q, --quiet", "Suppress all non-error output")
cli::app_add_option(app, "-c, --config", "forge.toml", "Configuration file path")

# 3. Add 'build' command with options and arguments
let build_cmd = cli::command("build", "Compile target source packages", ["b"])
cli::cmd_add_flag(build_cmd, "-r, --release", "Build optimized release binaries")
cli::cmd_add_option(build_cmd, "-o, --output", "dist", "Output directory")
cli::cmd_add_choice_option(build_cmd, "-t, --target", "native", ["native", "x64", "arm64"], "Target architecture")
cli::cmd_add_argument(build_cmd, "entrypoint", "Source file to compile", false, "src/main.alya")
cli::app_add_command(app, build_cmd)

# 4. Add 'test' command
let test_cmd = cli::command("test", "Execute unit and integration tests", ["t"])
cli::cmd_add_flag(test_cmd, "-f, --fail-fast", "Abort on first test failure")
cli::app_add_command(app, test_cmd)

# 5. Display automated help screen
say "=== Automated Help Screen Demo ==="
say "=== 1. Building the application ==="
let app = cli::new_app("forge", "Alya high-performance build and test tool", "1.0.0")
app.set_author("Alya Language Contributors")

# Global options
app.flag("-v, --verbose", "Enable verbose diagnostic logs")
app.flag("-q, --quiet", "Suppress all non-error output")
app.option("-c, --config", "forge.toml", "Configuration file path")
app.env_option("--token", "FORGE_TOKEN", "", "Access token for the registry")

# The `build` command
let build = cli::new_command("build", "Compile target source packages", ["b"])
build.flag("-r, --release", "Build optimized release binaries")
build.option("-o, --output", "dist", "Output directory")
build.choice_option("-t, --target", "native", ["native", "x64", "arm64"], "Target architecture")
build.argument("entrypoint", "Source file to compile", false, "src/main.alya")
app.command(build)

# The `test` command
let test_cmd = cli::new_command("test", "Execute unit and integration tests", ["t"])
test_cmd.flag("-f, --fail-fast", "Abort on the first test failure")
test_cmd.variadic_argument("filters", "Only run the matching test filters")
app.command(test_cmd)

# The `remote` command with a nested subcommand
let remote = cli::new_command("remote", "Manage the configured remotes")
let remote_add = cli::new_command("add", "Add a new remote")
remote_add.argument("name", "Remote name")
remote_add.argument("url", "Remote URL")
remote.subcommand(remote_add)
app.command(remote)

say "Application '" + app.name + "' v" + app.version + " is ready."
say ""

# ═══════════════════════════════════════════════════════════
say "=== 2. Generated usage screen ==="
@cfg(feature = "help")
say cli::help(app)
say cli::format_help(app)

# 6. Simulate parsing realistic CLI arguments
say "=== Parsing Simulated Command ==="
let sample_args = ["build", "-v", "--release", "--target=native", "-o", "bin/release", "server.alya"]
say "Running with args: build -v --release --target=native -o bin/release server.alya"
# ═══════════════════════════════════════════════════════════
say "=== 3. Parsing a simulated invocation ==="
let sample = ["build", "-v", "--release", "--target=native", "-o", "bin/release", "server.alya"]
say "forge " + "build -v --release --target=native -o bin/release server.alya"
say ""

let ctx = app.run(sample_args)
let ctx = app.parse(sample)
if ctx.has_errors()
say "Errors encountered during parsing:"
for err in ctx.errors()
say " * " + str_from_ptr(err)
say "Diagnostics:"
for error in ctx.get_errors()
say " * " + error.message
end
else
say "Command executed : " + ctx.command()
say "Verbose mode : " + (ctx.flag("verbose") == true ? "yes" : "no")
say "Release mode : " + (ctx.flag("release") == true ? "yes" : "no")
say "Target arch : " + ctx.option("target", "native")
say "Output path : " + ctx.option("output", "dist")
say "Source file : " + ctx.get_arg_named("entrypoint", "src/main.alya")
say "Command : " + ctx.get_command()
say "Command path : " + ctx.get_command_path()
say "Verbose mode : " + (ctx.get_flag("verbose") == true ? "yes" : "no")
say "Release mode : " + (ctx.get_flag("release") == true ? "yes" : "no")
say "Target arch : " + ctx.get_option("target", "native")
say "Output path : " + ctx.get_option("output", "dist")
say "Source file : " + ctx.get_arg_named("entrypoint", "src/main.alya")
say "Config file : " + ctx.get_option("config", "forge.toml")
end
say ""

# ═══════════════════════════════════════════════════════════
say "=== 4. Diagnostics for an invalid invocation ==="
let broken = app.parse(["deploy", "--format=xml"])
say "forge deploy --format=xml"
for error in broken.get_errors()
say " * " + error.message
end
say ""

# ═══════════════════════════════════════════════════════════
say "=== 5. Nested subcommand routing ==="
let nested = app.parse(["remote", "add", "origin", "https://github.com/alya-lang/cli.git"])
say "Command path : " + nested.get_command_path()
say "Remote name : " + nested.get_arg_named("name")
say "Remote url : " + nested.get_arg_named("url")
say ""

# ═══════════════════════════════════════════════════════════
say "=== 6. Strict parsing (structured errors) ==="
@cfg(feature = "strict")
let rejected = cli::try_parse(app, ["build", "--target=wasm"])
if rejected.is_error()
say "Rejected: " + rejected.message()
else
say "Accepted: " + rejected.context().get_option("target", "native")
end

@cfg(feature = "strict")
let accepted = cli::try_parse(app, ["build", "--target=native"])
if accepted.is_ok()
say "Accepted: " + accepted.context().get_option("target", "native")
else
say "Rejected: " + accepted.message()
end
end

Expand Down
Loading
Loading