{"id":16918179,"url":"https://github.com/vito/mothership","last_synced_at":"2025-08-05T04:15:51.375Z","repository":{"id":56884559,"uuid":"4819586","full_name":"vito/mothership","owner":"vito","description":"big honkin' extensible command-line application library","archived":false,"fork":false,"pushed_at":"2013-09-06T18:09:59.000Z","size":168,"stargazers_count":3,"open_issues_count":1,"forks_count":6,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-06-21T12:47:27.307Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/vito.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2012-06-28T11:04:34.000Z","updated_at":"2017-07-14T20:42:43.000Z","dependencies_parsed_at":"2022-08-20T23:40:40.540Z","dependency_job_id":null,"html_url":"https://github.com/vito/mothership","commit_stats":null,"previous_names":[],"tags_count":32,"template":false,"template_full_name":null,"purl":"pkg:github/vito/mothership","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vito%2Fmothership","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vito%2Fmothership/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vito%2Fmothership/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vito%2Fmothership/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/vito","download_url":"https://codeload.github.com/vito/mothership/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vito%2Fmothership/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":266478925,"owners_count":23935794,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-07-22T02:00:09.085Z","response_time":66,"last_error":null,"robots_txt_status":null,"robots_txt_updated_at":null,"robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-13T19:39:03.967Z","updated_at":"2025-08-05T04:15:51.360Z","avatar_url":"https://github.com/vito.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"Mothership\n==========\n\nThis gem is for defining a big honkin' extensible command-line application.\n\nIt's similar to Thor, but instead of focusing on having multiple composable\ncommand sets, there's simply a global command set that can be trivially\nextended while keeping the extensions isolated.\n\nCommands are defined in subclasses of `Mothership`. Because commands are\nisolated into their own classes, helper methods and constants can be safely\ndefined in it without risking a name collision with other extensions.\n\nAll inputs for a command are defined declaratively. They all have a name,\nwhich is used for the flag name, and a few options that I'll go over later.\n\nIn a more 'functional' style, commands take all of their inputs as a single\nargument, rather than having the inputs embedded in a stateful object. This\nmakes it easier for one command to invoke another with a given set of inputs,\nwithout risking any sort of collision based on their input names.\n\nFor example, to define a 'insult' command which insults the user, with an\noptional censoring flag:\n\n    class Insults \u003c Mothership\n      desc \"Insults the user.\"\n      input :censor, :type =\u003e :boolean, :alias =\u003e \"-c\"\n      def insult\n        puts \"Hey man, #{curse_word(input[:censor])} you.\"\n      end\n\n      private\n\n      def curse_word(censor = false)\n        if censor\n          \"$%#^*\"\n        else\n          \"foo\"\n        end\n      end\n    end\n\nWhen the 'insult' command is defined, it is added to the `Mothership`. When\nthe user invokes `myapp insult`, the `Insults` class is instantiated, the\ninputs are parsed, and the `insult` method is called with the user's inputs as\na hash-like object.\n\nA default value may be provided for an input by calling it with a block that\ngets called when the value is needed but not provided by the user:\n\n    input(:name) { ask(\"What's your name?\") }\n\nThe block is called on the instance of the object, the first time you try to\ndo `input[:foo]`. To pass values to the block, do `input[:foo, arg1, ...]`.\n\nThe returned value is cached, so you can just use `input[:name]` to access the\nvalue, and it will only ask the first time. For example:\n\n    desc \"Delete something.\"\n    input(:name) { ask(\"Delete what?\") }\n    input(:really) { |name| ask(\"Really delete #{name}?\") }\n    def delete(input)\n      return unless input[:really, input[:name]]\n\n      puts \"BAM, #{input[:name]} is gone forever.\"\n    end\n\n    # =\u003e myapp delete\n    #    Delete what?\u003e foo\n    #    Really delete foo?\u003e y\n    #    BAM, foo is gone forever.\n\nAn input can be accepted as an argument or splat-argument form by passing\n`:argument =\u003e true` or `:argument =\u003e :splat`:\n\n    desc \"Deleting something with the given reasons, if any.\"\n    input :name, :argument =\u003e true\n    input :reasons, :argument =\u003e :splat, :alias =\u003e \"--reason\"\n    def delete(input)\n      puts \"Deleting #{input[:name]} because: #{input[:reasons].join \", \"}\"\n    end\n\nThis will accept the following equivalent forms:\n\n    delete foo bar baz\n    delete --name foo bar baz\n    delete --name foo --reasons bar,baz\n    delete --name foo --reason bar,baz\n    delete bar baz --name foo\n    delete foo --reasons bar,baz\n    delete foo --reason bar,baz\n\nNote that that \"--reason\" alias accepts the more natural \"--reason foo\" for\nwhen there is only a single reason. Also note that flags are parsed before\narguments, so it doesn't matter if they're tacked on to the end or if they\nappear before the others.\n\nBecause both flags and arguments are defined with the same mechanic, the usage\nstring for a command can be auto-generated based on its input metadata, rather\nthan being hardcoded and possibly becoming inaccurate if a plugin changes the\ninput structure. For example, a command 'foo' with an optional argument `:bar`\nand a splat argument `:bazes` will have a usage string of `foo BAR BAZES...`.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvito%2Fmothership","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvito%2Fmothership","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvito%2Fmothership/lists"}