Compare commits
No commits in common. "3e230e09ddefba0e40ad9bc236068cddec690e67" and "3567d62882b52a045fefd72c812986c77a04b8d0" have entirely different histories.
3e230e09dd
...
3567d62882
|
|
@ -0,0 +1,19 @@
|
||||||
|
kind: pipeline
|
||||||
|
type: docker
|
||||||
|
name: xoana
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
- name: pnpm_cache
|
||||||
|
host:
|
||||||
|
path: /var/cache/drone/pnpm_cache
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: build
|
||||||
|
image: node
|
||||||
|
volumes:
|
||||||
|
- name: pnpm_cache
|
||||||
|
path: /drone/src/.pnpm-store/v3
|
||||||
|
commands:
|
||||||
|
- corepack enable
|
||||||
|
- pnpm config set registry https://registry.npmmirror.com
|
||||||
|
- pnpm install
|
||||||
|
|
@ -1,2 +1 @@
|
||||||
zig-cache/
|
node_modules/
|
||||||
zig-out/
|
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"bracketSpacing": true,
|
||||||
|
"bracketSameLine": true,
|
||||||
|
"vueIndentScriptAndStyle": true,
|
||||||
|
"tabWidth": 2,
|
||||||
|
"singleQuote": true,
|
||||||
|
"semi": true
|
||||||
|
}
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
# Zig CLI
|
|
||||||
|
|
||||||
A command argument parser library for Zig. Now it's in a heavy development.
|
|
||||||
|
|
||||||
See `demo.zig` for the usage.
|
|
||||||
39
build.zig
39
build.zig
|
|
@ -1,39 +0,0 @@
|
||||||
const std = @import("std");
|
|
||||||
|
|
||||||
pub fn build(b: *std.Build) void {
|
|
||||||
const target = b.standardTargetOptions(.{});
|
|
||||||
const optimize = b.standardOptimizeOption(.{});
|
|
||||||
|
|
||||||
const cli_mod = b.addModule("cli", .{ .root_source_file = .{ .path = "cli.zig" } });
|
|
||||||
const demo_exe = b.addExecutable(.{
|
|
||||||
.name = "cli-demo",
|
|
||||||
.root_source_file = .{ .path = "demo.zig" },
|
|
||||||
.optimize = optimize,
|
|
||||||
.target = target,
|
|
||||||
.link_libc = true,
|
|
||||||
});
|
|
||||||
demo_exe.root_module.addImport("cli", cli_mod);
|
|
||||||
|
|
||||||
// compile
|
|
||||||
const compile_step = b.step("compile", "Compile demo executable");
|
|
||||||
compile_step.dependOn(&demo_exe.step);
|
|
||||||
|
|
||||||
// install
|
|
||||||
b.installArtifact(demo_exe);
|
|
||||||
|
|
||||||
// run test
|
|
||||||
const run_demo_exe1 = b.addRunArtifact(demo_exe);
|
|
||||||
run_demo_exe1.addArgs(&[_][]const u8{ "-e", "4" });
|
|
||||||
const run_demo_exe2 = b.addRunArtifact(demo_exe);
|
|
||||||
run_demo_exe2.addArgs(&[_][]const u8{"subexample"});
|
|
||||||
const unit_tests = b.addTest(.{
|
|
||||||
.root_source_file = .{ .path = "cli.zig" },
|
|
||||||
.target = target,
|
|
||||||
.optimize = optimize,
|
|
||||||
});
|
|
||||||
const run_unit_tests = b.addRunArtifact(unit_tests);
|
|
||||||
const test_step = b.step("test", "Run unit tests");
|
|
||||||
test_step.dependOn(&run_unit_tests.step);
|
|
||||||
test_step.dependOn(&run_demo_exe1.step);
|
|
||||||
test_step.dependOn(&run_demo_exe2.step);
|
|
||||||
}
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
.{
|
|
||||||
.name = "zig-cli",
|
|
||||||
.version = "0.0.1",
|
|
||||||
.paths = .{
|
|
||||||
"README.md",
|
|
||||||
"build.zig",
|
|
||||||
"build.zig.zon",
|
|
||||||
"util.zig",
|
|
||||||
"cli.zig",
|
|
||||||
"demo.zig",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
233
cli.zig
233
cli.zig
|
|
@ -1,233 +0,0 @@
|
||||||
const std = @import("std");
|
|
||||||
const io = std.io;
|
|
||||||
const mem = std.mem;
|
|
||||||
const process = std.process;
|
|
||||||
const ArrayList = std.ArrayList;
|
|
||||||
const StringHashMap = std.StringHashMap;
|
|
||||||
const out = io.getStdOut().writer();
|
|
||||||
const util = @import("./util.zig");
|
|
||||||
|
|
||||||
pub const Option = struct {
|
|
||||||
short: []const u8,
|
|
||||||
long: []const u8,
|
|
||||||
optional: bool = true,
|
|
||||||
global: bool = false,
|
|
||||||
description: []const u8,
|
|
||||||
value_string: []const u8 = "",
|
|
||||||
value_int: i32 = 0,
|
|
||||||
value_float: f32 = 0.0,
|
|
||||||
value_bool: bool = false,
|
|
||||||
value_type: ValueType = .BOOL,
|
|
||||||
|
|
||||||
const ValueType = enum {
|
|
||||||
STRING,
|
|
||||||
BOOL,
|
|
||||||
INT,
|
|
||||||
FLOAT,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const help_option = Option{
|
|
||||||
.short = "h",
|
|
||||||
.long = "help",
|
|
||||||
.global = true,
|
|
||||||
.description = "Print help",
|
|
||||||
};
|
|
||||||
|
|
||||||
const version_option = Option{
|
|
||||||
.short = "v",
|
|
||||||
.long = "version",
|
|
||||||
.global = true,
|
|
||||||
.description = "Print version",
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Command = struct {
|
|
||||||
const Commands = StringHashMap(Command);
|
|
||||||
const Options = StringHashMap(Option);
|
|
||||||
const Args = ArrayList([]const u8);
|
|
||||||
pub const CommandConfig = struct {
|
|
||||||
name: []const u8,
|
|
||||||
short_description: []const u8,
|
|
||||||
long_description: []const u8,
|
|
||||||
usage: []const u8,
|
|
||||||
version: []const u8 = "0.0.1",
|
|
||||||
run: ?*const fn (cmd: *Command) anyerror!void,
|
|
||||||
};
|
|
||||||
|
|
||||||
subcommands: Commands,
|
|
||||||
options: Options,
|
|
||||||
arguments: Args,
|
|
||||||
cmd_args: Args,
|
|
||||||
allocator: mem.Allocator,
|
|
||||||
config: CommandConfig,
|
|
||||||
|
|
||||||
pub fn init(allocator: mem.Allocator, args: CommandConfig) !Command {
|
|
||||||
var options = Options.init(allocator);
|
|
||||||
try options.put(help_option.long, help_option);
|
|
||||||
try options.put(version_option.long, version_option);
|
|
||||||
return Command{
|
|
||||||
.subcommands = Commands.init(allocator),
|
|
||||||
.options = options,
|
|
||||||
.arguments = Args.init(allocator),
|
|
||||||
.cmd_args = Args.init(allocator),
|
|
||||||
.allocator = allocator,
|
|
||||||
.config = args,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deinit(self: *Command) void {
|
|
||||||
self.subcommands.deinit();
|
|
||||||
self.options.deinit();
|
|
||||||
self.arguments.deinit();
|
|
||||||
self.cmd_args.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn addOption(self: *Command, option: Option) !void {
|
|
||||||
try self.options.put(option.long, option);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn addCommand(self: *Command, command: Command) !void {
|
|
||||||
var option_iterator = self.options.iterator();
|
|
||||||
while (option_iterator.next()) |entry| {
|
|
||||||
const option = entry.value_ptr.*;
|
|
||||||
if (option.global) {
|
|
||||||
try @constCast(&command).addOption(option);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try self.subcommands.put(command.config.name, command);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn help(self: *Command) !void {
|
|
||||||
const description = if (self.config.long_description.len > 0)
|
|
||||||
self.config.long_description
|
|
||||||
else if (self.config.short_description.len > 0)
|
|
||||||
self.config.short_description
|
|
||||||
else
|
|
||||||
self.config.name;
|
|
||||||
|
|
||||||
try out.print("{s}\n\nUsage: {s}", .{ description, self.config.usage });
|
|
||||||
if (self.subcommands.count() > 0) {
|
|
||||||
try out.print(" [COMMAND]", .{});
|
|
||||||
}
|
|
||||||
try out.print(" [OPTIONS] [ARGUMENTS]\n\n", .{});
|
|
||||||
|
|
||||||
if (self.subcommands.count() > 0) {
|
|
||||||
try out.print("Commands: \n\n", .{});
|
|
||||||
var entry_iterator = self.subcommands.iterator();
|
|
||||||
while (entry_iterator.next()) |subcommand| {
|
|
||||||
try out.print(" {s: <20}{s}\n", .{ subcommand.key_ptr.*, subcommand.value_ptr.*.config.short_description });
|
|
||||||
}
|
|
||||||
try out.print("\n", .{});
|
|
||||||
}
|
|
||||||
|
|
||||||
try out.print("Options: \n\n", .{});
|
|
||||||
|
|
||||||
if (self.options.count() > 0) {
|
|
||||||
var entry_iterator = self.options.iterator();
|
|
||||||
while (entry_iterator.next()) |entry| {
|
|
||||||
const option = entry.value_ptr.*;
|
|
||||||
const flags = try mem.concat(self.allocator, u8, &[_][]const u8{ "-", option.short, ", --", option.long });
|
|
||||||
try out.print(" {s: <20}{s}", .{ flags, option.description });
|
|
||||||
if (!option.optional) {
|
|
||||||
try out.print(" (required)\n", .{});
|
|
||||||
} else {
|
|
||||||
try out.print("\n", .{});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn printVersion(self: *Command) !void {
|
|
||||||
_ = try io.getStdOut().write(try mem.concat(self.allocator, u8, &[_][]const u8{ self.config.version, "\n" }));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn defaultRun(self: *Command) !void {
|
|
||||||
if (self.options.get("help").?.value_bool) {
|
|
||||||
try self.help();
|
|
||||||
} else if (self.options.get("version").?.value_bool) {
|
|
||||||
try self.printVersion();
|
|
||||||
} else if (self.config.run) |run| {
|
|
||||||
try run(self);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse(self: *Command) !void {
|
|
||||||
var i: usize = 1;
|
|
||||||
var current_command = self.*;
|
|
||||||
var current_option: Option = undefined;
|
|
||||||
var parsing_option = false;
|
|
||||||
var subcommand_parse_over = false;
|
|
||||||
var option_parse_over = false;
|
|
||||||
parse_while_blk: while (i < self.cmd_args.items.len) {
|
|
||||||
const current_arg = self.cmd_args.items[i];
|
|
||||||
if (!subcommand_parse_over) {
|
|
||||||
if (current_command.subcommands.get(current_arg)) |subcommand| {
|
|
||||||
current_command = subcommand;
|
|
||||||
i += 1;
|
|
||||||
continue :parse_while_blk;
|
|
||||||
}
|
|
||||||
subcommand_parse_over = true;
|
|
||||||
}
|
|
||||||
if (!option_parse_over) {
|
|
||||||
if (parsing_option) {
|
|
||||||
switch (current_option.value_type) {
|
|
||||||
.STRING => current_option.value_string = current_arg,
|
|
||||||
.INT => current_option.value_int = util.strToInt(current_arg.ptr),
|
|
||||||
.FLOAT => current_option.value_float = util.strToFloat(current_arg.ptr),
|
|
||||||
else => {},
|
|
||||||
}
|
|
||||||
try current_command.options.put(current_option.long, current_option);
|
|
||||||
parsing_option = false;
|
|
||||||
i += 1;
|
|
||||||
continue :parse_while_blk;
|
|
||||||
}
|
|
||||||
var entry_iterator = current_command.options.iterator();
|
|
||||||
while (entry_iterator.next()) |entry| {
|
|
||||||
const option = entry.value_ptr.*;
|
|
||||||
if (mem.eql(u8, current_arg, try mem.concat(current_command.allocator, u8, &[_][]const u8{ "-", option.short })) or
|
|
||||||
mem.eql(u8, current_arg, try mem.concat(current_command.allocator, u8, &[_][]const u8{ "--", option.long })))
|
|
||||||
{
|
|
||||||
current_option = option;
|
|
||||||
parsing_option = true;
|
|
||||||
if (current_option.value_type == .BOOL) {
|
|
||||||
current_option.value_bool = true;
|
|
||||||
try current_command.options.put(entry.key_ptr.*, current_option);
|
|
||||||
parsing_option = false;
|
|
||||||
}
|
|
||||||
i += 1;
|
|
||||||
continue :parse_while_blk;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
option_parse_over = true;
|
|
||||||
}
|
|
||||||
try current_command.arguments.append(current_arg);
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
try current_command.defaultRun();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn execute(self: *Command) !void {
|
|
||||||
var arg_iterator = process.args();
|
|
||||||
while (arg_iterator.next()) |arg| {
|
|
||||||
try self.cmd_args.append(arg);
|
|
||||||
}
|
|
||||||
self.cmd_args.items[0] = self.config.name;
|
|
||||||
try self.parse();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn getBoolOption(self: *Command, option_name: []const u8) bool {
|
|
||||||
return if (self.options.get(option_name)) |option| option.value_bool else false;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn getStringOption(self: *Command, option_name: []const u8) []const u8 {
|
|
||||||
return if (self.options.get(option_name)) |option| option.value_string else "";
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn getIntOption(self: *Command, option_name: []const u8) i32 {
|
|
||||||
return if (self.options.get(option_name)) |option| option.value_int else 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn getFloatOption(self: *Command, option_name: []const u8) f32 {
|
|
||||||
return if (self.options.get(option_name)) |option| option.value_float else 0.0;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
43
demo.zig
43
demo.zig
|
|
@ -1,43 +0,0 @@
|
||||||
const std = @import("std");
|
|
||||||
const heap = std.heap;
|
|
||||||
const cli = @import("cli");
|
|
||||||
const Command = cli.Command;
|
|
||||||
const Option = cli.Option;
|
|
||||||
|
|
||||||
fn rootRun(cmd: *Command) anyerror!void {
|
|
||||||
const e = cmd.getIntOption("example");
|
|
||||||
std.debug.print("{d}\n", .{e});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn subcommandRun(cmd: *Command) anyerror!void {
|
|
||||||
std.debug.print("{s}\n", .{cmd.config.name});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn main() !void {
|
|
||||||
var gpa = heap.GeneralPurposeAllocator(.{}){};
|
|
||||||
const allocator = gpa.allocator();
|
|
||||||
const config = Command.CommandConfig{
|
|
||||||
.short_description = "Example",
|
|
||||||
.long_description = "Example command",
|
|
||||||
.usage = "example",
|
|
||||||
.name = "example",
|
|
||||||
.run = rootRun,
|
|
||||||
};
|
|
||||||
var root_command = try Command.init(allocator, config);
|
|
||||||
const subcommand = try Command.init(allocator, .{
|
|
||||||
.short_description = "SubExample",
|
|
||||||
.long_description = "Subcommand example",
|
|
||||||
.usage = "subexample",
|
|
||||||
.name = "subexample",
|
|
||||||
.run = subcommandRun,
|
|
||||||
});
|
|
||||||
try root_command.addCommand(subcommand);
|
|
||||||
try root_command.addOption(Option{
|
|
||||||
.short = "e",
|
|
||||||
.long = "example",
|
|
||||||
.description = "example option",
|
|
||||||
.value_type = .INT,
|
|
||||||
.value_int = 3,
|
|
||||||
});
|
|
||||||
try root_command.execute();
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
|
||||||
|
"npmClient": "pnpm",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"command": {
|
||||||
|
"run": {
|
||||||
|
"npmClient": "pnpm"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"name": "xoana",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"format": "prettier packages/**/*.{json,ts,js,tsx} ./*.{json,ts,js,yaml} -w --config .prettierrc.json"
|
||||||
|
},
|
||||||
|
"workspaces": [
|
||||||
|
"packages/*"
|
||||||
|
],
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/lodash": "^4.17.0",
|
||||||
|
"lerna": "^8.1.2",
|
||||||
|
"prettier": "^3.2.5",
|
||||||
|
"typescript": "^5.4.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
# `@xoana/api`
|
||||||
|
|
||||||
|
> TODO: description
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
const api = require('@xoana/api');
|
||||||
|
|
||||||
|
// TODO: DEMONSTRATE API
|
||||||
|
```
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const api = require('..');
|
||||||
|
const assert = require('assert').strict;
|
||||||
|
|
||||||
|
assert.strictEqual(api(), 'Hello from api');
|
||||||
|
console.info('api tests passed');
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
module.exports = api;
|
||||||
|
|
||||||
|
function api() {
|
||||||
|
return 'Hello from api';
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"name": "@xoana/api",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"description": "> TODO: description",
|
||||||
|
"author": "Juliane Alanisia <chen667ch@gmail.com>",
|
||||||
|
"homepage": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"main": "src/index.ts",
|
||||||
|
"directories": {
|
||||||
|
"lib": "lib",
|
||||||
|
"test": "__tests__"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"lib"
|
||||||
|
],
|
||||||
|
"publishConfig": {
|
||||||
|
"registry": "https://registry.npmmirror.com/"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"test": "node ./__tests__/@xoana/api.test.js"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
# `@xoana/app`
|
||||||
|
|
||||||
|
> TODO: description
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
const app = require('@xoana/app');
|
||||||
|
|
||||||
|
// TODO: DEMONSTRATE API
|
||||||
|
```
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const app = require('..');
|
||||||
|
const assert = require('assert').strict;
|
||||||
|
|
||||||
|
assert.strictEqual(app(), 'Hello from app');
|
||||||
|
console.info('app tests passed');
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
module.exports = app;
|
||||||
|
|
||||||
|
function app() {
|
||||||
|
return 'Hello from app';
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"name": "@xoana/app",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"description": "Xoana application",
|
||||||
|
"author": "Juliane Alanisia <chen667ch@gmail.com>",
|
||||||
|
"homepage": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"main": "src/index.ts",
|
||||||
|
"directories": {
|
||||||
|
"lib": "lib",
|
||||||
|
"test": "__tests__"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"lib"
|
||||||
|
],
|
||||||
|
"publishConfig": {
|
||||||
|
"registry": "https://registry.npmmirror.com/"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"test": "node ./__tests__/@xoana/app.test.js"
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,2 @@
|
||||||
|
packages:
|
||||||
|
- 'packages/*'
|
||||||
18
util.zig
18
util.zig
|
|
@ -1,18 +0,0 @@
|
||||||
const std = @import("std");
|
|
||||||
const testing = std.testing;
|
|
||||||
const stdlib = @cImport({
|
|
||||||
@cInclude("stdlib.h");
|
|
||||||
});
|
|
||||||
|
|
||||||
pub fn strToFloat(str: [*c]const u8) f32 {
|
|
||||||
return @floatCast(stdlib.atof(str));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn strToInt(str: [*c]const u8) i32 {
|
|
||||||
return @intCast(stdlib.atoi(str));
|
|
||||||
}
|
|
||||||
|
|
||||||
test "string cast to number" {
|
|
||||||
try testing.expect(3 == strToInt("3"));
|
|
||||||
try testing.expect(3.2 == strToFloat("3.2"));
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue