Skip to content

koeir/flagparse

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

198 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

flagparse

A simple flag parser for Zig programs.

Features

Config Options

Parse Config

  • allowDups: Don't error when duplicate flags are set. Default is false.
  • verbose: Print out error messages when errors occur. Default is false.
  • writer: Required when using verbose option. Doesn't really do anything without it. Default is null.
  • prefix: Print out a custom string for verbose messages. Default is null.
  • allowDashInput: Allow input type flags to hold strings that begin with "-". Default is true.
  • errOnNoArgs: Outputs an error if there are no arguments except argv[0]. Default is false.
  • exitFirstErr: Exit on first error found. Default is true.

Usage Config

Config for Type.Flags.usage() method

  • padding_left: Number of whitespaces before the tag. Default is 0
  • printUntagged: Print untagged flags. Default is false.
  • untaggedFirst: Print untagged flags first. Prints last when false. Default is true.

Usage

  1. Fetch with zig and add as module in build.zig
zig fetch --save https://github.com/koeir/flagparse/archive/refs/tags/v0.x.x.tar.gz
    // build.zig
    const flagparse = b.dependency("flagparse", .{
        .target = target,
        .optimize = optimize,
    });

    const exe = b.addExecutable(.{
        .name = "example",
        .root_module = b.createModule(.{
            .root_source_file = b.path("src/main.zig"),
            .target = target,
            .optimize = optimize,
        })
    });

    exe.root_module.addImport("flagparse", flagparse.module("flagparse"));
    b.installArtifact(exe);
  1. Declare a list of flags with the built-in structs
const Switch = flagparse.Type.Switch;                  // default is false
const Argumentative = flagparse.Type.Argumentative;    // default is null

const initflags: flagparse.Type.Flags = .{
    .list = &[_] flagparse.Type.Flag
    {
        .{
            .name = "recursive",
            .tag = "Switches",
            .long = "recursive",
            .short = 'r',
            .value = Switch,        // a different default value can be set with .{ Switch/Argumentative = <value> }
            .desc = "Recurse into directories",
        },
        .{
            .name = "force",
            .tag = "Switches",
            .long = "force",
            .short = 'f',
            .vanity = "-[n|f], --[no-]force",    // overwrites long and short flags in printing
            .value = Switch,
            .desc = "Skip confirmation prompts",
        },
        .{  // by default, untagged flags will not be printed
            .name = "no-force",
            .long = "no-force",
            .short = 'n',
            .value = Switch,
            .desc = "Do not skip confirmation prompts",
        },
        // Arguments will accept the next argv
        // e.g. -prf noob
        // "noob" will be accepted as the file
        .{
            .name = "file",
            .tag = "Input",
            .long = "path",
            .short = 'p',
            .value = Argumentative,
            .desc = "Path to file",
        },
    }
};
  1. Initialize args, allocators, and error pointer for handling
const std = @import("std");
const flagparse = @import("flagparse");

pub fn main(init: std.process.Init) !void {
    const io = init.io;
    const min = init.minimal;

    var gpa = std.heap.DebugAllocator(.{}){};
    var arena: std.heap.ArenaAllocator = .init(gpa.allocator());
    defer arena.deinit();

    // points to last flag on error
    var errptr: ?[]const u8 = null;
    ...
  1. Parse
const std = @import("std");
const flagparse = @import("flagparse");

pub fn main() !void {
    ...
    // returns a tuple of Flags and resulting args
    // resulting args is a maybe value
    const result = flagparse.parse(arena.allocator(), min.args, initflags, &errptr, .{}) catch |err| {
        const arg_error = errptr.?;
        // handle err
        return;
    };
    ...
  1. Use
    ...
    // retrieve tuple values
    const flags = result.flags;
    const flagless_args = result.argv;

    const recursive: bool = flags.get_value("recursive").?.Switch;
    const file: ?[:0]const u8 = flags.get_value("file").?.Argumentative;

    if (recursive) // do stuff

    if (flagless_args) |args| {
        // do stuff
    }
    ...

Other ways to retrieve flags:

    // returns null if not found
    pub fn get(self: *const Self, name: []const u8) ?*const Flag {
        return for (self.list) |flag| {
            if (std.mem.eql(u8, flag.name, name)) break &flag;
        } else null;
    }

    // errs if not found
    pub fn try_get(self: *const Self, name: []const u8) FlagError!*const Flag {
        return for (self.list) |flag| {
            if (std.mem.eql(u8, flag.name, name)) break &flag;
        } else FlagError.NoSuchFlag;
    }

    pub fn get_with_flag(self: *const Self, flag: []const u8) ?*const Flag {
        return for (self.list) |*ret| {
            if (ret.short) |short| {
                if (flag[0] == short) break ret;
            }

            if (ret.long) |long| {
                if (std.mem.eql(u8, flag, long)) break ret;
            }
        } else null;
    }

    pub fn get_value(self: *const Self, name: []const u8) ?FlagVal {
        const flag = self.get(name) orelse return null;
        return flag.value;
    }

Printing Format

    // Type.Flag
    pub const Format = struct {
        fillerStyle: u8 = ' ',
        greyOutFiller: bool = false,
        greyOutDesc: bool = false,
        columns: enum {
            one, two
        } = .two,
        padding: struct {
            left: usize = 1,
            desc_left: usize = 1, // useless for columns.two; applied on top of .left
            center: usize = 30, //useless for columns.one
        } = .{},
    };

    // Type.Flags
    pub const UsageConfig = struct {
        padding_left: usize = 0,
        printUntagged: bool = false,
        untaggedFirst: bool = true,
        tagStyle: enum {
            brackets, colon, underline
        } = .colon
    };

The Flags struct has a method usage() that prints all flags with their respective tags. Tags are printed in the order that they first appear in the default flags.

  Switches:
     -r, --recursive               Recurse into directories
     -[n|f], --[no-]force          Don\'t/skip confirmation prompts

  Input:
     -p <file>, --path <file>      Path to file

Individual flags: Type.Flag can be printed with their format() method via {f} print format.

// e.g.
// This affects the printing of `Type.Flags.usage()` too
flagparse.Type.Flag.fmt = {
    .style = '.',       // change what is printed between the flags and descriptions; default is whitespace (' ')
    .columns= .two,     // flags and descriptions as one/two columns; default is .two
    .left = 1,
    .center = 20,
}
 -r, --recursive.... Recurse into directories

More examples:

    // Different style
    flagparse.Type.Flag.fmt = .{
        .columns = .one,
        .padding = .{
            .left = 5,
        },
        .style = ' ',  // default
    };
    try stdout.writeAll("\nUsage:\n\n");
    try initflags.usage(stdout, .{ .padding_left = 2, .tagStyle = .brackets });
Usage:

  [Switches]
     -r, --recursive
     Recurse into directories

         --[no-]force
     Don\'t/skip confirmation prompts

  [Input]
     -p <file>, --path <file>
     Path to file

Errors

pub const FlagErrs = error {
    NoArgs,             // argc < 2
    NoSuchFlag,         // unrecognized flag in arg list
    FlagNotSwitch,      // non-switch/non-bool Flag treated as a switch/bool
    FlagNotArg,         // non-argumentative flag treated as an argumentative
    DuplicateFlag,      // flag appears twice in arg list; can be ignored with config
    ArgNoArg,           // no argument given to argumentative flag
    NoWriter,           // no writer given when verbose is true
    TypeMismatch,       // a more general FlagNotSwitch/FlagNotArg
}

About

Actually simple flag parser for CLI Zig programs.

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages