Coder Social home page Coder Social logo

claide's Introduction

Hi, I’m Claide, your command-line tool aide.

Build Status Gem Version

I was born out of a need for a simple option and command parser, while still providing an API that allows you to quickly create a full featured command-line interface.

Install

$ [sudo] gem install claide

Usage

For full documentation, on the API of CLAide, visit rubydoc.info.

Argument handling

At its core, a library, such as myself, needs to parse the parameters specified by the user.

Working with parameters is done through the CLAide::ARGV class. It takes an array of parameters and parses them as either flags, options, or arguments.

Parameter Description
--milk, --no-milk A boolean ‘flag’, which may be negated.
--sweetener=honey An ‘option’ consists of a key, a ‘=’, and a value.
tea An ‘argument’ is just a value.

Accessing flags, options, and arguments, with the following methods, will also remove the parameter from the remaining unprocessed parameters.

argv = CLAide::ARGV.new(['tea', '--no-milk', '--sweetener=honey'])
argv.shift_argument      # => 'tea'
argv.shift_argument      # => nil
argv.flag?('milk')       # => false
argv.flag?('milk')       # => nil
argv.option('sweetener') # => 'honey'
argv.option('sweetener') # => nil

In case the requested flag or option is not present, nil is returned. You can specify a default value to be used as the optional second method parameter:

argv = CLAide::ARGV.new(['tea'])
argv.flag?('milk', true)         # => true
argv.option('sweetener', 'sugar') # => 'sugar'

Unlike flags and options, accessing all of the arguments can be done in either a preserving or mutating way:

argv = CLAide::ARGV.new(['tea', 'coffee'])
argv.arguments  # => ['tea', 'coffee']
argv.arguments! # => ['tea', 'coffee']
argv.arguments  # => []

Command handling

Commands are actions that a tool can perform. Every command is represented by its own command class.

Commands may be nested, in which case they inherit from the ‘super command’ class. Some of these nested commands may not actually perform any work themselves, but are rather used as ‘super commands’ only, in which case they are ‘abtract commands’.

Running commands is typically done through the CLAide::Command.run(argv) method, which performs the following three steps:

  1. Parses the given parameters, finds the command class matching the parameters, and instantiates it with the remaining parameters. It’s each nested command class’ responsibility to remove the parameters it handles from the remaining parameters, before calling the super implementation.

  2. Asks the command instance to validate its parameters, but only after calling the super implementation. The super implementation will show a help banner in case the --help flag is specified, not all parameters were removed from the parameter list, or the command is an abstract command.

  3. Calls the run method on the command instance, where it may do its work.

  4. Catches any uncaught exception and shows it to user in a meaningful way.

    • A Help exception triggers a help banner to be shown for the command.
    • A exception that includes the InformativeError module will show only the message, unless disabled with the --verbose flag; and in red, depending on the color configuration.
    • Any other type of exception will be passed to Command.report_error(error) for custom error reporting (such as the one in CocoaPods).

In case you want to call commands from inside other commands, you should use the CLAide::Command.parse(argv) method to retrieve an instance of the command and call run on it. Unless you are using user-supplied parameters, there should not be a need to validate the parameters.

See the example for a illustration of how to define commands.

claide's People

Contributors

alisoftware avatar alloy avatar amorde avatar basthomas avatar bootstraponline avatar cl3m avatar dbgrandi avatar deivid-rodriguez avatar dependabot[bot] avatar dnkoutso avatar endocrimes avatar fabiopelosin avatar kerrizor avatar kylef avatar leshill avatar mrackwitz avatar nicolasleger avatar olleolleolle avatar orta avatar segiddins avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

claide's Issues

Subcommands separated by a colon instead of a space

This would clearly differentiate subcommands from arguments:

For example, assuming a local subcommands for the search command. We would have the following signatures.

pod search QUERY
pod search local QUERY

The problem with the above is that local could be a valid QUERY. The following approach avoids this confusion:

pod search QUERY
pod search:local QUERY

This approach has other benefits like simplified integration for say an autocompletion script.

The concept is taken from the heroku command line tool:

$ heroku addons --help
Usage: heroku addons

 list installed addons

Additional commands, type "heroku help COMMAND" for more details:

  addons:add ADDON                   #  install an addon
  addons:docs ADDON                  #  open an addon's documentation in your browser
  addons:downgrade ADDON             #  downgrade an existing addon
  addons:list                        #  list all available addons
  addons:open ADDON                  #  open an addon's dashboard in your browser
  addons:remove ADDON1 [ADDON2 ...]  #  uninstall one or more addons
  addons:upgrade ADDON               #  upgrade an existing addon

Allow to declare options

CLAide already allows declaring some of the attributes on the class itself as summary, description and more or less arguments, while the latter is not fully exposed in a DSL. I think this is inconsistent.
Allowing that would make it also a lot easier defining reusable pieces of the CLI and share them across different subcommands.
CocoaPods uses currently modules for that, but as they have to overwrite the initializer that works only good once and gets messy after that.

This could look like below:

class CoffeeMaker < CLAide::Command
  self.description = 'Make delicious coffee from the comfort of your terminal.'

  self.option '--[no-]milk', :flag, 'Don’t add milk', default: true
  # * parses `--milk` as `true`
  # * parses `--no-milk` as `false`
  # * defines an attribute accessor `milk?`

  self.option ['--sweetner', '-s'], 'sugar|honey', 'Use one of the available sweetners', multivalued: true
  # * parses `-s sugar -s honey` as `['sugar', 'milk']`
  # * parses `--sweetner sugar` as `['sugar']`
  # * defines an attribute accessor `sweetners`
end

This DSL syntax is inspired by Clamp.

Add a way to show an original exception in `--verbose` mode.

I’m thinking an attribute like CLAide::InformativeError#wraps_exception. When the user specifies the --verbose flag, the informative error would include the wrapped error message in its output and possibly show the wrapped backtrace instead of the Informative one.

Thoughts?

Support specification of alternative arguments

    $ pod try NAME_OR_URL

      Downloads the Pod with the given NAME (or Git URL), install its dependencies if
      needed and opens its demo project. If a Git URL is provided the head of the repo

something like: arguments = [[['NAME', 'URL'], :required]]

Report differently crashes caused by plug-ins

Currently in CLAide there is a safe net for plugin errors caused while they are loaded. However a runtime error can be identified as caused by a plugin only inspecting the backtrace. User for this reason might think that a crash has been caused by the base gem adopting CLAide.

Open source plugin system

I recently copy and pasted the plugin system you use into Fastlane, it would be awesome if you could extract that into a separate library. :)

CLAide incompatible with RubyGems-3.3.0+

This was originally logged in Cocoapods main issue list and in the rubygems list (where some great analysis took place, including a reference to the actual bisected commit that caused it)

Logging it here because the actual problem appears to be CLAide here using internals from RubyGems that were unsafe to touch, so an internal refactor of RubyGems for performance triggered a problem here, which transitively breaks cocoapods in combo with ruby-gems-3.3.x

A workaround for affected cocoapods users was posted on the cocoapods issue so users won't get stuck, but it seems to me (could be wrong) that the rubygems change was valid + desired from their perspective and the goal is to fix up CLAide here so it functions with new rubygems versions

That's the context. All technical details referenced out from one comment here: CocoaPods/CocoaPods#11134 (comment)

Find a better way to deal with options + inheritance

Related discussion in CocoaPods/cocoapods-plugins#13 here

CLAide::Command::options and Pod::Command::options set up some default options for the root command, which are usually concat with specific subcommand options each time we subclass… but sometimes we have to reject some too.

For example --verbose and --help are common both to root command and subcommands, but --silent is not always meaningfull to all commands (especially to commands that are designed to only print output and do no other action, like pod search).


As a result, we often see pattern like this:

def self.option
  [
    ["--full",  "Search by name, summary, and description"],
    ["--stats", "Show additional stats (like GitHub watchers and forks)"],
    ...
  ].concat(super.reject { |option, _| option == '--silent' })

There sure could be a better way to implement it and help us selecting which options in a root command are to be inherited by subcommands (maybe some only conditionally), and which are specific to that very command but not subcommands.
For example, sthg like separating def self.inheritable_options(printonly_command) from def self.options, or some other idea (open for discussion)

Fix warnings

CLAide is showing up in my rspec warnings list. 😭

/claide-1.0.0.beta.3/lib/claide/argv.rb:218: 
warning: private attribute?

/claide-1.0.0.beta.3/lib/claide/informative_error.rb:17: 
warning: method redefined; discarding old exit_status

Method missing: full_require_paths on macOS system Ruby

So, this is the stack trace. Specifically when calling danger, no bundle exec.

/System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/lib/ruby/2.0.0/rubygems/specification.rb:1896:in `method_missing': undefined method `full_require_paths' for #<Gem::Specification:0x3fc3d501ba48 claide-plugins-0.9.0> (NoMethodError)
    from /Users/Samantha/.gem/ruby/2.0.0/gems/claide-1.0.0/lib/claide/command/plugin_manager.rb:59:in `block (2 levels) in plugins_involved_in_exception'
    from /Users/Samantha/.gem/ruby/2.0.0/gems/claide-1.0.0/lib/claide/command/plugin_manager.rb:58:in `each'
    from /Users/Samantha/.gem/ruby/2.0.0/gems/claide-1.0.0/lib/claide/command/plugin_manager.rb:58:in `any?'
    from /Users/Samantha/.gem/ruby/2.0.0/gems/claide-1.0.0/lib/claide/command/plugin_manager.rb:58:in `block in plugins_involved_in_exception'
    from /Users/Samantha/.gem/ruby/2.0.0/gems/claide-1.0.0/lib/claide/command/plugin_manager.rb:57:in `select'
    from /Users/Samantha/.gem/ruby/2.0.0/gems/claide-1.0.0/lib/claide/command/plugin_manager.rb:57:in `plugins_involved_in_exception'
    from /Users/Samantha/.gem/ruby/2.0.0/gems/claide-1.0.0/lib/claide/command.rb:415:in `report_error'
    from /Users/Samantha/.gem/ruby/2.0.0/gems/claide-1.0.0/lib/claide/command.rb:396:in `handle_exception'
    from /Users/Samantha/.gem/ruby/2.0.0/gems/claide-1.0.0/lib/claide/command.rb:337:in `rescue in run'
    from /Users/Samantha/.gem/ruby/2.0.0/gems/claide-1.0.0/lib/claide/command.rb:325:in `run'
    from /Users/Samantha/.gem/ruby/2.0.0/gems/danger-0.10.1/bin/danger:5:in `<top (required)>'
    from /Users/Samantha/.gem/ruby/2.0.0/bin/danger:23:in `load'
    from /Users/Samantha/.gem/ruby/2.0.0/bin/danger:23:in `<main>'

$ gem -v = 2.0.14.1

It looks like full_require_paths was added in Rubygems 2.2 - which isn't installed by default http://blog.rubygems.org/2013/12/26/2.2.0-released.html

https://github.com/CocoaPods/CLAide/blob/master/lib/claide/command/plugin_manager.rb#L59

This only happens when a plugin crashes, which is why we've probably not seen it in CocoaPods yet, probably cause there's real software engineering going on over there 💃

/cc @samdmarshall

CLAide::Argument Exists ?

Does this exist anymore ? The gem installed on my system with cocoapods doesn't have it.

This is CLAide 0.6.1.

Add more colour to help banners

  • options could be coloured (blue)
  • the usage example could be coloured as well (commands and subcommands in green)

Inspiration from:

Handle interrupt

This is about porting this fragment from CocoaPods.

    def self.report_error(exception)
      if exception.is_a?(Interrupt)
        puts "[!] Cancelled".red
        Config.instance.verbose? ? raise : exit(1)
      else
          raise exception
      end
    end

/c @alloy

--help --verbose shows stack trace

If you call a command with --verbose and --help in combination, CLAide shows a stack trace at the end of the printed help text.

$ ruby examples/make.rb --help --verbose | sed -e "s|$(pwd)|INSTALL_PATH|g"
Usage:

    $ make COMMAND

      Make delicious beverages from the comfort of yourterminal.

Commands:

    + coffee                   Drink brewed from roasted coffee beans
    + tea                      Drink based on cured leaves

Options:

    --no-milk                  Don’t add milk to the beverage
    --sweetner=[sugar|honey]   Use one of the available sweetners
    --completion-script        Print the auto-completion script
    --version                  Show the version of the tool
    --verbose                  Show more debugging information
    --no-ansi                  Show output without ANSI codes
    --help                     Show help banner of specified command

REPO/lib/claide/command.rb:338:in `help!'
REPO/lib/claide/command.rb:456:in `help!'
REPO/lib/claide/command.rb:424:in `validate!'
examples/make.rb:34:in `validate!'
REPO/lib/claide/command.rb:270:in `run'
examples/make.rb:143:in `<main>'

warning: loading in progress, circular require considered harmful

If you require 'claide' and then run rspec with warnings, claide always has a circular require.

$ rspec --warnings
/Users/bootstraponline/.rbenv/versions/2.2.4/lib/ruby/site_ruby/2.2.0/rubygems/core_ext/kernel_require.rb:55: warning: loading in progress, circular require considered harmful - /Users/bootstraponline/.rbenv/versions/2.2.4/lib/ruby/gems/2.2.0/gems/claide-1.0.0.beta.3/lib/claide/ansi.rb

Add support for ASNI codes directly in CLAide

This is about implementing the functionality of the coloured and of the colorized gems directly in CLAide.

The support should be provided via two files which would be responsibility of the clients to include (so not interested clients would not be affected): claude/ansi and claude/ansi-stub. The latter would add empty methods to the String class so clients can conveniently control from a single point wether ansi codes should be used (instead of doing this).

This approach would provide the benefit on not requiring an an external dependency for this functionality.

Cannot take an option with values

can I add a command with option take a value?

for example:

  def self.options
    [
      ['--page=0', 'Page to begin fetch, by default 0.'],
    ].concat(super)
  end

Using argument --page=103 will failed with following error:

[!] Unknown arguments: --page=103

Have a repo/reference list of plugins?

With the growing number of cocoapods plugins that will be published next, maybe we should think about having some repo or any place to list them all?

Sure is everyone respect the cocoapods-<pluginname> naming convention, we can gem search -d cocoapods-, but this won't only match the plugins (beginning with cocoapods-core and cocoapods-downloader for example which are obviously not a plugins) and the naming convention is not a requirement for plugins anyway

This may be as simple as a YAML file listing all the plugins with their name, simple description and URL. This may be a more elaborate solution. But I think that keeping track of all the plugins that will emerge like cocoapods-open and cocoapods-docs will be necessary anyway, so that everyone is aware of what extensions/plugins are available 😉

`pod install help` (invalid command) raises exception

Report

  • What did you do?
$ pod install help
  • What did you expect to happen?

An error should be displayed

  • What happened instead?

An exception happened

Stack

   CocoaPods : 0.33.1
        Ruby : ruby 2.0.0p451 (2014-02-24 revision 45167) [universal.x86_64-darwin13]
    RubyGems : 2.0.14
        Host : Mac OS X 10.9.2 (13C1021)
       Xcode : 5.1.1 (5B1008)
Ruby lib dir : /System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/lib
Repositories : master - https://github.com/CocoaPods/Specs.git @ b947340d648f51d66a6df1cbd1b5f7d8cf5a6de0

Error

NoMethodError - undefined method `ansi' for nil:NilClass
/Users/kylef/gems/gems/claide-0.6.1/lib/claide/command/validation_helper.rb:77:in `prettify_validation_suggestion'
/Users/kylef/gems/gems/claide-0.6.1/lib/claide/command/validation_helper.rb:21:in `argument_suggestion'
/Users/kylef/gems/gems/claide-0.6.1/lib/claide/command.rb:436:in `validate!'
/Users/kylef/gems/gems/claide-0.6.1/lib/claide/command.rb:280:in `run'
/Users/kylef/gems/gems/cocoapods-0.33.1/lib/cocoapods/command.rb:48:in `run'
/Users/kylef/gems/gems/cocoapods-0.33.1/bin/pod:33:in `<top (required)>'
/Users/kylef/gems/bin/pod:23:in `load'
/Users/kylef/gems/bin/pod:23:in `<main>'

Use an array of tuples to describe arguments

Currently to describe the arguments of a command the following approach is used

class Command
  self.arguments = 'ARG_1 [ARG_2]'

  def validate!
    super
    help! "The ARG_1 argument is required." unless @ arg_1
  end
end

This proposal is about having a more structure approach:

class Command
  self.arguments = [
    ['ARG_1', :required],
    ['ARG_2', :optional]
  ]
end

With this information CLAide would take care of:

  • Printing the signature of the command, wrapping arguments in square brackets to signal that they are optional.
  • Validate automatically the presence of required arguments.

A description for arguments (like is done for the the options) is considered not required because the description of the command should take care of explaining them and thus would result in a duplication of information.

The compatibility with the current behaviour should be kept for one version and should just split the arguments by the space characters to convert them in an array and mark all of them as optional. A warning should be printed if a string is passed.

Remove ZSH completion code

It’s not used by all users or even developers that work on CLAide, so it’s quality cannot be guaranteed.

In addition it appears that it doesn’t fully work well, @AliSoftware can you comment on that?

All in all, it’s bloat and as such should be moved out of this library, possibly into a plugin, which could just be a CocoaPods plugin for now.

Rakefile should not try to install bundler

The bootstrap task should not install bundler (see this line). Instead, we should check if bundler is not installed and ask the user to install it manually.

See CocoaPods/Rainforest#13


Inspiration:

task :bootstrap do
  if system('which bundler')
    ...
  else
    puts red("\nPlease install bundler manually")
    puts "$ [sudo] gem install bundler"
  end
end

How to get user input when it run?

I am making a command from the CLAide.
This is my code:

      def run
          puts "please input your name:"
          userInput = gets.chomp.delete(' ')
          puts userInput
      end

but ,I got an error.

please input your name:
bundler: failed to load command: bin/dj (bin/dj)
Errno::ENOENT: No such file or directory @ rb_sysopen - code
  /Users/cw/Documents/Dy/tools/DYAutomate/lib/DYAutomate/Command/codeGenerate.rb:43:in `gets'
  /Users/cw/Documents/Dy/tools/DYAutomate/lib/DYAutomate/Command/codeGenerate.rb:43:in `gets'
  /Users/cw/Documents/Dy/tools/DYAutomate/lib/DYAutomate/Command/codeGenerate.rb:43:in `block in run'
  /Users/cw/Documents/Dy/tools/DYAutomate/lib/DYAutomate/Command/codeGenerate.rb:43:in `noecho'
  /Users/cw/Documents/Dy/tools/DYAutomate/lib/DYAutomate/Command/codeGenerate.rb:43:in `run'
  /Users/cw/.rvm/gems/ruby-2.4.1/gems/claide-1.0.3/lib/claide/command.rb:334:in `run'
  bin/dj:5:in `<top (required)>'

How can I do it?

ZSH completions

From this recommendation it would be nice, probably even possible, to setup some generation for CocoaPods zsh completions. Here is the homebrew completion library, as you can see you can do pretty much anything in them, including piping output from other commands to the completion list.

I would gladly write this setup. I think that it could be built dynamically at least to the extent of the commands and flags assuming we could track those down somewhere. The homebrew completion also completes things like brew install gi<tab> by searching all formula to this:

screen shot 2014-04-16 at 2 47 34 pm

Although I'm not sure I can think of a place for this.

Nil Class Error on multiple Arguments

/Library/Ruby/Gems/2.0.0/gems/claide-0.6.1/lib/claide/command/validation_helper.rb:77:in prettify_validation_suggestion': undefined methodansi' for nil:NilClass

The following works

xcake new hi

but this does not giving the error above

xcake new hi hi

and this is my class

module Xcake
class Command
class New < Command

  include Generators

  self.arguments = [
    ["GENERATOR", true]
  ]

  self.summary = 'Scaffolds new project.'

  def initialize(argv)
    @generator = argv.shift_argument
    @args = argv.remainder
    super
  end

  def validate!
    super
    unless @generator
      help! "You must specify a generator when creating a new project."
    end
  end

  def run
    Generators::invoke @generator, @args
  end
end

end
end

`--no-ansi` is not stripping colours

$ pod --version
[!] The specification of arguments as a string has been deprecated Pod::Command::Lib::Docstats: `NAME`
0.33.0

screen shot 2014-05-20 at 14 52 22

$ env
Apple_PubSub_Socket_Render=/tmp/launch-en5dW9/Render
CMD_DURATION=4.40s
COLORFGBG=7;0
DESTINATION=/var/folders/x3/rxbktkzx4xlf98p4bsn60p040000gn/T/iTerm 1.0.0.20140421 Update
EDITOR=vim
GEM_HOME=/Users/kylef/gems
HOME=/Users/kylef
ITERM_PROFILE=Default
ITERM_SESSION_ID=w2t0p0
LANG=en_GB.UTF-8
LOGNAME=kylef
LSCOLORS=Gxfxcxdxbxegedabagacad
PATH=/Users/kylef/gems/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin
PIP_REQUIRE_VIRTUALENV=true
PWD=/Users/kylef/Projects/cocode/palaver
SHELL=/usr/local/bin/fish
SHLVL=1
SSH_AUTH_SOCK=/tmp/launch-2JXa9u/Listeners
TERM=xterm-256color
TERM_PROGRAM=iTerm.app
TMPDIR=/var/folders/x3/rxbktkzx4xlf98p4bsn60p040000gn/T/
USER=kylef
__CF_USER_TEXT_ENCODING=0x1F5:0:0
__CHECKFIX1436934=1
__fish_bin_dir=/usr/local/Cellar/fish/2.1.0/bin
__fish_datadir=/usr/local/Cellar/fish/2.1.0/share/fish
__fish_help_dir=/usr/local/Cellar/fish/2.1.0/share/doc/fish
__fish_sysconfdir=/usr/local/Cellar/fish/2.1.0/etc/fish

Prefer usage of `def self.method` definition

Prefer usage of def self.method definition instead of class << self syntax for class methods.

In the command class where many methods are mirrored is hard to tell wether one is looking to a class or to an instance method without scrolling.

/c @alloy

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.