integrii / flaggy

Idiomatic Go input parsing with subcommands, positional values, and flags at any position. No required project or package layout and no external dependencies.
The Unlicense
858 stars 30 forks source link

How to organise your (sub)commands #39

Closed Ilyes512 closed 5 years ago

Ilyes512 commented 5 years ago

I like what I see except I don't know how to actually structure you app.

For instance... let's imagine I have an app with 5 command that each have there own (3?) subcommands.

I would like to have a single file per command. How would I nicely call the different modules without having allot of if statements likeif subcommand.Used`.

integrii commented 5 years ago

Hey @Ilyes512 - thanks for using flaggy!

I think its entirely up to users as to how they structure their flag commands. However, as a personal example, here is how I use flaggy in one of my programs. This function is executed from my init().

func setupFlaggy() {

    // setup basic app settings
    flaggy.SetName("ContainerCron.com CLI Client")
    flaggy.SetVersion("0.0.0")
    flaggy.SetDescription("The official CLI interface for ContainerCron.com")
    flaggy.DefaultParser.AdditionalHelpAppend = `
  Environment Variables:
    CCRON_SERVER: The API server URL to use.
`

    // debug mode option
    flaggy.Bool(&debug, "", "debug", "Enable debug output")

    // set a different server url
    flaggy.String(&serverAPIURL, "", "server", "Specify a different server URL")
    // flaggy.DefaultParser.Flags[len(flaggy.DefaultParser.Flags)-1].Hidden = true

    // TODO - imageSecret flag
    // TODO - server flag (target cc-server server to connect to)

    // CREATE
    // ccron create hellodocker '* * * * *' -- pass-in --arguments -h
    createCmd = flaggy.NewSubcommand("create")
    createCmd.Description = "Creates cron jobs."
    createCmd.AdditionalHelpAppend = `
  Set the schedule using single quotes:
    '* * * * *'
  Set the arguments for your container's run by using the -- delimiter:
    ccron create busybox '* * * * *' -- ls -al
  `
    createCmd.AddPositionalValue(&imageURL, "imageURL", 1, true, "The URL of the container to run")
    createCmd.AddPositionalValue(&schedule, "schedule", 2, true, "The schedule to run the container on.  Use single quotes: '* * * * *'")
    flaggy.AttachSubcommand(createCmd, 1)

    // LIST
    // ccron list
    listCmd = flaggy.NewSubcommand("list")
    listCmd.Description = "Lists active cron jobs."
    listCmd.AdditionalHelpAppend = ``
    flaggy.AttachSubcommand(listCmd, 1)

    // DELETE
    // ccron delete jobID
    deleteCmd = flaggy.NewSubcommand("delete")
    deleteCmd.Description = "Deletes cron jobs."
    deleteCmd.AddPositionalValue(&cronjobID, "jobID", 1, true, "The ID of the cron job to delete")
    flaggy.AttachSubcommand(deleteCmd, 1)

    // parse the user inputs and return any errors
    flaggy.Parse()
}

I prefer to do things one step at a time, so it's easy to read. I go through each subcommand, create it, assign properties, then attach it to the right place. If a subcommand also has subcommands, I do those right after I setup the parent subcommand.

Then, in the main of my program, I check which subcommand has been used and execute the right function. This can't be any shorter than a switch on the .Used properties of the subcommands. I prefer not to pass in functions to be run because that assumes too much, and the function would have to conform to some interface, which I think makes messier programs.

It is idiomatic to simply use a switch to determine which subcommand was used:

    switch {
    case createCmd.Used:
        runCronJobCreate()
    case listCmd.Used:
        runCronJobList()
    case deleteCmd.Used:
        runCronJobDelete()
    default:
        flaggy.ShowHelpAndExit("Invalid command")
    }

Does that help?