pax_global_header00006660000000000000000000000064135761701530014522gustar00rootroot0000000000000052 comment=fb625b223465692a9d8a88cc2a483e126f1a8978 thor-1.0.1/000077500000000000000000000000001357617015300124755ustar00rootroot00000000000000thor-1.0.1/.document000066400000000000000000000000611357617015300143110ustar00rootroot00000000000000lib/*.rb lib/**/*.rb - CHANGELOG.rdoc LICENSE.md thor-1.0.1/.gitignore000066400000000000000000000006171357617015300144710ustar00rootroot00000000000000!.gitignore *.gem *.rbc *.sw[a-p] *.tmproj *.tmproject *.un~ *~ .Spotlight-V100 .Trashes ._* .bundle .config .directory .elc .emacs.desktop .emacs.desktop.lock .idea .redcar .rvmrc .yardoc Desktop.ini Gemfile.lock Icon? InstalledFiles Session.vim \#*\# _yardoc auto-save-list coverage /doc/ lib/bundler/man pkg pkg/* rdoc spec/reports spec/sandbox test/tmp test/version_tmp tmp tmtags tramp .rbx b/ thor-1.0.1/.rspec000066400000000000000000000000131357617015300136040ustar00rootroot00000000000000-w --color thor-1.0.1/.rubocop.yml000066400000000000000000000064011357617015300147500ustar00rootroot00000000000000AllCops: TargetRubyVersion: 2.2 Metrics/ParameterLists: Max: 6 CountKeywordArgs: true Metrics/MethodLength: Max: 25 CountComments: false Metrics/AbcSize: Enabled: false Metrics/ClassLength: Max: 300 Metrics/ModuleLength: Max: 300 Metrics/BlockLength: Max: 30 Metrics/CyclomaticComplexity: Max: 12 Metrics/PerceivedComplexity: Max: 15 BlockNesting: Max: 4 Metrics/LineLength: Enabled: false AccessModifierIndentation: Enabled: false Documentation: Enabled: false # Enforce Ruby 1.8-compatible hash syntax HashSyntax: EnforcedStyle: hash_rockets # No spaces inside hash literals SpaceInsideHashLiteralBraces: EnforcedStyle: no_space # Allow dots at the end of lines DotPosition: Enabled: false # Don't require magic comment at the top of every file Encoding: Enabled: false # Enforce outdenting of access modifiers (i.e. public, private, protected) AccessModifierIndentation: EnforcedStyle: outdent EmptyLinesAroundAccessModifier: Enabled: true # Align ends correctly EndAlignment: EnforcedStyleAlignWith: variable # Indentation of when/else CaseIndentation: EnforcedStyle: end IndentOneStep: false DoubleNegation: Enabled: false StringLiterals: EnforcedStyle: double_quotes Style/SymbolLiteral: Enabled: false Lint/AssignmentInCondition: Exclude: - 'lib/thor/line_editor/readline.rb' - 'lib/thor/parser/arguments.rb' Lint/EndAlignment: Exclude: - 'lib/thor/actions.rb' - 'lib/thor/parser/option.rb' Security/Eval: Exclude: - 'spec/helper.rb' Lint/HandleExceptions: Exclude: - 'lib/thor/line_editor/readline.rb' Lint/PercentStringArray: Exclude: - 'spec/parser/options_spec.rb' Lint/UnusedMethodArgument: Exclude: - 'lib/thor.rb' - 'lib/thor/base.rb' - 'lib/thor/command.rb' - 'lib/thor/parser/arguments.rb' - 'lib/thor/shell/html.rb' - 'spec/actions/empty_directory_spec.rb' Style/AccessorMethodName: Exclude: - 'lib/thor/line_editor/basic.rb' Style/Alias: Enabled: false Style/ClassAndModuleChildren: Exclude: - 'lib/thor/group.rb' - 'lib/thor/runner.rb' - 'spec/shell_spec.rb' - 'spec/util_spec.rb' Style/ClassVars: Exclude: - 'lib/thor/util.rb' - 'spec/util_spec.rb' Style/ConstantName: Exclude: - 'spec/line_editor_spec.rb' Style/GlobalVars: Exclude: - 'bin/thor' - 'lib/thor.rb' - 'lib/thor/base.rb' - 'lib/thor/shell/basic.rb' - 'spec/helper.rb' - 'spec/rake_compat_spec.rb' - 'spec/register_spec.rb' - 'spec/thor_spec.rb' Style/IndentArray: EnforcedStyle: consistent Style/MethodMissing: Exclude: - 'lib/thor/core_ext/hash_with_indifferent_access.rb' - 'lib/thor/runner.rb' Style/MutableConstant: Enabled: false Style/NumericLiteralPrefix: Exclude: - 'spec/actions/file_manipulation_spec.rb' Style/NumericPredicate: Exclude: - 'spec/**/*' - 'lib/thor/parser/option.rb' Style/PerlBackrefs: Exclude: - 'lib/thor/actions/empty_directory.rb' - 'lib/thor/core_ext/hash_with_indifferent_access.rb' - 'lib/thor/parser/arguments.rb' - 'lib/thor/parser/options.rb' Style/TrailingUnderscoreVariable: Exclude: - 'lib/thor/group.rb' Style/TrailingWhitespace: Exclude: - 'spec/shell/basic_spec.rb' Style/AlignParameters: Enabled: false thor-1.0.1/.travis.yml000066400000000000000000000005711357617015300146110ustar00rootroot00000000000000before_install: - gem update --system 2.7.9 - gem install bundler:1.17.3 - rvm @global do gem uninstall did_you_mean bundler_args: --without development language: ruby rvm: - 2.0.0 - 2.1.10 - 2.2.10 - 2.3.8 - 2.4.7 - 2.5.6 - 2.6.4 - ruby-head matrix: allow_failures: - rvm: ruby-head fast_finish: true script: bundle exec thor spec cache: bundler thor-1.0.1/CHANGELOG.md000066400000000000000000000227471357617015300143220ustar00rootroot00000000000000# 1.0.1 * Fix thor when `thor/base` and `thor/group` are required without `thor.rb`. * Handle relative source path in `create_link`. # 1.0.0 * Drop support to Ruby 1.8 and 1.9. * Deprecate relying on default `exit_on_failure?`. In preparation to make Thor commands exit when there is a failure we are deprecating defining a command without defining what behavior is expected when there is a failure. To fix the deprecation you need to define a class method called `exit_on_failure?` returning `false` if you want the current behavior or `true` if you want the new behavior. * Deprecate defining an option with the default value using a different type as defined in the option. * Allow options to be repeatable. See #674. # 0.20.3 * Support old versions of `did_you_mean`. # 0.20.2 * Fix `did_you_mean` support. # 0.20.1 * Support new versions of ERB. * Fix `check_unknown_options!` to not check the content that was not parsed, i.e. after a `--` or after the first unknown with `stop_on_unknown_option!` * Add `did_you_mean` support. ## 0.20.0 * Add `check_default_type!` to check if the default value of an option matches the defined type. It removes the warning on usage and gives the command authors the possibility to check for programming errors. * Add `disable_required_check!` to disable check for required options in some commands. It is a substitute of `disable_class_options` that was not working as intended. * Add `inject_into_module`. ## 0.19.4, release 2016-11-28 * Rename `Thor::Base#thor_reserved_word?` to `#is_thor_reserved_word?` ## 0.19.3, release 2016-11-27 * Output a warning instead of raising an exception when a default option value doesn't match its specified type ## 0.19.2, release 2016-11-26 * Fix bug with handling of colors passed to `ask` (and methods like `yes?` and `no?` which it underpins) * Allow numeric arguments to be negative * Ensure that default option values are of the specified type (e.g. you can't specify `"foo"` as the default for a numeric option), but make symbols and strings interchangeable * Add `Thor::Shell::Basic#indent` method for intending output * Fix `remove_command` for an inherited command (see #451) * Allow hash arguments to only have each key provided once (see #455) * Allow commands to disable class options, for instance for "help" commands (see #363) * Do not generate a negative option (`--no-no-foo`) for already negative boolean options (`--no-foo`) * Improve compatibility of `Thor::CoreExt::HashWithIndifferentAccess` with Ruby standard library `Hash` * Allow specifying a custom binding for template evaluation (e.g. `#key?` and `#fetch`) * Fix support for subcommand-specific "help"s * Use a string buffer when handling ERB for Ruby 2.3 compatibility * Update dependencies ## 0.19.1, release 2014-03-24 * Fix `say` non-String break regression ## 0.19.0, release 2014-03-22 * Add support for a default to #ask * Avoid @namespace not initialized warning * Avoid private attribute? warning * Fix initializing with unknown options * Loosen required_rubygems_version for compatibility with Ubuntu 10.04 * Shell#ask: support a noecho option for stdin * Shell#ask: change API to be :echo => false * Display a message without a stack trace for ambiguous commands * Make say and say_status thread safe * Dependency for console io version check * Alias --help to help on subcommands * Use mime-types 1.x for Ruby 1.8.7 compatibility for Ruby 1.8 only * Accept .tt files as templates * Check if numeric value is in enum * Use Readline for user input * Fix dispatching of subcommands (concerning :help and *args) * Fix warnings when running specs with `$VERBOSE = true` * Make subcommand help more consistent * Make the current command chain accessible in command ## 0.18.1, release 2013-03-30 * Revert regressions found in 0.18.0 ## 0.18.0, release 2013-03-26 * Remove rake2thor * Only display colors if output medium supports colors * Pass parent_options to subcommands * Fix non-dash-prefixed aliases * Make error messages more helpful * Rename "task" to "command" * Add the method to allow for custom package name ## 0.17.0, release 2013-01-24 * Add better support for tasks that accept arbitrary additional arguments (e.g. things like `bundle exec`) * Add #stop_on_unknown_option! * Only strip from stdin.gets if it wasn't ended with EOF * Allow "send" as a task name * Allow passing options as arguments after "--" * Autoload Thor::Group ## 0.16.0, release 2012-08-14 * Add enum to string arguments ## 0.15.4, release 2012-06-29 * Fix regression when destination root contains reserved regexp characters ## 0.15.3, release 2012-06-18 * Support strict_args_position! for backwards compatibility * Escape Dir glob characters in paths ## 0.15.2, released 2012-05-07 * Added print_in_columns * Exposed terminal_width as a public API ## 0.15.1, release 2012-05-06 * Fix Ruby 1.8 truncation bug with unicode chars * Fix shell delegate methods to pass their block * Don't output trailing spaces when printing the last column in a table ## 0.15, released 2012-04-29 * Alias method_options to options * Refactor say to allow multiple colors * Exposed error as a public API * Exposed file_collision as a public API * Exposed print_wrapped as a public API * Exposed set_color as a public API * Fix number-formatting bugs in print_table * Fix "indent" typo in print_table * Fix Errno::EPIPE when piping tasks to `head` * More friendly error messages ## 0.14, released 2010-07-25 * Added CreateLink class and #link_file method * Made Thor::Actions#run use system as default method for system calls * Allow use of private methods from superclass as tasks * Added mute(&block) method which allows to run block without any output * Removed config[:pretend] * Enabled underscores for command line switches * Added Thor::Base.basename which is used by both Thor.banner and Thor::Group.banner * Deprecated invoke() without arguments * Added :only and :except to check_unknown_options ## 0.13, released 2010-02-03 * Added :lazy_default which is only triggered if a switch is given * Added Thor::Shell::HTML * Added subcommands * Decoupled Thor::Group and Thor, so it's easier to vendor * Added check_unknown_options! in case you want error messages to be raised in valid switches * run(command) should return the results of command ## 0.12, released 2010-01-02 * Methods generated by attr_* are automatically not marked as tasks * inject_into_file does not add the same content twice, unless :force is set * Removed rr in favor to rspec mock framework * Improved output for thor -T * [#7] Do not force white color on status * [#8] Yield a block with the filename on directory ## 0.11, released 2009-07-01 * Added a rake compatibility layer. It allows you to use spec and rdoc tasks on Thor classes. * BACKWARDS INCOMPATIBLE: aliases are not generated automatically anymore since it may cause wrong behavior in the invocation system. * thor help now show information about any class/task. All those calls are possible: thor help describe thor help describe:amazing Or even with default namespaces: thor help :spec * Thor::Runner now invokes the default task if none is supplied: thor describe # invokes the default task, usually help * Thor::Runner now works with mappings: thor describe -h * Added some documentation and code refactoring. ## 0.9.8, released 2008-10-20 * Fixed some tiny issues that were introduced lately. ## 0.9.7, released 2008-10-13 * Setting global method options on the initialize method works as expected: All other tasks will accept these global options in addition to their own. * Added 'group' notion to Thor task sets (class Thor); by default all tasks are in the 'standard' group. Running 'thor -T' will only show the standard tasks - adding --all will show all tasks. You can also filter on a specific group using the --group option: thor -T --group advanced ## 0.9.6, released 2008-09-13 * Generic improvements ## 0.9.5, released 2008-08-27 * Improve Windows compatibility * Update (incorrect) README and task.thor sample file * Options hash is now frozen (once returned) * Allow magic predicates on options object. For instance: `options.force?` * Add support for :numeric type * BACKWARDS INCOMPATIBLE: Refactor Thor::Options. You cannot access shorthand forms in options hash anymore (for instance, options[:f]) * Allow specifying optional args with default values: method_options(:user => "mislav") * Don't write options for nil or false values. This allows, for example, turning color off when running specs. * Exit with the status of the spec command to help CI stuff out some. ## 0.9.4, released 2008-08-13 * Try to add Windows compatibility. * BACKWARDS INCOMPATIBLE: options hash is now accessed as a property in your class and is not passed as last argument anymore * Allow options at the beginning of the argument list as well as the end. * Make options available with symbol keys in addition to string keys. * Allow true to be passed to Thor#method_options to denote a boolean option. * If loading a thor file fails, don't give up, just print a warning and keep going. * Make sure that we re-raise errors if they happened further down the pipe than we care about. * Only delete the old file on updating when the installation of the new one is a success * Make it Ruby 1.8.5 compatible. * Don't raise an error if a boolean switch is defined multiple times. * Thor::Options now doesn't parse through things that look like options but aren't. * Add URI detection to install task, and make sure we don't append ".thor" to URIs * Add rake2thor to the gem binfiles. * Make sure local Thorfiles override system-wide ones. thor-1.0.1/CONTRIBUTING.md000066400000000000000000000020061357617015300147240ustar00rootroot00000000000000Pull Requests ------------- Here are some reasons why a pull request may not be merged: 1. It hasn’t been reviewed. 2. It doesn’t include specs for new functionality. 3. It doesn’t include documentation for new functionality. 4. It changes behavior without changing the relevant documentation, comments, or specs. 5. It changes behavior of an existing public API, breaking backward compatibility. 6. It breaks the tests on a supported platform. 7. It doesn’t merge cleanly (requiring Git rebasing and conflict resolution). If you would like to help in this process, you can start by evaluating open pull requests against the criteria above. For example, if a pull request does not include specs for new functionality, you can add a comment like: “If you would like this feature to be added to Thor, please add specs to ensure that it does not break in the future.” This will help move a pull request closer to being merged. Include this emoji in the top of your ticket to signal to us that you read this file: 🌈 thor-1.0.1/Gemfile000066400000000000000000000005011357617015300137640ustar00rootroot00000000000000source "https://rubygems.org" gem "rake" group :development do gem "pry" gem "pry-byebug" end group :test do gem "childlabor" gem "coveralls", ">= 0.8.19" gem "rspec", ">= 3" gem "rspec-mocks", ">= 3" gem "rubocop", ">= 0.19" gem "simplecov", ">= 0.13" gem "webmock" end gem 'did_you_mean' gemspec thor-1.0.1/LICENSE.md000066400000000000000000000020631357617015300141020ustar00rootroot00000000000000Copyright (c) 2008 Yehuda Katz, Eric Hodel, et al. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. thor-1.0.1/README.md000066400000000000000000000034251357617015300137600ustar00rootroot00000000000000Thor ==== [![Gem Version](http://img.shields.io/gem/v/thor.svg)][gem] [![Build Status](http://img.shields.io/travis/erikhuda/thor.svg)][travis] [![Code Climate](http://img.shields.io/codeclimate/github/erikhuda/thor.svg)][codeclimate] [![Coverage Status](http://img.shields.io/coveralls/erikhuda/thor.svg)][coveralls] [gem]: https://rubygems.org/gems/thor [travis]: http://travis-ci.org/erikhuda/thor [codeclimate]: https://codeclimate.com/github/erikhuda/thor [coveralls]: https://coveralls.io/r/erikhuda/thor Description ----------- Thor is a simple and efficient tool for building self-documenting command line utilities. It removes the pain of parsing command line options, writing "USAGE:" banners, and can also be used as an alternative to the [Rake][rake] build tool. The syntax is Rake-like, so it should be familiar to most Rake users. Please note: Thor, by design, is a system tool created to allow seamless file and url access, which should not receive application user input. It relies on [open-uri][open-uri], which combined with application user input would provide a command injection attack vector. [rake]: https://github.com/ruby/rake [open-uri]: https://ruby-doc.org/stdlib-2.5.1/libdoc/open-uri/rdoc/index.html Installation ------------ gem install thor Usage and documentation ----------------------- Please see the [wiki][] for basic usage and other documentation on using Thor. You can also checkout the [official homepage][homepage]. [wiki]: https://github.com/erikhuda/thor/wiki [homepage]: http://whatisthor.com/ Contributing ------------ If you would like to help, please read the [CONTRIBUTING][] file for suggestions. [contributing]: CONTRIBUTING.md License ------- Released under the MIT License. See the [LICENSE][] file for further details. [license]: LICENSE.md thor-1.0.1/Thorfile000066400000000000000000000012771357617015300142030ustar00rootroot00000000000000$LOAD_PATH.unshift File.expand_path("../lib", __FILE__) require "bundler" require "thor/rake_compat" class Default < Thor include Thor::RakeCompat Bundler::GemHelper.install_tasks desc "build", "Build thor-#{Thor::VERSION}.gem into the pkg directory" def build Rake::Task["build"].execute end desc "install", "Build and install thor-#{Thor::VERSION}.gem into system gems" def install Rake::Task["install"].execute end desc "release", "Create tag v#{Thor::VERSION} and build and push thor-#{Thor::VERSION}.gem to Rubygems" def release Rake::Task["release"].invoke end desc "spec", "Run RSpec code examples" def spec exec "bundle exec rspec spec" end end thor-1.0.1/bin/000077500000000000000000000000001357617015300132455ustar00rootroot00000000000000thor-1.0.1/bin/thor000077500000000000000000000001471357617015300141510ustar00rootroot00000000000000#!/usr/bin/env ruby # -*- mode: ruby -*- require "thor/runner" $thor_runner = true Thor::Runner.start thor-1.0.1/lib/000077500000000000000000000000001357617015300132435ustar00rootroot00000000000000thor-1.0.1/lib/thor.rb000066400000000000000000000377161357617015300145620ustar00rootroot00000000000000require "set" require_relative "thor/base" class Thor class << self # Allows for custom "Command" package naming. # # === Parameters # name # options # def package_name(name, _ = {}) @package_name = name.nil? || name == "" ? nil : name end # Sets the default command when thor is executed without an explicit command to be called. # # ==== Parameters # meth:: name of the default command # def default_command(meth = nil) if meth @default_command = meth == :none ? "help" : meth.to_s else @default_command ||= from_superclass(:default_command, "help") end end alias_method :default_task, :default_command # Registers another Thor subclass as a command. # # ==== Parameters # klass:: Thor subclass to register # command:: Subcommand name to use # usage:: Short usage for the subcommand # description:: Description for the subcommand def register(klass, subcommand_name, usage, description, options = {}) if klass <= Thor::Group desc usage, description, options define_method(subcommand_name) { |*args| invoke(klass, args) } else desc usage, description, options subcommand subcommand_name, klass end end # Defines the usage and the description of the next command. # # ==== Parameters # usage # description # options # def desc(usage, description, options = {}) if options[:for] command = find_and_refresh_command(options[:for]) command.usage = usage if usage command.description = description if description else @usage = usage @desc = description @hide = options[:hide] || false end end # Defines the long description of the next command. # # ==== Parameters # long description # def long_desc(long_description, options = {}) if options[:for] command = find_and_refresh_command(options[:for]) command.long_description = long_description if long_description else @long_desc = long_description end end # Maps an input to a command. If you define: # # map "-T" => "list" # # Running: # # thor -T # # Will invoke the list command. # # ==== Parameters # Hash[String|Array => Symbol]:: Maps the string or the strings in the array to the given command. # def map(mappings = nil, **kw) @map ||= from_superclass(:map, {}) if mappings && !kw.empty? mappings = kw.merge!(mappings) else mappings ||= kw end if mappings mappings.each do |key, value| if key.respond_to?(:each) key.each { |subkey| @map[subkey] = value } else @map[key] = value end end end @map end # Declares the options for the next command to be declared. # # ==== Parameters # Hash[Symbol => Object]:: The hash key is the name of the option and the value # is the type of the option. Can be :string, :array, :hash, :boolean, :numeric # or :required (string). If you give a value, the type of the value is used. # def method_options(options = nil) @method_options ||= {} build_options(options, @method_options) if options @method_options end alias_method :options, :method_options # Adds an option to the set of method options. If :for is given as option, # it allows you to change the options from a previous defined command. # # def previous_command # # magic # end # # method_option :foo => :bar, :for => :previous_command # # def next_command # # magic # end # # ==== Parameters # name:: The name of the argument. # options:: Described below. # # ==== Options # :desc - Description for the argument. # :required - If the argument is required or not. # :default - Default value for this argument. It cannot be required and have default values. # :aliases - Aliases for this option. # :type - The type of the argument, can be :string, :hash, :array, :numeric or :boolean. # :banner - String to show on usage notes. # :hide - If you want to hide this option from the help. # def method_option(name, options = {}) scope = if options[:for] find_and_refresh_command(options[:for]).options else method_options end build_option(name, options, scope) end alias_method :option, :method_option # Prints help information for the given command. # # ==== Parameters # shell # command_name # def command_help(shell, command_name) meth = normalize_command_name(command_name) command = all_commands[meth] handle_no_command_error(meth) unless command shell.say "Usage:" shell.say " #{banner(command).split("\n").join("\n ")}" shell.say class_options_help(shell, nil => command.options.values) if command.long_description shell.say "Description:" shell.print_wrapped(command.long_description, :indent => 2) else shell.say command.description end end alias_method :task_help, :command_help # Prints help information for this class. # # ==== Parameters # shell # def help(shell, subcommand = false) list = printable_commands(true, subcommand) Thor::Util.thor_classes_in(self).each do |klass| list += klass.printable_commands(false) end list.sort! { |a, b| a[0] <=> b[0] } if defined?(@package_name) && @package_name shell.say "#{@package_name} commands:" else shell.say "Commands:" end shell.print_table(list, :indent => 2, :truncate => true) shell.say class_options_help(shell) end # Returns commands ready to be printed. def printable_commands(all = true, subcommand = false) (all ? all_commands : commands).map do |_, command| next if command.hidden? item = [] item << banner(command, false, subcommand) item << (command.description ? "# #{command.description.gsub(/\s+/m, ' ')}" : "") item end.compact end alias_method :printable_tasks, :printable_commands def subcommands @subcommands ||= from_superclass(:subcommands, []) end alias_method :subtasks, :subcommands def subcommand_classes @subcommand_classes ||= {} end def subcommand(subcommand, subcommand_class) subcommands << subcommand.to_s subcommand_class.subcommand_help subcommand subcommand_classes[subcommand.to_s] = subcommand_class define_method(subcommand) do |*args| args, opts = Thor::Arguments.split(args) invoke_args = [args, opts, {:invoked_via_subcommand => true, :class_options => options}] invoke_args.unshift "help" if opts.delete("--help") || opts.delete("-h") invoke subcommand_class, *invoke_args end subcommand_class.commands.each do |_meth, command| command.ancestor_name = subcommand end end alias_method :subtask, :subcommand # Extend check unknown options to accept a hash of conditions. # # === Parameters # options: A hash containing :only and/or :except keys def check_unknown_options!(options = {}) @check_unknown_options ||= {} options.each do |key, value| if value @check_unknown_options[key] = Array(value) else @check_unknown_options.delete(key) end end @check_unknown_options end # Overwrite check_unknown_options? to take subcommands and options into account. def check_unknown_options?(config) #:nodoc: options = check_unknown_options return false unless options command = config[:current_command] return true unless command name = command.name if subcommands.include?(name) false elsif options[:except] !options[:except].include?(name.to_sym) elsif options[:only] options[:only].include?(name.to_sym) else true end end # Stop parsing of options as soon as an unknown option or a regular # argument is encountered. All remaining arguments are passed to the command. # This is useful if you have a command that can receive arbitrary additional # options, and where those additional options should not be handled by # Thor. # # ==== Example # # To better understand how this is useful, let's consider a command that calls # an external command. A user may want to pass arbitrary options and # arguments to that command. The command itself also accepts some options, # which should be handled by Thor. # # class_option "verbose", :type => :boolean # stop_on_unknown_option! :exec # check_unknown_options! :except => :exec # # desc "exec", "Run a shell command" # def exec(*args) # puts "diagnostic output" if options[:verbose] # Kernel.exec(*args) # end # # Here +exec+ can be called with +--verbose+ to get diagnostic output, # e.g.: # # $ thor exec --verbose echo foo # diagnostic output # foo # # But if +--verbose+ is given after +echo+, it is passed to +echo+ instead: # # $ thor exec echo --verbose foo # --verbose foo # # ==== Parameters # Symbol ...:: A list of commands that should be affected. def stop_on_unknown_option!(*command_names) stop_on_unknown_option.merge(command_names) end def stop_on_unknown_option?(command) #:nodoc: command && stop_on_unknown_option.include?(command.name.to_sym) end # Disable the check for required options for the given commands. # This is useful if you have a command that does not need the required options # to work, like help. # # ==== Parameters # Symbol ...:: A list of commands that should be affected. def disable_required_check!(*command_names) disable_required_check.merge(command_names) end def disable_required_check?(command) #:nodoc: command && disable_required_check.include?(command.name.to_sym) end protected def stop_on_unknown_option #:nodoc: @stop_on_unknown_option ||= Set.new end # help command has the required check disabled by default. def disable_required_check #:nodoc: @disable_required_check ||= Set.new([:help]) end # The method responsible for dispatching given the args. def dispatch(meth, given_args, given_opts, config) #:nodoc: # rubocop:disable MethodLength meth ||= retrieve_command_name(given_args) command = all_commands[normalize_command_name(meth)] if !command && config[:invoked_via_subcommand] # We're a subcommand and our first argument didn't match any of our # commands. So we put it back and call our default command. given_args.unshift(meth) command = all_commands[normalize_command_name(default_command)] end if command args, opts = Thor::Options.split(given_args) if stop_on_unknown_option?(command) && !args.empty? # given_args starts with a non-option, so we treat everything as # ordinary arguments args.concat opts opts.clear end else args = given_args opts = nil command = dynamic_command_class.new(meth) end opts = given_opts || opts || [] config[:current_command] = command config[:command_options] = command.options instance = new(args, opts, config) yield instance if block_given? args = instance.args trailing = args[Range.new(arguments.size, -1)] instance.invoke_command(command, trailing || []) end # The banner for this class. You can customize it if you are invoking the # thor class by another ways which is not the Thor::Runner. It receives # the command that is going to be invoked and a boolean which indicates if # the namespace should be displayed as arguments. # def banner(command, namespace = nil, subcommand = false) $thor_runner ||= false command.formatted_usage(self, $thor_runner, subcommand).split("\n").map do |formatted_usage| "#{basename} #{formatted_usage}" end.join("\n") end def baseclass #:nodoc: Thor end def dynamic_command_class #:nodoc: Thor::DynamicCommand end def create_command(meth) #:nodoc: @usage ||= nil @desc ||= nil @long_desc ||= nil @hide ||= nil if @usage && @desc base_class = @hide ? Thor::HiddenCommand : Thor::Command commands[meth] = base_class.new(meth, @desc, @long_desc, @usage, method_options) @usage, @desc, @long_desc, @method_options, @hide = nil true elsif all_commands[meth] || meth == "method_missing" true else puts "[WARNING] Attempted to create command #{meth.inspect} without usage or description. " \ "Call desc if you want this method to be available as command or declare it inside a " \ "no_commands{} block. Invoked from #{caller[1].inspect}." false end end alias_method :create_task, :create_command def initialize_added #:nodoc: class_options.merge!(method_options) @method_options = nil end # Retrieve the command name from given args. def retrieve_command_name(args) #:nodoc: meth = args.first.to_s unless args.empty? args.shift if meth && (map[meth] || meth !~ /^\-/) end alias_method :retrieve_task_name, :retrieve_command_name # receives a (possibly nil) command name and returns a name that is in # the commands hash. In addition to normalizing aliases, this logic # will determine if a shortened command is an unambiguous substring of # a command or alias. # # +normalize_command_name+ also converts names like +animal-prison+ # into +animal_prison+. def normalize_command_name(meth) #:nodoc: return default_command.to_s.tr("-", "_") unless meth possibilities = find_command_possibilities(meth) raise AmbiguousTaskError, "Ambiguous command #{meth} matches [#{possibilities.join(', ')}]" if possibilities.size > 1 if possibilities.empty? meth ||= default_command elsif map[meth] meth = map[meth] else meth = possibilities.first end meth.to_s.tr("-", "_") # treat foo-bar as foo_bar end alias_method :normalize_task_name, :normalize_command_name # this is the logic that takes the command name passed in by the user # and determines whether it is an unambiguous substrings of a command or # alias name. def find_command_possibilities(meth) len = meth.to_s.length possibilities = all_commands.merge(map).keys.select { |n| meth == n[0, len] }.sort unique_possibilities = possibilities.map { |k| map[k] || k }.uniq if possibilities.include?(meth) [meth] elsif unique_possibilities.size == 1 unique_possibilities else possibilities end end alias_method :find_task_possibilities, :find_command_possibilities def subcommand_help(cmd) desc "help [COMMAND]", "Describe subcommands or one specific subcommand" class_eval " def help(command = nil, subcommand = true); super; end " end alias_method :subtask_help, :subcommand_help end include Thor::Base map HELP_MAPPINGS => :help desc "help [COMMAND]", "Describe available commands or one specific command" def help(command = nil, subcommand = false) if command if self.class.subcommands.include? command self.class.subcommand_classes[command].help(shell, true) else self.class.command_help(shell, command) end else self.class.help(shell, subcommand) end end end thor-1.0.1/lib/thor/000077500000000000000000000000001357617015300142175ustar00rootroot00000000000000thor-1.0.1/lib/thor/actions.rb000066400000000000000000000246111357617015300162100ustar00rootroot00000000000000require_relative "actions/create_file" require_relative "actions/create_link" require_relative "actions/directory" require_relative "actions/empty_directory" require_relative "actions/file_manipulation" require_relative "actions/inject_into_file" class Thor module Actions attr_accessor :behavior def self.included(base) #:nodoc: super(base) base.extend ClassMethods end module ClassMethods # Hold source paths for one Thor instance. source_paths_for_search is the # method responsible to gather source_paths from this current class, # inherited paths and the source root. # def source_paths @_source_paths ||= [] end # Stores and return the source root for this class def source_root(path = nil) @_source_root = path if path @_source_root ||= nil end # Returns the source paths in the following order: # # 1) This class source paths # 2) Source root # 3) Parents source paths # def source_paths_for_search paths = [] paths += source_paths paths << source_root if source_root paths += from_superclass(:source_paths, []) paths end # Add runtime options that help actions execution. # def add_runtime_options! class_option :force, :type => :boolean, :aliases => "-f", :group => :runtime, :desc => "Overwrite files that already exist" class_option :pretend, :type => :boolean, :aliases => "-p", :group => :runtime, :desc => "Run but do not make any changes" class_option :quiet, :type => :boolean, :aliases => "-q", :group => :runtime, :desc => "Suppress status output" class_option :skip, :type => :boolean, :aliases => "-s", :group => :runtime, :desc => "Skip files that already exist" end end # Extends initializer to add more configuration options. # # ==== Configuration # behavior:: The actions default behavior. Can be :invoke or :revoke. # It also accepts :force, :skip and :pretend to set the behavior # and the respective option. # # destination_root:: The root directory needed for some actions. # def initialize(args = [], options = {}, config = {}) self.behavior = case config[:behavior].to_s when "force", "skip" _cleanup_options_and_set(options, config[:behavior]) :invoke when "revoke" :revoke else :invoke end super self.destination_root = config[:destination_root] end # Wraps an action object and call it accordingly to the thor class behavior. # def action(instance) #:nodoc: if behavior == :revoke instance.revoke! else instance.invoke! end end # Returns the root for this thor class (also aliased as destination root). # def destination_root @destination_stack.last end # Sets the root for this thor class. Relatives path are added to the # directory where the script was invoked and expanded. # def destination_root=(root) @destination_stack ||= [] @destination_stack[0] = File.expand_path(root || "") end # Returns the given path relative to the absolute root (ie, root where # the script started). # def relative_to_original_destination_root(path, remove_dot = true) root = @destination_stack[0] if path.start_with?(root) && [File::SEPARATOR, File::ALT_SEPARATOR, nil, ''].include?(path[root.size..root.size]) path = path.dup path[0...root.size] = '.' remove_dot ? (path[2..-1] || "") : path else path end end # Holds source paths in instance so they can be manipulated. # def source_paths @source_paths ||= self.class.source_paths_for_search end # Receives a file or directory and search for it in the source paths. # def find_in_source_paths(file) possible_files = [file, file + TEMPLATE_EXTNAME] relative_root = relative_to_original_destination_root(destination_root, false) source_paths.each do |source| possible_files.each do |f| source_file = File.expand_path(f, File.join(source, relative_root)) return source_file if File.exist?(source_file) end end message = "Could not find #{file.inspect} in any of your source paths. ".dup unless self.class.source_root message << "Please invoke #{self.class.name}.source_root(PATH) with the PATH containing your templates. " end message << if source_paths.empty? "Currently you have no source paths." else "Your current source paths are: \n#{source_paths.join("\n")}" end raise Error, message end # Do something in the root or on a provided subfolder. If a relative path # is given it's referenced from the current root. The full path is yielded # to the block you provide. The path is set back to the previous path when # the method exits. # # ==== Parameters # dir:: the directory to move to. # config:: give :verbose => true to log and use padding. # def inside(dir = "", config = {}, &block) verbose = config.fetch(:verbose, false) pretend = options[:pretend] say_status :inside, dir, verbose shell.padding += 1 if verbose @destination_stack.push File.expand_path(dir, destination_root) # If the directory doesnt exist and we're not pretending if !File.exist?(destination_root) && !pretend require "fileutils" FileUtils.mkdir_p(destination_root) end if pretend # In pretend mode, just yield down to the block block.arity == 1 ? yield(destination_root) : yield else require "fileutils" FileUtils.cd(destination_root) { block.arity == 1 ? yield(destination_root) : yield } end @destination_stack.pop shell.padding -= 1 if verbose end # Goes to the root and execute the given block. # def in_root inside(@destination_stack.first) { yield } end # Loads an external file and execute it in the instance binding. # # ==== Parameters # path:: The path to the file to execute. Can be a web address or # a relative path from the source root. # # ==== Examples # # apply "http://gist.github.com/103208" # # apply "recipes/jquery.rb" # def apply(path, config = {}) verbose = config.fetch(:verbose, true) is_uri = path =~ %r{^https?\://} path = find_in_source_paths(path) unless is_uri say_status :apply, path, verbose shell.padding += 1 if verbose contents = if is_uri require "open-uri" open(path, "Accept" => "application/x-thor-template", &:read) else open(path, &:read) end instance_eval(contents, path) shell.padding -= 1 if verbose end # Executes a command returning the contents of the command. # # ==== Parameters # command:: the command to be executed. # config:: give :verbose => false to not log the status, :capture => true to hide to output. Specify :with # to append an executable to command execution. # # ==== Example # # inside('vendor') do # run('ln -s ~/edge rails') # end # def run(command, config = {}) return unless behavior == :invoke destination = relative_to_original_destination_root(destination_root, false) desc = "#{command} from #{destination.inspect}" if config[:with] desc = "#{File.basename(config[:with].to_s)} #{desc}" command = "#{config[:with]} #{command}" end say_status :run, desc, config.fetch(:verbose, true) return if options[:pretend] env_splat = [config[:env]] if config[:env] if config[:capture] require "open3" result, status = Open3.capture2e(*env_splat, command.to_s) success = status.success? else result = system(*env_splat, command.to_s) success = result end abort if !success && config.fetch(:abort_on_failure, self.class.exit_on_failure?) result end # Executes a ruby script (taking into account WIN32 platform quirks). # # ==== Parameters # command:: the command to be executed. # config:: give :verbose => false to not log the status. # def run_ruby_script(command, config = {}) return unless behavior == :invoke run command, config.merge(:with => Thor::Util.ruby_command) end # Run a thor command. A hash of options can be given and it's converted to # switches. # # ==== Parameters # command:: the command to be invoked # args:: arguments to the command # config:: give :verbose => false to not log the status, :capture => true to hide to output. # Other options are given as parameter to Thor. # # # ==== Examples # # thor :install, "http://gist.github.com/103208" # #=> thor install http://gist.github.com/103208 # # thor :list, :all => true, :substring => 'rails' # #=> thor list --all --substring=rails # def thor(command, *args) config = args.last.is_a?(Hash) ? args.pop : {} verbose = config.key?(:verbose) ? config.delete(:verbose) : true pretend = config.key?(:pretend) ? config.delete(:pretend) : false capture = config.key?(:capture) ? config.delete(:capture) : false args.unshift(command) args.push Thor::Options.to_switches(config) command = args.join(" ").strip run command, :with => :thor, :verbose => verbose, :pretend => pretend, :capture => capture end protected # Allow current root to be shared between invocations. # def _shared_configuration #:nodoc: super.merge!(:destination_root => destination_root) end def _cleanup_options_and_set(options, key) #:nodoc: case options when Array %w(--force -f --skip -s).each { |i| options.delete(i) } options << "--#{key}" when Hash [:force, :skip, "force", "skip"].each { |i| options.delete(i) } options.merge!(key => true) end end end end thor-1.0.1/lib/thor/actions/000077500000000000000000000000001357617015300156575ustar00rootroot00000000000000thor-1.0.1/lib/thor/actions/create_file.rb000066400000000000000000000060401357617015300204460ustar00rootroot00000000000000require_relative "empty_directory" class Thor module Actions # Create a new file relative to the destination root with the given data, # which is the return value of a block or a data string. # # ==== Parameters # destination:: the relative path to the destination root. # data:: the data to append to the file. # config:: give :verbose => false to not log the status. # # ==== Examples # # create_file "lib/fun_party.rb" do # hostname = ask("What is the virtual hostname I should use?") # "vhost.name = #{hostname}" # end # # create_file "config/apache.conf", "your apache config" # def create_file(destination, *args, &block) config = args.last.is_a?(Hash) ? args.pop : {} data = args.first action CreateFile.new(self, destination, block || data.to_s, config) end alias_method :add_file, :create_file # CreateFile is a subset of Template, which instead of rendering a file with # ERB, it gets the content from the user. # class CreateFile < EmptyDirectory #:nodoc: attr_reader :data def initialize(base, destination, data, config = {}) @data = data super(base, destination, config) end # Checks if the content of the file at the destination is identical to the rendered result. # # ==== Returns # Boolean:: true if it is identical, false otherwise. # def identical? exists? && File.binread(destination) == render end # Holds the content to be added to the file. # def render @render ||= if data.is_a?(Proc) data.call else data end end def invoke! invoke_with_conflict_check do require "fileutils" FileUtils.mkdir_p(File.dirname(destination)) File.open(destination, "wb") { |f| f.write render } end given_destination end protected # Now on conflict we check if the file is identical or not. # def on_conflict_behavior(&block) if identical? say_status :identical, :blue else options = base.options.merge(config) force_or_skip_or_conflict(options[:force], options[:skip], &block) end end # If force is true, run the action, otherwise check if it's not being # skipped. If both are false, show the file_collision menu, if the menu # returns true, force it, otherwise skip. # def force_or_skip_or_conflict(force, skip, &block) if force say_status :force, :yellow yield unless pretend? elsif skip say_status :skip, :yellow else say_status :conflict, :red force_or_skip_or_conflict(force_on_collision?, true, &block) end end # Shows the file collision menu to the user and gets the result. # def force_on_collision? base.shell.file_collision(destination) { render } end end end end thor-1.0.1/lib/thor/actions/create_link.rb000066400000000000000000000035441357617015300204720ustar00rootroot00000000000000require_relative "create_file" class Thor module Actions # Create a new file relative to the destination root from the given source. # # ==== Parameters # destination:: the relative path to the destination root. # source:: the relative path to the source root. # config:: give :verbose => false to not log the status. # :: give :symbolic => false for hard link. # # ==== Examples # # create_link "config/apache.conf", "/etc/apache.conf" # def create_link(destination, *args) config = args.last.is_a?(Hash) ? args.pop : {} source = args.first action CreateLink.new(self, destination, source, config) end alias_method :add_link, :create_link # CreateLink is a subset of CreateFile, which instead of taking a block of # data, just takes a source string from the user. # class CreateLink < CreateFile #:nodoc: attr_reader :data # Checks if the content of the file at the destination is identical to the rendered result. # # ==== Returns # Boolean:: true if it is identical, false otherwise. # def identical? source = File.expand_path(render, File.dirname(destination)) exists? && File.identical?(source, destination) end def invoke! invoke_with_conflict_check do require "fileutils" FileUtils.mkdir_p(File.dirname(destination)) # Create a symlink by default config[:symbolic] = true if config[:symbolic].nil? File.unlink(destination) if exists? if config[:symbolic] File.symlink(render, destination) else File.link(render, destination) end end given_destination end def exists? super || File.symlink?(destination) end end end end thor-1.0.1/lib/thor/actions/directory.rb000066400000000000000000000073561357617015300202230ustar00rootroot00000000000000require_relative "empty_directory" class Thor module Actions # Copies recursively the files from source directory to root directory. # If any of the files finishes with .tt, it's considered to be a template # and is placed in the destination without the extension .tt. If any # empty directory is found, it's copied and all .empty_directory files are # ignored. If any file name is wrapped within % signs, the text within # the % signs will be executed as a method and replaced with the returned # value. Let's suppose a doc directory with the following files: # # doc/ # components/.empty_directory # README # rdoc.rb.tt # %app_name%.rb # # When invoked as: # # directory "doc" # # It will create a doc directory in the destination with the following # files (assuming that the `app_name` method returns the value "blog"): # # doc/ # components/ # README # rdoc.rb # blog.rb # # Encoded path note: Since Thor internals use Object#respond_to? to check if it can # expand %something%, this `something` should be a public method in the class calling # #directory. If a method is private, Thor stack raises PrivateMethodEncodedError. # # ==== Parameters # source:: the relative path to the source root. # destination:: the relative path to the destination root. # config:: give :verbose => false to not log the status. # If :recursive => false, does not look for paths recursively. # If :mode => :preserve, preserve the file mode from the source. # If :exclude_pattern => /regexp/, prevents copying files that match that regexp. # # ==== Examples # # directory "doc" # directory "doc", "docs", :recursive => false # def directory(source, *args, &block) config = args.last.is_a?(Hash) ? args.pop : {} destination = args.first || source action Directory.new(self, source, destination || source, config, &block) end class Directory < EmptyDirectory #:nodoc: attr_reader :source def initialize(base, source, destination = nil, config = {}, &block) @source = File.expand_path(Dir[Util.escape_globs(base.find_in_source_paths(source.to_s))].first) @block = block super(base, destination, {:recursive => true}.merge(config)) end def invoke! base.empty_directory given_destination, config execute! end def revoke! execute! end protected def execute! lookup = Util.escape_globs(source) lookup = config[:recursive] ? File.join(lookup, "**") : lookup lookup = file_level_lookup(lookup) files(lookup).sort.each do |file_source| next if File.directory?(file_source) next if config[:exclude_pattern] && file_source.match(config[:exclude_pattern]) file_destination = File.join(given_destination, file_source.gsub(source, ".")) file_destination.gsub!("/./", "/") case file_source when /\.empty_directory$/ dirname = File.dirname(file_destination).gsub(%r{/\.$}, "") next if dirname == given_destination base.empty_directory(dirname, config) when /#{TEMPLATE_EXTNAME}$/ base.template(file_source, file_destination[0..-4], config, &@block) else base.copy_file(file_source, file_destination, config, &@block) end end end def file_level_lookup(previous_lookup) File.join(previous_lookup, "*") end def files(lookup) Dir.glob(lookup, File::FNM_DOTMATCH) end end end end thor-1.0.1/lib/thor/actions/empty_directory.rb000066400000000000000000000103411357617015300214250ustar00rootroot00000000000000class Thor module Actions # Creates an empty directory. # # ==== Parameters # destination:: the relative path to the destination root. # config:: give :verbose => false to not log the status. # # ==== Examples # # empty_directory "doc" # def empty_directory(destination, config = {}) action EmptyDirectory.new(self, destination, config) end # Class which holds create directory logic. This is the base class for # other actions like create_file and directory. # # This implementation is based in Templater actions, created by Jonas Nicklas # and Michael S. Klishin under MIT LICENSE. # class EmptyDirectory #:nodoc: attr_reader :base, :destination, :given_destination, :relative_destination, :config # Initializes given the source and destination. # # ==== Parameters # base:: A Thor::Base instance # source:: Relative path to the source of this file # destination:: Relative path to the destination of this file # config:: give :verbose => false to not log the status. # def initialize(base, destination, config = {}) @base = base @config = {:verbose => true}.merge(config) self.destination = destination end # Checks if the destination file already exists. # # ==== Returns # Boolean:: true if the file exists, false otherwise. # def exists? ::File.exist?(destination) end def invoke! invoke_with_conflict_check do require "fileutils" ::FileUtils.mkdir_p(destination) end end def revoke! say_status :remove, :red require "fileutils" ::FileUtils.rm_rf(destination) if !pretend? && exists? given_destination end protected # Shortcut for pretend. # def pretend? base.options[:pretend] end # Sets the absolute destination value from a relative destination value. # It also stores the given and relative destination. Let's suppose our # script is being executed on "dest", it sets the destination root to # "dest". The destination, given_destination and relative_destination # are related in the following way: # # inside "bar" do # empty_directory "baz" # end # # destination #=> dest/bar/baz # relative_destination #=> bar/baz # given_destination #=> baz # def destination=(destination) return unless destination @given_destination = convert_encoded_instructions(destination.to_s) @destination = ::File.expand_path(@given_destination, base.destination_root) @relative_destination = base.relative_to_original_destination_root(@destination) end # Filenames in the encoded form are converted. If you have a file: # # %file_name%.rb # # It calls #file_name from the base and replaces %-string with the # return value (should be String) of #file_name: # # user.rb # # The method referenced can be either public or private. # def convert_encoded_instructions(filename) filename.gsub(/%(.*?)%/) do |initial_string| method = $1.strip base.respond_to?(method, true) ? base.send(method) : initial_string end end # Receives a hash of options and just execute the block if some # conditions are met. # def invoke_with_conflict_check(&block) if exists? on_conflict_behavior(&block) else yield unless pretend? say_status :create, :green end destination rescue Errno::EISDIR, Errno::EEXIST on_file_clash_behavior end def on_file_clash_behavior say_status :file_clash, :red end # What to do when the destination file already exists. # def on_conflict_behavior say_status :exist, :blue end # Shortcut to say_status shell method. # def say_status(status, color) base.shell.say_status status, relative_destination, color if config[:verbose] end end end end thor-1.0.1/lib/thor/actions/file_manipulation.rb000066400000000000000000000313361357617015300217110ustar00rootroot00000000000000require "erb" class Thor module Actions # Copies the file from the relative source to the relative destination. If # the destination is not given it's assumed to be equal to the source. # # ==== Parameters # source:: the relative path to the source root. # destination:: the relative path to the destination root. # config:: give :verbose => false to not log the status, and # :mode => :preserve, to preserve the file mode from the source. # # ==== Examples # # copy_file "README", "doc/README" # # copy_file "doc/README" # def copy_file(source, *args, &block) config = args.last.is_a?(Hash) ? args.pop : {} destination = args.first || source source = File.expand_path(find_in_source_paths(source.to_s)) resulting_destination = create_file destination, nil, config do content = File.binread(source) content = yield(content) if block content end if config[:mode] == :preserve mode = File.stat(source).mode chmod(resulting_destination, mode, config) end end # Links the file from the relative source to the relative destination. If # the destination is not given it's assumed to be equal to the source. # # ==== Parameters # source:: the relative path to the source root. # destination:: the relative path to the destination root. # config:: give :verbose => false to not log the status. # # ==== Examples # # link_file "README", "doc/README" # # link_file "doc/README" # def link_file(source, *args) config = args.last.is_a?(Hash) ? args.pop : {} destination = args.first || source source = File.expand_path(find_in_source_paths(source.to_s)) create_link destination, source, config end # Gets the content at the given address and places it at the given relative # destination. If a block is given instead of destination, the content of # the url is yielded and used as location. # # +get+ relies on open-uri, so passing application user input would provide # a command injection attack vector. # # ==== Parameters # source:: the address of the given content. # destination:: the relative path to the destination root. # config:: give :verbose => false to not log the status. # # ==== Examples # # get "http://gist.github.com/103208", "doc/README" # # get "http://gist.github.com/103208" do |content| # content.split("\n").first # end # def get(source, *args, &block) config = args.last.is_a?(Hash) ? args.pop : {} destination = args.first render = if source =~ %r{^https?\://} require "open-uri" URI.send(:open, source) { |input| input.binmode.read } else source = File.expand_path(find_in_source_paths(source.to_s)) open(source) { |input| input.binmode.read } end destination ||= if block_given? block.arity == 1 ? yield(render) : yield else File.basename(source) end create_file destination, render, config end # Gets an ERB template at the relative source, executes it and makes a copy # at the relative destination. If the destination is not given it's assumed # to be equal to the source removing .tt from the filename. # # ==== Parameters # source:: the relative path to the source root. # destination:: the relative path to the destination root. # config:: give :verbose => false to not log the status. # # ==== Examples # # template "README", "doc/README" # # template "doc/README" # def template(source, *args, &block) config = args.last.is_a?(Hash) ? args.pop : {} destination = args.first || source.sub(/#{TEMPLATE_EXTNAME}$/, "") source = File.expand_path(find_in_source_paths(source.to_s)) context = config.delete(:context) || instance_eval("binding") create_file destination, nil, config do match = ERB.version.match(/(\d+\.\d+\.\d+)/) capturable_erb = if match && match[1] >= "2.2.0" # Ruby 2.6+ CapturableERB.new(::File.binread(source), :trim_mode => "-", :eoutvar => "@output_buffer") else CapturableERB.new(::File.binread(source), nil, "-", "@output_buffer") end content = capturable_erb.tap do |erb| erb.filename = source end.result(context) content = yield(content) if block content end end # Changes the mode of the given file or directory. # # ==== Parameters # mode:: the file mode # path:: the name of the file to change mode # config:: give :verbose => false to not log the status. # # ==== Example # # chmod "script/server", 0755 # def chmod(path, mode, config = {}) return unless behavior == :invoke path = File.expand_path(path, destination_root) say_status :chmod, relative_to_original_destination_root(path), config.fetch(:verbose, true) unless options[:pretend] require "fileutils" FileUtils.chmod_R(mode, path) end end # Prepend text to a file. Since it depends on insert_into_file, it's reversible. # # ==== Parameters # path:: path of the file to be changed # data:: the data to prepend to the file, can be also given as a block. # config:: give :verbose => false to not log the status. # # ==== Example # # prepend_to_file 'config/environments/test.rb', 'config.gem "rspec"' # # prepend_to_file 'config/environments/test.rb' do # 'config.gem "rspec"' # end # def prepend_to_file(path, *args, &block) config = args.last.is_a?(Hash) ? args.pop : {} config[:after] = /\A/ insert_into_file(path, *(args << config), &block) end alias_method :prepend_file, :prepend_to_file # Append text to a file. Since it depends on insert_into_file, it's reversible. # # ==== Parameters # path:: path of the file to be changed # data:: the data to append to the file, can be also given as a block. # config:: give :verbose => false to not log the status. # # ==== Example # # append_to_file 'config/environments/test.rb', 'config.gem "rspec"' # # append_to_file 'config/environments/test.rb' do # 'config.gem "rspec"' # end # def append_to_file(path, *args, &block) config = args.last.is_a?(Hash) ? args.pop : {} config[:before] = /\z/ insert_into_file(path, *(args << config), &block) end alias_method :append_file, :append_to_file # Injects text right after the class definition. Since it depends on # insert_into_file, it's reversible. # # ==== Parameters # path:: path of the file to be changed # klass:: the class to be manipulated # data:: the data to append to the class, can be also given as a block. # config:: give :verbose => false to not log the status. # # ==== Examples # # inject_into_class "app/controllers/application_controller.rb", ApplicationController, " filter_parameter :password\n" # # inject_into_class "app/controllers/application_controller.rb", ApplicationController do # " filter_parameter :password\n" # end # def inject_into_class(path, klass, *args, &block) config = args.last.is_a?(Hash) ? args.pop : {} config[:after] = /class #{klass}\n|class #{klass} .*\n/ insert_into_file(path, *(args << config), &block) end # Injects text right after the module definition. Since it depends on # insert_into_file, it's reversible. # # ==== Parameters # path:: path of the file to be changed # module_name:: the module to be manipulated # data:: the data to append to the class, can be also given as a block. # config:: give :verbose => false to not log the status. # # ==== Examples # # inject_into_module "app/helpers/application_helper.rb", ApplicationHelper, " def help; 'help'; end\n" # # inject_into_module "app/helpers/application_helper.rb", ApplicationHelper do # " def help; 'help'; end\n" # end # def inject_into_module(path, module_name, *args, &block) config = args.last.is_a?(Hash) ? args.pop : {} config[:after] = /module #{module_name}\n|module #{module_name} .*\n/ insert_into_file(path, *(args << config), &block) end # Run a regular expression replacement on a file. # # ==== Parameters # path:: path of the file to be changed # flag:: the regexp or string to be replaced # replacement:: the replacement, can be also given as a block # config:: give :verbose => false to not log the status. # # ==== Example # # gsub_file 'app/controllers/application_controller.rb', /#\s*(filter_parameter_logging :password)/, '\1' # # gsub_file 'README', /rake/, :green do |match| # match << " no more. Use thor!" # end # def gsub_file(path, flag, *args, &block) return unless behavior == :invoke config = args.last.is_a?(Hash) ? args.pop : {} path = File.expand_path(path, destination_root) say_status :gsub, relative_to_original_destination_root(path), config.fetch(:verbose, true) unless options[:pretend] content = File.binread(path) content.gsub!(flag, *args, &block) File.open(path, "wb") { |file| file.write(content) } end end # Uncomment all lines matching a given regex. It will leave the space # which existed before the comment hash in tact but will remove any spacing # between the comment hash and the beginning of the line. # # ==== Parameters # path:: path of the file to be changed # flag:: the regexp or string used to decide which lines to uncomment # config:: give :verbose => false to not log the status. # # ==== Example # # uncomment_lines 'config/initializers/session_store.rb', /active_record/ # def uncomment_lines(path, flag, *args) flag = flag.respond_to?(:source) ? flag.source : flag gsub_file(path, /^(\s*)#[[:blank:]]*(.*#{flag})/, '\1\2', *args) end # Comment all lines matching a given regex. It will leave the space # which existed before the beginning of the line in tact and will insert # a single space after the comment hash. # # ==== Parameters # path:: path of the file to be changed # flag:: the regexp or string used to decide which lines to comment # config:: give :verbose => false to not log the status. # # ==== Example # # comment_lines 'config/initializers/session_store.rb', /cookie_store/ # def comment_lines(path, flag, *args) flag = flag.respond_to?(:source) ? flag.source : flag gsub_file(path, /^(\s*)([^#\n]*#{flag})/, '\1# \2', *args) end # Removes a file at the given location. # # ==== Parameters # path:: path of the file to be changed # config:: give :verbose => false to not log the status. # # ==== Example # # remove_file 'README' # remove_file 'app/controllers/application_controller.rb' # def remove_file(path, config = {}) return unless behavior == :invoke path = File.expand_path(path, destination_root) say_status :remove, relative_to_original_destination_root(path), config.fetch(:verbose, true) if !options[:pretend] && File.exist?(path) require "fileutils" ::FileUtils.rm_rf(path) end end alias_method :remove_dir, :remove_file attr_accessor :output_buffer private :output_buffer, :output_buffer= private def concat(string) @output_buffer.concat(string) end def capture(*args) with_output_buffer { yield(*args) } end def with_output_buffer(buf = "".dup) #:nodoc: raise ArgumentError, "Buffer can not be a frozen object" if buf.frozen? old_buffer = output_buffer self.output_buffer = buf yield output_buffer ensure self.output_buffer = old_buffer end # Thor::Actions#capture depends on what kind of buffer is used in ERB. # Thus CapturableERB fixes ERB to use String buffer. class CapturableERB < ERB def set_eoutvar(compiler, eoutvar = "_erbout") compiler.put_cmd = "#{eoutvar}.concat" compiler.insert_cmd = "#{eoutvar}.concat" compiler.pre_cmd = ["#{eoutvar} = ''.dup"] compiler.post_cmd = [eoutvar] end end end end thor-1.0.1/lib/thor/actions/inject_into_file.rb000066400000000000000000000067571357617015300215270ustar00rootroot00000000000000require_relative "empty_directory" class Thor module Actions # Injects the given content into a file. Different from gsub_file, this # method is reversible. # # ==== Parameters # destination:: Relative path to the destination root # data:: Data to add to the file. Can be given as a block. # config:: give :verbose => false to not log the status and the flag # for injection (:after or :before) or :force => true for # insert two or more times the same content. # # ==== Examples # # insert_into_file "config/environment.rb", "config.gem :thor", :after => "Rails::Initializer.run do |config|\n" # # insert_into_file "config/environment.rb", :after => "Rails::Initializer.run do |config|\n" do # gems = ask "Which gems would you like to add?" # gems.split(" ").map{ |gem| " config.gem :#{gem}" }.join("\n") # end # WARNINGS = { unchanged_no_flag: 'File unchanged! The supplied flag value not found!' } def insert_into_file(destination, *args, &block) data = block_given? ? block : args.shift config = args.shift || {} config[:after] = /\z/ unless config.key?(:before) || config.key?(:after) action InjectIntoFile.new(self, destination, data, config) end alias_method :inject_into_file, :insert_into_file class InjectIntoFile < EmptyDirectory #:nodoc: attr_reader :replacement, :flag, :behavior def initialize(base, destination, data, config) super(base, destination, {:verbose => true}.merge(config)) @behavior, @flag = if @config.key?(:after) [:after, @config.delete(:after)] else [:before, @config.delete(:before)] end @replacement = data.is_a?(Proc) ? data.call : data @flag = Regexp.escape(@flag) unless @flag.is_a?(Regexp) end def invoke! content = if @behavior == :after '\0' + replacement else replacement + '\0' end if exists? if replace!(/#{flag}/, content, config[:force]) say_status(:invoke) else say_status(:unchanged, warning: WARNINGS[:unchanged_no_flag], color: :red) end else unless pretend? raise Thor::Error, "The file #{ destination } does not appear to exist" end end end def revoke! say_status :revoke regexp = if @behavior == :after content = '\1\2' /(#{flag})(.*)(#{Regexp.escape(replacement)})/m else content = '\2\3' /(#{Regexp.escape(replacement)})(.*)(#{flag})/m end replace!(regexp, content, true) end protected def say_status(behavior, warning: nil, color: nil) status = if behavior == :invoke if flag == /\A/ :prepend elsif flag == /\z/ :append else :insert end elsif warning warning else :subtract end super(status, (color || config[:verbose])) end # Adds the content to the file. # def replace!(regexp, string, force) return if pretend? content = File.read(destination) if force || !content.include?(replacement) success = content.gsub!(regexp, string) File.open(destination, "wb") { |file| file.write(content) } success end end end end end thor-1.0.1/lib/thor/base.rb000066400000000000000000000575051357617015300154720ustar00rootroot00000000000000require_relative "command" require_relative "core_ext/hash_with_indifferent_access" require_relative "error" require_relative "invocation" require_relative "nested_context" require_relative "parser" require_relative "shell" require_relative "line_editor" require_relative "util" class Thor autoload :Actions, File.expand_path("actions", __dir__) autoload :RakeCompat, File.expand_path("rake_compat", __dir__) autoload :Group, File.expand_path("group", __dir__) # Shortcuts for help. HELP_MAPPINGS = %w(-h -? --help -D) # Thor methods that should not be overwritten by the user. THOR_RESERVED_WORDS = %w(invoke shell options behavior root destination_root relative_root action add_file create_file in_root inside run run_ruby_script) TEMPLATE_EXTNAME = ".tt" class << self def deprecation_warning(message) #:nodoc: unless ENV['THOR_SILENCE_DEPRECATION'] warn "Deprecation warning: #{message}\n" + 'You can silence deprecations warning by setting the environment variable THOR_SILENCE_DEPRECATION.' end end end module Base attr_accessor :options, :parent_options, :args # It receives arguments in an Array and two hashes, one for options and # other for configuration. # # Notice that it does not check if all required arguments were supplied. # It should be done by the parser. # # ==== Parameters # args:: An array of objects. The objects are applied to their # respective accessors declared with argument. # # options:: An options hash that will be available as self.options. # The hash given is converted to a hash with indifferent # access, magic predicates (options.skip?) and then frozen. # # config:: Configuration for this Thor class. # def initialize(args = [], local_options = {}, config = {}) parse_options = self.class.class_options # The start method splits inbound arguments at the first argument # that looks like an option (starts with - or --). It then calls # new, passing in the two halves of the arguments Array as the # first two parameters. command_options = config.delete(:command_options) # hook for start parse_options = parse_options.merge(command_options) if command_options if local_options.is_a?(Array) array_options = local_options hash_options = {} else # Handle the case where the class was explicitly instantiated # with pre-parsed options. array_options = [] hash_options = local_options end # Let Thor::Options parse the options first, so it can remove # declared options from the array. This will leave us with # a list of arguments that weren't declared. stop_on_unknown = self.class.stop_on_unknown_option? config[:current_command] disable_required_check = self.class.disable_required_check? config[:current_command] opts = Thor::Options.new(parse_options, hash_options, stop_on_unknown, disable_required_check) self.options = opts.parse(array_options) self.options = config[:class_options].merge(options) if config[:class_options] # If unknown options are disallowed, make sure that none of the # remaining arguments looks like an option. opts.check_unknown! if self.class.check_unknown_options?(config) # Add the remaining arguments from the options parser to the # arguments passed in to initialize. Then remove any positional # arguments declared using #argument (this is primarily used # by Thor::Group). Tis will leave us with the remaining # positional arguments. to_parse = args to_parse += opts.remaining unless self.class.strict_args_position?(config) thor_args = Thor::Arguments.new(self.class.arguments) thor_args.parse(to_parse).each { |k, v| __send__("#{k}=", v) } @args = thor_args.remaining end class << self def included(base) #:nodoc: super(base) base.extend ClassMethods base.send :include, Invocation base.send :include, Shell end # Returns the classes that inherits from Thor or Thor::Group. # # ==== Returns # Array[Class] # def subclasses @subclasses ||= [] end # Returns the files where the subclasses are kept. # # ==== Returns # Hash[path => Class] # def subclass_files @subclass_files ||= Hash.new { |h, k| h[k] = [] } end # Whenever a class inherits from Thor or Thor::Group, we should track the # class and the file on Thor::Base. This is the method responsible for it. # def register_klass_file(klass) #:nodoc: file = caller[1].match(/(.*):\d+/)[1] Thor::Base.subclasses << klass unless Thor::Base.subclasses.include?(klass) file_subclasses = Thor::Base.subclass_files[File.expand_path(file)] file_subclasses << klass unless file_subclasses.include?(klass) end end module ClassMethods def attr_reader(*) #:nodoc: no_commands { super } end def attr_writer(*) #:nodoc: no_commands { super } end def attr_accessor(*) #:nodoc: no_commands { super } end # If you want to raise an error for unknown options, call check_unknown_options! # This is disabled by default to allow dynamic invocations. def check_unknown_options! @check_unknown_options = true end def check_unknown_options #:nodoc: @check_unknown_options ||= from_superclass(:check_unknown_options, false) end def check_unknown_options?(config) #:nodoc: !!check_unknown_options end # If you want to raise an error when the default value of an option does not match # the type call check_default_type! # This will be the default; for compatibility a deprecation warning is issued if necessary. def check_default_type! @check_default_type = true end # If you want to use defaults that don't match the type of an option, # either specify `check_default_type: false` or call `allow_incompatible_default_type!` def allow_incompatible_default_type! @check_default_type = false end def check_default_type #:nodoc: @check_default_type = from_superclass(:check_default_type, nil) unless defined?(@check_default_type) @check_default_type end # If true, option parsing is suspended as soon as an unknown option or a # regular argument is encountered. All remaining arguments are passed to # the command as regular arguments. def stop_on_unknown_option?(command_name) #:nodoc: false end # If true, option set will not suspend the execution of the command when # a required option is not provided. def disable_required_check?(command_name) #:nodoc: false end # If you want only strict string args (useful when cascading thor classes), # call strict_args_position! This is disabled by default to allow dynamic # invocations. def strict_args_position! @strict_args_position = true end def strict_args_position #:nodoc: @strict_args_position ||= from_superclass(:strict_args_position, false) end def strict_args_position?(config) #:nodoc: !!strict_args_position end # Adds an argument to the class and creates an attr_accessor for it. # # Arguments are different from options in several aspects. The first one # is how they are parsed from the command line, arguments are retrieved # from position: # # thor command NAME # # Instead of: # # thor command --name=NAME # # Besides, arguments are used inside your code as an accessor (self.argument), # while options are all kept in a hash (self.options). # # Finally, arguments cannot have type :default or :boolean but can be # optional (supplying :optional => :true or :required => false), although # you cannot have a required argument after a non-required argument. If you # try it, an error is raised. # # ==== Parameters # name:: The name of the argument. # options:: Described below. # # ==== Options # :desc - Description for the argument. # :required - If the argument is required or not. # :optional - If the argument is optional or not. # :type - The type of the argument, can be :string, :hash, :array, :numeric. # :default - Default value for this argument. It cannot be required and have default values. # :banner - String to show on usage notes. # # ==== Errors # ArgumentError:: Raised if you supply a required argument after a non required one. # def argument(name, options = {}) is_thor_reserved_word?(name, :argument) no_commands { attr_accessor name } required = if options.key?(:optional) !options[:optional] elsif options.key?(:required) options[:required] else options[:default].nil? end remove_argument name if required arguments.each do |argument| next if argument.required? raise ArgumentError, "You cannot have #{name.to_s.inspect} as required argument after " \ "the non-required argument #{argument.human_name.inspect}." end end options[:required] = required arguments << Thor::Argument.new(name, options) end # Returns this class arguments, looking up in the ancestors chain. # # ==== Returns # Array[Thor::Argument] # def arguments @arguments ||= from_superclass(:arguments, []) end # Adds a bunch of options to the set of class options. # # class_options :foo => false, :bar => :required, :baz => :string # # If you prefer more detailed declaration, check class_option. # # ==== Parameters # Hash[Symbol => Object] # def class_options(options = nil) @class_options ||= from_superclass(:class_options, {}) build_options(options, @class_options) if options @class_options end # Adds an option to the set of class options # # ==== Parameters # name:: The name of the argument. # options:: Described below. # # ==== Options # :desc:: -- Description for the argument. # :required:: -- If the argument is required or not. # :default:: -- Default value for this argument. # :group:: -- The group for this options. Use by class options to output options in different levels. # :aliases:: -- Aliases for this option. Note: Thor follows a convention of one-dash-one-letter options. Thus aliases like "-something" wouldn't be parsed; use either "\--something" or "-s" instead. # :type:: -- The type of the argument, can be :string, :hash, :array, :numeric or :boolean. # :banner:: -- String to show on usage notes. # :hide:: -- If you want to hide this option from the help. # def class_option(name, options = {}) build_option(name, options, class_options) end # Removes a previous defined argument. If :undefine is given, undefine # accessors as well. # # ==== Parameters # names:: Arguments to be removed # # ==== Examples # # remove_argument :foo # remove_argument :foo, :bar, :baz, :undefine => true # def remove_argument(*names) options = names.last.is_a?(Hash) ? names.pop : {} names.each do |name| arguments.delete_if { |a| a.name == name.to_s } undef_method name, "#{name}=" if options[:undefine] end end # Removes a previous defined class option. # # ==== Parameters # names:: Class options to be removed # # ==== Examples # # remove_class_option :foo # remove_class_option :foo, :bar, :baz # def remove_class_option(*names) names.each do |name| class_options.delete(name) end end # Defines the group. This is used when thor list is invoked so you can specify # that only commands from a pre-defined group will be shown. Defaults to standard. # # ==== Parameters # name # def group(name = nil) if name @group = name.to_s else @group ||= from_superclass(:group, "standard") end end # Returns the commands for this Thor class. # # ==== Returns # Hash:: An ordered hash with commands names as keys and Thor::Command # objects as values. # def commands @commands ||= Hash.new end alias_method :tasks, :commands # Returns the commands for this Thor class and all subclasses. # # ==== Returns # Hash:: An ordered hash with commands names as keys and Thor::Command # objects as values. # def all_commands @all_commands ||= from_superclass(:all_commands, Hash.new) @all_commands.merge!(commands) end alias_method :all_tasks, :all_commands # Removes a given command from this Thor class. This is usually done if you # are inheriting from another class and don't want it to be available # anymore. # # By default it only remove the mapping to the command. But you can supply # :undefine => true to undefine the method from the class as well. # # ==== Parameters # name:: The name of the command to be removed # options:: You can give :undefine => true if you want commands the method # to be undefined from the class as well. # def remove_command(*names) options = names.last.is_a?(Hash) ? names.pop : {} names.each do |name| commands.delete(name.to_s) all_commands.delete(name.to_s) undef_method name if options[:undefine] end end alias_method :remove_task, :remove_command # All methods defined inside the given block are not added as commands. # # So you can do: # # class MyScript < Thor # no_commands do # def this_is_not_a_command # end # end # end # # You can also add the method and remove it from the command list: # # class MyScript < Thor # def this_is_not_a_command # end # remove_command :this_is_not_a_command # end # def no_commands(&block) no_commands_context.enter(&block) end alias_method :no_tasks, :no_commands def no_commands_context @no_commands_context ||= NestedContext.new end def no_commands? no_commands_context.entered? end # Sets the namespace for the Thor or Thor::Group class. By default the # namespace is retrieved from the class name. If your Thor class is named # Scripts::MyScript, the help method, for example, will be called as: # # thor scripts:my_script -h # # If you change the namespace: # # namespace :my_scripts # # You change how your commands are invoked: # # thor my_scripts -h # # Finally, if you change your namespace to default: # # namespace :default # # Your commands can be invoked with a shortcut. Instead of: # # thor :my_command # def namespace(name = nil) if name @namespace = name.to_s else @namespace ||= Thor::Util.namespace_from_thor_class(self) end end # Parses the command and options from the given args, instantiate the class # and invoke the command. This method is used when the arguments must be parsed # from an array. If you are inside Ruby and want to use a Thor class, you # can simply initialize it: # # script = MyScript.new(args, options, config) # script.invoke(:command, first_arg, second_arg, third_arg) # def start(given_args = ARGV, config = {}) config[:shell] ||= Thor::Base.shell.new dispatch(nil, given_args.dup, nil, config) rescue Thor::Error => e config[:debug] || ENV["THOR_DEBUG"] == "1" ? (raise e) : config[:shell].error(e.message) exit(false) if exit_on_failure? rescue Errno::EPIPE # This happens if a thor command is piped to something like `head`, # which closes the pipe when it's done reading. This will also # mean that if the pipe is closed, further unnecessary # computation will not occur. exit(true) end # Allows to use private methods from parent in child classes as commands. # # ==== Parameters # names:: Method names to be used as commands # # ==== Examples # # public_command :foo # public_command :foo, :bar, :baz # def public_command(*names) names.each do |name| class_eval "def #{name}(*); super end" end end alias_method :public_task, :public_command def handle_no_command_error(command, has_namespace = $thor_runner) #:nodoc: raise UndefinedCommandError.new(command, all_commands.keys, (namespace if has_namespace)) end alias_method :handle_no_task_error, :handle_no_command_error def handle_argument_error(command, error, args, arity) #:nodoc: name = [command.ancestor_name, command.name].compact.join(" ") msg = "ERROR: \"#{basename} #{name}\" was called with ".dup msg << "no arguments" if args.empty? msg << "arguments " << args.inspect unless args.empty? msg << "\nUsage: \"#{banner(command).split("\n").join("\"\n \"")}\"" raise InvocationError, msg end # A flag that makes the process exit with status 1 if any error happens. def exit_on_failure? Thor.deprecation_warning "Thor exit with status 0 on errors. To keep this behavior, you must define `exit_on_failure?` in `#{self.name}`" false end protected # Prints the class options per group. If an option does not belong to # any group, it's printed as Class option. # def class_options_help(shell, groups = {}) #:nodoc: # Group options by group class_options.each do |_, value| groups[value.group] ||= [] groups[value.group] << value end # Deal with default group global_options = groups.delete(nil) || [] print_options(shell, global_options) # Print all others groups.each do |group_name, options| print_options(shell, options, group_name) end end # Receives a set of options and print them. def print_options(shell, options, group_name = nil) return if options.empty? list = [] padding = options.map { |o| o.aliases.size }.max.to_i * 4 options.each do |option| next if option.hide item = [option.usage(padding)] item.push(option.description ? "# #{option.description}" : "") list << item list << ["", "# Default: #{option.default}"] if option.show_default? list << ["", "# Possible values: #{option.enum.join(', ')}"] if option.enum end shell.say(group_name ? "#{group_name} options:" : "Options:") shell.print_table(list, :indent => 2) shell.say "" end # Raises an error if the word given is a Thor reserved word. def is_thor_reserved_word?(word, type) #:nodoc: return false unless THOR_RESERVED_WORDS.include?(word.to_s) raise "#{word.inspect} is a Thor reserved word and cannot be defined as #{type}" end # Build an option and adds it to the given scope. # # ==== Parameters # name:: The name of the argument. # options:: Described in both class_option and method_option. # scope:: Options hash that is being built up def build_option(name, options, scope) #:nodoc: scope[name] = Thor::Option.new(name, {:check_default_type => check_default_type}.merge!(options)) end # Receives a hash of options, parse them and add to the scope. This is a # fast way to set a bunch of options: # # build_options :foo => true, :bar => :required, :baz => :string # # ==== Parameters # Hash[Symbol => Object] def build_options(options, scope) #:nodoc: options.each do |key, value| scope[key] = Thor::Option.parse(key, value) end end # Finds a command with the given name. If the command belongs to the current # class, just return it, otherwise dup it and add the fresh copy to the # current command hash. def find_and_refresh_command(name) #:nodoc: if commands[name.to_s] commands[name.to_s] elsif command = all_commands[name.to_s] # rubocop:disable AssignmentInCondition commands[name.to_s] = command.clone else raise ArgumentError, "You supplied :for => #{name.inspect}, but the command #{name.inspect} could not be found." end end alias_method :find_and_refresh_task, :find_and_refresh_command # Everytime someone inherits from a Thor class, register the klass # and file into baseclass. def inherited(klass) super(klass) Thor::Base.register_klass_file(klass) klass.instance_variable_set(:@no_commands, 0) end # Fire this callback whenever a method is added. Added methods are # tracked as commands by invoking the create_command method. def method_added(meth) super(meth) meth = meth.to_s if meth == "initialize" initialize_added return end # Return if it's not a public instance method return unless public_method_defined?(meth.to_sym) return if no_commands? || !create_command(meth) is_thor_reserved_word?(meth, :command) Thor::Base.register_klass_file(self) end # Retrieves a value from superclass. If it reaches the baseclass, # returns default. def from_superclass(method, default = nil) if self == baseclass || !superclass.respond_to?(method, true) default else value = superclass.send(method) # Ruby implements `dup` on Object, but raises a `TypeError` # if the method is called on immediates. As a result, we # don't have a good way to check whether dup will succeed # without calling it and rescuing the TypeError. begin value.dup rescue TypeError value end end end # # The basename of the program invoking the thor class. # def basename File.basename($PROGRAM_NAME).split(" ").first end # SIGNATURE: Sets the baseclass. This is where the superclass lookup # finishes. def baseclass #:nodoc: end # SIGNATURE: Creates a new command if valid_command? is true. This method is # called when a new method is added to the class. def create_command(meth) #:nodoc: end alias_method :create_task, :create_command # SIGNATURE: Defines behavior when the initialize method is added to the # class. def initialize_added #:nodoc: end # SIGNATURE: The hook invoked by start. def dispatch(command, given_args, given_opts, config) #:nodoc: raise NotImplementedError end end end end thor-1.0.1/lib/thor/command.rb000066400000000000000000000111731357617015300161650ustar00rootroot00000000000000class Thor class Command < Struct.new(:name, :description, :long_description, :usage, :options, :ancestor_name) FILE_REGEXP = /^#{Regexp.escape(File.dirname(__FILE__))}/ def initialize(name, description, long_description, usage, options = nil) super(name.to_s, description, long_description, usage, options || {}) end def initialize_copy(other) #:nodoc: super(other) self.options = other.options.dup if other.options end def hidden? false end # By default, a command invokes a method in the thor class. You can change this # implementation to create custom commands. def run(instance, args = []) arity = nil if private_method?(instance) instance.class.handle_no_command_error(name) elsif public_method?(instance) arity = instance.method(name).arity instance.__send__(name, *args) elsif local_method?(instance, :method_missing) instance.__send__(:method_missing, name.to_sym, *args) else instance.class.handle_no_command_error(name) end rescue ArgumentError => e handle_argument_error?(instance, e, caller) ? instance.class.handle_argument_error(self, e, args, arity) : (raise e) rescue NoMethodError => e handle_no_method_error?(instance, e, caller) ? instance.class.handle_no_command_error(name) : (raise e) end # Returns the formatted usage by injecting given required arguments # and required options into the given usage. def formatted_usage(klass, namespace = true, subcommand = false) if ancestor_name formatted = "#{ancestor_name} ".dup # add space elsif namespace namespace = klass.namespace formatted = "#{namespace.gsub(/^(default)/, '')}:".dup end formatted ||= "#{klass.namespace.split(':').last} ".dup if subcommand formatted ||= "".dup Array(usage).map do |specific_usage| formatted_specific_usage = formatted formatted_specific_usage += required_arguments_for(klass, specific_usage) # Add required options formatted_specific_usage += " #{required_options}" # Strip and go! formatted_specific_usage.strip end.join("\n") end protected # Add usage with required arguments def required_arguments_for(klass, usage) if klass && !klass.arguments.empty? usage.to_s.gsub(/^#{name}/) do |match| match << " " << klass.arguments.map(&:usage).compact.join(" ") end else usage.to_s end end def not_debugging?(instance) !(instance.class.respond_to?(:debugging) && instance.class.debugging) end def required_options @required_options ||= options.map { |_, o| o.usage if o.required? }.compact.sort.join(" ") end # Given a target, checks if this class name is a public method. def public_method?(instance) #:nodoc: !(instance.public_methods & [name.to_s, name.to_sym]).empty? end def private_method?(instance) !(instance.private_methods & [name.to_s, name.to_sym]).empty? end def local_method?(instance, name) methods = instance.public_methods(false) + instance.private_methods(false) + instance.protected_methods(false) !(methods & [name.to_s, name.to_sym]).empty? end def sans_backtrace(backtrace, caller) #:nodoc: saned = backtrace.reject { |frame| frame =~ FILE_REGEXP || (frame =~ /\.java:/ && RUBY_PLATFORM =~ /java/) || (frame =~ %r{^kernel/} && RUBY_ENGINE =~ /rbx/) } saned - caller end def handle_argument_error?(instance, error, caller) not_debugging?(instance) && (error.message =~ /wrong number of arguments/ || error.message =~ /given \d*, expected \d*/) && begin saned = sans_backtrace(error.backtrace, caller) saned.empty? || saned.size == 1 end end def handle_no_method_error?(instance, error, caller) not_debugging?(instance) && error.message =~ /^undefined method `#{name}' for #{Regexp.escape(instance.to_s)}$/ end end Task = Command # A command that is hidden in help messages but still invocable. class HiddenCommand < Command def hidden? true end end HiddenTask = HiddenCommand # A dynamic command that handles method missing scenarios. class DynamicCommand < Command def initialize(name, options = nil) super(name.to_s, "A dynamically-generated command", name.to_s, name.to_s, options) end def run(instance, args = []) if (instance.methods & [name.to_s, name.to_sym]).empty? super else instance.class.handle_no_command_error(name) end end end DynamicTask = DynamicCommand end thor-1.0.1/lib/thor/core_ext/000077500000000000000000000000001357617015300160275ustar00rootroot00000000000000thor-1.0.1/lib/thor/core_ext/hash_with_indifferent_access.rb000066400000000000000000000041551357617015300242350ustar00rootroot00000000000000class Thor module CoreExt #:nodoc: # A hash with indifferent access and magic predicates. # # hash = Thor::CoreExt::HashWithIndifferentAccess.new 'foo' => 'bar', 'baz' => 'bee', 'force' => true # # hash[:foo] #=> 'bar' # hash['foo'] #=> 'bar' # hash.foo? #=> true # class HashWithIndifferentAccess < ::Hash #:nodoc: def initialize(hash = {}) super() hash.each do |key, value| self[convert_key(key)] = value end end def [](key) super(convert_key(key)) end def []=(key, value) super(convert_key(key), value) end def delete(key) super(convert_key(key)) end def fetch(key, *args) super(convert_key(key), *args) end def key?(key) super(convert_key(key)) end def values_at(*indices) indices.map { |key| self[convert_key(key)] } end def merge(other) dup.merge!(other) end def merge!(other) other.each do |key, value| self[convert_key(key)] = value end self end def reverse_merge(other) self.class.new(other).merge(self) end def reverse_merge!(other_hash) replace(reverse_merge(other_hash)) end def replace(other_hash) super(other_hash) end # Convert to a Hash with String keys. def to_hash Hash.new(default).merge!(self) end protected def convert_key(key) key.is_a?(Symbol) ? key.to_s : key end # Magic predicates. For instance: # # options.force? # => !!options['force'] # options.shebang # => "/usr/lib/local/ruby" # options.test_framework?(:rspec) # => options[:test_framework] == :rspec # def method_missing(method, *args) method = method.to_s if method =~ /^(\w+)\?$/ if args.empty? !!self[$1] else self[$1] == args.first end else self[method] end end end end end thor-1.0.1/lib/thor/error.rb000066400000000000000000000062221357617015300156770ustar00rootroot00000000000000class Thor Correctable = if defined?(DidYouMean::SpellChecker) && defined?(DidYouMean::Correctable) # In order to support versions of Ruby that don't have keyword # arguments, we need our own spell checker class that doesn't take key # words. Even though this code wouldn't be hit because of the check # above, it's still necessary because the interpreter would otherwise be # unable to parse the file. class NoKwargSpellChecker < DidYouMean::SpellChecker # :nodoc: def initialize(dictionary) @dictionary = dictionary end end DidYouMean::Correctable end # Thor::Error is raised when it's caused by wrong usage of thor classes. Those # errors have their backtrace suppressed and are nicely shown to the user. # # Errors that are caused by the developer, like declaring a method which # overwrites a thor keyword, SHOULD NOT raise a Thor::Error. This way, we # ensure that developer errors are shown with full backtrace. class Error < StandardError end # Raised when a command was not found. class UndefinedCommandError < Error class SpellChecker attr_reader :error def initialize(error) @error = error end def corrections @corrections ||= spell_checker.correct(error.command).map(&:inspect) end def spell_checker NoKwargSpellChecker.new(error.all_commands) end end attr_reader :command, :all_commands def initialize(command, all_commands, namespace) @command = command @all_commands = all_commands message = "Could not find command #{command.inspect}" message = namespace ? "#{message} in #{namespace.inspect} namespace." : "#{message}." super(message) end prepend Correctable if Correctable end UndefinedTaskError = UndefinedCommandError class AmbiguousCommandError < Error end AmbiguousTaskError = AmbiguousCommandError # Raised when a command was found, but not invoked properly. class InvocationError < Error end class UnknownArgumentError < Error class SpellChecker attr_reader :error def initialize(error) @error = error end def corrections @corrections ||= error.unknown.flat_map { |unknown| spell_checker.correct(unknown) }.uniq.map(&:inspect) end def spell_checker @spell_checker ||= NoKwargSpellChecker.new(error.switches) end end attr_reader :switches, :unknown def initialize(switches, unknown) @switches = switches @unknown = unknown super("Unknown switches #{unknown.map(&:inspect).join(', ')}") end prepend Correctable if Correctable end class RequiredArgumentMissingError < InvocationError end class MalformattedArgumentError < InvocationError end if Correctable DidYouMean::SPELL_CHECKERS.merge!( 'Thor::UndefinedCommandError' => UndefinedCommandError::SpellChecker, 'Thor::UnknownArgumentError' => UnknownArgumentError::SpellChecker ) end end thor-1.0.1/lib/thor/group.rb000066400000000000000000000213421357617015300157020ustar00rootroot00000000000000require_relative "base" # Thor has a special class called Thor::Group. The main difference to Thor class # is that it invokes all commands at once. It also include some methods that allows # invocations to be done at the class method, which are not available to Thor # commands. class Thor::Group class << self # The description for this Thor::Group. If none is provided, but a source root # exists, tries to find the USAGE one folder above it, otherwise searches # in the superclass. # # ==== Parameters # description:: The description for this Thor::Group. # def desc(description = nil) if description @desc = description else @desc ||= from_superclass(:desc, nil) end end # Prints help information. # # ==== Options # short:: When true, shows only usage. # def help(shell) shell.say "Usage:" shell.say " #{banner}\n" shell.say class_options_help(shell) shell.say desc if desc end # Stores invocations for this class merging with superclass values. # def invocations #:nodoc: @invocations ||= from_superclass(:invocations, {}) end # Stores invocation blocks used on invoke_from_option. # def invocation_blocks #:nodoc: @invocation_blocks ||= from_superclass(:invocation_blocks, {}) end # Invoke the given namespace or class given. It adds an instance # method that will invoke the klass and command. You can give a block to # configure how it will be invoked. # # The namespace/class given will have its options showed on the help # usage. Check invoke_from_option for more information. # def invoke(*names, &block) options = names.last.is_a?(Hash) ? names.pop : {} verbose = options.fetch(:verbose, true) names.each do |name| invocations[name] = false invocation_blocks[name] = block if block_given? class_eval <<-METHOD, __FILE__, __LINE__ + 1 def _invoke_#{name.to_s.gsub(/\W/, '_')} klass, command = self.class.prepare_for_invocation(nil, #{name.inspect}) if klass say_status :invoke, #{name.inspect}, #{verbose.inspect} block = self.class.invocation_blocks[#{name.inspect}] _invoke_for_class_method klass, command, &block else say_status :error, %(#{name.inspect} [not found]), :red end end METHOD end end # Invoke a thor class based on the value supplied by the user to the # given option named "name". A class option must be created before this # method is invoked for each name given. # # ==== Examples # # class GemGenerator < Thor::Group # class_option :test_framework, :type => :string # invoke_from_option :test_framework # end # # ==== Boolean options # # In some cases, you want to invoke a thor class if some option is true or # false. This is automatically handled by invoke_from_option. Then the # option name is used to invoke the generator. # # ==== Preparing for invocation # # In some cases you want to customize how a specified hook is going to be # invoked. You can do that by overwriting the class method # prepare_for_invocation. The class method must necessarily return a klass # and an optional command. # # ==== Custom invocations # # You can also supply a block to customize how the option is going to be # invoked. The block receives two parameters, an instance of the current # class and the klass to be invoked. # def invoke_from_option(*names, &block) options = names.last.is_a?(Hash) ? names.pop : {} verbose = options.fetch(:verbose, :white) names.each do |name| unless class_options.key?(name) raise ArgumentError, "You have to define the option #{name.inspect} " \ "before setting invoke_from_option." end invocations[name] = true invocation_blocks[name] = block if block_given? class_eval <<-METHOD, __FILE__, __LINE__ + 1 def _invoke_from_option_#{name.to_s.gsub(/\W/, '_')} return unless options[#{name.inspect}] value = options[#{name.inspect}] value = #{name.inspect} if TrueClass === value klass, command = self.class.prepare_for_invocation(#{name.inspect}, value) if klass say_status :invoke, value, #{verbose.inspect} block = self.class.invocation_blocks[#{name.inspect}] _invoke_for_class_method klass, command, &block else say_status :error, %(\#{value} [not found]), :red end end METHOD end end # Remove a previously added invocation. # # ==== Examples # # remove_invocation :test_framework # def remove_invocation(*names) names.each do |name| remove_command(name) remove_class_option(name) invocations.delete(name) invocation_blocks.delete(name) end end # Overwrite class options help to allow invoked generators options to be # shown recursively when invoking a generator. # def class_options_help(shell, groups = {}) #:nodoc: get_options_from_invocations(groups, class_options) do |klass| klass.send(:get_options_from_invocations, groups, class_options) end super(shell, groups) end # Get invocations array and merge options from invocations. Those # options are added to group_options hash. Options that already exists # in base_options are not added twice. # def get_options_from_invocations(group_options, base_options) #:nodoc: # rubocop:disable MethodLength invocations.each do |name, from_option| value = if from_option option = class_options[name] option.type == :boolean ? name : option.default else name end next unless value klass, _ = prepare_for_invocation(name, value) next unless klass && klass.respond_to?(:class_options) value = value.to_s human_name = value.respond_to?(:classify) ? value.classify : value group_options[human_name] ||= [] group_options[human_name] += klass.class_options.values.select do |class_option| base_options[class_option.name.to_sym].nil? && class_option.group.nil? && !group_options.values.flatten.any? { |i| i.name == class_option.name } end yield klass if block_given? end end # Returns commands ready to be printed. def printable_commands(*) item = [] item << banner item << (desc ? "# #{desc.gsub(/\s+/m, ' ')}" : "") [item] end alias_method :printable_tasks, :printable_commands def handle_argument_error(command, error, _args, arity) #:nodoc: msg = "#{basename} #{command.name} takes #{arity} argument".dup msg << "s" if arity > 1 msg << ", but it should not." raise error, msg end protected # The method responsible for dispatching given the args. def dispatch(command, given_args, given_opts, config) #:nodoc: if Thor::HELP_MAPPINGS.include?(given_args.first) help(config[:shell]) return end args, opts = Thor::Options.split(given_args) opts = given_opts || opts instance = new(args, opts, config) yield instance if block_given? if command instance.invoke_command(all_commands[command]) else instance.invoke_all end end # The banner for this class. You can customize it if you are invoking the # thor class by another ways which is not the Thor::Runner. def banner "#{basename} #{self_command.formatted_usage(self, false)}" end # Represents the whole class as a command. def self_command #:nodoc: Thor::DynamicCommand.new(namespace, class_options) end alias_method :self_task, :self_command def baseclass #:nodoc: Thor::Group end def create_command(meth) #:nodoc: commands[meth.to_s] = Thor::Command.new(meth, nil, nil, nil, nil) true end alias_method :create_task, :create_command end include Thor::Base protected # Shortcut to invoke with padding and block handling. Use internally by # invoke and invoke_from_option class methods. def _invoke_for_class_method(klass, command = nil, *args, &block) #:nodoc: with_padding do if block case block.arity when 3 yield(self, klass, command) when 2 yield(self, klass) when 1 instance_exec(klass, &block) end else invoke klass, command, *args end end end end thor-1.0.1/lib/thor/invocation.rb000066400000000000000000000137131357617015300167220ustar00rootroot00000000000000class Thor module Invocation def self.included(base) #:nodoc: super(base) base.extend ClassMethods end module ClassMethods # This method is responsible for receiving a name and find the proper # class and command for it. The key is an optional parameter which is # available only in class methods invocations (i.e. in Thor::Group). def prepare_for_invocation(key, name) #:nodoc: case name when Symbol, String Thor::Util.find_class_and_command_by_namespace(name.to_s, !key) else name end end end # Make initializer aware of invocations and the initialization args. def initialize(args = [], options = {}, config = {}, &block) #:nodoc: @_invocations = config[:invocations] || Hash.new { |h, k| h[k] = [] } @_initializer = [args, options, config] super end # Make the current command chain accessible with in a Thor-(sub)command def current_command_chain @_invocations.values.flatten.map(&:to_sym) end # Receives a name and invokes it. The name can be a string (either "command" or # "namespace:command"), a Thor::Command, a Class or a Thor instance. If the # command cannot be guessed by name, it can also be supplied as second argument. # # You can also supply the arguments, options and configuration values for # the command to be invoked, if none is given, the same values used to # initialize the invoker are used to initialize the invoked. # # When no name is given, it will invoke the default command of the current class. # # ==== Examples # # class A < Thor # def foo # invoke :bar # invoke "b:hello", ["Erik"] # end # # def bar # invoke "b:hello", ["Erik"] # end # end # # class B < Thor # def hello(name) # puts "hello #{name}" # end # end # # You can notice that the method "foo" above invokes two commands: "bar", # which belongs to the same class and "hello" which belongs to the class B. # # By using an invocation system you ensure that a command is invoked only once. # In the example above, invoking "foo" will invoke "b:hello" just once, even # if it's invoked later by "bar" method. # # When class A invokes class B, all arguments used on A initialization are # supplied to B. This allows lazy parse of options. Let's suppose you have # some rspec commands: # # class Rspec < Thor::Group # class_option :mock_framework, :type => :string, :default => :rr # # def invoke_mock_framework # invoke "rspec:#{options[:mock_framework]}" # end # end # # As you noticed, it invokes the given mock framework, which might have its # own options: # # class Rspec::RR < Thor::Group # class_option :style, :type => :string, :default => :mock # end # # Since it's not rspec concern to parse mock framework options, when RR # is invoked all options are parsed again, so RR can extract only the options # that it's going to use. # # If you want Rspec::RR to be initialized with its own set of options, you # have to do that explicitly: # # invoke "rspec:rr", [], :style => :foo # # Besides giving an instance, you can also give a class to invoke: # # invoke Rspec::RR, [], :style => :foo # def invoke(name = nil, *args) if name.nil? warn "[Thor] Calling invoke() without argument is deprecated. Please use invoke_all instead.\n#{caller.join("\n")}" return invoke_all end args.unshift(nil) if args.first.is_a?(Array) || args.first.nil? command, args, opts, config = args klass, command = _retrieve_class_and_command(name, command) raise "Missing Thor class for invoke #{name}" unless klass raise "Expected Thor class, got #{klass}" unless klass <= Thor::Base args, opts, config = _parse_initialization_options(args, opts, config) klass.send(:dispatch, command, args, opts, config) do |instance| instance.parent_options = options end end # Invoke the given command if the given args. def invoke_command(command, *args) #:nodoc: current = @_invocations[self.class] unless current.include?(command.name) current << command.name command.run(self, *args) end end alias_method :invoke_task, :invoke_command # Invoke all commands for the current instance. def invoke_all #:nodoc: self.class.all_commands.map { |_, command| invoke_command(command) } end # Invokes using shell padding. def invoke_with_padding(*args) with_padding { invoke(*args) } end protected # Configuration values that are shared between invocations. def _shared_configuration #:nodoc: {:invocations => @_invocations} end # This method simply retrieves the class and command to be invoked. # If the name is nil or the given name is a command in the current class, # use the given name and return self as class. Otherwise, call # prepare_for_invocation in the current class. def _retrieve_class_and_command(name, sent_command = nil) #:nodoc: if name.nil? [self.class, nil] elsif self.class.all_commands[name.to_s] [self.class, name.to_s] else klass, command = self.class.prepare_for_invocation(nil, name) [klass, command || sent_command] end end alias_method :_retrieve_class_and_task, :_retrieve_class_and_command # Initialize klass using values stored in the @_initializer. def _parse_initialization_options(args, opts, config) #:nodoc: stored_args, stored_opts, stored_config = @_initializer args ||= stored_args.dup opts ||= stored_opts.dup config ||= {} config = stored_config.merge(_shared_configuration).merge!(config) [args, opts, config] end end end thor-1.0.1/lib/thor/line_editor.rb000066400000000000000000000005541357617015300170450ustar00rootroot00000000000000require_relative "line_editor/basic" require_relative "line_editor/readline" class Thor module LineEditor def self.readline(prompt, options = {}) best_available.new(prompt, options).readline end def self.best_available [ Thor::LineEditor::Readline, Thor::LineEditor::Basic ].detect(&:available?) end end end thor-1.0.1/lib/thor/line_editor/000077500000000000000000000000001357617015300165145ustar00rootroot00000000000000thor-1.0.1/lib/thor/line_editor/basic.rb000066400000000000000000000011641357617015300201240ustar00rootroot00000000000000class Thor module LineEditor class Basic attr_reader :prompt, :options def self.available? true end def initialize(prompt, options) @prompt = prompt @options = options end def readline $stdout.print(prompt) get_input end private def get_input if echo? $stdin.gets else # Lazy-load io/console since it is gem-ified as of 2.3 require "io/console" $stdin.noecho(&:gets) end end def echo? options.fetch(:echo, true) end end end end thor-1.0.1/lib/thor/line_editor/readline.rb000066400000000000000000000034561357617015300206340ustar00rootroot00000000000000class Thor module LineEditor class Readline < Basic def self.available? begin require "readline" rescue LoadError end Object.const_defined?(:Readline) end def readline if echo? ::Readline.completion_append_character = nil # rb-readline does not allow Readline.completion_proc= to receive nil. if complete = completion_proc ::Readline.completion_proc = complete end ::Readline.readline(prompt, add_to_history?) else super end end private def add_to_history? options.fetch(:add_to_history, true) end def completion_proc if use_path_completion? proc { |text| PathCompletion.new(text).matches } elsif completion_options.any? proc do |text| completion_options.select { |option| option.start_with?(text) } end end end def completion_options options.fetch(:limited_to, []) end def use_path_completion? options.fetch(:path, false) end class PathCompletion attr_reader :text private :text def initialize(text) @text = text end def matches relative_matches end private def relative_matches absolute_matches.map { |path| path.sub(base_path, "") } end def absolute_matches Dir[glob_pattern].map do |path| if File.directory?(path) "#{path}/" else path end end end def glob_pattern "#{base_path}#{text}*" end def base_path "#{Dir.pwd}/" end end end end end thor-1.0.1/lib/thor/nested_context.rb000066400000000000000000000004421357617015300175720ustar00rootroot00000000000000class Thor class NestedContext def initialize @depth = 0 end def enter push yield ensure pop end def entered? @depth > 0 end private def push @depth += 1 end def pop @depth -= 1 end end end thor-1.0.1/lib/thor/parser.rb000066400000000000000000000002121357617015300160330ustar00rootroot00000000000000require_relative "parser/argument" require_relative "parser/arguments" require_relative "parser/option" require_relative "parser/options" thor-1.0.1/lib/thor/parser/000077500000000000000000000000001357617015300155135ustar00rootroot00000000000000thor-1.0.1/lib/thor/parser/argument.rb000066400000000000000000000033731357617015300176700ustar00rootroot00000000000000class Thor class Argument #:nodoc: VALID_TYPES = [:numeric, :hash, :array, :string] attr_reader :name, :description, :enum, :required, :type, :default, :banner alias_method :human_name, :name def initialize(name, options = {}) class_name = self.class.name.split("::").last type = options[:type] raise ArgumentError, "#{class_name} name can't be nil." if name.nil? raise ArgumentError, "Type :#{type} is not valid for #{class_name.downcase}s." if type && !valid_type?(type) @name = name.to_s @description = options[:desc] @required = options.key?(:required) ? options[:required] : true @type = (type || :string).to_sym @default = options[:default] @banner = options[:banner] || default_banner @enum = options[:enum] validate! # Trigger specific validations end def usage required? ? banner : "[#{banner}]" end def required? required end def show_default? case default when Array, String, Hash !default.empty? else default end end protected def validate! raise ArgumentError, "An argument cannot be required and have default value." if required? && !default.nil? raise ArgumentError, "An argument cannot have an enum other than an array." if @enum && !@enum.is_a?(Array) end def valid_type?(type) self.class::VALID_TYPES.include?(type.to_sym) end def default_banner case type when :boolean nil when :string, :default human_name.upcase when :numeric "N" when :hash "key:value" when :array "one two three" end end end end thor-1.0.1/lib/thor/parser/arguments.rb000066400000000000000000000106431357617015300200510ustar00rootroot00000000000000class Thor class Arguments #:nodoc: # rubocop:disable ClassLength NUMERIC = /[-+]?(\d*\.\d+|\d+)/ # Receives an array of args and returns two arrays, one with arguments # and one with switches. # def self.split(args) arguments = [] args.each do |item| break if item.is_a?(String) && item =~ /^-/ arguments << item end [arguments, args[Range.new(arguments.size, -1)]] end def self.parse(*args) to_parse = args.pop new(*args).parse(to_parse) end # Takes an array of Thor::Argument objects. # def initialize(arguments = []) @assigns = {} @non_assigned_required = [] @switches = arguments arguments.each do |argument| if !argument.default.nil? @assigns[argument.human_name] = argument.default elsif argument.required? @non_assigned_required << argument end end end def parse(args) @pile = args.dup @switches.each do |argument| break unless peek @non_assigned_required.delete(argument) @assigns[argument.human_name] = send(:"parse_#{argument.type}", argument.human_name) end check_requirement! @assigns end def remaining @pile end private def no_or_skip?(arg) arg =~ /^--(no|skip)-([-\w]+)$/ $2 end def last? @pile.empty? end def peek @pile.first end def shift @pile.shift end def unshift(arg) if arg.is_a?(Array) @pile = arg + @pile else @pile.unshift(arg) end end def current_is_value? peek && peek.to_s !~ /^-{1,2}\S+/ end # Runs through the argument array getting strings that contains ":" and # mark it as a hash: # # [ "name:string", "age:integer" ] # # Becomes: # # { "name" => "string", "age" => "integer" } # def parse_hash(name) return shift if peek.is_a?(Hash) hash = {} while current_is_value? && peek.include?(":") key, value = shift.split(":", 2) raise MalformattedArgumentError, "You can't specify '#{key}' more than once in option '#{name}'; got #{key}:#{hash[key]} and #{key}:#{value}" if hash.include? key hash[key] = value end hash end # Runs through the argument array getting all strings until no string is # found or a switch is found. # # ["a", "b", "c"] # # And returns it as an array: # # ["a", "b", "c"] # def parse_array(name) return shift if peek.is_a?(Array) array = [] array << shift while current_is_value? array end # Check if the peek is numeric format and return a Float or Integer. # Check if the peek is included in enum if enum is provided. # Otherwise raises an error. # def parse_numeric(name) return shift if peek.is_a?(Numeric) unless peek =~ NUMERIC && $& == peek raise MalformattedArgumentError, "Expected numeric value for '#{name}'; got #{peek.inspect}" end value = $&.index(".") ? shift.to_f : shift.to_i if @switches.is_a?(Hash) && switch = @switches[name] if switch.enum && !switch.enum.include?(value) raise MalformattedArgumentError, "Expected '#{name}' to be one of #{switch.enum.join(', ')}; got #{value}" end end value end # Parse string: # for --string-arg, just return the current value in the pile # for --no-string-arg, nil # Check if the peek is included in enum if enum is provided. Otherwise raises an error. # def parse_string(name) if no_or_skip?(name) nil else value = shift if @switches.is_a?(Hash) && switch = @switches[name] if switch.enum && !switch.enum.include?(value) raise MalformattedArgumentError, "Expected '#{name}' to be one of #{switch.enum.join(', ')}; got #{value}" end end value end end # Raises an error if @non_assigned_required array is not empty. # def check_requirement! return if @non_assigned_required.empty? names = @non_assigned_required.map do |o| o.respond_to?(:switch_name) ? o.switch_name : o.human_name end.join("', '") class_name = self.class.name.split("::").last.downcase raise RequiredArgumentMissingError, "No value provided for required #{class_name} '#{names}'" end end end thor-1.0.1/lib/thor/parser/option.rb000066400000000000000000000106111357617015300173470ustar00rootroot00000000000000class Thor class Option < Argument #:nodoc: attr_reader :aliases, :group, :lazy_default, :hide, :repeatable VALID_TYPES = [:boolean, :numeric, :hash, :array, :string] def initialize(name, options = {}) @check_default_type = options[:check_default_type] options[:required] = false unless options.key?(:required) @repeatable = options.fetch(:repeatable, false) super @lazy_default = options[:lazy_default] @group = options[:group].to_s.capitalize if options[:group] @aliases = Array(options[:aliases]) @hide = options[:hide] end # This parse quick options given as method_options. It makes several # assumptions, but you can be more specific using the option method. # # parse :foo => "bar" # #=> Option foo with default value bar # # parse [:foo, :baz] => "bar" # #=> Option foo with default value bar and alias :baz # # parse :foo => :required # #=> Required option foo without default value # # parse :foo => 2 # #=> Option foo with default value 2 and type numeric # # parse :foo => :numeric # #=> Option foo without default value and type numeric # # parse :foo => true # #=> Option foo with default value true and type boolean # # The valid types are :boolean, :numeric, :hash, :array and :string. If none # is given a default type is assumed. This default type accepts arguments as # string (--foo=value) or booleans (just --foo). # # By default all options are optional, unless :required is given. # def self.parse(key, value) if key.is_a?(Array) name, *aliases = key else name = key aliases = [] end name = name.to_s default = value type = case value when Symbol default = nil if VALID_TYPES.include?(value) value elsif required = (value == :required) # rubocop:disable AssignmentInCondition :string end when TrueClass, FalseClass :boolean when Numeric :numeric when Hash, Array, String value.class.name.downcase.to_sym end new(name.to_s, :required => required, :type => type, :default => default, :aliases => aliases) end def switch_name @switch_name ||= dasherized? ? name : dasherize(name) end def human_name @human_name ||= dasherized? ? undasherize(name) : name end def usage(padding = 0) sample = if banner && !banner.to_s.empty? "#{switch_name}=#{banner}".dup else switch_name end sample = "[#{sample}]".dup unless required? if boolean? sample << ", [#{dasherize('no-' + human_name)}]" unless (name == "force") || name.start_with?("no-") end if aliases.empty? (" " * padding) << sample else "#{aliases.join(', ')}, #{sample}" end end VALID_TYPES.each do |type| class_eval <<-RUBY, __FILE__, __LINE__ + 1 def #{type}? self.type == #{type.inspect} end RUBY end protected def validate! raise ArgumentError, "An option cannot be boolean and required." if boolean? && required? validate_default_type! end def validate_default_type! default_type = case @default when nil return when TrueClass, FalseClass required? ? :string : :boolean when Numeric :numeric when Symbol :string when Hash, Array, String @default.class.name.downcase.to_sym end expected_type = (@repeatable && @type != :hash) ? :array : @type if default_type != expected_type err = "Expected #{expected_type} default value for '#{switch_name}'; got #{@default.inspect} (#{default_type})" if @check_default_type raise ArgumentError, err elsif @check_default_type == nil Thor.deprecation_warning "#{err}.\n" + 'This will be rejected in the future unless you explicitly pass the options `check_default_type: false`' + ' or call `allow_incompatible_default_type!` in your code' end end end def dasherized? name.index("-") == 0 end def undasherize(str) str.sub(/^-{1,2}/, "") end def dasherize(str) (str.length > 1 ? "--" : "-") + str.tr("_", "-") end end end thor-1.0.1/lib/thor/parser/options.rb000066400000000000000000000150151357617015300175350ustar00rootroot00000000000000class Thor class Options < Arguments #:nodoc: # rubocop:disable ClassLength LONG_RE = /^(--\w+(?:-\w+)*)$/ SHORT_RE = /^(-[a-z])$/i EQ_RE = /^(--\w+(?:-\w+)*|-[a-z])=(.*)$/i SHORT_SQ_RE = /^-([a-z]{2,})$/i # Allow either -x -v or -xv style for single char args SHORT_NUM = /^(-[a-z])#{NUMERIC}$/i OPTS_END = "--".freeze # Receives a hash and makes it switches. def self.to_switches(options) options.map do |key, value| case value when true "--#{key}" when Array "--#{key} #{value.map(&:inspect).join(' ')}" when Hash "--#{key} #{value.map { |k, v| "#{k}:#{v}" }.join(' ')}" when nil, false nil else "--#{key} #{value.inspect}" end end.compact.join(" ") end # Takes a hash of Thor::Option and a hash with defaults. # # If +stop_on_unknown+ is true, #parse will stop as soon as it encounters # an unknown option or a regular argument. def initialize(hash_options = {}, defaults = {}, stop_on_unknown = false, disable_required_check = false) @stop_on_unknown = stop_on_unknown @disable_required_check = disable_required_check options = hash_options.values super(options) # Add defaults defaults.each do |key, value| @assigns[key.to_s] = value @non_assigned_required.delete(hash_options[key]) end @shorts = {} @switches = {} @extra = [] @stopped_parsing_after_extra_index = nil options.each do |option| @switches[option.switch_name] = option option.aliases.each do |short| name = short.to_s.sub(/^(?!\-)/, "-") @shorts[name] ||= option.switch_name end end end def remaining @extra end def peek return super unless @parsing_options result = super if result == OPTS_END shift @parsing_options = false @stopped_parsing_after_extra_index ||= @extra.size super else result end end def parse(args) # rubocop:disable MethodLength @pile = args.dup @parsing_options = true while peek if parsing_options? match, is_switch = current_is_switch? shifted = shift if is_switch case shifted when SHORT_SQ_RE unshift($1.split("").map { |f| "-#{f}" }) next when EQ_RE, SHORT_NUM unshift($2) switch = $1 when LONG_RE, SHORT_RE switch = $1 end switch = normalize_switch(switch) option = switch_option(switch) result = parse_peek(switch, option) assign_result!(option, result) elsif @stop_on_unknown @parsing_options = false @extra << shifted @stopped_parsing_after_extra_index ||= @extra.size @extra << shift while peek break elsif match @extra << shifted @extra << shift while peek && peek !~ /^-/ else @extra << shifted end else @extra << shift end end check_requirement! unless @disable_required_check assigns = Thor::CoreExt::HashWithIndifferentAccess.new(@assigns) assigns.freeze assigns end def check_unknown! to_check = @stopped_parsing_after_extra_index ? @extra[0...@stopped_parsing_after_extra_index] : @extra # an unknown option starts with - or -- and has no more --'s afterward. unknown = to_check.select { |str| str =~ /^--?(?:(?!--).)*$/ } raise UnknownArgumentError.new(@switches.keys, unknown) unless unknown.empty? end protected def assign_result!(option, result) if option.repeatable && option.type == :hash (@assigns[option.human_name] ||= {}).merge!(result) elsif option.repeatable (@assigns[option.human_name] ||= []) << result else @assigns[option.human_name] = result end end # Check if the current value in peek is a registered switch. # # Two booleans are returned. The first is true if the current value # starts with a hyphen; the second is true if it is a registered switch. def current_is_switch? case peek when LONG_RE, SHORT_RE, EQ_RE, SHORT_NUM [true, switch?($1)] when SHORT_SQ_RE [true, $1.split("").any? { |f| switch?("-#{f}") }] else [false, false] end end def current_is_switch_formatted? case peek when LONG_RE, SHORT_RE, EQ_RE, SHORT_NUM, SHORT_SQ_RE true else false end end def current_is_value? peek && (!parsing_options? || super) end def switch?(arg) !switch_option(normalize_switch(arg)).nil? end def switch_option(arg) if match = no_or_skip?(arg) # rubocop:disable AssignmentInCondition @switches[arg] || @switches["--#{match}"] else @switches[arg] end end # Check if the given argument is actually a shortcut. # def normalize_switch(arg) (@shorts[arg] || arg).tr("_", "-") end def parsing_options? peek @parsing_options end # Parse boolean values which can be given as --foo=true, --foo or --no-foo. # def parse_boolean(switch) if current_is_value? if ["true", "TRUE", "t", "T", true].include?(peek) shift true elsif ["false", "FALSE", "f", "F", false].include?(peek) shift false else @switches.key?(switch) || !no_or_skip?(switch) end else @switches.key?(switch) || !no_or_skip?(switch) end end # Parse the value at the peek analyzing if it requires an input or not. # def parse_peek(switch, option) if parsing_options? && (current_is_switch_formatted? || last?) if option.boolean? # No problem for boolean types elsif no_or_skip?(switch) return nil # User set value to nil elsif option.string? && !option.required? # Return the default if there is one, else the human name return option.lazy_default || option.default || option.human_name elsif option.lazy_default return option.lazy_default else raise MalformattedArgumentError, "No value provided for option '#{switch}'" end end @non_assigned_required.delete(option) send(:"parse_#{option.type}", switch) end end end thor-1.0.1/lib/thor/rake_compat.rb000066400000000000000000000040041357617015300170270ustar00rootroot00000000000000require "rake" require "rake/dsl_definition" class Thor # Adds a compatibility layer to your Thor classes which allows you to use # rake package tasks. For example, to use rspec rake tasks, one can do: # # require 'thor/rake_compat' # require 'rspec/core/rake_task' # # class Default < Thor # include Thor::RakeCompat # # RSpec::Core::RakeTask.new(:spec) do |t| # t.spec_opts = ['--options', './.rspec'] # t.spec_files = FileList['spec/**/*_spec.rb'] # end # end # module RakeCompat include Rake::DSL if defined?(Rake::DSL) def self.rake_classes @rake_classes ||= [] end def self.included(base) super(base) # Hack. Make rakefile point to invoker, so rdoc task is generated properly. rakefile = File.basename(caller[0].match(/(.*):\d+/)[1]) Rake.application.instance_variable_set(:@rakefile, rakefile) rake_classes << base end end end # override task on (main), for compatibility with Rake 0.9 instance_eval do alias rake_namespace namespace def task(*) task = super if klass = Thor::RakeCompat.rake_classes.last # rubocop:disable AssignmentInCondition non_namespaced_name = task.name.split(":").last description = non_namespaced_name description << task.arg_names.map { |n| n.to_s.upcase }.join(" ") description.strip! klass.desc description, Rake.application.last_description || non_namespaced_name Rake.application.last_description = nil klass.send :define_method, non_namespaced_name do |*args| Rake::Task[task.name.to_sym].invoke(*args) end end task end def namespace(name) if klass = Thor::RakeCompat.rake_classes.last # rubocop:disable AssignmentInCondition const_name = Thor::Util.camel_case(name.to_s).to_sym klass.const_set(const_name, Class.new(Thor)) new_klass = klass.const_get(const_name) Thor::RakeCompat.rake_classes << new_klass end super Thor::RakeCompat.rake_classes.pop end end thor-1.0.1/lib/thor/runner.rb000066400000000000000000000231131357617015300160550ustar00rootroot00000000000000require_relative "../thor" require_relative "group" require "yaml" require "digest/md5" require "pathname" class Thor::Runner < Thor #:nodoc: # rubocop:disable ClassLength autoload :OpenURI, "open-uri" map "-T" => :list, "-i" => :install, "-u" => :update, "-v" => :version def self.banner(command, all = false, subcommand = false) "thor " + command.formatted_usage(self, all, subcommand) end def self.exit_on_failure? true end # Override Thor#help so it can give information about any class and any method. # def help(meth = nil) if meth && !respond_to?(meth) initialize_thorfiles(meth) klass, command = Thor::Util.find_class_and_command_by_namespace(meth) self.class.handle_no_command_error(command, false) if klass.nil? klass.start(["-h", command].compact, :shell => shell) else super end end # If a command is not found on Thor::Runner, method missing is invoked and # Thor::Runner is then responsible for finding the command in all classes. # def method_missing(meth, *args) meth = meth.to_s initialize_thorfiles(meth) klass, command = Thor::Util.find_class_and_command_by_namespace(meth) self.class.handle_no_command_error(command, false) if klass.nil? args.unshift(command) if command klass.start(args, :shell => shell) end desc "install NAME", "Install an optionally named Thor file into your system commands" method_options :as => :string, :relative => :boolean, :force => :boolean def install(name) # rubocop:disable MethodLength initialize_thorfiles # If a directory name is provided as the argument, look for a 'main.thor' # command in said directory. begin if File.directory?(File.expand_path(name)) base = File.join(name, "main.thor") package = :directory contents = open(base, &:read) else base = name package = :file contents = open(name, &:read) end rescue OpenURI::HTTPError raise Error, "Error opening URI '#{name}'" rescue Errno::ENOENT raise Error, "Error opening file '#{name}'" end say "Your Thorfile contains:" say contents unless options["force"] return false if no?("Do you wish to continue [y/N]?") end as = options["as"] || begin first_line = contents.split("\n")[0] (match = first_line.match(/\s*#\s*module:\s*([^\n]*)/)) ? match[1].strip : nil end unless as basename = File.basename(name) as = ask("Please specify a name for #{name} in the system repository [#{basename}]:") as = basename if as.empty? end location = if options[:relative] || name =~ %r{^https?://} name else File.expand_path(name) end thor_yaml[as] = { :filename => Digest::MD5.hexdigest(name + as), :location => location, :namespaces => Thor::Util.namespaces_in_content(contents, base) } save_yaml(thor_yaml) say "Storing thor file in your system repository" destination = File.join(thor_root, thor_yaml[as][:filename]) if package == :file File.open(destination, "w") { |f| f.puts contents } else require "fileutils" FileUtils.cp_r(name, destination) end thor_yaml[as][:filename] # Indicate success end desc "version", "Show Thor version" def version require_relative "version" say "Thor #{Thor::VERSION}" end desc "uninstall NAME", "Uninstall a named Thor module" def uninstall(name) raise Error, "Can't find module '#{name}'" unless thor_yaml[name] say "Uninstalling #{name}." require "fileutils" FileUtils.rm_rf(File.join(thor_root, (thor_yaml[name][:filename]).to_s)) thor_yaml.delete(name) save_yaml(thor_yaml) puts "Done." end desc "update NAME", "Update a Thor file from its original location" def update(name) raise Error, "Can't find module '#{name}'" if !thor_yaml[name] || !thor_yaml[name][:location] say "Updating '#{name}' from #{thor_yaml[name][:location]}" old_filename = thor_yaml[name][:filename] self.options = options.merge("as" => name) if File.directory? File.expand_path(name) require "fileutils" FileUtils.rm_rf(File.join(thor_root, old_filename)) thor_yaml.delete(old_filename) save_yaml(thor_yaml) filename = install(name) else filename = install(thor_yaml[name][:location]) end File.delete(File.join(thor_root, old_filename)) unless filename == old_filename end desc "installed", "List the installed Thor modules and commands" method_options :internal => :boolean def installed initialize_thorfiles(nil, true) display_klasses(true, options["internal"]) end desc "list [SEARCH]", "List the available thor commands (--substring means .*SEARCH)" method_options :substring => :boolean, :group => :string, :all => :boolean, :debug => :boolean def list(search = "") initialize_thorfiles search = ".*#{search}" if options["substring"] search = /^#{search}.*/i group = options[:group] || "standard" klasses = Thor::Base.subclasses.select do |k| (options[:all] || k.group == group) && k.namespace =~ search end display_klasses(false, false, klasses) end private def thor_root Thor::Util.thor_root end def thor_yaml @thor_yaml ||= begin yaml_file = File.join(thor_root, "thor.yml") yaml = YAML.load_file(yaml_file) if File.exist?(yaml_file) yaml || {} end end # Save the yaml file. If none exists in thor root, creates one. # def save_yaml(yaml) yaml_file = File.join(thor_root, "thor.yml") unless File.exist?(yaml_file) require "fileutils" FileUtils.mkdir_p(thor_root) yaml_file = File.join(thor_root, "thor.yml") FileUtils.touch(yaml_file) end File.open(yaml_file, "w") { |f| f.puts yaml.to_yaml } end # Load the Thorfiles. If relevant_to is supplied, looks for specific files # in the thor_root instead of loading them all. # # By default, it also traverses the current path until find Thor files, as # described in thorfiles. This look up can be skipped by supplying # skip_lookup true. # def initialize_thorfiles(relevant_to = nil, skip_lookup = false) thorfiles(relevant_to, skip_lookup).each do |f| Thor::Util.load_thorfile(f, nil, options[:debug]) unless Thor::Base.subclass_files.keys.include?(File.expand_path(f)) end end # Finds Thorfiles by traversing from your current directory down to the root # directory of your system. If at any time we find a Thor file, we stop. # # We also ensure that system-wide Thorfiles are loaded first, so local # Thorfiles can override them. # # ==== Example # # If we start at /Users/wycats/dev/thor ... # # 1. /Users/wycats/dev/thor # 2. /Users/wycats/dev # 3. /Users/wycats <-- we find a Thorfile here, so we stop # # Suppose we start at c:\Documents and Settings\james\dev\thor ... # # 1. c:\Documents and Settings\james\dev\thor # 2. c:\Documents and Settings\james\dev # 3. c:\Documents and Settings\james # 4. c:\Documents and Settings # 5. c:\ <-- no Thorfiles found! # def thorfiles(relevant_to = nil, skip_lookup = false) thorfiles = [] unless skip_lookup Pathname.pwd.ascend do |path| thorfiles = Thor::Util.globs_for(path).map { |g| Dir[g] }.flatten break unless thorfiles.empty? end end files = (relevant_to ? thorfiles_relevant_to(relevant_to) : Thor::Util.thor_root_glob) files += thorfiles files -= ["#{thor_root}/thor.yml"] files.map! do |file| File.directory?(file) ? File.join(file, "main.thor") : file end end # Load Thorfiles relevant to the given method. If you provide "foo:bar" it # will load all thor files in the thor.yaml that has "foo" e "foo:bar" # namespaces registered. # def thorfiles_relevant_to(meth) lookup = [meth, meth.split(":")[0...-1].join(":")] files = thor_yaml.select do |_, v| v[:namespaces] && !(v[:namespaces] & lookup).empty? end files.map { |_, v| File.join(thor_root, (v[:filename]).to_s) } end # Display information about the given klasses. If with_module is given, # it shows a table with information extracted from the yaml file. # def display_klasses(with_modules = false, show_internal = false, klasses = Thor::Base.subclasses) klasses -= [Thor, Thor::Runner, Thor::Group] unless show_internal raise Error, "No Thor commands available" if klasses.empty? show_modules if with_modules && !thor_yaml.empty? list = Hash.new { |h, k| h[k] = [] } groups = klasses.select { |k| k.ancestors.include?(Thor::Group) } # Get classes which inherit from Thor (klasses - groups).each { |k| list[k.namespace.split(":").first] += k.printable_commands(false) } # Get classes which inherit from Thor::Base groups.map! { |k| k.printable_commands(false).first } list["root"] = groups # Order namespaces with default coming first list = list.sort { |a, b| a[0].sub(/^default/, "") <=> b[0].sub(/^default/, "") } list.each { |n, commands| display_commands(n, commands) unless commands.empty? } end def display_commands(namespace, list) #:nodoc: list.sort! { |a, b| a[0] <=> b[0] } say shell.set_color(namespace, :blue, true) say "-" * namespace.size print_table(list, :truncate => true) say end alias_method :display_tasks, :display_commands def show_modules #:nodoc: info = [] labels = %w(Modules Namespaces) info << labels info << ["-" * labels[0].size, "-" * labels[1].size] thor_yaml.each do |name, hash| info << [name, hash[:namespaces].join(", ")] end print_table info say "" end end thor-1.0.1/lib/thor/shell.rb000066400000000000000000000043131357617015300156540ustar00rootroot00000000000000require "rbconfig" class Thor module Base class << self attr_writer :shell # Returns the shell used in all Thor classes. If you are in a Unix platform # it will use a colored log, otherwise it will use a basic one without color. # def shell @shell ||= if ENV["THOR_SHELL"] && !ENV["THOR_SHELL"].empty? Thor::Shell.const_get(ENV["THOR_SHELL"]) elsif RbConfig::CONFIG["host_os"] =~ /mswin|mingw/ && !ENV["ANSICON"] Thor::Shell::Basic else Thor::Shell::Color end end end end module Shell SHELL_DELEGATED_METHODS = [:ask, :error, :set_color, :yes?, :no?, :say, :say_status, :print_in_columns, :print_table, :print_wrapped, :file_collision, :terminal_width] attr_writer :shell autoload :Basic, File.expand_path("shell/basic", __dir__) autoload :Color, File.expand_path("shell/color", __dir__) autoload :HTML, File.expand_path("shell/html", __dir__) # Add shell to initialize config values. # # ==== Configuration # shell:: An instance of the shell to be used. # # ==== Examples # # class MyScript < Thor # argument :first, :type => :numeric # end # # MyScript.new [1.0], { :foo => :bar }, :shell => Thor::Shell::Basic.new # def initialize(args = [], options = {}, config = {}) super self.shell = config[:shell] shell.base ||= self if shell.respond_to?(:base) end # Holds the shell for the given Thor instance. If no shell is given, # it gets a default shell from Thor::Base.shell. def shell @shell ||= Thor::Base.shell.new end # Common methods that are delegated to the shell. SHELL_DELEGATED_METHODS.each do |method| module_eval <<-METHOD, __FILE__, __LINE__ + 1 def #{method}(*args,&block) shell.#{method}(*args,&block) end METHOD end # Yields the given block with padding. def with_padding shell.padding += 1 yield ensure shell.padding -= 1 end protected # Allow shell to be shared between invocations. # def _shared_configuration #:nodoc: super.merge!(:shell => shell) end end end thor-1.0.1/lib/thor/shell/000077500000000000000000000000001357617015300153265ustar00rootroot00000000000000thor-1.0.1/lib/thor/shell/basic.rb000066400000000000000000000345151357617015300167440ustar00rootroot00000000000000class Thor module Shell class Basic DEFAULT_TERMINAL_WIDTH = 80 attr_accessor :base attr_reader :padding # Initialize base, mute and padding to nil. # def initialize #:nodoc: @base = nil @mute = false @padding = 0 @always_force = false end # Mute everything that's inside given block # def mute @mute = true yield ensure @mute = false end # Check if base is muted # def mute? @mute end # Sets the output padding, not allowing less than zero values. # def padding=(value) @padding = [0, value].max end # Sets the output padding while executing a block and resets it. # def indent(count = 1) orig_padding = padding self.padding = padding + count yield self.padding = orig_padding end # Asks something to the user and receives a response. # # If a default value is specified it will be presented to the user # and allows them to select that value with an empty response. This # option is ignored when limited answers are supplied. # # If asked to limit the correct responses, you can pass in an # array of acceptable answers. If one of those is not supplied, # they will be shown a message stating that one of those answers # must be given and re-asked the question. # # If asking for sensitive information, the :echo option can be set # to false to mask user input from $stdin. # # If the required input is a path, then set the path option to # true. This will enable tab completion for file paths relative # to the current working directory on systems that support # Readline. # # ==== Example # ask("What is your name?") # # ask("What is the planet furthest from the sun?", :default => "Pluto") # # ask("What is your favorite Neopolitan flavor?", :limited_to => ["strawberry", "chocolate", "vanilla"]) # # ask("What is your password?", :echo => false) # # ask("Where should the file be saved?", :path => true) # def ask(statement, *args) options = args.last.is_a?(Hash) ? args.pop : {} color = args.first if options[:limited_to] ask_filtered(statement, color, options) else ask_simply(statement, color, options) end end # Say (print) something to the user. If the sentence ends with a whitespace # or tab character, a new line is not appended (print + flush). Otherwise # are passed straight to puts (behavior got from Highline). # # ==== Example # say("I know you knew that.") # def say(message = "", color = nil, force_new_line = (message.to_s !~ /( |\t)\Z/)) buffer = prepare_message(message, *color) buffer << "\n" if force_new_line && !message.to_s.end_with?("\n") stdout.print(buffer) stdout.flush end # Say a status with the given color and appends the message. Since this # method is used frequently by actions, it allows nil or false to be given # in log_status, avoiding the message from being shown. If a Symbol is # given in log_status, it's used as the color. # def say_status(status, message, log_status = true) return if quiet? || log_status == false spaces = " " * (padding + 1) color = log_status.is_a?(Symbol) ? log_status : :green status = status.to_s.rjust(12) status = set_color status, color, true if color buffer = "#{status}#{spaces}#{message}" buffer = "#{buffer}\n" unless buffer.end_with?("\n") stdout.print(buffer) stdout.flush end # Make a question the to user and returns true if the user replies "y" or # "yes". # def yes?(statement, color = nil) !!(ask(statement, color, :add_to_history => false) =~ is?(:yes)) end # Make a question the to user and returns true if the user replies "n" or # "no". # def no?(statement, color = nil) !!(ask(statement, color, :add_to_history => false) =~ is?(:no)) end # Prints values in columns # # ==== Parameters # Array[String, String, ...] # def print_in_columns(array) return if array.empty? colwidth = (array.map { |el| el.to_s.size }.max || 0) + 2 array.each_with_index do |value, index| # Don't output trailing spaces when printing the last column if ((((index + 1) % (terminal_width / colwidth))).zero? && !index.zero?) || index + 1 == array.length stdout.puts value else stdout.printf("%-#{colwidth}s", value) end end end # Prints a table. # # ==== Parameters # Array[Array[String, String, ...]] # # ==== Options # indent:: Indent the first column by indent value. # colwidth:: Force the first column to colwidth spaces wide. # def print_table(array, options = {}) # rubocop:disable MethodLength return if array.empty? formats = [] indent = options[:indent].to_i colwidth = options[:colwidth] options[:truncate] = terminal_width if options[:truncate] == true formats << "%-#{colwidth + 2}s".dup if colwidth start = colwidth ? 1 : 0 colcount = array.max { |a, b| a.size <=> b.size }.size maximas = [] start.upto(colcount - 1) do |index| maxima = array.map { |row| row[index] ? row[index].to_s.size : 0 }.max maximas << maxima formats << if index == colcount - 1 # Don't output 2 trailing spaces when printing the last column "%-s".dup else "%-#{maxima + 2}s".dup end end formats[0] = formats[0].insert(0, " " * indent) formats << "%s" array.each do |row| sentence = "".dup row.each_with_index do |column, index| maxima = maximas[index] f = if column.is_a?(Numeric) if index == row.size - 1 # Don't output 2 trailing spaces when printing the last column "%#{maxima}s" else "%#{maxima}s " end else formats[index] end sentence << f % column.to_s end sentence = truncate(sentence, options[:truncate]) if options[:truncate] stdout.puts sentence end end # Prints a long string, word-wrapping the text to the current width of the # terminal display. Ideal for printing heredocs. # # ==== Parameters # String # # ==== Options # indent:: Indent each line of the printed paragraph by indent value. # def print_wrapped(message, options = {}) indent = options[:indent] || 0 width = terminal_width - indent paras = message.split("\n\n") paras.map! do |unwrapped| counter = 0 unwrapped.split(" ").inject do |memo, word| word = word.gsub(/\n\005/, "\n").gsub(/\005/, "\n") counter = 0 if word.include? "\n" if (counter + word.length + 1) < width memo = "#{memo} #{word}" counter += (word.length + 1) else memo = "#{memo}\n#{word}" counter = word.length end memo end end.compact! paras.each do |para| para.split("\n").each do |line| stdout.puts line.insert(0, " " * indent) end stdout.puts unless para == paras.last end end # Deals with file collision and returns true if the file should be # overwritten and false otherwise. If a block is given, it uses the block # response as the content for the diff. # # ==== Parameters # destination:: the destination file to solve conflicts # block:: an optional block that returns the value to be used in diff and merge # def file_collision(destination) return true if @always_force options = block_given? ? "[Ynaqdhm]" : "[Ynaqh]" loop do answer = ask( %[Overwrite #{destination}? (enter "h" for help) #{options}], :add_to_history => false ) case answer when nil say "" return true when is?(:yes), is?(:force), "" return true when is?(:no), is?(:skip) return false when is?(:always) return @always_force = true when is?(:quit) say "Aborting..." raise SystemExit when is?(:diff) show_diff(destination, yield) if block_given? say "Retrying..." when is?(:merge) if block_given? && !merge_tool.empty? merge(destination, yield) return nil end say "Please specify merge tool to `THOR_MERGE` env." else say file_collision_help end end end # This code was copied from Rake, available under MIT-LICENSE # Copyright (c) 2003, 2004 Jim Weirich def terminal_width result = if ENV["THOR_COLUMNS"] ENV["THOR_COLUMNS"].to_i else unix? ? dynamic_width : DEFAULT_TERMINAL_WIDTH end result < 10 ? DEFAULT_TERMINAL_WIDTH : result rescue DEFAULT_TERMINAL_WIDTH end # Called if something goes wrong during the execution. This is used by Thor # internally and should not be used inside your scripts. If something went # wrong, you can always raise an exception. If you raise a Thor::Error, it # will be rescued and wrapped in the method below. # def error(statement) stderr.puts statement end # Apply color to the given string with optional bold. Disabled in the # Thor::Shell::Basic class. # def set_color(string, *) #:nodoc: string end protected def prepare_message(message, *color) spaces = " " * padding spaces + set_color(message.to_s, *color) end def can_display_colors? false end def lookup_color(color) return color unless color.is_a?(Symbol) self.class.const_get(color.to_s.upcase) end def stdout $stdout end def stderr $stderr end def is?(value) #:nodoc: value = value.to_s if value.size == 1 /\A#{value}\z/i else /\A(#{value}|#{value[0, 1]})\z/i end end def file_collision_help #:nodoc: <<-HELP Y - yes, overwrite n - no, do not overwrite a - all, overwrite this and all others q - quit, abort d - diff, show the differences between the old and the new h - help, show this help m - merge, run merge tool HELP end def show_diff(destination, content) #:nodoc: diff_cmd = ENV["THOR_DIFF"] || ENV["RAILS_DIFF"] || "diff -u" require "tempfile" Tempfile.open(File.basename(destination), File.dirname(destination)) do |temp| temp.write content temp.rewind system %(#{diff_cmd} "#{destination}" "#{temp.path}") end end def quiet? #:nodoc: mute? || (base && base.options[:quiet]) end # Calculate the dynamic width of the terminal def dynamic_width @dynamic_width ||= (dynamic_width_stty.nonzero? || dynamic_width_tput) end def dynamic_width_stty `stty size 2>/dev/null`.split[1].to_i end def dynamic_width_tput `tput cols 2>/dev/null`.to_i end def unix? RUBY_PLATFORM =~ /(aix|darwin|linux|(net|free|open)bsd|cygwin|solaris|irix|hpux)/i end def truncate(string, width) as_unicode do chars = string.chars.to_a if chars.length <= width chars.join else chars[0, width - 3].join + "..." end end end if "".respond_to?(:encode) def as_unicode yield end else def as_unicode old = $KCODE $KCODE = "U" yield ensure $KCODE = old end end def ask_simply(statement, color, options) default = options[:default] message = [statement, ("(#{default})" if default), nil].uniq.join(" ") message = prepare_message(message, *color) result = Thor::LineEditor.readline(message, options) return unless result result = result.strip if default && result == "" default else result end end def ask_filtered(statement, color, options) answer_set = options[:limited_to] case_insensitive = options.fetch(:case_insensitive, false) correct_answer = nil until correct_answer answers = answer_set.join(", ") answer = ask_simply("#{statement} [#{answers}]", color, options) correct_answer = answer_match(answer_set, answer, case_insensitive) say("Your response must be one of: [#{answers}]. Please try again.") unless correct_answer end correct_answer end def answer_match(possibilities, answer, case_insensitive) if case_insensitive possibilities.detect{ |possibility| possibility.downcase == answer.downcase } else possibilities.detect{ |possibility| possibility == answer } end end def merge(destination, content) #:nodoc: require "tempfile" Tempfile.open([File.basename(destination), File.extname(destination)], File.dirname(destination)) do |temp| temp.write content temp.rewind system %(#{merge_tool} "#{temp.path}" "#{destination}") end end def merge_tool #:nodoc: @merge_tool ||= ENV["THOR_MERGE"] || git_merge_tool end def git_merge_tool #:nodoc: `git config merge.tool`.rstrip rescue "" end end end end thor-1.0.1/lib/thor/shell/color.rb000066400000000000000000000113331357617015300167720ustar00rootroot00000000000000require_relative "basic" class Thor module Shell # Inherit from Thor::Shell::Basic and add set_color behavior. Check # Thor::Shell::Basic to see all available methods. # class Color < Basic # Embed in a String to clear all previous ANSI sequences. CLEAR = "\e[0m" # The start of an ANSI bold sequence. BOLD = "\e[1m" # Set the terminal's foreground ANSI color to black. BLACK = "\e[30m" # Set the terminal's foreground ANSI color to red. RED = "\e[31m" # Set the terminal's foreground ANSI color to green. GREEN = "\e[32m" # Set the terminal's foreground ANSI color to yellow. YELLOW = "\e[33m" # Set the terminal's foreground ANSI color to blue. BLUE = "\e[34m" # Set the terminal's foreground ANSI color to magenta. MAGENTA = "\e[35m" # Set the terminal's foreground ANSI color to cyan. CYAN = "\e[36m" # Set the terminal's foreground ANSI color to white. WHITE = "\e[37m" # Set the terminal's background ANSI color to black. ON_BLACK = "\e[40m" # Set the terminal's background ANSI color to red. ON_RED = "\e[41m" # Set the terminal's background ANSI color to green. ON_GREEN = "\e[42m" # Set the terminal's background ANSI color to yellow. ON_YELLOW = "\e[43m" # Set the terminal's background ANSI color to blue. ON_BLUE = "\e[44m" # Set the terminal's background ANSI color to magenta. ON_MAGENTA = "\e[45m" # Set the terminal's background ANSI color to cyan. ON_CYAN = "\e[46m" # Set the terminal's background ANSI color to white. ON_WHITE = "\e[47m" # Set color by using a string or one of the defined constants. If a third # option is set to true, it also adds bold to the string. This is based # on Highline implementation and it automatically appends CLEAR to the end # of the returned String. # # Pass foreground, background and bold options to this method as # symbols. # # Example: # # set_color "Hi!", :red, :on_white, :bold # # The available colors are: # # :bold # :black # :red # :green # :yellow # :blue # :magenta # :cyan # :white # :on_black # :on_red # :on_green # :on_yellow # :on_blue # :on_magenta # :on_cyan # :on_white def set_color(string, *colors) if colors.compact.empty? || !can_display_colors? string elsif colors.all? { |color| color.is_a?(Symbol) || color.is_a?(String) } ansi_colors = colors.map { |color| lookup_color(color) } "#{ansi_colors.join}#{string}#{CLEAR}" else # The old API was `set_color(color, bold=boolean)`. We # continue to support the old API because you should never # break old APIs unnecessarily :P foreground, bold = colors foreground = self.class.const_get(foreground.to_s.upcase) if foreground.is_a?(Symbol) bold = bold ? BOLD : "" "#{bold}#{foreground}#{string}#{CLEAR}" end end protected def can_display_colors? stdout.tty? && !are_colors_disabled? end def are_colors_disabled? !ENV['NO_COLOR'].nil? end # Overwrite show_diff to show diff with colors if Diff::LCS is # available. # def show_diff(destination, content) #:nodoc: if diff_lcs_loaded? && ENV["THOR_DIFF"].nil? && ENV["RAILS_DIFF"].nil? actual = File.binread(destination).to_s.split("\n") content = content.to_s.split("\n") Diff::LCS.sdiff(actual, content).each do |diff| output_diff_line(diff) end else super end end def output_diff_line(diff) #:nodoc: case diff.action when "-" say "- #{diff.old_element.chomp}", :red, true when "+" say "+ #{diff.new_element.chomp}", :green, true when "!" say "- #{diff.old_element.chomp}", :red, true say "+ #{diff.new_element.chomp}", :green, true else say " #{diff.old_element.chomp}", nil, true end end # Check if Diff::LCS is loaded. If it is, use it to create pretty output # for diff. # def diff_lcs_loaded? #:nodoc: return true if defined?(Diff::LCS) return @diff_lcs_loaded unless @diff_lcs_loaded.nil? @diff_lcs_loaded = begin require "diff/lcs" true rescue LoadError false end end end end end thor-1.0.1/lib/thor/shell/html.rb000066400000000000000000000105301357617015300166160ustar00rootroot00000000000000require_relative "basic" class Thor module Shell # Inherit from Thor::Shell::Basic and add set_color behavior. Check # Thor::Shell::Basic to see all available methods. # class HTML < Basic # The start of an HTML bold sequence. BOLD = "font-weight: bold" # Set the terminal's foreground HTML color to black. BLACK = "color: black" # Set the terminal's foreground HTML color to red. RED = "color: red" # Set the terminal's foreground HTML color to green. GREEN = "color: green" # Set the terminal's foreground HTML color to yellow. YELLOW = "color: yellow" # Set the terminal's foreground HTML color to blue. BLUE = "color: blue" # Set the terminal's foreground HTML color to magenta. MAGENTA = "color: magenta" # Set the terminal's foreground HTML color to cyan. CYAN = "color: cyan" # Set the terminal's foreground HTML color to white. WHITE = "color: white" # Set the terminal's background HTML color to black. ON_BLACK = "background-color: black" # Set the terminal's background HTML color to red. ON_RED = "background-color: red" # Set the terminal's background HTML color to green. ON_GREEN = "background-color: green" # Set the terminal's background HTML color to yellow. ON_YELLOW = "background-color: yellow" # Set the terminal's background HTML color to blue. ON_BLUE = "background-color: blue" # Set the terminal's background HTML color to magenta. ON_MAGENTA = "background-color: magenta" # Set the terminal's background HTML color to cyan. ON_CYAN = "background-color: cyan" # Set the terminal's background HTML color to white. ON_WHITE = "background-color: white" # Set color by using a string or one of the defined constants. If a third # option is set to true, it also adds bold to the string. This is based # on Highline implementation and it automatically appends CLEAR to the end # of the returned String. # def set_color(string, *colors) if colors.all? { |color| color.is_a?(Symbol) || color.is_a?(String) } html_colors = colors.map { |color| lookup_color(color) } "#{Thor::Util.escape_html(string)}" else color, bold = colors html_color = self.class.const_get(color.to_s.upcase) if color.is_a?(Symbol) styles = [html_color] styles << BOLD if bold "#{Thor::Util.escape_html(string)}" end end # Ask something to the user and receives a response. # # ==== Example # ask("What is your name?") # # TODO: Implement #ask for Thor::Shell::HTML def ask(statement, color = nil) raise NotImplementedError, "Implement #ask for Thor::Shell::HTML" end protected def can_display_colors? true end # Overwrite show_diff to show diff with colors if Diff::LCS is # available. # def show_diff(destination, content) #:nodoc: if diff_lcs_loaded? && ENV["THOR_DIFF"].nil? && ENV["RAILS_DIFF"].nil? actual = File.binread(destination).to_s.split("\n") content = content.to_s.split("\n") Diff::LCS.sdiff(actual, content).each do |diff| output_diff_line(diff) end else super end end def output_diff_line(diff) #:nodoc: case diff.action when "-" say "- #{diff.old_element.chomp}", :red, true when "+" say "+ #{diff.new_element.chomp}", :green, true when "!" say "- #{diff.old_element.chomp}", :red, true say "+ #{diff.new_element.chomp}", :green, true else say " #{diff.old_element.chomp}", nil, true end end # Check if Diff::LCS is loaded. If it is, use it to create pretty output # for diff. # def diff_lcs_loaded? #:nodoc: return true if defined?(Diff::LCS) return @diff_lcs_loaded unless @diff_lcs_loaded.nil? @diff_lcs_loaded = begin require "diff/lcs" true rescue LoadError false end end end end end thor-1.0.1/lib/thor/util.rb000066400000000000000000000211201357617015300155150ustar00rootroot00000000000000require "rbconfig" class Thor module Sandbox #:nodoc: end # This module holds several utilities: # # 1) Methods to convert thor namespaces to constants and vice-versa. # # Thor::Util.namespace_from_thor_class(Foo::Bar::Baz) #=> "foo:bar:baz" # # 2) Loading thor files and sandboxing: # # Thor::Util.load_thorfile("~/.thor/foo") # module Util class << self # Receives a namespace and search for it in the Thor::Base subclasses. # # ==== Parameters # namespace:: The namespace to search for. # def find_by_namespace(namespace) namespace = "default#{namespace}" if namespace.empty? || namespace =~ /^:/ Thor::Base.subclasses.detect { |klass| klass.namespace == namespace } end # Receives a constant and converts it to a Thor namespace. Since Thor # commands can be added to a sandbox, this method is also responsible for # removing the sandbox namespace. # # This method should not be used in general because it's used to deal with # older versions of Thor. On current versions, if you need to get the # namespace from a class, just call namespace on it. # # ==== Parameters # constant:: The constant to be converted to the thor path. # # ==== Returns # String:: If we receive Foo::Bar::Baz it returns "foo:bar:baz" # def namespace_from_thor_class(constant) constant = constant.to_s.gsub(/^Thor::Sandbox::/, "") constant = snake_case(constant).squeeze(":") constant end # Given the contents, evaluate it inside the sandbox and returns the # namespaces defined in the sandbox. # # ==== Parameters # contents # # ==== Returns # Array[Object] # def namespaces_in_content(contents, file = __FILE__) old_constants = Thor::Base.subclasses.dup Thor::Base.subclasses.clear load_thorfile(file, contents) new_constants = Thor::Base.subclasses.dup Thor::Base.subclasses.replace(old_constants) new_constants.map!(&:namespace) new_constants.compact! new_constants end # Returns the thor classes declared inside the given class. # def thor_classes_in(klass) stringfied_constants = klass.constants.map(&:to_s) Thor::Base.subclasses.select do |subclass| next unless subclass.name stringfied_constants.include?(subclass.name.gsub("#{klass.name}::", "")) end end # Receives a string and convert it to snake case. SnakeCase returns snake_case. # # ==== Parameters # String # # ==== Returns # String # def snake_case(str) return str.downcase if str =~ /^[A-Z_]+$/ str.gsub(/\B[A-Z]/, '_\&').squeeze("_") =~ /_*(.*)/ $+.downcase end # Receives a string and convert it to camel case. camel_case returns CamelCase. # # ==== Parameters # String # # ==== Returns # String # def camel_case(str) return str if str !~ /_/ && str =~ /[A-Z]+.*/ str.split("_").map(&:capitalize).join end # Receives a namespace and tries to retrieve a Thor or Thor::Group class # from it. It first searches for a class using the all the given namespace, # if it's not found, removes the highest entry and searches for the class # again. If found, returns the highest entry as the class name. # # ==== Examples # # class Foo::Bar < Thor # def baz # end # end # # class Baz::Foo < Thor::Group # end # # Thor::Util.namespace_to_thor_class("foo:bar") #=> Foo::Bar, nil # will invoke default command # Thor::Util.namespace_to_thor_class("baz:foo") #=> Baz::Foo, nil # Thor::Util.namespace_to_thor_class("foo:bar:baz") #=> Foo::Bar, "baz" # # ==== Parameters # namespace # def find_class_and_command_by_namespace(namespace, fallback = true) if namespace.include?(":") # look for a namespaced command pieces = namespace.split(":") command = pieces.pop klass = Thor::Util.find_by_namespace(pieces.join(":")) end unless klass # look for a Thor::Group with the right name klass = Thor::Util.find_by_namespace(namespace) command = nil end if !klass && fallback # try a command in the default namespace command = namespace klass = Thor::Util.find_by_namespace("") end [klass, command] end alias_method :find_class_and_task_by_namespace, :find_class_and_command_by_namespace # Receives a path and load the thor file in the path. The file is evaluated # inside the sandbox to avoid namespacing conflicts. # def load_thorfile(path, content = nil, debug = false) content ||= File.binread(path) begin Thor::Sandbox.class_eval(content, path) rescue StandardError => e $stderr.puts("WARNING: unable to load thorfile #{path.inspect}: #{e.message}") if debug $stderr.puts(*e.backtrace) else $stderr.puts(e.backtrace.first) end end end def user_home @@user_home ||= if ENV["HOME"] ENV["HOME"] elsif ENV["USERPROFILE"] ENV["USERPROFILE"] elsif ENV["HOMEDRIVE"] && ENV["HOMEPATH"] File.join(ENV["HOMEDRIVE"], ENV["HOMEPATH"]) elsif ENV["APPDATA"] ENV["APPDATA"] else begin File.expand_path("~") rescue if File::ALT_SEPARATOR "C:/" else "/" end end end end # Returns the root where thor files are located, depending on the OS. # def thor_root File.join(user_home, ".thor").tr('\\', "/") end # Returns the files in the thor root. On Windows thor_root will be something # like this: # # C:\Documents and Settings\james\.thor # # If we don't #gsub the \ character, Dir.glob will fail. # def thor_root_glob files = Dir["#{escape_globs(thor_root)}/*"] files.map! do |file| File.directory?(file) ? File.join(file, "main.thor") : file end end # Where to look for Thor files. # def globs_for(path) path = escape_globs(path) ["#{path}/Thorfile", "#{path}/*.thor", "#{path}/tasks/*.thor", "#{path}/lib/tasks/*.thor"] end # Return the path to the ruby interpreter taking into account multiple # installations and windows extensions. # def ruby_command @ruby_command ||= begin ruby_name = RbConfig::CONFIG["ruby_install_name"] ruby = File.join(RbConfig::CONFIG["bindir"], ruby_name) ruby << RbConfig::CONFIG["EXEEXT"] # avoid using different name than ruby (on platforms supporting links) if ruby_name != "ruby" && File.respond_to?(:readlink) begin alternate_ruby = File.join(RbConfig::CONFIG["bindir"], "ruby") alternate_ruby << RbConfig::CONFIG["EXEEXT"] # ruby is a symlink if File.symlink? alternate_ruby linked_ruby = File.readlink alternate_ruby # symlink points to 'ruby_install_name' ruby = alternate_ruby if linked_ruby == ruby_name || linked_ruby == ruby end rescue NotImplementedError # rubocop:disable HandleExceptions # just ignore on windows end end # escape string in case path to ruby executable contain spaces. ruby.sub!(/.*\s.*/m, '"\&"') ruby end end # Returns a string that has had any glob characters escaped. # The glob characters are `* ? { } [ ]`. # # ==== Examples # # Thor::Util.escape_globs('[apps]') # => '\[apps\]' # # ==== Parameters # String # # ==== Returns # String # def escape_globs(path) path.to_s.gsub(/[*?{}\[\]]/, '\\\\\\&') end # Returns a string that has had any HTML characters escaped. # # ==== Examples # # Thor::Util.escape_html('
') # => "<div>" # # ==== Parameters # String # # ==== Returns # String # def escape_html(string) CGI.escapeHTML(string) end end end end thor-1.0.1/lib/thor/version.rb000066400000000000000000000000431357617015300162260ustar00rootroot00000000000000class Thor VERSION = "1.0.1" end thor-1.0.1/spec/000077500000000000000000000000001357617015300134275ustar00rootroot00000000000000thor-1.0.1/spec/actions/000077500000000000000000000000001357617015300150675ustar00rootroot00000000000000thor-1.0.1/spec/actions/create_file_spec.rb000066400000000000000000000142071357617015300206740ustar00rootroot00000000000000require "helper" require "thor/actions" describe Thor::Actions::CreateFile do before do @silence = false ::FileUtils.rm_rf(destination_root) end def create_file(destination = nil, config = {}, options = {}) @base = MyCounter.new([1, 2], options, :destination_root => destination_root) allow(@base).to receive(:file_name).and_return("rdoc") @action = Thor::Actions::CreateFile.new(@base, destination, "CONFIGURATION", {:verbose => !@silence}.merge(config)) end def invoke! capture(:stdout) { @action.invoke! } end def revoke! capture(:stdout) { @action.revoke! } end def silence! @silence = true end describe "#invoke!" do it "creates a file" do create_file("doc/config.rb") invoke! expect(File.exist?(File.join(destination_root, "doc/config.rb"))).to be true end it "does not create a file if pretending" do create_file("doc/config.rb", {}, :pretend => true) invoke! expect(File.exist?(File.join(destination_root, "doc/config.rb"))).to be false end it "shows created status to the user" do create_file("doc/config.rb") expect(invoke!).to eq(" create doc/config.rb\n") end it "does not show any information if log status is false" do silence! create_file("doc/config.rb") expect(invoke!).to be_empty end it "returns the given destination" do capture(:stdout) do expect(create_file("doc/config.rb").invoke!).to eq("doc/config.rb") end end it "converts encoded instructions" do create_file("doc/%file_name%.rb.tt") invoke! expect(File.exist?(File.join(destination_root, "doc/rdoc.rb.tt"))).to be true end describe "when file exists" do before do create_file("doc/config.rb") invoke! end describe "and is identical" do it "shows identical status" do create_file("doc/config.rb") invoke! expect(invoke!).to eq(" identical doc/config.rb\n") end end describe "and is not identical" do before do File.open(File.join(destination_root, "doc/config.rb"), "w") { |f| f.write("FOO = 3") } end it "shows forced status to the user if force is given" do expect(create_file("doc/config.rb", {}, :force => true)).not_to be_identical expect(invoke!).to eq(" force doc/config.rb\n") end it "shows skipped status to the user if skip is given" do expect(create_file("doc/config.rb", {}, :skip => true)).not_to be_identical expect(invoke!).to eq(" skip doc/config.rb\n") end it "shows forced status to the user if force is configured" do expect(create_file("doc/config.rb", :force => true)).not_to be_identical expect(invoke!).to eq(" force doc/config.rb\n") end it "shows skipped status to the user if skip is configured" do expect(create_file("doc/config.rb", :skip => true)).not_to be_identical expect(invoke!).to eq(" skip doc/config.rb\n") end it "shows conflict status to the user" do file = File.join(destination_root, "doc/config.rb") expect(create_file("doc/config.rb")).not_to be_identical expect(Thor::LineEditor).to receive(:readline).with("Overwrite #{file}? (enter \"h\" for help) [Ynaqdhm] ", anything).and_return("s") content = invoke! expect(content).to match(%r{conflict doc/config\.rb}) expect(content).to match(%r{skip doc/config\.rb}) end it "creates the file if the file collision menu returns true" do create_file("doc/config.rb") expect(Thor::LineEditor).to receive(:readline).and_return("y") expect(invoke!).to match(%r{force doc/config\.rb}) end it "skips the file if the file collision menu returns false" do create_file("doc/config.rb") expect(Thor::LineEditor).to receive(:readline).and_return("n") expect(invoke!).to match(%r{skip doc/config\.rb}) end it "executes the block given to show file content" do create_file("doc/config.rb") expect(Thor::LineEditor).to receive(:readline).and_return("d", "n") expect(@base.shell).to receive(:system).with(/diff -u/) invoke! end it "executes the block given to run merge tool" do create_file("doc/config.rb") allow(@base.shell).to receive(:merge_tool).and_return("meld") expect(Thor::LineEditor).to receive(:readline).and_return("m") expect(@base.shell).to receive(:system).with(/meld/) invoke! end end end context "when file exists and it causes a file clash" do before do create_file("doc/config") invoke! end it "generates a file clash" do create_file("doc/config/config.rb") expect(invoke!).to eq(" file_clash doc/config/config.rb\n") end end context "when directory exists and it causes a file clash" do before do create_file("doc/config/hello") invoke! end it "generates a file clash" do create_file("doc/config") expect(invoke!) .to eq(" file_clash doc/config\n") end end end describe "#revoke!" do it "removes the destination file" do create_file("doc/config.rb") invoke! revoke! expect(File.exist?(@action.destination)).to be false end it "does not raise an error if the file does not exist" do create_file("doc/config.rb") revoke! expect(File.exist?(@action.destination)).to be false end end describe "#exists?" do it "returns true if the destination file exists" do create_file("doc/config.rb") expect(@action.exists?).to be false invoke! expect(@action.exists?).to be true end end describe "#identical?" do it "returns true if the destination file exists and is identical" do create_file("doc/config.rb") expect(@action.identical?).to be false invoke! expect(@action.identical?).to be true end end end thor-1.0.1/spec/actions/create_link_spec.rb000066400000000000000000000063131357617015300207110ustar00rootroot00000000000000require "helper" require "thor/actions" require "tempfile" describe Thor::Actions::CreateLink, :unless => windows? do before do @hardlink_to = File.join(Dir.tmpdir, "linkdest.rb") ::FileUtils.rm_rf(destination_root) ::FileUtils.rm_rf(@hardlink_to) end let(:config) { {} } let(:options) { {} } let(:base) do base = MyCounter.new([1, 2], options, :destination_root => destination_root) allow(base).to receive(:file_name).and_return("rdoc") base end let(:tempfile) { Tempfile.new("config.rb") } let(:source) { tempfile.path } let(:destination) { "doc/config.rb" } let(:action) do Thor::Actions::CreateLink.new(base, destination, source, config) end def invoke! capture(:stdout) { action.invoke! } end def revoke! capture(:stdout) { action.revoke! } end describe "#invoke!" do context "specifying :symbolic => true" do let(:config) { {:symbolic => true} } it "creates a symbolic link" do invoke! destination_path = File.join(destination_root, "doc/config.rb") expect(File.exist?(destination_path)).to be true expect(File.symlink?(destination_path)).to be true end end context "specifying :symbolic => false" do let(:config) { {:symbolic => false} } let(:destination) { @hardlink_to } it "creates a hard link" do invoke! destination_path = @hardlink_to expect(File.exist?(destination_path)).to be true expect(File.symlink?(destination_path)).to be false end end it "creates a symbolic link by default" do invoke! destination_path = File.join(destination_root, "doc/config.rb") expect(File.exist?(destination_path)).to be true expect(File.symlink?(destination_path)).to be true end context "specifying :pretend => true" do let(:options) { {:pretend => true} } it "does not create a link" do invoke! expect(File.exist?(File.join(destination_root, "doc/config.rb"))).to be false end end it "shows created status to the user" do expect(invoke!).to eq(" create doc/config.rb\n") end context "specifying :verbose => false" do let(:config) { {:verbose => false} } it "does not show any information" do expect(invoke!).to be_empty end end end describe "#identical?" do it "returns true if the destination link exists and is identical" do expect(action.identical?).to be false invoke! expect(action.identical?).to be true end context "with source path relative to destination" do let(:source) do destination_path = File.dirname(File.join(destination_root, destination)) Pathname.new(super()).relative_path_from(Pathname.new(destination_path)).to_s end it "returns true if the destination link exists and is identical" do expect(action.identical?).to be false invoke! expect(action.identical?).to be true end end end describe "#revoke!" do it "removes the symbolic link of non-existent destination" do invoke! File.delete(tempfile.path) revoke! expect(File.symlink?(action.destination)).to be false end end end thor-1.0.1/spec/actions/directory_spec.rb000066400000000000000000000140121357617015300204300ustar00rootroot00000000000000require "tmpdir" require "helper" require "thor/actions" describe Thor::Actions::Directory do before do ::FileUtils.rm_rf(destination_root) allow(invoker).to receive(:file_name).and_return("rdoc") end def invoker @invoker ||= WhinyGenerator.new([1, 2], {}, :destination_root => destination_root) end def revoker @revoker ||= WhinyGenerator.new([1, 2], {}, :destination_root => destination_root, :behavior => :revoke) end def invoke!(*args, &block) capture(:stdout) { invoker.directory(*args, &block) } end def revoke!(*args, &block) capture(:stdout) { revoker.directory(*args, &block) } end def exists_and_identical?(source_path, destination_path) %w(config.rb README).each do |file| source = File.join(source_root, source_path, file) destination = File.join(destination_root, destination_path, file) expect(File.exist?(destination)).to be true expect(FileUtils.identical?(source, destination)).to be true end end describe "#invoke!" do it "raises an error if the source does not exist" do expect do invoke! "unknown" end.to raise_error(Thor::Error, /Could not find "unknown" in any of your source paths/) end it "does not create a directory in pretend mode" do invoke! "doc", "ghost", :pretend => true expect(File.exist?("ghost")).to be false end it "copies the whole directory recursively to the default destination" do invoke! "doc" exists_and_identical?("doc", "doc") end it "copies the whole directory recursively to the specified destination" do invoke! "doc", "docs" exists_and_identical?("doc", "docs") end it "copies only the first level files if recursive" do invoke! ".", "commands", :recursive => false file = File.join(destination_root, "commands", "group.thor") expect(File.exist?(file)).to be true file = File.join(destination_root, "commands", "doc") expect(File.exist?(file)).to be false file = File.join(destination_root, "commands", "doc", "README") expect(File.exist?(file)).to be false end it "ignores files within excluding/ directories when exclude_pattern is provided" do invoke! "doc", "docs", :exclude_pattern => %r{excluding/} file = File.join(destination_root, "docs", "excluding", "rdoc.rb") expect(File.exist?(file)).to be false end it "copies and evaluates files within excluding/ directory when no exclude_pattern is present" do invoke! "doc", "docs" file = File.join(destination_root, "docs", "excluding", "rdoc.rb") expect(File.exist?(file)).to be true expect(File.read(file)).to eq("BAR = BAR\n") end it "copies files from the source relative to the current path" do invoker.inside "doc" do invoke! "." end exists_and_identical?("doc", "doc") end it "copies and evaluates templates" do invoke! "doc", "docs" file = File.join(destination_root, "docs", "rdoc.rb") expect(File.exist?(file)).to be true expect(File.read(file)).to eq("FOO = FOO\n") end it "copies directories and preserves file mode" do invoke! "preserve", "preserved", :mode => :preserve original = File.join(source_root, "preserve", "script.sh") copy = File.join(destination_root, "preserved", "script.sh") expect(File.stat(original).mode).to eq(File.stat(copy).mode) end it "copies directories" do invoke! "doc", "docs" file = File.join(destination_root, "docs", "components") expect(File.exist?(file)).to be true expect(File.directory?(file)).to be true end it "does not copy .empty_directory files" do invoke! "doc", "docs" file = File.join(destination_root, "docs", "components", ".empty_directory") expect(File.exist?(file)).to be false end it "copies directories even if they are empty" do invoke! "doc/components", "docs/components" file = File.join(destination_root, "docs", "components") expect(File.exist?(file)).to be true end it "does not copy empty directories twice" do content = invoke!("doc/components", "docs/components") expect(content).not_to match(/exist/) end it "logs status" do content = invoke!("doc") expect(content).to match(%r{create doc/README}) expect(content).to match(%r{create doc/config\.rb}) expect(content).to match(%r{create doc/rdoc\.rb}) expect(content).to match(%r{create doc/components}) end it "yields a block" do checked = false invoke!("doc") do |content| checked ||= !!(content =~ /FOO/) end expect(checked).to be true end it "works with glob characters in the path" do content = invoke!("app{1}") expect(content).to match(%r{create app\{1\}/README}) end context "windows temp directories", :if => windows? do let(:spec_dir) { File.join(@temp_dir, "spec") } before(:each) do @temp_dir = Dir.mktmpdir("thor") Dir.mkdir(spec_dir) File.new(File.join(spec_dir, "spec_helper.rb"), "w").close end after(:each) { FileUtils.rm_rf(@temp_dir) } it "works with windows temp dir" do invoke! spec_dir, "specs" file = File.join(destination_root, "specs") expect(File.exist?(file)).to be true expect(File.directory?(file)).to be true end end end describe "#revoke!" do it "removes the destination file" do invoke! "doc" revoke! "doc" expect(File.exist?(File.join(destination_root, "doc", "README"))).to be false expect(File.exist?(File.join(destination_root, "doc", "config.rb"))).to be false expect(File.exist?(File.join(destination_root, "doc", "components"))).to be false end it "works with glob characters in the path" do invoke! "app{1}" expect(File.exist?(File.join(destination_root, "app{1}", "README"))).to be true revoke! "app{1}" expect(File.exist?(File.join(destination_root, "app{1}", "README"))).to be false end end end thor-1.0.1/spec/actions/empty_directory_spec.rb000066400000000000000000000071311357617015300216520ustar00rootroot00000000000000require "helper" require "thor/actions" describe Thor::Actions::EmptyDirectory do before do ::FileUtils.rm_rf(destination_root) end def empty_directory(destination, options = {}) @action = Thor::Actions::EmptyDirectory.new(base, destination) end def invoke! capture(:stdout) { @action.invoke! } end def revoke! capture(:stdout) { @action.revoke! } end def base @base ||= MyCounter.new([1, 2], {}, :destination_root => destination_root) end describe "#destination" do it "returns the full destination with the destination_root" do expect(empty_directory("doc").destination).to eq(File.join(destination_root, "doc")) end it "takes relative root into account" do base.inside("doc") do expect(empty_directory("contents").destination).to eq(File.join(destination_root, "doc", "contents")) end end end describe "#relative_destination" do it "returns the relative destination to the original destination root" do base.inside("doc") do expect(empty_directory("contents").relative_destination).to eq("doc/contents") end end end describe "#given_destination" do it "returns the destination supplied by the user" do base.inside("doc") do expect(empty_directory("contents").given_destination).to eq("contents") end end end describe "#invoke!" do it "copies the file to the specified destination" do empty_directory("doc") invoke! expect(File.exist?(File.join(destination_root, "doc"))).to be true end it "shows created status to the user" do empty_directory("doc") expect(invoke!).to eq(" create doc\n") end it "does not create a directory if pretending" do base.inside("foo", :pretend => true) do empty_directory("ghost") end expect(File.exist?(File.join(base.destination_root, "ghost"))).to be false end describe "when directory exists" do it "shows exist status" do empty_directory("doc") invoke! expect(invoke!).to eq(" exist doc\n") end end end describe "#revoke!" do it "removes the destination file" do empty_directory("doc") invoke! revoke! expect(File.exist?(@action.destination)).to be false end end describe "#exists?" do it "returns true if the destination file exists" do empty_directory("doc") expect(@action.exists?).to be false invoke! expect(@action.exists?).to be true end end context "protected methods" do describe "#convert_encoded_instructions" do before do empty_directory("test_dir") allow(@action.base).to receive(:file_name).and_return("expected") end it "accepts and executes a 'legal' %\w+% encoded instruction" do expect(@action.send(:convert_encoded_instructions, "%file_name%.txt")).to eq("expected.txt") end it "accepts and executes a private %\w+% encoded instruction" do @action.base.extend Module.new { def private_file_name "expected" end private :private_file_name } expect(@action.send(:convert_encoded_instructions, "%private_file_name%.txt")).to eq("expected.txt") end it "ignores an 'illegal' %\w+% encoded instruction" do expect(@action.send(:convert_encoded_instructions, "%some_name%.txt")).to eq("%some_name%.txt") end it "ignores incorrectly encoded instruction" do expect(@action.send(:convert_encoded_instructions, "%some.name%.txt")).to eq("%some.name%.txt") end end end end thor-1.0.1/spec/actions/file_manipulation_spec.rb000066400000000000000000000374321357617015300221360ustar00rootroot00000000000000require "helper" class Application; end module ApplicationHelper; end describe Thor::Actions do def runner(options = {}) @runner ||= MyCounter.new([1], options, :destination_root => destination_root) end def action(*args, &block) capture(:stdout) { runner.send(*args, &block) } end def exists_and_identical?(source, destination) destination = File.join(destination_root, destination) expect(File.exist?(destination)).to be true source = File.join(source_root, source) expect(FileUtils).to be_identical(source, destination) end def file File.join(destination_root, "foo") end before do ::FileUtils.rm_rf(destination_root) end describe "#chmod" do it "executes the command given" do expect(FileUtils).to receive(:chmod_R).with(0755, file) action :chmod, "foo", 0755 end it "does not execute the command if pretending" do expect(FileUtils).not_to receive(:chmod_R) runner(:pretend => true) action :chmod, "foo", 0755 end it "logs status" do expect(FileUtils).to receive(:chmod_R).with(0755, file) expect(action(:chmod, "foo", 0755)).to eq(" chmod foo\n") end it "does not log status if required" do expect(FileUtils).to receive(:chmod_R).with(0755, file) expect(action(:chmod, "foo", 0755, :verbose => false)).to be_empty end end describe "#copy_file" do it "copies file from source to default destination" do action :copy_file, "command.thor" exists_and_identical?("command.thor", "command.thor") end it "copies file from source to the specified destination" do action :copy_file, "command.thor", "foo.thor" exists_and_identical?("command.thor", "foo.thor") end it "copies file from the source relative to the current path" do runner.inside("doc") do action :copy_file, "README" end exists_and_identical?("doc/README", "doc/README") end it "copies file from source to default destination and preserves file mode" do action :copy_file, "preserve/script.sh", :mode => :preserve original = File.join(source_root, "preserve/script.sh") copy = File.join(destination_root, "preserve/script.sh") expect(File.stat(original).mode).to eq(File.stat(copy).mode) end it "copies file from source to default destination and preserves file mode for templated filenames" do expect(runner).to receive(:filename).and_return("app") action :copy_file, "preserve/%filename%.sh", :mode => :preserve original = File.join(source_root, "preserve/%filename%.sh") copy = File.join(destination_root, "preserve/app.sh") expect(File.stat(original).mode).to eq(File.stat(copy).mode) end it "logs status" do expect(action(:copy_file, "command.thor")).to eq(" create command.thor\n") end it "accepts a block to change output" do action :copy_file, "command.thor" do |content| "OMG" + content end expect(File.read(File.join(destination_root, "command.thor"))).to match(/^OMG/) end end describe "#link_file", :unless => windows? do it "links file from source to default destination" do action :link_file, "command.thor" exists_and_identical?("command.thor", "command.thor") end it "links file from source to the specified destination" do action :link_file, "command.thor", "foo.thor" exists_and_identical?("command.thor", "foo.thor") end it "links file from the source relative to the current path" do runner.inside("doc") do action :link_file, "README" end exists_and_identical?("doc/README", "doc/README") end it "logs status" do expect(action(:link_file, "command.thor")).to eq(" create command.thor\n") end end describe "#get" do it "copies file from source to the specified destination" do action :get, "doc/README", "docs/README" exists_and_identical?("doc/README", "docs/README") end it "uses just the source basename as destination if none is specified" do action :get, "doc/README" exists_and_identical?("doc/README", "README") end it "allows the destination to be set as a block result" do action(:get, "doc/README") { "docs/README" } exists_and_identical?("doc/README", "docs/README") end it "yields file content to a block" do action :get, "doc/README" do |content| expect(content).to eq("__start__\nREADME\n__end__\n") end end it "logs status" do expect(action(:get, "doc/README", "docs/README")).to eq(" create docs/README\n") end it "accepts http remote sources" do body = "__start__\nHTTPFILE\n__end__\n" stub_request(:get, "http://example.com/file.txt").to_return(:body => body.dup) action :get, "http://example.com/file.txt" do |content| expect(a_request(:get, "http://example.com/file.txt")).to have_been_made expect(content).to eq(body) end end it "accepts https remote sources" do body = "__start__\nHTTPSFILE\n__end__\n" stub_request(:get, "https://example.com/file.txt").to_return(:body => body.dup) action :get, "https://example.com/file.txt" do |content| expect(a_request(:get, "https://example.com/file.txt")).to have_been_made expect(content).to eq(body) end end end describe "#template" do it "allows using block helpers in the template" do action :template, "doc/block_helper.rb" file = File.join(destination_root, "doc/block_helper.rb") expect(File.read(file)).to eq("Hello world!") end it "evaluates the template given as source" do runner.instance_variable_set("@klass", "Config") action :template, "doc/config.rb" file = File.join(destination_root, "doc/config.rb") expect(File.read(file)).to eq("class Config; end\n") end it "copies the template to the specified destination" do runner.instance_variable_set("@klass", "Config") action :template, "doc/config.rb", "doc/configuration.rb" file = File.join(destination_root, "doc/configuration.rb") expect(File.exist?(file)).to be true end it "converts encoded instructions" do expect(runner).to receive(:file_name).and_return("rdoc") action :template, "doc/%file_name%.rb.tt" file = File.join(destination_root, "doc/rdoc.rb") expect(File.exist?(file)).to be true end it "accepts filename without .tt for template method" do expect(runner).to receive(:file_name).and_return("rdoc") action :template, "doc/%file_name%.rb" file = File.join(destination_root, "doc/rdoc.rb") expect(File.exist?(file)).to be true end it "logs status" do runner.instance_variable_set("@klass", "Config") expect(capture(:stdout) { runner.template("doc/config.rb") }).to eq(" create doc/config.rb\n") end it "accepts a block to change output" do runner.instance_variable_set("@klass", "Config") action :template, "doc/config.rb" do |content| "OMG" + content end expect(File.read(File.join(destination_root, "doc/config.rb"))).to match(/^OMG/) end it "accepts a context to use as the binding" do begin @klass = "FooBar" action :template, "doc/config.rb", :context => eval("binding") expect(File.read(File.join(destination_root, "doc/config.rb"))).to eq("class FooBar; end\n") ensure remove_instance_variable(:@klass) end end it "guesses the destination name when given only a source" do action :template, "doc/config.yaml.tt" file = File.join(destination_root, "doc/config.yaml") expect(File.exist?(file)).to be true end it "has proper ERB stacktraces" do error = nil begin action :template, "template/bad_config.yaml.tt" rescue => e error = e end expect(error).to be_a NameError expect(error.backtrace.to_s).to include("bad_config.yaml.tt:2") end end describe "when changing existent files" do before do ::FileUtils.cp_r(source_root, destination_root) end def file File.join(destination_root, "doc", "README") end describe "#remove_file" do it "removes the file given" do action :remove_file, "doc/README" expect(File.exist?(file)).to be false end it "removes directories too" do action :remove_dir, "doc" expect(File.exist?(File.join(destination_root, "doc"))).to be false end it "does not remove if pretending" do runner(:pretend => true) action :remove_file, "doc/README" expect(File.exist?(file)).to be true end it "logs status" do expect(action(:remove_file, "doc/README")).to eq(" remove doc/README\n") end it "does not log status if required" do expect(action(:remove_file, "doc/README", :verbose => false)).to be_empty end end describe "#gsub_file" do it "replaces the content in the file" do action :gsub_file, "doc/README", "__start__", "START" expect(File.binread(file)).to eq("START\nREADME\n__end__\n") end it "does not replace if pretending" do runner(:pretend => true) action :gsub_file, "doc/README", "__start__", "START" expect(File.binread(file)).to eq("__start__\nREADME\n__end__\n") end it "accepts a block" do action(:gsub_file, "doc/README", "__start__") { |match| match.gsub("__", "").upcase } expect(File.binread(file)).to eq("START\nREADME\n__end__\n") end it "logs status" do expect(action(:gsub_file, "doc/README", "__start__", "START")).to eq(" gsub doc/README\n") end it "does not log status if required" do expect(action(:gsub_file, file, "__", :verbose => false) { |match| match * 2 }).to be_empty end end describe "#append_to_file" do it "appends content to the file" do action :append_to_file, "doc/README", "END\n" expect(File.binread(file)).to eq("__start__\nREADME\n__end__\nEND\n") end it "accepts a block" do action(:append_to_file, "doc/README") { "END\n" } expect(File.binread(file)).to eq("__start__\nREADME\n__end__\nEND\n") end it "logs status" do expect(action(:append_to_file, "doc/README", "END")).to eq(" append doc/README\n") end end describe "#prepend_to_file" do it "prepends content to the file" do action :prepend_to_file, "doc/README", "START\n" expect(File.binread(file)).to eq("START\n__start__\nREADME\n__end__\n") end it "accepts a block" do action(:prepend_to_file, "doc/README") { "START\n" } expect(File.binread(file)).to eq("START\n__start__\nREADME\n__end__\n") end it "logs status" do expect(action(:prepend_to_file, "doc/README", "START")).to eq(" prepend doc/README\n") end end describe "#inject_into_class" do def file File.join(destination_root, "application.rb") end it "appends content to a class" do action :inject_into_class, "application.rb", Application, " filter_parameters :password\n" expect(File.binread(file)).to eq("class Application < Base\n filter_parameters :password\nend\n") end it "accepts a block" do action(:inject_into_class, "application.rb", Application) { " filter_parameters :password\n" } expect(File.binread(file)).to eq("class Application < Base\n filter_parameters :password\nend\n") end it "logs status" do expect(action(:inject_into_class, "application.rb", Application, " filter_parameters :password\n")).to eq(" insert application.rb\n") end it "does not append if class name does not match" do action :inject_into_class, "application.rb", "App", " filter_parameters :password\n" expect(File.binread(file)).to eq("class Application < Base\nend\n") end end describe "#inject_into_module" do def file File.join(destination_root, "application_helper.rb") end it "appends content to a module" do action :inject_into_module, "application_helper.rb", ApplicationHelper, " def help; 'help'; end\n" expect(File.binread(file)).to eq("module ApplicationHelper\n def help; 'help'; end\nend\n") end it "accepts a block" do action(:inject_into_module, "application_helper.rb", ApplicationHelper) { " def help; 'help'; end\n" } expect(File.binread(file)).to eq("module ApplicationHelper\n def help; 'help'; end\nend\n") end it "logs status" do expect(action(:inject_into_module, "application_helper.rb", ApplicationHelper, " def help; 'help'; end\n")).to eq(" insert application_helper.rb\n") end it "does not append if module name does not match" do action :inject_into_module, "application_helper.rb", "App", " def help; 'help'; end\n" expect(File.binread(file)).to eq("module ApplicationHelper\nend\n") end end end describe "when adjusting comments" do before do ::FileUtils.cp_r(source_root, destination_root) end def file File.join(destination_root, "doc", "COMMENTER") end unmodified_comments_file = /__start__\n # greenblue\n#\n# yellowblue\n#yellowred\n #greenred\norange\n purple\n ind#igo\n # ind#igo\n__end__/ describe "#uncomment_lines" do it "uncomments all matching lines in the file" do action :uncomment_lines, "doc/COMMENTER", "green" expect(File.binread(file)).to match(/__start__\n greenblue\n#\n# yellowblue\n#yellowred\n greenred\norange\n purple\n ind#igo\n # ind#igo\n__end__/) action :uncomment_lines, "doc/COMMENTER", "red" expect(File.binread(file)).to match(/__start__\n greenblue\n#\n# yellowblue\nyellowred\n greenred\norange\n purple\n ind#igo\n # ind#igo\n__end__/) end it "correctly uncomments lines with hashes in them" do action :uncomment_lines, "doc/COMMENTER", "ind#igo" expect(File.binread(file)).to match(/__start__\n # greenblue\n#\n# yellowblue\n#yellowred\n #greenred\norange\n purple\n ind#igo\n ind#igo\n__end__/) end it "does not modify already uncommented lines in the file" do action :uncomment_lines, "doc/COMMENTER", "orange" action :uncomment_lines, "doc/COMMENTER", "purple" expect(File.binread(file)).to match(unmodified_comments_file) end it "does not uncomment the wrong line when uncommenting lines preceded by blank commented line" do action :uncomment_lines, "doc/COMMENTER", "yellow" expect(File.binread(file)).to match(/__start__\n # greenblue\n#\nyellowblue\nyellowred\n #greenred\norange\n purple\n ind#igo\n # ind#igo\n__end__/) end end describe "#comment_lines" do it "comments lines which are not commented" do action :comment_lines, "doc/COMMENTER", "orange" expect(File.binread(file)).to match(/__start__\n # greenblue\n#\n# yellowblue\n#yellowred\n #greenred\n# orange\n purple\n ind#igo\n # ind#igo\n__end__/) action :comment_lines, "doc/COMMENTER", "purple" expect(File.binread(file)).to match(/__start__\n # greenblue\n#\n# yellowblue\n#yellowred\n #greenred\n# orange\n # purple\n ind#igo\n # ind#igo\n__end__/) end it "correctly comments lines with hashes in them" do action :comment_lines, "doc/COMMENTER", "ind#igo" expect(File.binread(file)).to match(/__start__\n # greenblue\n#\n# yellowblue\n#yellowred\n #greenred\norange\n purple\n # ind#igo\n # ind#igo\n__end__/) end it "does not modify already commented lines" do action :comment_lines, "doc/COMMENTER", "green" expect(File.binread(file)).to match(unmodified_comments_file) end end end end thor-1.0.1/spec/actions/inject_into_file_spec.rb000066400000000000000000000143371357617015300217420ustar00rootroot00000000000000# encoding: utf-8 require "helper" require "thor/actions" describe Thor::Actions::InjectIntoFile do before do ::FileUtils.rm_rf(destination_root) ::FileUtils.cp_r(source_root, destination_root) end def invoker(options = {}) @invoker ||= MyCounter.new([1, 2], options, :destination_root => destination_root) end def revoker @revoker ||= MyCounter.new([1, 2], {}, :destination_root => destination_root, :behavior => :revoke) end def invoke!(*args, &block) capture(:stdout) { invoker.insert_into_file(*args, &block) } end def revoke!(*args, &block) capture(:stdout) { revoker.insert_into_file(*args, &block) } end def file File.join(destination_root, "doc/README") end describe "#invoke!" do it "changes the file adding content after the flag" do invoke! "doc/README", "\nmore content", :after => "__start__" expect(File.read(file)).to eq("__start__\nmore content\nREADME\n__end__\n") end it "changes the file adding content before the flag" do invoke! "doc/README", "more content\n", :before => "__end__" expect(File.read(file)).to eq("__start__\nREADME\nmore content\n__end__\n") end it "appends content to the file if before and after arguments not provided" do invoke!("doc/README", "more content\n") expect(File.read(file)).to eq("__start__\nREADME\n__end__\nmore content\n") end it "does not change the file and logs the warning if flag not found in the file" do expect(invoke!("doc/README", "more content\n", after: "whatever")).to( eq("#{Thor::Actions::WARNINGS[:unchanged_no_flag]} doc/README\n") ) end it "accepts data as a block" do invoke! "doc/README", :before => "__end__" do "more content\n" end expect(File.read(file)).to eq("__start__\nREADME\nmore content\n__end__\n") end it "logs status" do expect(invoke!("doc/README", "\nmore content", :after => "__start__")).to eq(" insert doc/README\n") end it "does not change the file if pretending" do invoker :pretend => true invoke! "doc/README", "\nmore content", :after => "__start__" expect(File.read(file)).to eq("__start__\nREADME\n__end__\n") end it "does not change the file if already includes content" do invoke! "doc/README", :before => "__end__" do "more content\n" end expect(File.read(file)).to eq("__start__\nREADME\nmore content\n__end__\n") invoke! "doc/README", :before => "__end__" do "more content\n" end expect(File.read(file)).to eq("__start__\nREADME\nmore content\n__end__\n") end it "does not attempt to change the file if it doesn't exist - instead raises Thor::Error" do expect do invoke! "idontexist", :before => "something" do "any content" end end.to raise_error(Thor::Error, /does not appear to exist/) expect(File.exist?("idontexist")).to be_falsey end it "does not attempt to change the file if it doesn't exist and pretending" do expect do invoker :pretend => true invoke! "idontexist", :before => "something" do "any content" end end.not_to raise_error expect(File.exist?("idontexist")).to be_falsey end it "does change the file if already includes content and :force is true" do invoke! "doc/README", :before => "__end__" do "more content\n" end expect(File.read(file)).to eq("__start__\nREADME\nmore content\n__end__\n") invoke! "doc/README", :before => "__end__", :force => true do "more content\n" end expect(File.read(file)).to eq("__start__\nREADME\nmore content\nmore content\n__end__\n") end it "can insert chinese" do encoding_original = Encoding.default_external begin Encoding.default_external = Encoding.find("UTF-8") invoke! "doc/README.zh", "\n中文", :after => "__start__" expect(File.read(File.join(destination_root, "doc/README.zh"))).to eq("__start__\n中文\n说明\n__end__\n") ensure Encoding.default_external = encoding_original end end end describe "#revoke!" do it "subtracts the destination file after injection" do invoke! "doc/README", "\nmore content", :after => "__start__" revoke! "doc/README", "\nmore content", :after => "__start__" expect(File.read(file)).to eq("__start__\nREADME\n__end__\n") end it "subtracts the destination file before injection" do invoke! "doc/README", "more content\n", :before => "__start__" revoke! "doc/README", "more content\n", :before => "__start__" expect(File.read(file)).to eq("__start__\nREADME\n__end__\n") end it "subtracts even with double after injection" do invoke! "doc/README", "\nmore content", :after => "__start__" invoke! "doc/README", "\nanother stuff", :after => "__start__" revoke! "doc/README", "\nmore content", :after => "__start__" expect(File.read(file)).to eq("__start__\nanother stuff\nREADME\n__end__\n") end it "subtracts even with double before injection" do invoke! "doc/README", "more content\n", :before => "__start__" invoke! "doc/README", "another stuff\n", :before => "__start__" revoke! "doc/README", "more content\n", :before => "__start__" expect(File.read(file)).to eq("another stuff\n__start__\nREADME\n__end__\n") end it "subtracts when prepending" do invoke! "doc/README", "more content\n", :after => /\A/ invoke! "doc/README", "another stuff\n", :after => /\A/ revoke! "doc/README", "more content\n", :after => /\A/ expect(File.read(file)).to eq("another stuff\n__start__\nREADME\n__end__\n") end it "subtracts when appending" do invoke! "doc/README", "more content\n", :before => /\z/ invoke! "doc/README", "another stuff\n", :before => /\z/ revoke! "doc/README", "more content\n", :before => /\z/ expect(File.read(file)).to eq("__start__\nREADME\n__end__\nanother stuff\n") end it "shows progress information to the user" do invoke!("doc/README", "\nmore content", :after => "__start__") expect(revoke!("doc/README", "\nmore content", :after => "__start__")).to eq(" subtract doc/README\n") end end end thor-1.0.1/spec/actions_spec.rb000066400000000000000000000327201357617015300164320ustar00rootroot00000000000000require "helper" describe Thor::Actions do def runner(options = {}) @runner ||= MyCounter.new([1], options, :destination_root => destination_root) end def action(*args, &block) capture(:stdout) { runner.send(*args, &block) } end def file File.join(destination_root, "foo") end describe "on include" do it "adds runtime options to the base class" do expect(MyCounter.class_options.keys).to include(:pretend) expect(MyCounter.class_options.keys).to include(:force) expect(MyCounter.class_options.keys).to include(:quiet) expect(MyCounter.class_options.keys).to include(:skip) end end describe "#initialize" do it "has default behavior invoke" do expect(runner.behavior).to eq(:invoke) end it "can have behavior revoke" do expect(MyCounter.new([1], {}, :behavior => :revoke).behavior).to eq(:revoke) end it "when behavior is set to force, overwrite options" do runner = MyCounter.new([1], {:force => false, :skip => true}, :behavior => :force) expect(runner.behavior).to eq(:invoke) expect(runner.options.force).to be true expect(runner.options.skip).not_to be true end it "when behavior is set to skip, overwrite options" do runner = MyCounter.new([1], %w(--force), :behavior => :skip) expect(runner.behavior).to eq(:invoke) expect(runner.options.force).not_to be true expect(runner.options.skip).to be true end end describe "accessors" do describe "#destination_root=" do it "gets the current directory and expands the path to set the root" do base = MyCounter.new([1]) base.destination_root = "here" expect(base.destination_root).to eq(File.expand_path(File.join(File.dirname(__FILE__), "..", "here"))) end it "does not use the current directory if one is given" do root = File.expand_path("/") base = MyCounter.new([1]) base.destination_root = root expect(base.destination_root).to eq(root) end it "uses the current directory if none is given" do base = MyCounter.new([1]) expect(base.destination_root).to eq(File.expand_path(File.join(File.dirname(__FILE__), ".."))) end end describe "#relative_to_original_destination_root" do it "returns the path relative to the absolute root" do expect(runner.relative_to_original_destination_root(file)).to eq("foo") end it "does not remove dot if required" do expect(runner.relative_to_original_destination_root(file, false)).to eq("./foo") end it "always use the absolute root" do runner.inside("foo") do expect(runner.relative_to_original_destination_root(file)).to eq("foo") end end it "creates proper relative paths for absolute file location" do expect(runner.relative_to_original_destination_root("/test/file")).to eq("/test/file") end it "doesn't remove the root path from the absolute path if it is not at the begining" do runner.destination_root = "/app" expect(runner.relative_to_original_destination_root("/something/app/project")).to eq("/something/app/project") end it "doesn't removes the root path from the absolute path only if it is only the partial name of the directory" do runner.destination_root = "/app" expect(runner.relative_to_original_destination_root("/application/project")).to eq("/application/project") end it "removes the root path from the absolute path only once" do runner.destination_root = "/app" expect(runner.relative_to_original_destination_root("/app/app/project")).to eq("app/project") end it "does not fail with files containing regexp characters" do runner = MyCounter.new([1], {}, :destination_root => File.join(destination_root, "fo[o-b]ar")) expect(runner.relative_to_original_destination_root("bar")).to eq("bar") end describe "#source_paths_for_search" do it "add source_root to source_paths_for_search" do expect(MyCounter.source_paths_for_search).to include(File.expand_path("fixtures", File.dirname(__FILE__))) end it "keeps only current source root in source paths" do expect(ClearCounter.source_paths_for_search).to include(File.expand_path("fixtures/bundle", File.dirname(__FILE__))) expect(ClearCounter.source_paths_for_search).not_to include(File.expand_path("fixtures", File.dirname(__FILE__))) end it "customized source paths should be before source roots" do expect(ClearCounter.source_paths_for_search[0]).to eq(File.expand_path("fixtures/doc", File.dirname(__FILE__))) expect(ClearCounter.source_paths_for_search[1]).to eq(File.expand_path("fixtures/bundle", File.dirname(__FILE__))) end it "keeps inherited source paths at the end" do expect(ClearCounter.source_paths_for_search.last).to eq(File.expand_path("fixtures/broken", File.dirname(__FILE__))) end end end describe "#find_in_source_paths" do it "raises an error if source path is empty" do expect do A.new.find_in_source_paths("foo") end.to raise_error(Thor::Error, /Currently you have no source paths/) end it "finds a template inside the source path" do expect(runner.find_in_source_paths("doc")).to eq(File.expand_path("doc", source_root)) expect { runner.find_in_source_paths("README") }.to raise_error(Thor::Error, /Could not find "README" in any of your source paths./) new_path = File.join(source_root, "doc") runner.instance_variable_set(:@source_paths, nil) runner.source_paths.unshift(new_path) expect(runner.find_in_source_paths("README")).to eq(File.expand_path("README", new_path)) end end end describe "#inside" do it "executes the block inside the given folder" do runner.inside("foo") do expect(Dir.pwd).to eq(file) end end it "changes the base root" do runner.inside("foo") do expect(runner.destination_root).to eq(file) end end it "creates the directory if it does not exist" do runner.inside("foo") do expect(File.exist?(file)).to be true end end describe "when pretending" do it "no directories should be created" do runner.inside("bar", :pretend => true) {} expect(File.exist?("bar")).to be false end end describe "when verbose" do it "logs status" do expect(capture(:stdout) do runner.inside("foo", :verbose => true) {} end).to match(/inside foo/) end it "uses padding in next status" do expect(capture(:stdout) do runner.inside("foo", :verbose => true) do runner.say_status :cool, :padding end end).to match(/cool padding/) end it "removes padding after block" do expect(capture(:stdout) do runner.inside("foo", :verbose => true) {} runner.say_status :no, :padding end).to match(/no padding/) end end end describe "#in_root" do it "executes the block in the root folder" do runner.inside("foo") do runner.in_root { expect(Dir.pwd).to eq(destination_root) } end end it "changes the base root" do runner.inside("foo") do runner.in_root { expect(runner.destination_root).to eq(destination_root) } end end it "returns to the previous state" do runner.inside("foo") do runner.in_root {} expect(runner.destination_root).to eq(file) end end end describe "#apply" do before do @template = <<-TEMPLATE.dup @foo = "FOO" say_status :cool, :padding TEMPLATE allow(@template).to receive(:read).and_return(@template) @file = "/" allow(runner).to receive(:open).and_return(@template) end it "accepts a URL as the path" do @file = "http://gist.github.com/103208.txt" expect(runner).to receive(:open).with(@file, "Accept" => "application/x-thor-template").and_return(@template) action(:apply, @file) end it "accepts a secure URL as the path" do @file = "https://gist.github.com/103208.txt" expect(runner).to receive(:open).with(@file, "Accept" => "application/x-thor-template").and_return(@template) action(:apply, @file) end it "accepts a local file path with spaces" do @file = File.expand_path("fixtures/path with spaces", File.dirname(__FILE__)) expect(runner).to receive(:open).with(@file).and_return(@template) action(:apply, @file) end it "opens a file and executes its content in the instance binding" do action :apply, @file expect(runner.instance_variable_get("@foo")).to eq("FOO") end it "applies padding to the content inside the file" do expect(action(:apply, @file)).to match(/cool padding/) end it "logs its status" do expect(action(:apply, @file)).to match(/ apply #{@file}\n/) end it "does not log status" do content = action(:apply, @file, :verbose => false) expect(content).to match(/cool padding/) expect(content).not_to match(/apply http/) end end describe "#run" do describe "when not pretending" do before do expect(runner).to receive(:system).with("ls") end it "executes the command given" do action :run, "ls" end it "logs status" do expect(action(:run, "ls")).to eq(" run ls from \".\"\n") end it "does not log status if required" do expect(action(:run, "ls", :verbose => false)).to be_empty end it "accepts a color as status" do expect(runner.shell).to receive(:say_status).with(:run, 'ls from "."', :yellow) action :run, "ls", :verbose => :yellow end end describe "when pretending" do it "doesn't execute the command" do runner = MyCounter.new([1], %w(--pretend)) expect(runner).not_to receive(:system) runner.run("ls", :verbose => false) end end describe "when not capturing" do it "aborts when abort_on_failure is given and command fails" do expect { action :run, "false", :abort_on_failure => true }.to raise_error(SystemExit) end it "succeeds when abort_on_failure is given and command succeeds" do expect { action :run, "true", :abort_on_failure => true }.not_to raise_error end it "supports env option" do expect { action :run, "echo $BAR", :env => { "BAR" => "foo" } }.to output("foo\n").to_stdout_from_any_process end end describe "when capturing" do it "aborts when abort_on_failure is given, capture is given and command fails" do expect { action :run, "false", :abort_on_failure => true, :capture => true }.to raise_error(SystemExit) end it "succeeds when abort_on_failure is given and command succeeds" do expect { action :run, "true", :abort_on_failure => true, :capture => true }.not_to raise_error end it "supports env option" do silence(:stdout) do expect(runner.run "echo $BAR", :env => { "BAR" => "foo" }, :capture => true).to eq("foo\n") end end end context "exit_on_failure? is true" do before do allow(MyCounter).to receive(:exit_on_failure?).and_return(true) end it "aborts when command fails even if abort_on_failure is not given" do expect { action :run, "false" }.to raise_error(SystemExit) end it "does not abort when abort_on_failure is false even if the command fails" do expect { action :run, "false", :abort_on_failure => false }.not_to raise_error end end end describe "#run_ruby_script" do before do allow(Thor::Util).to receive(:ruby_command).and_return("/opt/jruby") expect(runner).to receive(:system).with("/opt/jruby script.rb") end it "executes the ruby script" do action :run_ruby_script, "script.rb" end it "logs status" do expect(action(:run_ruby_script, "script.rb")).to eq(" run jruby script.rb from \".\"\n") end it "does not log status if required" do expect(action(:run_ruby_script, "script.rb", :verbose => false)).to be_empty end end describe "#thor" do it "executes the thor command" do expect(runner).to receive(:system).with("thor list") action :thor, :list, :verbose => true end it "converts extra arguments to command arguments" do expect(runner).to receive(:system).with("thor list foo bar") action :thor, :list, "foo", "bar" end it "converts options hash to switches" do expect(runner).to receive(:system).with("thor list foo bar --foo") action :thor, :list, "foo", "bar", :foo => true expect(runner).to receive(:system).with("thor list --foo 1 2 3") action :thor, :list, :foo => [1, 2, 3] end it "logs status" do expect(runner).to receive(:system).with("thor list") expect(action(:thor, :list)).to eq(" run thor list from \".\"\n") end it "does not log status if required" do expect(runner).to receive(:system).with("thor list --foo 1 2 3") expect(action(:thor, :list, :foo => [1, 2, 3], :verbose => false)).to be_empty end it "captures the output when :capture is given" do expect(runner).to receive(:run).with("list", hash_including(:capture => true)) action :thor, :list, :capture => true end end end thor-1.0.1/spec/base_spec.rb000066400000000000000000000227311357617015300157050ustar00rootroot00000000000000require "helper" require "thor/base" class Amazing desc "hello", "say hello" def hello puts "Hello" end end describe Thor::Base do describe "#initialize" do it "sets arguments array" do base = MyCounter.new [1, 2] expect(base.first).to eq(1) expect(base.second).to eq(2) end it "sets arguments default values" do base = MyCounter.new [1] expect(base.second).to eq(2) end it "sets options default values" do base = MyCounter.new [1, 2] expect(base.options[:third]).to eq(3) end it "allows options to be given as symbols or strings" do base = MyCounter.new [1, 2], :third => 4 expect(base.options[:third]).to eq(4) base = MyCounter.new [1, 2], "third" => 4 expect(base.options[:third]).to eq(4) end it "creates options with indifferent access" do base = MyCounter.new [1, 2], :third => 3 expect(base.options["third"]).to eq(3) end it "creates options with magic predicates" do base = MyCounter.new [1, 2], :third => 3 expect(base.options.third).to eq(3) end end describe "#no_commands" do it "avoids methods being added as commands" do expect(MyScript.commands.keys).to include("animal") expect(MyScript.commands.keys).not_to include("this_is_not_a_command") expect(MyScript.commands.keys).not_to include("neither_is_this") end end describe "#argument" do it "sets a value as required and creates an accessor for it" do expect(MyCounter.start(%w(1 2 --third 3))[0]).to eq(1) expect(Scripts::MyScript.start(%w(zoo my_special_param --param=normal_param))).to eq("my_special_param") end it "does not set a value in the options hash" do expect(BrokenCounter.start(%w(1 2 --third 3))[0]).to be nil end end describe "#arguments" do it "returns the arguments for the class" do expect(MyCounter.arguments.size).to be(2) end end describe ":aliases" do it "supports string aliases without a dash prefix" do expect(MyCounter.start(%w(1 2 -z 3))[4]).to eq(3) end it "supports symbol aliases" do expect(MyCounter.start(%w(1 2 -y 3))[5]).to eq(3) expect(MyCounter.start(%w(1 2 -r 3))[5]).to eq(3) end end describe "#class_option" do it "sets options class wise" do expect(MyCounter.start(%w(1 2 --third 3))[2]).to eq(3) end it "does not create an accessor for it" do expect(BrokenCounter.start(%w(1 2 --third 3))[3]).to be false end end describe "#class_options" do it "sets default options overwriting superclass definitions" do options = Scripts::MyScript.class_options expect(options[:force]).not_to be_required end end describe "#remove_argument" do it "removes previously defined arguments from class" do expect(ClearCounter.arguments).to be_empty end it "undefine accessors if required" do expect(ClearCounter.new).not_to respond_to(:first) expect(ClearCounter.new).not_to respond_to(:second) end end describe "#remove_class_option" do it "removes previous defined class option" do expect(ClearCounter.class_options[:third]).to be nil end end describe "#class_options_help" do before do @content = capture(:stdout) { MyCounter.help(Thor::Base.shell.new) } end it "shows option's description" do expect(@content).to match(/# The third argument/) end it "shows usage with banner content" do expect(@content).to match(/\[\-\-third=THREE\]/) end it "shows default values below descriptions" do expect(@content).to match(/# Default: 3/) end it "shows options in different groups" do expect(@content).to match(/Options\:/) expect(@content).to match(/Runtime options\:/) expect(@content).to match(/\-p, \[\-\-pretend\]/) end it "use padding in options that do not have aliases" do expect(@content).to match(/^ -t, \[--third/) expect(@content).to match(/^ \[--fourth/) end it "allows extra options to be given" do hash = {"Foo" => B.class_options.values} content = capture(:stdout) { MyCounter.send(:class_options_help, Thor::Base.shell.new, hash) } expect(content).to match(/Foo options\:/) expect(content).to match(/--last-name=LAST_NAME/) end it "displays choices for enums" do content = capture(:stdout) { Enum.help(Thor::Base.shell.new) } expect(content).to match(/Possible values\: apple, banana/) end end describe "#namespace" do it "returns the default class namespace" do expect(Scripts::MyScript.namespace).to eq("scripts:my_script") end it "sets a namespace to the class" do expect(Scripts::MyDefaults.namespace).to eq("default") end end describe "#group" do it "sets a group" do expect(MyScript.group).to eq("script") end it "inherits the group from parent" do expect(MyChildScript.group).to eq("script") end it "defaults to standard if no group is given" do expect(Amazing.group).to eq("standard") end end describe "#subclasses" do it "tracks its subclasses in an Array" do expect(Thor::Base.subclasses).to include(MyScript) expect(Thor::Base.subclasses).to include(MyChildScript) expect(Thor::Base.subclasses).to include(Scripts::MyScript) end end describe "#subclass_files" do it "returns tracked subclasses, grouped by the files they come from" do thorfile = File.join(File.dirname(__FILE__), "fixtures", "script.thor") expect(Thor::Base.subclass_files[File.expand_path(thorfile)]).to eq([ MyScript, MyScript::AnotherScript, MyChildScript, Barn, PackageNameScript, Scripts::MyScript, Scripts::MyDefaults, Scripts::ChildDefault, Scripts::Arities ]) end it "tracks a single subclass across multiple files" do thorfile = File.join(File.dirname(__FILE__), "fixtures", "command.thor") expect(Thor::Base.subclass_files[File.expand_path(thorfile)]).to include(Amazing) expect(Thor::Base.subclass_files[File.expand_path(__FILE__)]).to include(Amazing) end end describe "#commands" do it "returns a list with all commands defined in this class" do expect(MyChildScript.new).to respond_to("animal") expect(MyChildScript.commands.keys).to include("animal") end it "raises an error if a command with reserved word is defined" do expect do klass = Class.new(Thor::Group) klass.class_eval "def shell; end" end.to raise_error(RuntimeError, /"shell" is a Thor reserved word and cannot be defined as command/) end end describe "#all_commands" do it "returns a list with all commands defined in this class plus superclasses" do expect(MyChildScript.new).to respond_to("foo") expect(MyChildScript.all_commands.keys).to include("foo") end end describe "#remove_command" do it "removes the command from its commands hash" do expect(MyChildScript.all_commands.keys).not_to include("name_with_dashes") expect(MyChildScript.commands.keys).not_to include("boom") end it "undefines the method if desired" do expect(MyChildScript.new).not_to respond_to("boom") end end describe "#from_superclass" do it "does not send a method to the superclass if the superclass does not respond to it" do expect(MyCounter.get_from_super).to eq(13) end end describe "#start" do it "raises an error instead of rescuing if THOR_DEBUG=1 is given" do begin ENV["THOR_DEBUG"] = "1" expect do MyScript.start %w(what --debug) end.to raise_error(Thor::UndefinedCommandError, 'Could not find command "what" in "my_script" namespace.') ensure ENV["THOR_DEBUG"] = nil end end it "raises an error instead of rescuing if :debug option is given" do expect do MyScript.start %w(what), :debug => true end.to raise_error(Thor::UndefinedCommandError, 'Could not find command "what" in "my_script" namespace.') end it "suggests commands that are similar if there is a typo" do expected = "Could not find command \"paintz\" in \"barn\" namespace.\n" expected << "Did you mean? \"paint\"\n" if Thor::Correctable expect(capture(:stderr) { Barn.start(%w(paintz)) }).to eq(expected) end it "does not steal args" do args = %w(foo bar --force true) MyScript.start(args) expect(args).to eq(%w(foo bar --force true)) end it "checks unknown options" do expect(capture(:stderr) do MyScript.start(%w(foo bar --force true --unknown baz)) end.strip).to eq("Unknown switches \"--unknown\"") end it "checks unknown options except specified" do expect(capture(:stderr) do expect(MyScript.start(%w(with_optional NAME --omg --invalid))).to eq(["NAME", {}, %w(--omg --invalid)]) end.strip).to be_empty end end describe "attr_*" do it "does not add attr_reader as a command" do expect(capture(:stderr) { MyScript.start(%w(another_attribute)) }).to match(/Could not find/) end it "does not add attr_writer as a command" do expect(capture(:stderr) { MyScript.start(%w(another_attribute= foo)) }).to match(/Could not find/) end it "does not add attr_accessor as a command" do expect(capture(:stderr) { MyScript.start(["some_attribute"]) }).to match(/Could not find/) expect(capture(:stderr) { MyScript.start(["some_attribute=", "foo"]) }).to match(/Could not find/) end end end thor-1.0.1/spec/command_spec.rb000066400000000000000000000057261357617015300164160ustar00rootroot00000000000000require "helper" describe Thor::Command do def command(options = {}, usage = "can_has") options.each do |key, value| options[key] = Thor::Option.parse(key, value) end @command ||= Thor::Command.new(:can_has, "I can has cheezburger", "I can has cheezburger\nLots and lots of it", usage, options) end describe "#formatted_usage" do it "includes namespace within usage" do object = Struct.new(:namespace, :arguments).new("foo", []) expect(command(:bar => :required).formatted_usage(object)).to eq("foo:can_has --bar=BAR") end it "includes subcommand name within subcommand usage" do object = Struct.new(:namespace, :arguments).new("main:foo", []) expect(command(:bar => :required).formatted_usage(object, false, true)).to eq("foo can_has --bar=BAR") end it "removes default from namespace" do object = Struct.new(:namespace, :arguments).new("default:foo", []) expect(command(:bar => :required).formatted_usage(object)).to eq(":foo:can_has --bar=BAR") end it "injects arguments into usage" do options = {:required => true, :type => :string} object = Struct.new(:namespace, :arguments).new("foo", [Thor::Argument.new(:bar, options)]) expect(command(:foo => :required).formatted_usage(object)).to eq("foo:can_has BAR --foo=FOO") end it "allows multiple usages" do object = Struct.new(:namespace, :arguments).new("foo", []) expect(command({ :bar => :required }, ["can_has FOO", "can_has BAR"]).formatted_usage(object, false)).to eq("can_has FOO --bar=BAR\ncan_has BAR --bar=BAR") end end describe "#dynamic" do it "creates a dynamic command with the given name" do expect(Thor::DynamicCommand.new("command").name).to eq("command") expect(Thor::DynamicCommand.new("command").description).to eq("A dynamically-generated command") expect(Thor::DynamicCommand.new("command").usage).to eq("command") expect(Thor::DynamicCommand.new("command").options).to eq({}) end it "does not invoke an existing method" do dub = double expect(dub.class).to receive(:handle_no_command_error).with("to_s") Thor::DynamicCommand.new("to_s").run(dub) end end describe "#dup" do it "dup options hash" do command = Thor::Command.new("can_has", nil, nil, nil, :foo => true, :bar => :required) command.dup.options.delete(:foo) expect(command.options[:foo]).to be end end describe "#run" do it "runs a command by calling a method in the given instance" do dub = double expect(dub).to receive(:can_has) { |*args| args } expect(command.run(dub, [1, 2, 3])).to eq([1, 2, 3]) end it "raises an error if the method to be invoked is private" do klass = Class.new do def self.handle_no_command_error(name) name end def can_has "fail" end private :can_has end expect(command.run(klass.new)).to eq("can_has") end end end thor-1.0.1/spec/core_ext/000077500000000000000000000000001357617015300152375ustar00rootroot00000000000000thor-1.0.1/spec/core_ext/hash_with_indifferent_access_spec.rb000066400000000000000000000052671357617015300244640ustar00rootroot00000000000000require "helper" require "thor/core_ext/hash_with_indifferent_access" describe Thor::CoreExt::HashWithIndifferentAccess do before do @hash = Thor::CoreExt::HashWithIndifferentAccess.new :foo => "bar", "baz" => "bee", :force => true end it "has values accessible by either strings or symbols" do expect(@hash["foo"]).to eq("bar") expect(@hash[:foo]).to eq("bar") expect(@hash.values_at(:foo, :baz)).to eq(%w(bar bee)) expect(@hash.delete(:foo)).to eq("bar") end it "supports fetch" do expect(@hash.fetch("foo")).to eq("bar") expect(@hash.fetch("foo", nil)).to eq("bar") expect(@hash.fetch(:foo)).to eq("bar") expect(@hash.fetch(:foo, nil)).to eq("bar") expect(@hash.fetch("baz")).to eq("bee") expect(@hash.fetch("baz", nil)).to eq("bee") expect(@hash.fetch(:baz)).to eq("bee") expect(@hash.fetch(:baz, nil)).to eq("bee") expect { @hash.fetch(:missing) }.to raise_error(IndexError) expect(@hash.fetch(:missing, :found)).to eq(:found) end it "has key checkable by either strings or symbols" do expect(@hash.key?("foo")).to be true expect(@hash.key?(:foo)).to be true expect(@hash.key?("nothing")).to be false expect(@hash.key?(:nothing)).to be false end it "handles magic boolean predicates" do expect(@hash.force?).to be true expect(@hash.foo?).to be true expect(@hash.nothing?).to be false end it "handles magic comparisons" do expect(@hash.foo?("bar")).to be true expect(@hash.foo?("bee")).to be false end it "maps methods to keys" do expect(@hash.foo).to eq(@hash["foo"]) end it "merges keys independent if they are symbols or strings" do @hash["force"] = false @hash[:baz] = "boom" expect(@hash[:force]).to eq(false) expect(@hash["baz"]).to eq("boom") end it "creates a new hash by merging keys independent if they are symbols or strings" do other = @hash.merge("force" => false, :baz => "boom") expect(other[:force]).to eq(false) expect(other["baz"]).to eq("boom") end it "converts to a traditional hash" do expect(@hash.to_hash.class).to eq(Hash) expect(@hash).to eq("foo" => "bar", "baz" => "bee", "force" => true) end it "handles reverse_merge" do other = {:foo => "qux", "boo" => "bae"} new_hash = @hash.reverse_merge(other) expect(@hash.object_id).not_to eq(new_hash.object_id) expect(new_hash[:foo]).to eq("bar") expect(new_hash[:boo]).to eq("bae") end it "handles reverse_merge!" do other = {:foo => "qux", "boo" => "bae"} new_hash = @hash.reverse_merge!(other) expect(@hash.object_id).to eq(new_hash.object_id) expect(new_hash[:foo]).to eq("bar") expect(new_hash[:boo]).to eq("bae") end end thor-1.0.1/spec/exit_condition_spec.rb000066400000000000000000000006621357617015300200110ustar00rootroot00000000000000require "helper" require "thor/base" describe "Exit conditions" do it "exits 0, not bubble up EPIPE, if EPIPE is raised" do epiped = false command = Class.new(Thor) do desc "my_action", "testing EPIPE" define_method :my_action do epiped = true raise Errno::EPIPE end end expect { command.start(["my_action"]) }.to raise_error(SystemExit) expect(epiped).to eq(true) end end thor-1.0.1/spec/fixtures/000077500000000000000000000000001357617015300153005ustar00rootroot00000000000000thor-1.0.1/spec/fixtures/application.rb000066400000000000000000000000351357617015300201260ustar00rootroot00000000000000class Application < Base end thor-1.0.1/spec/fixtures/application_helper.rb000066400000000000000000000000351357617015300214650ustar00rootroot00000000000000module ApplicationHelper end thor-1.0.1/spec/fixtures/app{1}/000077500000000000000000000000001357617015300165315ustar00rootroot00000000000000thor-1.0.1/spec/fixtures/app{1}/README000066400000000000000000000000311357617015300174030ustar00rootroot00000000000000__start__ README __end__ thor-1.0.1/spec/fixtures/command.thor000066400000000000000000000004461357617015300176200ustar00rootroot00000000000000# module: random class Amazing < Thor def self.exit_on_failure? false end desc "describe NAME", "say that someone is amazing" method_options :forcefully => :boolean def describe(name, opts) ret = "#{name} is amazing" puts opts["forcefully"] ? ret.upcase : ret end end thor-1.0.1/spec/fixtures/doc/000077500000000000000000000000001357617015300160455ustar00rootroot00000000000000thor-1.0.1/spec/fixtures/doc/%file_name%.rb.tt000066400000000000000000000000231357617015300210440ustar00rootroot00000000000000FOO = <%= "FOO" %> thor-1.0.1/spec/fixtures/doc/COMMENTER000066400000000000000000000001541357617015300173210ustar00rootroot00000000000000__start__ # greenblue # # yellowblue #yellowred #greenred orange purple ind#igo # ind#igo __end__ thor-1.0.1/spec/fixtures/doc/README000066400000000000000000000000311357617015300167170ustar00rootroot00000000000000__start__ README __end__ thor-1.0.1/spec/fixtures/doc/README.zh000066400000000000000000000000311357617015300173370ustar00rootroot00000000000000__start__ 说明 __end__ thor-1.0.1/spec/fixtures/doc/block_helper.rb000066400000000000000000000000411357617015300210160ustar00rootroot00000000000000<% world do -%> Hello <% end -%> thor-1.0.1/spec/fixtures/doc/components/000077500000000000000000000000001357617015300202325ustar00rootroot00000000000000thor-1.0.1/spec/fixtures/doc/components/.empty_directory000066400000000000000000000000001357617015300234430ustar00rootroot00000000000000thor-1.0.1/spec/fixtures/doc/config.rb000066400000000000000000000000311357617015300176310ustar00rootroot00000000000000class <%= @klass %>; end thor-1.0.1/spec/fixtures/doc/config.yaml.tt000066400000000000000000000000211357617015300206150ustar00rootroot00000000000000--- Hi from yaml thor-1.0.1/spec/fixtures/doc/excluding/000077500000000000000000000000001357617015300200275ustar00rootroot00000000000000thor-1.0.1/spec/fixtures/doc/excluding/%file_name%.rb.tt000066400000000000000000000000231357617015300230260ustar00rootroot00000000000000BAR = <%= "BAR" %> thor-1.0.1/spec/fixtures/enum.thor000066400000000000000000000003101357617015300171340ustar00rootroot00000000000000class Enum < Thor::Group include Thor::Actions desc "snack" class_option "fruit", :aliases => "-f", :type => :string, :enum => %w(apple banana) def snack puts options['fruit'] end end thor-1.0.1/spec/fixtures/exit_status.thor000066400000000000000000000004141357617015300205510ustar00rootroot00000000000000require "thor" class ExitStatus < Thor def self.exit_on_failure? true end desc "error", "exit with a planned error" def error raise Thor::Error.new("planned error") end desc "ok", "exit with no error" def ok end end ExitStatus.start(ARGV) thor-1.0.1/spec/fixtures/group.thor000066400000000000000000000045361357617015300173420ustar00rootroot00000000000000class MyCounter < Thor::Group include Thor::Actions add_runtime_options! def self.exit_on_failure? false end def self.get_from_super from_superclass(:get_from_super, 13) end source_root File.expand_path(File.dirname(__FILE__)) source_paths << File.expand_path("broken", File.dirname(__FILE__)) argument :first, :type => :numeric argument :second, :type => :numeric, :default => 2 class_option :third, :type => :numeric, :desc => "The third argument", :default => 3, :banner => "THREE", :aliases => "-t" class_option :fourth, :type => :numeric, :desc => "The fourth argument" class_option :simple, :type => :numeric, :aliases => 'z' class_option :symbolic, :type => :numeric, :aliases => [:y, :r] desc <<-FOO Description: This generator runs three commands: one, two and three. FOO def one first end def two second end def three options[:third] end def four options[:fourth] end def five options[:simple] end def six options[:symbolic] end def self.inherited(base) super base.source_paths.unshift(File.expand_path(File.join(File.dirname(__FILE__), "doc"))) end no_commands do def world(&block) result = capture(&block) concat(result.strip + " world!") end end end class ClearCounter < MyCounter remove_argument :first, :second, :undefine => true remove_class_option :third def self.source_root File.expand_path(File.join(File.dirname(__FILE__), "bundle")) end end class BrokenCounter < MyCounter namespace "app:broken:counter" class_option :fail, :type => :boolean, :default => false class << self undef_method :source_root end def one options[:first] end def four respond_to?(:fail) end def five options[:fail] ? this_method_does_not_exist : 5 end end class WhinyGenerator < Thor::Group include Thor::Actions def self.source_root File.expand_path(File.dirname(__FILE__)) end def wrong_arity(required) end end class CommandConflict < Thor::Group desc "A group with the same name as a default command" def group puts "group" end end class ParentGroup < Thor::Group private def foo "foo" end def baz(name = 'baz') name end end class ChildGroup < ParentGroup def bar "bar" end public_command :foo, :baz end thor-1.0.1/spec/fixtures/help.thor000066400000000000000000000002761357617015300171330ustar00rootroot00000000000000Bundler.require :development, :default class Help < Thor desc :bugs, "ALL THE BUGZ!" option "--not_help", :type => :boolean def bugs puts "Invoked!" end end Help.start(ARGV) thor-1.0.1/spec/fixtures/invoke.thor000066400000000000000000000036541357617015300175010ustar00rootroot00000000000000class A < Thor include Thor::Actions desc "one", "invoke one" def one p 1 invoke :two invoke :three end desc "two", "invoke two" def two p 2 invoke :three end desc "three", "invoke three" def three p 3 end desc "four", "invoke four" def four p 4 invoke "defined:five" end desc "five N", "check if number is equal 5" def five(number) number == 5 end desc "invoker", "invoke a b command" def invoker(*args) invoke :b, :one, ["Jose"] end end class B < Thor class_option :last_name, :type => :string desc "one FIRST_NAME", "invoke one" def one(first_name) "#{options.last_name}, #{first_name}" end desc "two", "invoke two" def two options end desc "three", "invoke three" def three self end desc "four", "invoke four" option :defaulted_value, :type => :string, :default => 'default' def four options.defaulted_value end end class C < Thor::Group include Thor::Actions def one p 1 end def two p 2 end def three p 3 end end class Defined < Thor::Group class_option :unused, :type => :boolean, :desc => "This option has no use" def one p 1 invoke "a:two" invoke "a:three" invoke "a:four" invoke "defined:five" end def five p 5 end def print_status say_status :finished, :counting end end class E < Thor::Group invoke Defined end class F < Thor::Group invoke "b:one" do |instance, klass, command| instance.invoke klass, command, [ "Jose" ], :last_name => "Valim" end end class G < Thor::Group class_option :invoked, :type => :string, :default => "defined" invoke_from_option :invoked end class H < Thor::Group class_option :defined, :type => :boolean, :default => true invoke_from_option :defined end class I < Thor desc "two", "Two" def two current_command_chain end end class J < Thor desc "i", "I" subcommand :one, I end thor-1.0.1/spec/fixtures/path with spaces000066400000000000000000000000001357617015300203400ustar00rootroot00000000000000thor-1.0.1/spec/fixtures/preserve/000077500000000000000000000000001357617015300171335ustar00rootroot00000000000000thor-1.0.1/spec/fixtures/preserve/%filename%.sh000077500000000000000000000000221357617015300213560ustar00rootroot00000000000000#!/bin/sh exit 0 thor-1.0.1/spec/fixtures/preserve/script.sh000077500000000000000000000000221357617015300207700ustar00rootroot00000000000000#!/bin/sh exit 0 thor-1.0.1/spec/fixtures/script.thor000066400000000000000000000120271357617015300175040ustar00rootroot00000000000000class MyScript < Thor check_unknown_options! :except => :with_optional def self.exit_on_failure? false end attr_accessor :some_attribute attr_writer :another_attribute attr_reader :another_attribute private attr_reader :private_attribute public group :script default_command :example_default_command map "-T" => :animal, ["-f", "--foo"] => :foo map "animal_prison" => "zoo" desc "zoo", "zoo around" def zoo true end desc "animal TYPE", "horse around" no_commands do no_commands do def this_is_not_a_command end end def neither_is_this end end def animal(type) [type] end map "hid" => "hidden" desc "hidden TYPE", "this is hidden", :hide => true def hidden(type) [type] end map "fu" => "zoo" desc "foo BAR", < :boolean, :desc => "Force to do some fooing" def foo(bar) [bar, options] end method_option :all, :desc => "Do bazing for all the things" desc ["baz THING", "baz --all"], "super cool" def baz(thing = nil) raise if thing.nil? && !options.include?(:all) end desc "example_default_command", "example!" method_options :with => :string def example_default_command options.empty? ? "default command" : options end desc "call_myself_with_wrong_arity", "get the right error" def call_myself_with_wrong_arity call_myself_with_wrong_arity(4) end desc "call_unexistent_method", "Call unexistent method inside a command" def call_unexistent_method boom! end desc "long_description", "a" * 80 long_desc <<-D This is a really really really long description. Here you go. So very long. It even has two paragraphs. D def long_description end desc "name-with-dashes", "Ensure normalization of command names" def name_with_dashes end method_options :all => :boolean method_option :lazy, :lazy_default => "yes" method_option :lazy_numeric, :type => :numeric, :lazy_default => 42 method_option :lazy_array, :type => :array, :lazy_default => %w[eat at joes] method_option :lazy_hash, :type => :hash, :lazy_default => {'swedish' => 'meatballs'} desc "with_optional NAME", "invoke with optional name" def with_optional(name=nil, *args) [name, options, args] end class AnotherScript < Thor desc "baz", "do some bazing" def baz end end desc "send", "send as a command name" def send true end private def method_missing(meth, *args) if meth == :boom! super else [meth, args] end end desc "what", "what" def what end end class MyChildScript < MyScript remove_command :name_with_dashes method_options :force => :boolean, :param => :numeric def initialize(*args) super end desc "zoo", "zoo around" method_options :param => :required def zoo options end desc "animal TYPE", "horse around" def animal(type) [type, options] end method_option :other, :type => :string, :default => "method default", :for => :animal desc "animal KIND", "fish around", :for => :animal desc "boom", "explodes everything" def boom end remove_command :boom, :undefine => true end class Barn < Thor def self.exit_on_failure? false end desc "open [ITEM]", "open the barn door" def open(item = nil) if item == "shotgun" puts "That's going to leave a mark." else puts "Open sesame!" end end desc "paint [COLOR]", "paint the barn" method_option :coats, :type => :numeric, :default => 2, :desc => 'how many coats of paint' def paint(color='red') puts "#{options[:coats]} coats of #{color} paint" end end class PackageNameScript < Thor package_name "Baboon" end module Scripts class MyScript < MyChildScript argument :accessor, :type => :string class_options :force => :boolean method_option :new_option, :type => :string, :for => :example_default_command def zoo self.accessor end end class MyDefaults < Thor check_unknown_options! def self.exit_on_failure? false end namespace :default desc "cow", "prints 'moo'" def cow puts "moo" end desc "command_conflict", "only gets called when prepended with a colon" def command_conflict puts "command" end desc "barn", "commands to manage the barn" subcommand "barn", Barn end class ChildDefault < Thor namespace "default:child" end class Arities < Thor def self.exit_on_failure? false end desc "zero_args", "takes zero args" def zero_args end desc "one_arg ARG", "takes one arg" def one_arg(arg) end desc "two_args ARG1 ARG2", "takes two args" def two_args(arg1, arg2) end desc "optional_arg [ARG]", "takes an optional arg" def optional_arg(arg='default') end desc ["multiple_usages ARG --foo", "multiple_usages ARG --bar"], "takes mutually exclusive combinations of args and flags" def multiple_usages(arg) end end end thor-1.0.1/spec/fixtures/subcommand.thor000066400000000000000000000004121357617015300203230ustar00rootroot00000000000000module TestSubcommands class Subcommand < Thor desc "print_opt", "My method" def print_opt print options["opt"] end end class Parent < Thor class_option "opt" desc "sub", "My subcommand" subcommand "sub", Subcommand end end thor-1.0.1/spec/fixtures/template/000077500000000000000000000000001357617015300171135ustar00rootroot00000000000000thor-1.0.1/spec/fixtures/template/bad_config.yaml.tt000066400000000000000000000000541357617015300224770ustar00rootroot00000000000000--- Hi from yaml <%= unresolved_variable %> thor-1.0.1/spec/fixtures/verbose.thor000066400000000000000000000001321357617015300176370ustar00rootroot00000000000000#!/usr/bin/ruby $VERBOSE = true require 'thor' class Test < Thor end Test.start(ARGV) thor-1.0.1/spec/group_spec.rb000066400000000000000000000157451357617015300161360ustar00rootroot00000000000000require "helper" describe Thor::Group do describe "command" do it "allows to use private methods from parent class as commands" do expect(ChildGroup.start).to eq(%w(bar foo baz)) expect(ChildGroup.new.baz("bar")).to eq("bar") end end describe "#start" do it "invokes all the commands under the Thor group" do expect(MyCounter.start(%w(1 2 --third 3))).to eq([1, 2, 3, nil, nil, nil]) end it "uses argument's default value" do expect(MyCounter.start(%w(1 --third 3))).to eq([1, 2, 3, nil, nil, nil]) end it "invokes all the commands in the Thor group and its parents" do expect(BrokenCounter.start(%w(1 2 --third 3))).to eq([nil, 2, 3, false, 5, nil]) end it "raises an error if a required argument is added after a non-required" do expect do MyCounter.argument(:foo, :type => :string) end.to raise_error(ArgumentError, 'You cannot have "foo" as required argument after the non-required argument "second".') end it "raises when an exception happens within the command call" do expect { BrokenCounter.start(%w(1 2 --fail)) }.to raise_error(NameError, /undefined local variable or method `this_method_does_not_exist'/) end it "raises an error when a Thor group command expects arguments" do expect { WhinyGenerator.start }.to raise_error(ArgumentError, /thor wrong_arity takes 1 argument, but it should not/) end it "invokes help message if any of the shortcuts are given" do expect(MyCounter).to receive(:help) MyCounter.start(%w(-h)) end end describe "#desc" do it "sets the description for a given class" do expect(MyCounter.desc).to eq("Description:\n This generator runs three commands: one, two and three.\n") end it "can be inherited" do expect(BrokenCounter.desc).to eq("Description:\n This generator runs three commands: one, two and three.\n") end it "can be nil" do expect(WhinyGenerator.desc).to be nil end end describe "#help" do before do @content = capture(:stdout) { MyCounter.help(Thor::Base.shell.new) } end it "provides usage information" do expect(@content).to match(/my_counter N \[N\]/) end it "shows description" do expect(@content).to match(/Description:/) expect(@content).to match(/This generator runs three commands: one, two and three./) end it "shows options information" do expect(@content).to match(/Options/) expect(@content).to match(/\[\-\-third=THREE\]/) end end describe "#invoke" do before do @content = capture(:stdout) { E.start } end it "allows to invoke a class from the class binding" do expect(@content).to match(/1\n2\n3\n4\n5\n/) end it "shows invocation information to the user" do expect(@content).to match(/invoke Defined/) end it "uses padding on status generated by the invoked class" do expect(@content).to match(/finished counting/) end it "allows invocation to be configured with blocks" do capture(:stdout) do expect(F.start).to eq(["Valim, Jose"]) end end it "shows invoked options on help" do content = capture(:stdout) { E.help(Thor::Base.shell.new) } expect(content).to match(/Defined options:/) expect(content).to match(/\[--unused\]/) expect(content).to match(/# This option has no use/) end end describe "#invoke_from_option" do describe "with default type" do before do @content = capture(:stdout) { G.start } end it "allows to invoke a class from the class binding by a default option" do expect(@content).to match(/1\n2\n3\n4\n5\n/) end it "does not invoke if the option is nil" do expect(capture(:stdout) { G.start(%w(--skip-invoked)) }).not_to match(/invoke/) end it "prints a message if invocation cannot be found" do content = capture(:stdout) { G.start(%w(--invoked unknown)) } expect(content).to match(/error unknown \[not found\]/) end it "allows to invoke a class from the class binding by the given option" do error = nil content = capture(:stdout) do error = capture(:stderr) do G.start(%w(--invoked e)) end end expect(content).to match(/invoke e/) expect(error).to match(/ERROR: "thor two" was called with arguments/) end it "shows invocation information to the user" do expect(@content).to match(/invoke defined/) end it "uses padding on status generated by the invoked class" do expect(@content).to match(/finished counting/) end it "shows invoked options on help" do content = capture(:stdout) { G.help(Thor::Base.shell.new) } expect(content).to match(/defined options:/) expect(content).to match(/\[--unused\]/) expect(content).to match(/# This option has no use/) end end describe "with boolean type" do before do @content = capture(:stdout) { H.start } end it "allows to invoke a class from the class binding by a default option" do expect(@content).to match(/1\n2\n3\n4\n5\n/) end it "does not invoke if the option is false" do expect(capture(:stdout) { H.start(%w(--no-defined)) }).not_to match(/invoke/) end it "shows invocation information to the user" do expect(@content).to match(/invoke defined/) end it "uses padding on status generated by the invoked class" do expect(@content).to match(/finished counting/) end it "shows invoked options on help" do content = capture(:stdout) { H.help(Thor::Base.shell.new) } expect(content).to match(/defined options:/) expect(content).to match(/\[--unused\]/) expect(content).to match(/# This option has no use/) end end end describe "edge-cases" do it "can handle boolean options followed by arguments" do klass = Class.new(Thor::Group) do desc "say hi to name" argument :name, :type => :string class_option :loud, :type => :boolean def hi self.name = name.upcase if options[:loud] "Hi #{name}" end end expect(klass.start(%w(jose))).to eq(["Hi jose"]) expect(klass.start(%w(jose --loud))).to eq(["Hi JOSE"]) expect(klass.start(%w(--loud jose))).to eq(["Hi JOSE"]) end it "provides extra args as `args`" do klass = Class.new(Thor::Group) do desc "say hi to name" argument :name, :type => :string class_option :loud, :type => :boolean def hi self.name = name.upcase if options[:loud] out = "Hi #{name}" out << ": " << args.join(", ") unless args.empty? out end end expect(klass.start(%w(jose))).to eq(["Hi jose"]) expect(klass.start(%w(jose --loud))).to eq(["Hi JOSE"]) expect(klass.start(%w(--loud jose))).to eq(["Hi JOSE"]) end end end thor-1.0.1/spec/helper.rb000066400000000000000000000037671357617015300152500ustar00rootroot00000000000000$TESTING = true require "simplecov" require "coveralls" SimpleCov.formatters = [SimpleCov::Formatter::HTMLFormatter, Coveralls::SimpleCov::Formatter] SimpleCov.start do add_filter "/spec" minimum_coverage(90) end $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "..", "lib")) require "thor" require "thor/group" require "stringio" require "rdoc" require "rspec" require "diff/lcs" # You need diff/lcs installed to run specs (but not to run Thor). require "webmock/rspec" WebMock.disable_net_connect!(:allow => "coveralls.io") # Set shell to basic ENV["THOR_COLUMNS"] = "10000" $0 = "thor" $thor_runner = true ARGV.clear Thor::Base.shell = Thor::Shell::Basic # Load fixtures load File.join(File.dirname(__FILE__), "fixtures", "enum.thor") load File.join(File.dirname(__FILE__), "fixtures", "group.thor") load File.join(File.dirname(__FILE__), "fixtures", "invoke.thor") load File.join(File.dirname(__FILE__), "fixtures", "script.thor") load File.join(File.dirname(__FILE__), "fixtures", "subcommand.thor") load File.join(File.dirname(__FILE__), "fixtures", "command.thor") RSpec.configure do |config| config.before do ARGV.replace [] end config.expect_with :rspec do |c| c.syntax = :expect end def capture(stream) begin stream = stream.to_s eval "$#{stream} = StringIO.new" yield result = eval("$#{stream}").string ensure eval("$#{stream} = #{stream.upcase}") end result end def source_root File.join(File.dirname(__FILE__), "fixtures") end def destination_root File.join(File.dirname(__FILE__), "sandbox") end # This code was adapted from Ruby on Rails, available under MIT-LICENSE # Copyright (c) 2004-2013 David Heinemeier Hansson def silence_warnings old_verbose = $VERBOSE $VERBOSE = nil yield ensure $VERBOSE = old_verbose end # true if running on windows, used for conditional spec skips # # @return [TrueClass/FalseClass] def windows? Gem.win_platform? end alias silence capture end thor-1.0.1/spec/invocation_spec.rb000066400000000000000000000073411357617015300171440ustar00rootroot00000000000000require "helper" require "thor/base" describe Thor::Invocation do describe "#invoke" do it "invokes a command inside another command" do expect(capture(:stdout) { A.new.invoke(:two) }).to eq("2\n3\n") end it "invokes a command just once" do expect(capture(:stdout) { A.new.invoke(:one) }).to eq("1\n2\n3\n") end it "invokes a command just once even if they belongs to different classes" do expect(capture(:stdout) { Defined.new.invoke(:one) }).to eq("1\n2\n3\n4\n5\n") end it "invokes a command with arguments" do expect(A.new.invoke(:five, [5])).to be true expect(A.new.invoke(:five, [7])).to be false end it "invokes the default command if none is given to a Thor class" do content = capture(:stdout) { A.new.invoke("b") } expect(content).to match(/Commands/) expect(content).to match(/LAST_NAME/) end it "accepts a class as argument without a command to invoke" do content = capture(:stdout) { A.new.invoke(B) } expect(content).to match(/Commands/) expect(content).to match(/LAST_NAME/) end it "accepts a class as argument with a command to invoke" do base = A.new([], :last_name => "Valim") expect(base.invoke(B, :one, %w(Jose))).to eq("Valim, Jose") end it "allows customized options to be given" do base = A.new([], :last_name => "Wrong") expect(base.invoke(B, :one, %w(Jose), :last_name => "Valim")).to eq("Valim, Jose") end it "reparses options in the new class" do expect(A.start(%w(invoker --last-name Valim))).to eq("Valim, Jose") end it "shares initialize options with invoked class" do expect(A.new([], :foo => :bar).invoke("b:two")).to eq("foo" => :bar) end it "uses default options from invoked class if no matching arguments are given" do expect(A.new([]).invoke("b:four")).to eq("default") end it "overrides default options if options are passed to the invoker" do expect(A.new([], :defaulted_value => "not default").invoke("b:four")).to eq("not default") end it "returns the command chain" do expect(I.new.invoke("two")).to eq([:two]) expect(J.start(%w(one two))).to eq([:one, :two]) end it "dump configuration values to be used in the invoked class" do base = A.new expect(base.invoke("b:three").shell).to eq(base.shell) end it "allow extra configuration values to be given" do base = A.new shell = Thor::Base.shell.new expect(base.invoke("b:three", [], {}, :shell => shell).shell).to eq(shell) end it "invokes a Thor::Group and all of its commands" do expect(capture(:stdout) { A.new.invoke(:c) }).to eq("1\n2\n3\n") end it "does not invoke a Thor::Group twice" do base = A.new silence(:stdout) { base.invoke(:c) } expect(capture(:stdout) { base.invoke(:c) }).to be_empty end it "does not invoke any of Thor::Group commands twice" do base = A.new silence(:stdout) { base.invoke(:c) } expect(capture(:stdout) { base.invoke("c:one") }).to be_empty end it "raises Thor::UndefinedCommandError if the command can't be found" do expect do A.new.invoke("foo:bar") end.to raise_error(Thor::UndefinedCommandError) end it "raises Thor::UndefinedCommandError if the command can't be found even if all commands were already executed" do base = C.new silence(:stdout) { base.invoke_all } expect do base.invoke("foo:bar") end.to raise_error(Thor::UndefinedCommandError) end it "raises an error if a non Thor class is given" do expect do A.new.invoke(Object) end.to raise_error(RuntimeError, "Expected Thor class, got Object") end end end thor-1.0.1/spec/line_editor/000077500000000000000000000000001357617015300157245ustar00rootroot00000000000000thor-1.0.1/spec/line_editor/basic_spec.rb000066400000000000000000000017121357617015300203450ustar00rootroot00000000000000require "helper" describe Thor::LineEditor::Basic do describe ".available?" do it "returns true" do expect(Thor::LineEditor::Basic).to be_available end end describe "#readline" do it "uses $stdin and $stdout to get input from the user" do expect($stdout).to receive(:print).with("Enter your name ") expect($stdin).to receive(:gets).and_return("George") expect($stdin).not_to receive(:noecho) editor = Thor::LineEditor::Basic.new("Enter your name ", {}) expect(editor.readline).to eq("George") end it "disables echo when asked to" do expect($stdout).to receive(:print).with("Password: ") noecho_stdin = double("noecho_stdin") expect(noecho_stdin).to receive(:gets).and_return("secret") expect($stdin).to receive(:noecho).and_yield(noecho_stdin) editor = Thor::LineEditor::Basic.new("Password: ", :echo => false) expect(editor.readline).to eq("secret") end end end thor-1.0.1/spec/line_editor/readline_spec.rb000066400000000000000000000051331357617015300210500ustar00rootroot00000000000000require "helper" describe Thor::LineEditor::Readline do before do unless defined? ::Readline ::Readline = double("Readline") allow(::Readline).to receive(:completion_append_character=).with(nil) end end describe ".available?" do it "returns true when ::Readline exists" do allow(Object).to receive(:const_defined?).with(:Readline).and_return(true) expect(described_class).to be_available end it "returns false when ::Readline does not exist" do allow(Object).to receive(:const_defined?).with(:Readline).and_return(false) expect(described_class).not_to be_available end end describe "#readline" do it "invokes the readline library" do expect(::Readline).to receive(:readline).with("> ", true).and_return("foo") expect(::Readline).to_not receive(:completion_proc=) editor = Thor::LineEditor::Readline.new("> ", {}) expect(editor.readline).to eq("foo") end it "supports the add_to_history option" do expect(::Readline).to receive(:readline).with("> ", false).and_return("foo") expect(::Readline).to_not receive(:completion_proc=) editor = Thor::LineEditor::Readline.new("> ", :add_to_history => false) expect(editor.readline).to eq("foo") end it "provides tab completion when given a limited_to option" do expect(::Readline).to receive(:readline) expect(::Readline).to receive(:completion_proc=) do |proc| expect(proc.call("")).to eq %w(Apples Chicken Chocolate) expect(proc.call("Ch")).to eq %w(Chicken Chocolate) expect(proc.call("Chi")).to eq ["Chicken"] end editor = Thor::LineEditor::Readline.new("Best food: ", :limited_to => %w(Apples Chicken Chocolate)) editor.readline end it "provides path tab completion when given the path option" do expect(::Readline).to receive(:readline) expect(::Readline).to receive(:completion_proc=) do |proc| expect(proc.call("../line_ed").sort).to eq ["../line_editor/", "../line_editor_spec.rb"].sort end editor = Thor::LineEditor::Readline.new("Path to file: ", :path => true) Dir.chdir(File.dirname(__FILE__)) { editor.readline } end it "uses STDIN when asked not to echo input" do expect($stdout).to receive(:print).with("Password: ") noecho_stdin = double("noecho_stdin") expect(noecho_stdin).to receive(:gets).and_return("secret") expect($stdin).to receive(:noecho).and_yield(noecho_stdin) editor = Thor::LineEditor::Readline.new("Password: ", :echo => false) expect(editor.readline).to eq("secret") end end end thor-1.0.1/spec/line_editor_spec.rb000066400000000000000000000025361357617015300172710ustar00rootroot00000000000000require "helper" describe Thor::LineEditor, "on a system with Readline support" do before do @original_readline = ::Readline if defined? ::Readline silence_warnings { ::Readline = double("Readline") } end after do silence_warnings { ::Readline = @original_readline } end describe ".readline" do it "uses the Readline line editor" do editor = double("Readline") expect(Thor::LineEditor::Readline).to receive(:new).with("Enter your name ", :default => "Brian").and_return(editor) expect(editor).to receive(:readline).and_return("George") expect(Thor::LineEditor.readline("Enter your name ", :default => "Brian")).to eq("George") end end end describe Thor::LineEditor, "on a system without Readline support" do before do if defined? ::Readline @original_readline = ::Readline Object.send(:remove_const, :Readline) end end after do silence_warnings { ::Readline = @original_readline } end describe ".readline" do it "uses the Basic line editor" do editor = double("Basic") expect(Thor::LineEditor::Basic).to receive(:new).with("Enter your name ", :default => "Brian").and_return(editor) expect(editor).to receive(:readline).and_return("George") expect(Thor::LineEditor.readline("Enter your name ", :default => "Brian")).to eq("George") end end end thor-1.0.1/spec/nested_context_spec.rb000066400000000000000000000006501357617015300200150ustar00rootroot00000000000000require "helper" describe Thor::NestedContext do subject(:context) { described_class.new } describe "#enter" do it "is never empty within the entered block" do context.enter do context.enter {} expect(context).to be_entered end end it "is empty when outside of all blocks" do context.enter { context.enter {} } expect(context).not_to be_entered end end endthor-1.0.1/spec/no_warnings_spec.rb000066400000000000000000000003771357617015300173210ustar00rootroot00000000000000require "open3" context "when $VERBOSE is enabled" do it "prints no warnings" do root = File.expand_path("..", __dir__) _, err, _ = Open3.capture3("ruby -I #{root}/lib #{root}/spec/fixtures/verbose.thor") expect(err).to be_empty end end thor-1.0.1/spec/parser/000077500000000000000000000000001357617015300147235ustar00rootroot00000000000000thor-1.0.1/spec/parser/argument_spec.rb000066400000000000000000000030671357617015300201120ustar00rootroot00000000000000require "helper" require "thor/parser" describe Thor::Argument do def argument(name, options = {}) @argument ||= Thor::Argument.new(name, options) end describe "errors" do it "raises an error if name is not supplied" do expect do argument(nil) end.to raise_error(ArgumentError, "Argument name can't be nil.") end it "raises an error if type is unknown" do expect do argument(:command, :type => :unknown) end.to raise_error(ArgumentError, "Type :unknown is not valid for arguments.") end it "raises an error if argument is required and has default values" do expect do argument(:command, :type => :string, :default => "bar", :required => true) end.to raise_error(ArgumentError, "An argument cannot be required and have default value.") end it "raises an error if enum isn't an array" do expect do argument(:command, :type => :string, :enum => "bar") end.to raise_error(ArgumentError, "An argument cannot have an enum other than an array.") end end describe "#usage" do it "returns usage for string types" do expect(argument(:foo, :type => :string).usage).to eq("FOO") end it "returns usage for numeric types" do expect(argument(:foo, :type => :numeric).usage).to eq("N") end it "returns usage for array types" do expect(argument(:foo, :type => :array).usage).to eq("one two three") end it "returns usage for hash types" do expect(argument(:foo, :type => :hash).usage).to eq("key:value") end end end thor-1.0.1/spec/parser/arguments_spec.rb000066400000000000000000000053001357617015300202650ustar00rootroot00000000000000require "helper" require "thor/parser" describe Thor::Arguments do def create(opts = {}) arguments = opts.map do |type, default| options = {:required => default.nil?, :type => type, :default => default} Thor::Argument.new(type.to_s, options) end arguments.sort! { |a, b| b.name <=> a.name } @opt = Thor::Arguments.new(arguments) end def parse(*args) @opt.parse(args) end describe "#parse" do it "parses arguments in the given order" do create :string => nil, :numeric => nil expect(parse("name", "13")["string"]).to eq("name") expect(parse("name", "13")["numeric"]).to eq(13) expect(parse("name", "+13")["numeric"]).to eq(13) expect(parse("name", "+13.3")["numeric"]).to eq(13.3) expect(parse("name", "-13")["numeric"]).to eq(-13) expect(parse("name", "-13.3")["numeric"]).to eq(-13.3) end it "accepts hashes" do create :string => nil, :hash => nil expect(parse("product", "title:string", "age:integer")["string"]).to eq("product") expect(parse("product", "title:string", "age:integer")["hash"]).to eq("title" => "string", "age" => "integer") expect(parse("product", "url:http://www.amazon.com/gp/product/123")["hash"]).to eq("url" => "http://www.amazon.com/gp/product/123") end it "accepts arrays" do create :string => nil, :array => nil expect(parse("product", "title", "age")["string"]).to eq("product") expect(parse("product", "title", "age")["array"]).to eq(%w(title age)) end it "accepts - as an array argument" do create :array => nil expect(parse("-")["array"]).to eq(%w(-)) expect(parse("-", "title", "-")["array"]).to eq(%w(- title -)) end describe "with no inputs" do it "and no arguments returns an empty hash" do create expect(parse).to eq({}) end it "and required arguments raises an error" do create :string => nil, :numeric => nil expect { parse }.to raise_error(Thor::RequiredArgumentMissingError, "No value provided for required arguments 'string', 'numeric'") end it "and default arguments returns default values" do create :string => "name", :numeric => 13 expect(parse).to eq("string" => "name", "numeric" => 13) end end it "returns the input if it's already parsed" do create :string => nil, :hash => nil, :array => nil, :numeric => nil expect(parse("", 0, {}, [])).to eq("string" => "", "numeric" => 0, "hash" => {}, "array" => []) end it "returns the default value if none is provided" do create :string => "foo", :numeric => 3.0 expect(parse("bar")).to eq("string" => "bar", "numeric" => 3.0) end end end thor-1.0.1/spec/parser/option_spec.rb000066400000000000000000000206711357617015300176000ustar00rootroot00000000000000require "helper" require "thor/parser" describe Thor::Option do def parse(key, value) Thor::Option.parse(key, value) end def option(name, options = {}) @option ||= Thor::Option.new(name, options) end describe "#parse" do describe "with value as a symbol" do describe "and symbol is a valid type" do it "has type equals to the symbol" do expect(parse(:foo, :string).type).to eq(:string) expect(parse(:foo, :numeric).type).to eq(:numeric) end it "has no default value" do expect(parse(:foo, :string).default).to be nil expect(parse(:foo, :numeric).default).to be nil end end describe "equals to :required" do it "has type equals to :string" do expect(parse(:foo, :required).type).to eq(:string) end it "has no default value" do expect(parse(:foo, :required).default).to be nil end end describe "and symbol is not a reserved key" do it "has type equal to :string" do expect(parse(:foo, :bar).type).to eq(:string) end it "has no default value" do expect(parse(:foo, :bar).default).to be nil end end end describe "with value as hash" do it "has default type :hash" do expect(parse(:foo, :a => :b).type).to eq(:hash) end it "has default value equal to the hash" do expect(parse(:foo, :a => :b).default).to eq(:a => :b) end end describe "with value as array" do it "has default type :array" do expect(parse(:foo, [:a, :b]).type).to eq(:array) end it "has default value equal to the array" do expect(parse(:foo, [:a, :b]).default).to eq([:a, :b]) end end describe "with value as string" do it "has default type :string" do expect(parse(:foo, "bar").type).to eq(:string) end it "has default value equal to the string" do expect(parse(:foo, "bar").default).to eq("bar") end end describe "with value as numeric" do it "has default type :numeric" do expect(parse(:foo, 2.0).type).to eq(:numeric) end it "has default value equal to the numeric" do expect(parse(:foo, 2.0).default).to eq(2.0) end end describe "with value as boolean" do it "has default type :boolean" do expect(parse(:foo, true).type).to eq(:boolean) expect(parse(:foo, false).type).to eq(:boolean) end it "has default value equal to the boolean" do expect(parse(:foo, true).default).to eq(true) expect(parse(:foo, false).default).to eq(false) end end describe "with key as a symbol" do it "sets the name equal to the key" do expect(parse(:foo, true).name).to eq("foo") end end describe "with key as an array" do it "sets the first items in the array to the name" do expect(parse([:foo, :bar, :baz], true).name).to eq("foo") end it "sets all other items as aliases" do expect(parse([:foo, :bar, :baz], true).aliases).to eq([:bar, :baz]) end end end it "returns the switch name" do expect(option("foo").switch_name).to eq("--foo") expect(option("--foo").switch_name).to eq("--foo") end it "returns the human name" do expect(option("foo").human_name).to eq("foo") expect(option("--foo").human_name).to eq("foo") end it "converts underscores to dashes" do expect(option("foo_bar").switch_name).to eq("--foo-bar") end it "can be required and have default values" do option = option("foo", :required => true, :type => :string, :default => "bar") expect(option.default).to eq("bar") expect(option).to be_required end it "raises an error if default is inconsistent with type and check_default_type is true" do expect do option("foo_bar", :type => :numeric, :default => "baz", :check_default_type => true) end.to raise_error(ArgumentError, 'Expected numeric default value for \'--foo-bar\'; got "baz" (string)') end it "raises an error if repeatable and default is inconsistent with type and check_default_type is true" do expect do option("foo_bar", :type => :numeric, :repeatable => true, :default => "baz", :check_default_type => true) end.to raise_error(ArgumentError, 'Expected array default value for \'--foo-bar\'; got "baz" (string)') end it "raises an error type hash is repeatable and default is inconsistent with type and check_default_type is true" do expect do option("foo_bar", :type => :hash, :repeatable => true, :default => "baz", :check_default_type => true) end.to raise_error(ArgumentError, 'Expected hash default value for \'--foo-bar\'; got "baz" (string)') end it "does not raises an error if type hash is repeatable and default is consistent with type and check_default_type is true" do expect do option("foo_bar", :type => :hash, :repeatable => true, :default => {}, :check_default_type => true) end.not_to raise_error end it "does not raises an error if repeatable and default is consistent with type and check_default_type is true" do expect do option("foo_bar", :type => :numeric, :repeatable => true, :default => [1], :check_default_type => true) end.not_to raise_error end it "does not raises an error if default is an symbol and type string and check_default_type is true" do expect do option("foo", :type => :string, :default => :bar, :check_default_type => true) end.not_to raise_error end it "does not raises an error if default is inconsistent with type and check_default_type is false" do expect do option("foo_bar", :type => :numeric, :default => "baz", :check_default_type => false) end.not_to raise_error end it "boolean options cannot be required" do expect do option("foo", :required => true, :type => :boolean) end.to raise_error(ArgumentError, "An option cannot be boolean and required.") end it "does not raises an error if default is a boolean and it is required" do expect do option("foo", :required => true, :default => true) end.not_to raise_error end it "allows type predicates" do expect(parse(:foo, :string)).to be_string expect(parse(:foo, :boolean)).to be_boolean expect(parse(:foo, :numeric)).to be_numeric end it "raises an error on method missing" do expect do parse(:foo, :string).unknown? end.to raise_error(NoMethodError) end describe "#usage" do it "returns usage for string types" do expect(parse(:foo, :string).usage).to eq("[--foo=FOO]") end it "returns usage for numeric types" do expect(parse(:foo, :numeric).usage).to eq("[--foo=N]") end it "returns usage for array types" do expect(parse(:foo, :array).usage).to eq("[--foo=one two three]") end it "returns usage for hash types" do expect(parse(:foo, :hash).usage).to eq("[--foo=key:value]") end it "returns usage for boolean types" do expect(parse(:foo, :boolean).usage).to eq("[--foo], [--no-foo]") end it "does not use padding when no aliases are given" do expect(parse(:foo, :boolean).usage).to eq("[--foo], [--no-foo]") end it "documents a negative option when boolean" do expect(parse(:foo, :boolean).usage).to include("[--no-foo]") end it "does not document a negative option for a negative boolean" do expect(parse(:'no-foo', :boolean).usage).not_to include("[--no-no-foo]") end it "documents a negative option for a positive boolean starting with 'no'" do expect(parse(:'nougat', :boolean).usage).to include("[--no-nougat]") end it "uses banner when supplied" do expect(option(:foo, :required => false, :type => :string, :banner => "BAR").usage).to eq("[--foo=BAR]") end it "checks when banner is an empty string" do expect(option(:foo, :required => false, :type => :string, :banner => "").usage).to eq("[--foo]") end describe "with required values" do it "does not show the usage between brackets" do expect(parse(:foo, :required).usage).to eq("--foo=FOO") end end describe "with aliases" do it "does not show the usage between brackets" do expect(parse([:foo, "-f", "-b"], :required).usage).to eq("-f, -b, --foo=FOO") end it "does not negate the aliases" do expect(parse([:foo, "-f", "-b"], :boolean).usage).to eq("-f, -b, [--foo], [--no-foo]") end end end end thor-1.0.1/spec/parser/options_spec.rb000066400000000000000000000413411357617015300177600ustar00rootroot00000000000000require "helper" require "thor/parser" describe Thor::Options do def create(opts, defaults = {}, stop_on_unknown = false) opts.each do |key, value| opts[key] = Thor::Option.parse(key, value) unless value.is_a?(Thor::Option) end @opt = Thor::Options.new(opts, defaults, stop_on_unknown) end def parse(*args) @opt.parse(args.flatten) end def check_unknown! @opt.check_unknown! end def remaining @opt.remaining end describe "#to_switches" do it "turns true values into a flag" do expect(Thor::Options.to_switches(:color => true)).to eq("--color") end it "ignores nil" do expect(Thor::Options.to_switches(:color => nil)).to eq("") end it "ignores false" do expect(Thor::Options.to_switches(:color => false)).to eq("") end it "avoids extra spaces" do expect(Thor::Options.to_switches(:color => false, :foo => nil)).to eq("") end it "writes --name value for anything else" do expect(Thor::Options.to_switches(:format => "specdoc")).to eq('--format "specdoc"') end it "joins several values" do switches = Thor::Options.to_switches(:color => true, :foo => "bar").split(" ").sort expect(switches).to eq(%w("bar" --color --foo)) end it "accepts arrays" do expect(Thor::Options.to_switches(:count => [1, 2, 3])).to eq("--count 1 2 3") end it "accepts hashes" do expect(Thor::Options.to_switches(:count => {:a => :b})).to eq("--count a:b") end it "accepts underscored options" do expect(Thor::Options.to_switches(:under_score_option => "foo bar")).to eq('--under_score_option "foo bar"') end end describe "#parse" do it "allows multiple aliases for a given switch" do create %w(--foo --bar --baz) => :string expect(parse("--foo", "12")["foo"]).to eq("12") expect(parse("--bar", "12")["foo"]).to eq("12") expect(parse("--baz", "12")["foo"]).to eq("12") end it "allows custom short names" do create "-f" => :string expect(parse("-f", "12")).to eq("f" => "12") end it "allows custom short-name aliases" do create %w(--bar -f) => :string expect(parse("-f", "12")).to eq("bar" => "12") end it "accepts conjoined short switches" do create %w(--foo -f) => true, %w(--bar -b) => true, %w(--app -a) => true opts = parse("-fba") expect(opts["foo"]).to be true expect(opts["bar"]).to be true expect(opts["app"]).to be true end it "accepts conjoined short switches with input" do create %w(--foo -f) => true, %w(--bar -b) => true, %w(--app -a) => :required opts = parse "-fba", "12" expect(opts["foo"]).to be true expect(opts["bar"]).to be true expect(opts["app"]).to eq("12") end it "returns the default value if none is provided" do create :foo => "baz", :bar => :required expect(parse("--bar", "boom")["foo"]).to eq("baz") end it "returns the default value from defaults hash to required arguments" do create Hash[:bar => :required], Hash[:bar => "baz"] expect(parse["bar"]).to eq("baz") end it "gives higher priority to defaults given in the hash" do create Hash[:bar => true], Hash[:bar => false] expect(parse["bar"]).to eq(false) end it "raises an error for unknown switches" do create :foo => "baz", :bar => :required parse("--bar", "baz", "--baz", "unknown") expected = "Unknown switches \"--baz\"" expected << "\nDid you mean? \"--bar\"" if Thor::Correctable expect { check_unknown! }.to raise_error(Thor::UnknownArgumentError, expected) end it "skips leading non-switches" do create(:foo => "baz") expect(parse("asdf", "--foo", "bar")).to eq("foo" => "bar") end it "correctly recognizes things that look kind of like options, but aren't, as not options" do create(:foo => "baz") expect(parse("--asdf---asdf", "baz", "--foo", "--asdf---dsf--asdf")).to eq("foo" => "--asdf---dsf--asdf") check_unknown! end it "accepts underscores in commandline args hash for boolean" do create :foo_bar => :boolean expect(parse("--foo_bar")["foo_bar"]).to eq(true) expect(parse("--no_foo_bar")["foo_bar"]).to eq(false) end it "accepts underscores in commandline args hash for strings" do create :foo_bar => :string, :baz_foo => :string expect(parse("--foo_bar", "baz")["foo_bar"]).to eq("baz") expect(parse("--baz_foo", "foo bar")["baz_foo"]).to eq("foo bar") end it "interprets everything after -- as args instead of options" do create(:foo => :string, :bar => :required) expect(parse(%w(--bar abc moo -- --foo def -a))).to eq("bar" => "abc") expect(remaining).to eq(%w(moo --foo def -a)) end it "ignores -- when looking for single option values" do create(:foo => :string, :bar => :required) expect(parse(%w(--bar -- --foo def -a))).to eq("bar" => "--foo") expect(remaining).to eq(%w(def -a)) end it "ignores -- when looking for array option values" do create(:foo => :array) expect(parse(%w(--foo a b -- c d -e))).to eq("foo" => %w(a b c d -e)) expect(remaining).to eq([]) end it "ignores -- when looking for hash option values" do create(:foo => :hash) expect(parse(%w(--foo a:b -- c:d -e))).to eq("foo" => {"a" => "b", "c" => "d"}) expect(remaining).to eq(%w(-e)) end it "ignores trailing --" do create(:foo => :string) expect(parse(%w(--foo --))).to eq("foo" => nil) expect(remaining).to eq([]) end describe "with no input" do it "and no switches returns an empty hash" do create({}) expect(parse).to eq({}) end it "and several switches returns an empty hash" do create "--foo" => :boolean, "--bar" => :string expect(parse).to eq({}) end it "and a required switch raises an error" do create "--foo" => :required expect { parse }.to raise_error(Thor::RequiredArgumentMissingError, "No value provided for required options '--foo'") end end describe "with one required and one optional switch" do before do create "--foo" => :required, "--bar" => :boolean end it "raises an error if the required switch has no argument" do expect { parse("--foo") }.to raise_error(Thor::MalformattedArgumentError) end it "raises an error if the required switch isn't given" do expect { parse("--bar") }.to raise_error(Thor::RequiredArgumentMissingError) end it "raises an error if the required switch is set to nil" do expect { parse("--no-foo") }.to raise_error(Thor::RequiredArgumentMissingError) end it "does not raises an error if the required option has a default value" do options = {:required => true, :type => :string, :default => "baz"} create :foo => Thor::Option.new("foo", options), :bar => :boolean expect { parse("--bar") }.not_to raise_error end end context "when stop_on_unknown is true" do before do create({:foo => :string, :verbose => :boolean}, {}, true) end it "stops parsing on first non-option" do expect(parse(%w(foo --verbose))).to eq({}) expect(remaining).to eq(%w(foo --verbose)) end it "stops parsing on unknown option" do expect(parse(%w(--bar --verbose))).to eq({}) expect(remaining).to eq(%w(--bar --verbose)) end it "retains -- after it has stopped parsing" do expect(parse(%w(--bar -- whatever))).to eq({}) expect(remaining).to eq(%w(--bar -- whatever)) end it "still accepts options that are given before non-options" do expect(parse(%w(--verbose foo))).to eq("verbose" => true) expect(remaining).to eq(%w(foo)) end it "still accepts options that require a value" do expect(parse(%w(--foo bar baz))).to eq("foo" => "bar") expect(remaining).to eq(%w(baz)) end it "still interprets everything after -- as args instead of options" do expect(parse(%w(-- --verbose))).to eq({}) expect(remaining).to eq(%w(--verbose)) end end describe "with :string type" do before do create %w(--foo -f) => :required end it "accepts a switch assignment" do expect(parse("--foo", "12")["foo"]).to eq("12") end it "accepts a switch= assignment" do expect(parse("-f=12")["foo"]).to eq("12") expect(parse("--foo=12")["foo"]).to eq("12") expect(parse("--foo=bar=baz")["foo"]).to eq("bar=baz") end it "must accept underscores switch=value assignment" do create :foo_bar => :required expect(parse("--foo_bar=http://example.com/under_score/")["foo_bar"]).to eq("http://example.com/under_score/") end it "accepts a --no-switch format" do create "--foo" => "bar" expect(parse("--no-foo")["foo"]).to be nil end it "does not consume an argument for --no-switch format" do create "--cheese" => :string expect(parse("burger", "--no-cheese", "fries")["cheese"]).to be nil end it "accepts a --switch format on non required types" do create "--foo" => :string expect(parse("--foo")["foo"]).to eq("foo") end it "accepts a --switch format on non required types with default values" do create "--baz" => :string, "--foo" => "bar" expect(parse("--baz", "bang", "--foo")["foo"]).to eq("bar") end it "overwrites earlier values with later values" do expect(parse("--foo=bar", "--foo", "12")["foo"]).to eq("12") expect(parse("--foo", "12", "--foo", "13")["foo"]).to eq("13") end it "raises error when value isn't in enum" do enum = %w(apple banana) create :fruit => Thor::Option.new("fruit", :type => :string, :enum => enum) expect { parse("--fruit", "orange") }.to raise_error(Thor::MalformattedArgumentError, "Expected '--fruit' to be one of #{enum.join(', ')}; got orange") end it "allows multiple values if repeatable is specified" do create :foo => Thor::Option.new("foo", :type => :string, :repeatable => true) expect(parse("--foo=bar", "--foo", "12")["foo"]).to eq(["bar", "12"]) expect(parse("--foo", "13", "--foo", "14")["foo"]).to eq(["bar", "12", "13", "14"]) end end describe "with :boolean type" do before do create "--foo" => false end it "accepts --opt assignment" do expect(parse("--foo")["foo"]).to eq(true) expect(parse("--foo", "--bar")["foo"]).to eq(true) end it "uses the default value if no switch is given" do expect(parse("")["foo"]).to eq(false) end it "accepts --opt=value assignment" do expect(parse("--foo=true")["foo"]).to eq(true) expect(parse("--foo=false")["foo"]).to eq(false) end it "accepts --[no-]opt variant, setting false for value" do expect(parse("--no-foo")["foo"]).to eq(false) end it "accepts --[skip-]opt variant, setting false for value" do expect(parse("--skip-foo")["foo"]).to eq(false) end it "accepts --[skip-]opt variant, setting false for value, even if there's a trailing non-switch" do expect(parse("--skip-foo", "asdf")["foo"]).to eq(false) end it "will prefer 'no-opt' variant over inverting 'opt' if explicitly set" do create "--no-foo" => true expect(parse("--no-foo")["no-foo"]).to eq(true) end it "will prefer 'skip-opt' variant over inverting 'opt' if explicitly set" do create "--skip-foo" => true expect(parse("--skip-foo")["skip-foo"]).to eq(true) end it "will prefer 'skip-opt' variant over inverting 'opt' if explicitly set, even if there's a trailing non-switch" do create "--skip-foo" => true expect(parse("--skip-foo", "asdf")["skip-foo"]).to eq(true) end it "will prefer 'skip-opt' variant over inverting 'opt' if explicitly set, and given a value" do create "--skip-foo" => true expect(parse("--skip-foo=f")["skip-foo"]).to eq(false) expect(parse("--skip-foo=false")["skip-foo"]).to eq(false) expect(parse("--skip-foo=t")["skip-foo"]).to eq(true) expect(parse("--skip-foo=true")["skip-foo"]).to eq(true) end it "accepts inputs in the human name format" do create :foo_bar => :boolean expect(parse("--foo-bar")["foo_bar"]).to eq(true) expect(parse("--no-foo-bar")["foo_bar"]).to eq(false) expect(parse("--skip-foo-bar")["foo_bar"]).to eq(false) end it "doesn't eat the next part of the param" do expect(parse("--foo", "bar")).to eq("foo" => true) expect(@opt.remaining).to eq(%w(bar)) end it "doesn't eat the next part of the param with 'no-opt' variant" do expect(parse("--no-foo", "bar")).to eq("foo" => false) expect(@opt.remaining).to eq(%w(bar)) end it "doesn't eat the next part of the param with 'skip-opt' variant" do expect(parse("--skip-foo", "bar")).to eq("foo" => false) expect(@opt.remaining).to eq(%w(bar)) end it "allows multiple values if repeatable is specified" do create :verbose => Thor::Option.new("verbose", :type => :boolean, :aliases => '-v', :repeatable => true) expect(parse("-v", "-v", "-v")["verbose"].count).to eq(3) end end describe "with :hash type" do before do create "--attributes" => :hash end it "accepts a switch= assignment" do expect(parse("--attributes=name:string", "age:integer")["attributes"]).to eq("name" => "string", "age" => "integer") end it "accepts a switch assignment" do expect(parse("--attributes", "name:string", "age:integer")["attributes"]).to eq("name" => "string", "age" => "integer") end it "must not mix values with other switches" do expect(parse("--attributes", "name:string", "age:integer", "--baz", "cool")["attributes"]).to eq("name" => "string", "age" => "integer") end it "must not allow the same hash key to be specified multiple times" do expect { parse("--attributes", "name:string", "name:integer") }.to raise_error(Thor::MalformattedArgumentError, "You can't specify 'name' more than once in option '--attributes'; got name:string and name:integer") end it "allows multiple values if repeatable is specified" do create :attributes => Thor::Option.new("attributes", :type => :hash, :repeatable => true) expect(parse("--attributes", "name:one", "foo:1", "--attributes", "name:two", "bar:2")["attributes"]).to eq({"name"=>"two", "foo"=>"1", "bar" => "2"}) end end describe "with :array type" do before do create "--attributes" => :array end it "accepts a switch= assignment" do expect(parse("--attributes=a", "b", "c")["attributes"]).to eq(%w(a b c)) end it "accepts a switch assignment" do expect(parse("--attributes", "a", "b", "c")["attributes"]).to eq(%w(a b c)) end it "must not mix values with other switches" do expect(parse("--attributes", "a", "b", "c", "--baz", "cool")["attributes"]).to eq(%w(a b c)) end it "allows multiple values if repeatable is specified" do create :attributes => Thor::Option.new("attributes", :type => :array, :repeatable => true) expect(parse("--attributes", "1", "2", "--attributes", "3", "4")["attributes"]).to eq([["1", "2"], ["3", "4"]]) end end describe "with :numeric type" do before do create "n" => :numeric, "m" => 5 end it "accepts a -nXY assignment" do expect(parse("-n12")["n"]).to eq(12) end it "converts values to numeric types" do expect(parse("-n", "3", "-m", ".5")).to eq("n" => 3, "m" => 0.5) end it "raises error when value isn't numeric" do expect { parse("-n", "foo") }.to raise_error(Thor::MalformattedArgumentError, "Expected numeric value for '-n'; got \"foo\"") end it "raises error when value isn't in enum" do enum = [1, 2] create :limit => Thor::Option.new("limit", :type => :numeric, :enum => enum) expect { parse("--limit", "3") }.to raise_error(Thor::MalformattedArgumentError, "Expected '--limit' to be one of #{enum.join(', ')}; got 3") end it "allows multiple values if repeatable is specified" do create :run => Thor::Option.new("run", :type => :numeric, :repeatable => true) expect(parse("--run", "1", "--run", "2")["run"]).to eq([1, 2]) end end end end thor-1.0.1/spec/quality_spec.rb000066400000000000000000000037571357617015300164720ustar00rootroot00000000000000describe "The library itself" do def check_for_spec_defs_with_single_quotes(filename) failing_lines = [] File.readlines(filename).each_with_index do |line, number| failing_lines << number + 1 if line =~ /^ *(describe|it|context) {1}'{1}/ end "#{filename} uses inconsistent single quotes on lines #{failing_lines.join(', ')}" unless failing_lines.empty? end def check_for_tab_characters(filename) failing_lines = [] File.readlines(filename).each_with_index do |line, number| failing_lines << number + 1 if line =~ /\t/ end "#{filename} has tab characters on lines #{failing_lines.join(', ')}" unless failing_lines.empty? end def check_for_extra_spaces(filename) failing_lines = [] File.readlines(filename).each_with_index do |line, number| next if line =~ /^\s+#.*\s+\n$/ failing_lines << number + 1 if line =~ /\s+\n$/ end "#{filename} has spaces on the EOL on lines #{failing_lines.join(', ')}" unless failing_lines.empty? end RSpec::Matchers.define :be_well_formed do failure_message do |actual| actual.join("\n") end match(&:empty?) end it "has no malformed whitespace" do exempt = /\.gitmodules|\.marshal|fixtures|vendor|spec|ssl_certs|LICENSE/ error_messages = [] Dir.chdir(File.expand_path("../..", __FILE__)) do `git ls-files`.split("\n").each do |filename| next if filename =~ exempt error_messages << check_for_tab_characters(filename) error_messages << check_for_extra_spaces(filename) end end expect(error_messages.compact).to be_well_formed end it "uses double-quotes consistently in specs" do included = /spec/ error_messages = [] Dir.chdir(File.expand_path("../", __FILE__)) do `git ls-files`.split("\n").each do |filename| next unless filename =~ included error_messages << check_for_spec_defs_with_single_quotes(filename) end end expect(error_messages.compact).to be_well_formed end end thor-1.0.1/spec/rake_compat_spec.rb000066400000000000000000000031221357617015300172510ustar00rootroot00000000000000require "helper" require "thor/rake_compat" require "rake/tasklib" $main = self class RakeTask < Rake::TaskLib def initialize define end def define $main.instance_eval do desc "Say it's cool" task :cool do puts "COOL" end namespace :hiper_mega do task :super do puts "HIPER MEGA SUPER" end end end end end class ThorTask < Thor include Thor::RakeCompat RakeTask.new end describe Thor::RakeCompat do it "sets the rakefile application" do expect(%w(rake_compat_spec.rb Thorfile)).to include(Rake.application.rakefile) end it "adds rake tasks to thor classes too" do task = ThorTask.tasks["cool"] expect(task).to be end it "uses rake tasks descriptions on thor" do expect(ThorTask.tasks["cool"].description).to eq("Say it's cool") end it "gets usage from rake tasks name" do expect(ThorTask.tasks["cool"].usage).to eq("cool") end it "uses non namespaced name as description if non is available" do expect(ThorTask::HiperMega.tasks["super"].description).to eq("super") end it "converts namespaces to classes" do expect(ThorTask.const_get(:HiperMega)).to eq(ThorTask::HiperMega) end it "does not add tasks from higher namespaces in lowers namespaces" do expect(ThorTask.tasks["super"]).not_to be end it "invoking the thor task invokes the rake task" do expect(capture(:stdout) do ThorTask.start %w(cool) end).to eq("COOL\n") expect(capture(:stdout) do ThorTask::HiperMega.start %w(super) end).to eq("HIPER MEGA SUPER\n") end end thor-1.0.1/spec/register_spec.rb000066400000000000000000000133061357617015300166150ustar00rootroot00000000000000require "helper" class BoringVendorProvidedCLI < Thor desc "boring", "do boring stuff" def boring puts "bored. " end end class ExcitingPluginCLI < Thor desc "hooray", "say hooray!" def hooray puts "hooray!" end desc "fireworks", "exciting fireworks!" def fireworks puts "kaboom!" end end class SuperSecretPlugin < Thor default_command :squirrel desc "squirrel", "All of secret squirrel's secrets" def squirrel puts "I love nuts" end end class GroupPlugin < Thor::Group desc "part one" def part_one puts "part one" end desc "part two" def part_two puts "part two" end end class ClassOptionGroupPlugin < Thor::Group class_option :who, :type => :string, :aliases => "-w", :default => "zebra" end class PluginInheritingFromClassOptionsGroup < ClassOptionGroupPlugin desc "animal" def animal p options[:who] end end class PluginWithDefault < Thor desc "say MSG", "print MSG" def say(msg) puts msg end default_command :say end class PluginWithDefaultMultipleArguments < Thor desc "say MSG [MSG]", "print multiple messages" def say(*args) puts args end default_command :say end class PluginWithDefaultcommandAndDeclaredArgument < Thor desc "say MSG [MSG]", "print multiple messages" argument :msg def say puts msg end default_command :say end class SubcommandWithDefault < Thor default_command :default desc "default", "default subcommand" def default puts "default" end desc "with_args", "subcommand with arguments" def with_args(*args) puts "received arguments: " + args.join(",") end end BoringVendorProvidedCLI.register( ExcitingPluginCLI, "exciting", "do exciting things", "Various non-boring actions" ) BoringVendorProvidedCLI.register( SuperSecretPlugin, "secret", "secret stuff", "Nothing to see here. Move along.", :hide => true ) BoringVendorProvidedCLI.register( GroupPlugin, "groupwork", "Do a bunch of things in a row", "purple monkey dishwasher" ) BoringVendorProvidedCLI.register( PluginInheritingFromClassOptionsGroup, "zoo", "zoo [-w animal]", "Shows a provided animal or just zebra" ) BoringVendorProvidedCLI.register( PluginWithDefault, "say", "say message", "subcommands ftw" ) BoringVendorProvidedCLI.register( PluginWithDefaultMultipleArguments, "say_multiple", "say message", "subcommands ftw" ) BoringVendorProvidedCLI.register( PluginWithDefaultcommandAndDeclaredArgument, "say_argument", "say message", "subcommands ftw" ) BoringVendorProvidedCLI.register(SubcommandWithDefault, "subcommand", "subcommand", "Run subcommands") describe ".register-ing a Thor subclass" do it "registers the plugin as a subcommand" do fireworks_output = capture(:stdout) { BoringVendorProvidedCLI.start(%w(exciting fireworks)) } expect(fireworks_output).to eq("kaboom!\n") end it "includes the plugin's usage in the help" do help_output = capture(:stdout) { BoringVendorProvidedCLI.start(%w(help)) } expect(help_output).to include("do exciting things") end context "with a default command," do it "invokes the default command correctly" do output = capture(:stdout) { BoringVendorProvidedCLI.start(%w(say hello)) } expect(output).to include("hello") end it "invokes the default command correctly with multiple args" do output = capture(:stdout) { BoringVendorProvidedCLI.start(%w(say_multiple hello adam)) } expect(output).to include("hello") expect(output).to include("adam") end it "invokes the default command correctly with a declared argument" do output = capture(:stdout) { BoringVendorProvidedCLI.start(%w(say_argument hello)) } expect(output).to include("hello") end it "displays the subcommand's help message" do output = capture(:stdout) { BoringVendorProvidedCLI.start(%w(subcommand help)) } expect(output).to include("default subcommand") expect(output).to include("subcommand with argument") end it "invokes commands with their actual args" do output = capture(:stdout) { BoringVendorProvidedCLI.start(%w(subcommand with_args actual_argument)) } expect(output.strip).to eql("received arguments: actual_argument") end end context "when $thor_runner is false" do it "includes the plugin's subcommand name in subcommand's help" do begin $thor_runner = false help_output = capture(:stdout) { BoringVendorProvidedCLI.start(%w(exciting)) } expect(help_output).to include("thor exciting fireworks") ensure $thor_runner = true end end end context "when hidden" do it "omits the hidden plugin's usage from the help" do help_output = capture(:stdout) { BoringVendorProvidedCLI.start(%w(help)) } expect(help_output).not_to include("secret stuff") end it "registers the plugin as a subcommand" do secret_output = capture(:stdout) { BoringVendorProvidedCLI.start(%w(secret squirrel)) } expect(secret_output).to eq("I love nuts\n") end end end describe ".register-ing a Thor::Group subclass" do it "registers the group as a single command" do group_output = capture(:stdout) { BoringVendorProvidedCLI.start(%w(groupwork)) } expect(group_output).to eq("part one\npart two\n") end end describe ".register-ing a Thor::Group subclass with class options" do it "works w/o command options" do group_output = capture(:stdout) { BoringVendorProvidedCLI.start(%w(zoo)) } expect(group_output).to match(/zebra/) end it "works w/command options" do group_output = capture(:stdout) { BoringVendorProvidedCLI.start(%w(zoo -w lion)) } expect(group_output).to match(/lion/) end end thor-1.0.1/spec/runner_spec.rb000066400000000000000000000213431357617015300163020ustar00rootroot00000000000000require "helper" require "thor/runner" describe Thor::Runner do def when_no_thorfiles_exist old_dir = Dir.pwd Dir.chdir ".." delete = Thor::Base.subclasses.select { |e| e.namespace == "default" } delete.each { |e| Thor::Base.subclasses.delete e } yield Thor::Base.subclasses.concat delete Dir.chdir old_dir end describe "#help" do it "shows information about Thor::Runner itself" do expect(capture(:stdout) { Thor::Runner.start(%w(help)) }).to match(/List the available thor commands/) end it "shows information about a specific Thor::Runner command" do content = capture(:stdout) { Thor::Runner.start(%w(help list)) } expect(content).to match(/List the available thor commands/) expect(content).not_to match(/help \[COMMAND\]/) end it "shows information about a specific Thor class" do content = capture(:stdout) { Thor::Runner.start(%w(help my_script)) } expect(content).to match(/zoo\s+# zoo around/m) end it "shows information about a specific command from a specific Thor class" do content = capture(:stdout) { Thor::Runner.start(%w(help my_script:zoo)) } expect(content).to match(/zoo around/) expect(content).not_to match(/help \[COMMAND\]/) end it "shows information about a specific Thor group class" do content = capture(:stdout) { Thor::Runner.start(%w(help my_counter)) } expect(content).to match(/my_counter N/) end it "raises error if a class/command cannot be found" do content = capture(:stderr) { Thor::Runner.start(%w(help unknown)) } expect(content.strip).to eq('Could not find command "unknown" in "default" namespace.') end it "raises error if a class/command cannot be found for a setup without thorfiles" do when_no_thorfiles_exist do expect(Thor::Runner).to receive :exit content = capture(:stderr) { Thor::Runner.start(%w(help unknown)) } expect(content.strip).to eq('Could not find command "unknown".') end end end describe "#start" do it "invokes a command from Thor::Runner" do ARGV.replace %w(list) expect(capture(:stdout) { Thor::Runner.start }).to match(/my_counter N/) end it "invokes a command from a specific Thor class" do ARGV.replace %w(my_script:zoo) expect(Thor::Runner.start).to be true end it "invokes the default command from a specific Thor class if none is specified" do ARGV.replace %w(my_script) expect(Thor::Runner.start).to eq("default command") end it "forwards arguments to the invoked command" do ARGV.replace %w(my_script:animal horse) expect(Thor::Runner.start).to eq(%w(horse)) end it "invokes commands through shortcuts" do ARGV.replace %w(my_script -T horse) expect(Thor::Runner.start).to eq(%w(horse)) end it "invokes a Thor::Group" do ARGV.replace %w(my_counter 1 2 --third 3) expect(Thor::Runner.start).to eq([1, 2, 3, nil, nil, nil]) end it "raises an error if class/command can't be found" do ARGV.replace %w(unknown) content = capture(:stderr) { Thor::Runner.start } expect(content.strip).to eq('Could not find command "unknown" in "default" namespace.') end it "raises an error if class/command can't be found in a setup without thorfiles" do when_no_thorfiles_exist do ARGV.replace %w(unknown) expect(Thor::Runner).to receive :exit content = capture(:stderr) { Thor::Runner.start } expect(content.strip).to eq('Could not find command "unknown".') end end it "does not swallow NoMethodErrors that occur inside the called method" do ARGV.replace %w(my_script:call_unexistent_method) expect { Thor::Runner.start }.to raise_error(NoMethodError) end it "does not swallow Thor::Group InvocationError" do ARGV.replace %w(whiny_generator) expect { Thor::Runner.start }.to raise_error(ArgumentError, /thor wrong_arity takes 1 argument, but it should not/) end it "does not swallow Thor InvocationError" do ARGV.replace %w(my_script:animal) content = capture(:stderr) { Thor::Runner.start } expect(content.strip).to eq('ERROR: "thor animal" was called with no arguments Usage: "thor my_script:animal TYPE"') end end describe "commands" do before do @location = "#{File.dirname(__FILE__)}/fixtures/command.thor" @original_yaml = { "random" => { :location => @location, :filename => "4a33b894ffce85d7b412fc1b36f88fe0", :namespaces => %w(amazing) } } root_file = File.join(Thor::Util.thor_root, "thor.yml") # Stub load and save to avoid thor.yaml from being overwritten allow(YAML).to receive(:load_file).and_return(@original_yaml) allow(File).to receive(:exist?).with(root_file).and_return(true) allow(File).to receive(:open).with(root_file, "w") end describe "list" do it "gives a list of the available commands" do ARGV.replace %w(list) content = capture(:stdout) { Thor::Runner.start } expect(content).to match(/amazing:describe NAME\s+# say that someone is amazing/m) end it "gives a list of the available Thor::Group classes" do ARGV.replace %w(list) expect(capture(:stdout) { Thor::Runner.start }).to match(/my_counter N/) end it "can filter a list of the available commands by --group" do ARGV.replace %w(list --group standard) expect(capture(:stdout) { Thor::Runner.start }).to match(/amazing:describe NAME/) ARGV.replace [] expect(capture(:stdout) { Thor::Runner.start }).not_to match(/my_script:animal TYPE/) ARGV.replace %w(list --group script) expect(capture(:stdout) { Thor::Runner.start }).to match(/my_script:animal TYPE/) end it "can skip all filters to show all commands using --all" do ARGV.replace %w(list --all) content = capture(:stdout) { Thor::Runner.start } expect(content).to match(/amazing:describe NAME/) expect(content).to match(/my_script:animal TYPE/) end it "doesn't list superclass commands in the subclass" do ARGV.replace %w(list) expect(capture(:stdout) { Thor::Runner.start }).not_to match(/amazing:help/) end it "presents commands in the default namespace with an empty namespace" do ARGV.replace %w(list) expect(capture(:stdout) { Thor::Runner.start }).to match(/^thor :cow\s+# prints 'moo'/m) end it "runs commands with an empty namespace from the default namespace" do ARGV.replace %w(:command_conflict) expect(capture(:stdout) { Thor::Runner.start }).to eq("command\n") end it "runs groups even when there is a command with the same name" do ARGV.replace %w(command_conflict) expect(capture(:stdout) { Thor::Runner.start }).to eq("group\n") end it "runs commands with no colon in the default namespace" do ARGV.replace %w(cow) expect(capture(:stdout) { Thor::Runner.start }).to eq("moo\n") end end describe "uninstall" do before do path = File.join(Thor::Util.thor_root, @original_yaml["random"][:filename]) expect(FileUtils).to receive(:rm_rf).with(path) end it "uninstalls existing thor modules" do silence(:stdout) { Thor::Runner.start(%w(uninstall random)) } end end describe "installed" do before do expect(Dir).to receive(:[]).and_return([]) end it "displays the modules installed in a pretty way" do stdout = capture(:stdout) { Thor::Runner.start(%w(installed)) } expect(stdout).to match(/random\s*amazing/) expect(stdout).to match(/amazing:describe NAME\s+# say that someone is amazing/m) end end describe "install/update" do before do allow(FileUtils).to receive(:mkdir_p) allow(FileUtils).to receive(:touch) allow(Thor::LineEditor).to receive(:readline).and_return("Y") path = File.join(Thor::Util.thor_root, Digest::MD5.hexdigest(@location + "random")) expect(File).to receive(:open).with(path, "w") end it "updates existing thor files" do path = File.join(Thor::Util.thor_root, @original_yaml["random"][:filename]) if File.directory? path expect(FileUtils).to receive(:rm_rf).with(path) else expect(File).to receive(:delete).with(path) end silence_warnings do silence(:stdout) { Thor::Runner.start(%w(update random)) } end end it "installs thor files" do ARGV.replace %W(install #{@location}) silence_warnings do silence(:stdout) { Thor::Runner.start } end end end end end thor-1.0.1/spec/script_exit_status_spec.rb000066400000000000000000000015661357617015300207360ustar00rootroot00000000000000describe "when the Thor class's exit_with_failure? method returns true" do def thor_command(command) gem_dir= File.expand_path("#{File.dirname(__FILE__)}/..") lib_path= "#{gem_dir}/lib" script_path= "#{gem_dir}/spec/fixtures/exit_status.thor" ruby_lib= ENV['RUBYLIB'].nil? ? lib_path : "#{lib_path}:#{ENV['RUBYLIB']}" full_command= "ruby #{script_path} #{command}" r,w= IO.pipe pid= spawn({'RUBYLIB' => ruby_lib}, full_command, {:out => w, :err => [:child, :out]}) w.close _, exit_status= Process.wait2(pid) r.read r.close exit_status.exitstatus end it "a command that raises a Thor::Error exits with a status of 1" do expect(thor_command("error")).to eq(1) end it "a command that does not raise a Thor::Error exits with a status of 0" do expect(thor_command("ok")).to eq(0) end end thor-1.0.1/spec/shell/000077500000000000000000000000001357617015300145365ustar00rootroot00000000000000thor-1.0.1/spec/shell/basic_spec.rb000066400000000000000000000407031357617015300171620ustar00rootroot00000000000000# coding: utf-8 require "helper" describe Thor::Shell::Basic do def shell @shell ||= Thor::Shell::Basic.new end describe "#padding" do it "cannot be set to below zero" do shell.padding = 10 expect(shell.padding).to eq(10) shell.padding = -1 expect(shell.padding).to eq(0) end end describe "#indent" do it "sets the padding temporarily" do shell.indent { expect(shell.padding).to eq(1) } expect(shell.padding).to eq(0) end it "derives padding from original value" do shell.padding = 6 shell.indent { expect(shell.padding).to eq(7) } end it "accepts custom indentation amounts" do shell.indent(6) do expect(shell.padding).to eq(6) end end it "increases the padding when nested" do shell.indent do expect(shell.padding).to eq(1) shell.indent do expect(shell.padding).to eq(2) end end expect(shell.padding).to eq(0) end end describe "#ask" do it "prints a message to the user and gets the response" do expect(Thor::LineEditor).to receive(:readline).with("Should I overwrite it? ", {}).and_return("Sure") expect(shell.ask("Should I overwrite it?")).to eq("Sure") end it "prints a message to the user prefixed with the current padding" do expect(Thor::LineEditor).to receive(:readline).with(" Enter your name: ", {}).and_return("George") shell.padding = 2 shell.ask("Enter your name:") end it "prints a message and returns nil if EOF is given as input" do expect(Thor::LineEditor).to receive(:readline).with(" ", {}).and_return(nil) expect(shell.ask("")).to eq(nil) end it "prints a message to the user and does not echo stdin if the echo option is set to false" do expect($stdout).to receive(:print).with('What\'s your password? ') expect($stdin).to receive(:noecho).and_return("mysecretpass") expect(shell.ask("What's your password?", :echo => false)).to eq("mysecretpass") end it "prints a message to the user with the available options, expects case-sensitive matching, and determines the correctness of the answer" do flavors = %w(strawberry chocolate vanilla) expect(Thor::LineEditor).to receive(:readline).with('What\'s your favorite Neopolitan flavor? [strawberry, chocolate, vanilla] ', :limited_to => flavors).and_return("chocolate") expect(shell.ask('What\'s your favorite Neopolitan flavor?', :limited_to => flavors)).to eq("chocolate") end it "prints a message to the user with the available options, expects case-sensitive matching, and reasks the question after an incorrect response" do flavors = %w(strawberry chocolate vanilla) expect($stdout).to receive(:print).with("Your response must be one of: [strawberry, chocolate, vanilla]. Please try again.\n") expect(Thor::LineEditor).to receive(:readline).with('What\'s your favorite Neopolitan flavor? [strawberry, chocolate, vanilla] ', :limited_to => flavors).and_return("moose tracks", "chocolate") expect(shell.ask('What\'s your favorite Neopolitan flavor?', :limited_to => flavors)).to eq("chocolate") end it "prints a message to the user with the available options, expects case-sensitive matching, and reasks the question after a case-insensitive match" do flavors = %w(strawberry chocolate vanilla) expect($stdout).to receive(:print).with("Your response must be one of: [strawberry, chocolate, vanilla]. Please try again.\n") expect(Thor::LineEditor).to receive(:readline).with('What\'s your favorite Neopolitan flavor? [strawberry, chocolate, vanilla] ', :limited_to => flavors).and_return("cHoCoLaTe", "chocolate") expect(shell.ask('What\'s your favorite Neopolitan flavor?', :limited_to => flavors)).to eq("chocolate") end it "prints a message to the user with the available options, expects case-insensitive matching, and determines the correctness of the answer" do flavors = %w(strawberry chocolate vanilla) expect(Thor::LineEditor).to receive(:readline).with('What\'s your favorite Neopolitan flavor? [strawberry, chocolate, vanilla] ', :limited_to => flavors, :case_insensitive => true).and_return("CHOCOLATE") expect(shell.ask('What\'s your favorite Neopolitan flavor?', :limited_to => flavors, :case_insensitive => true)).to eq("chocolate") end it "prints a message to the user with the available options, expects case-insensitive matching, and reasks the question after an incorrect response" do flavors = %w(strawberry chocolate vanilla) expect($stdout).to receive(:print).with("Your response must be one of: [strawberry, chocolate, vanilla]. Please try again.\n") expect(Thor::LineEditor).to receive(:readline).with('What\'s your favorite Neopolitan flavor? [strawberry, chocolate, vanilla] ', :limited_to => flavors, :case_insensitive => true).and_return("moose tracks", "chocolate") expect(shell.ask('What\'s your favorite Neopolitan flavor?', :limited_to => flavors, :case_insensitive => true)).to eq("chocolate") end it "prints a message to the user containing a default and sets the default if only enter is pressed" do expect(Thor::LineEditor).to receive(:readline).with('What\'s your favorite Neopolitan flavor? (vanilla) ', :default => "vanilla").and_return("") expect(shell.ask('What\'s your favorite Neopolitan flavor?', :default => "vanilla")).to eq("vanilla") end it "prints a message to the user with the available options and reasks the question after an incorrect response and then returns the default" do flavors = %w(strawberry chocolate vanilla) expect($stdout).to receive(:print).with("Your response must be one of: [strawberry, chocolate, vanilla]. Please try again.\n") expect(Thor::LineEditor).to receive(:readline).with('What\'s your favorite Neopolitan flavor? [strawberry, chocolate, vanilla] (vanilla) ', :default => "vanilla", :limited_to => flavors).and_return("moose tracks", "") expect(shell.ask("What's your favorite Neopolitan flavor?", :default => "vanilla", :limited_to => flavors)).to eq("vanilla") end end describe "#yes?" do it "asks the user and returns true if the user replies yes" do expect(Thor::LineEditor).to receive(:readline).with("Should I overwrite it? ", :add_to_history => false).and_return("y") expect(shell.yes?("Should I overwrite it?")).to be true end it "asks the user and returns false if the user replies no" do expect(Thor::LineEditor).to receive(:readline).with("Should I overwrite it? ", :add_to_history => false).and_return("n") expect(shell.yes?("Should I overwrite it?")).not_to be true end it "asks the user and returns false if the user replies with an answer other than yes or no" do expect(Thor::LineEditor).to receive(:readline).with("Should I overwrite it? ", :add_to_history => false).and_return("foobar") expect(shell.yes?("Should I overwrite it?")).to be false end end describe "#no?" do it "asks the user and returns true if the user replies no" do expect(Thor::LineEditor).to receive(:readline).with("Should I overwrite it? ", :add_to_history => false).and_return("n") expect(shell.no?("Should I overwrite it?")).to be true end it "asks the user and returns false if the user replies yes" do expect(Thor::LineEditor).to receive(:readline).with("Should I overwrite it? ", :add_to_history => false).and_return("Yes") expect(shell.no?("Should I overwrite it?")).to be false end it "asks the user and returns false if the user replies with an answer other than yes or no" do expect(Thor::LineEditor).to receive(:readline).with("Should I overwrite it? ", :add_to_history => false).and_return("foobar") expect(shell.no?("Should I overwrite it?")).to be false end end describe "#say" do it "prints a message to the user" do expect($stdout).to receive(:print).with("Running...\n") shell.say("Running...") end it "prints a message to the user without new line if it ends with a whitespace" do expect($stdout).to receive(:print).with("Running... ") shell.say("Running... ") end it "does not use a new line with whitespace+newline embedded" do expect($stdout).to receive(:print).with("It's \nRunning...\n") shell.say("It's \nRunning...") end it "prints a message to the user without new line" do expect($stdout).to receive(:print).with("Running...") shell.say("Running...", nil, false) end it "coerces everything to a string before printing" do expect($stdout).to receive(:print).with("this_is_not_a_string\n") shell.say(:this_is_not_a_string, nil, true) end end describe "#say_status" do it "prints a message to the user with status" do expect($stdout).to receive(:print).with(" create ~/.thor/command.thor\n") shell.say_status(:create, "~/.thor/command.thor") end it "always uses new line" do expect($stdout).to receive(:print).with(" create \n") shell.say_status(:create, "") end it "does not print a message if base is muted" do expect(shell).to receive(:mute?).and_return(true) expect($stdout).not_to receive(:print) shell.mute do shell.say_status(:created, "~/.thor/command.thor") end end it "does not print a message if base is set to quiet" do base = MyCounter.new [1, 2] expect(base).to receive(:options).and_return(:quiet => true) expect($stdout).not_to receive(:print) shell.base = base shell.say_status(:created, "~/.thor/command.thor") end it "does not print a message if log status is set to false" do expect($stdout).not_to receive(:print) shell.say_status(:created, "~/.thor/command.thor", false) end it "uses padding to set message's left margin" do shell.padding = 2 expect($stdout).to receive(:print).with(" create ~/.thor/command.thor\n") shell.say_status(:create, "~/.thor/command.thor") end end describe "#print_in_columns" do before do @array = [1_234_567_890] @array += ("a".."e").to_a end it "prints in columns" do content = capture(:stdout) { shell.print_in_columns(@array) } expect(content.rstrip).to eq("1234567890 a b c d e") end end describe "#print_table" do before do @table = [] @table << ["abc", "#123", "first three"] @table << ["", "#0", "empty"] @table << ["xyz", "#786", "last three"] end it "prints a table" do content = capture(:stdout) { shell.print_table(@table) } expect(content).to eq(<<-TABLE) abc #123 first three #0 empty xyz #786 last three TABLE end it "prints a table with indentation" do content = capture(:stdout) { shell.print_table(@table, :indent => 2) } expect(content).to eq(<<-TABLE) abc #123 first three #0 empty xyz #786 last three TABLE end it "uses maximum terminal width" do @table << ["def", "#456", "Lançam foo bar"] @table << ["ghi", "#789", "بالله عليكم"] expect(shell).to receive(:terminal_width).and_return(20) content = capture(:stdout) { shell.print_table(@table, :indent => 2, :truncate => true) } expect(content).to eq(<<-TABLE) abc #123 firs... #0 empty xyz #786 last... def #456 Lanç... ghi #789 بالل... TABLE end it "honors the colwidth option" do content = capture(:stdout) { shell.print_table(@table, :colwidth => 10) } expect(content).to eq(<<-TABLE) abc #123 first three #0 empty xyz #786 last three TABLE end it "prints tables with implicit columns" do 2.times { @table.first.pop } content = capture(:stdout) { shell.print_table(@table) } expect(content).to eq(<<-TABLE) abc #0 empty xyz #786 last three TABLE end it "prints a table with small numbers and right-aligns them" do table = [ ["Name", "Number", "Color"], # rubocop: disable WordArray ["Erik", 1, "green"] ] content = capture(:stdout) { shell.print_table(table) } expect(content).to eq(<<-TABLE) Name Number Color Erik 1 green TABLE end it "doesn't output extra spaces for right-aligned columns in the last column" do table = [ ["Name", "Number"], # rubocop: disable WordArray ["Erik", 1] ] content = capture(:stdout) { shell.print_table(table) } expect(content).to eq(<<-TABLE) Name Number Erik 1 TABLE end it "prints a table with big numbers" do table = [ ["Name", "Number", "Color"], # rubocop: disable WordArray ["Erik", 1_234_567_890_123, "green"] ] content = capture(:stdout) { shell.print_table(table) } expect(content).to eq(<<-TABLE) Name Number Color Erik 1234567890123 green TABLE end end describe "#file_collision" do it "shows a menu with options" do expect(Thor::LineEditor).to receive(:readline).with('Overwrite foo? (enter "h" for help) [Ynaqh] ', :add_to_history => false).and_return("n") shell.file_collision("foo") end it "outputs a new line and returns true if stdin is closed" do expect($stdout).to receive(:print).with("\n") expect(Thor::LineEditor).to receive(:readline).and_return(nil) expect(shell.file_collision("foo")).to be true end it "returns true if the user chooses default option" do expect(Thor::LineEditor).to receive(:readline).and_return("") expect(shell.file_collision("foo")).to be true end it "returns false if the user chooses no" do expect(Thor::LineEditor).to receive(:readline).and_return("n") expect(shell.file_collision("foo")).to be false end it "returns true if the user chooses yes" do expect(Thor::LineEditor).to receive(:readline).and_return("y") expect(shell.file_collision("foo")).to be true end it "shows help usage if the user chooses help" do expect(Thor::LineEditor).to receive(:readline).and_return("h", "n") help = capture(:stdout) { shell.file_collision("foo") } expect(help).to match(/h \- help, show this help/) end it "quits if the user chooses quit" do expect($stdout).to receive(:print).with("Aborting...\n") expect(Thor::LineEditor).to receive(:readline).and_return("q") expect do shell.file_collision("foo") end.to raise_error(SystemExit) end it "always returns true if the user chooses always" do expect(Thor::LineEditor).to receive(:readline).with('Overwrite foo? (enter "h" for help) [Ynaqh] ', :add_to_history => false).and_return("a") expect(shell.file_collision("foo")).to be true expect($stdout).not_to receive(:print) expect(shell.file_collision("foo")).to be true end describe "when a block is given" do it "displays diff and merge options to the user" do expect(Thor::LineEditor).to receive(:readline).with('Overwrite foo? (enter "h" for help) [Ynaqdhm] ', :add_to_history => false).and_return("s") shell.file_collision("foo") {} end it "invokes the diff command" do expect(Thor::LineEditor).to receive(:readline).and_return("d") expect(Thor::LineEditor).to receive(:readline).and_return("n") expect(shell).to receive(:system).with(/diff -u/) capture(:stdout) { shell.file_collision("foo") {} } end it "invokes the merge tool" do allow(shell).to receive(:merge_tool).and_return("meld") expect(Thor::LineEditor).to receive(:readline).and_return("m") expect(shell).to receive(:system).with(/meld/) capture(:stdout) { shell.file_collision("foo") {} } end it "invokes the merge tool that specified at ENV['THOR_MERGE']" do allow(ENV).to receive(:[]).with("THOR_MERGE").and_return("meld") expect(Thor::LineEditor).to receive(:readline).and_return("m") expect(shell).to receive(:system).with(/meld/) capture(:stdout) { shell.file_collision("foo") {} } end it "show warning if user chooses merge but merge tool is not specified" do allow(shell).to receive(:merge_tool).and_return("") expect(Thor::LineEditor).to receive(:readline).and_return("m") expect(Thor::LineEditor).to receive(:readline).and_return("n") help = capture(:stdout) { shell.file_collision("foo") {} } expect(help).to match(/Please specify merge tool to `THOR_MERGE` env/) end end end end thor-1.0.1/spec/shell/color_spec.rb000066400000000000000000000125751357617015300172250ustar00rootroot00000000000000require "helper" describe Thor::Shell::Color do def shell @shell ||= Thor::Shell::Color.new end before do allow($stdout).to receive(:tty?).and_return(true) allow_any_instance_of(StringIO).to receive(:tty?).and_return(true) end describe "#ask" do it "sets the color if specified and tty?" do expect(Thor::LineEditor).to receive(:readline).with("\e[32mIs this green? \e[0m", anything).and_return("yes") shell.ask "Is this green?", :green expect(Thor::LineEditor).to receive(:readline).with("\e[32mIs this green? [Yes, No, Maybe] \e[0m", anything).and_return("Yes") shell.ask "Is this green?", :green, :limited_to => %w(Yes No Maybe) end it "does not set the color if specified and NO_COLOR is set" do allow(ENV).to receive(:[]).with("NO_COLOR").and_return("") expect(Thor::LineEditor).to receive(:readline).with("Is this green? ", anything).and_return("yes") shell.ask "Is this green?", :green expect(Thor::LineEditor).to receive(:readline).with("Is this green? [Yes, No, Maybe] ", anything).and_return("Yes") shell.ask "Is this green?", :green, :limited_to => %w(Yes No Maybe) end it "handles an Array of colors" do expect(Thor::LineEditor).to receive(:readline).with("\e[32m\e[47m\e[1mIs this green on white? \e[0m", anything).and_return("yes") shell.ask "Is this green on white?", [:green, :on_white, :bold] end it "supports the legacy color syntax" do expect(Thor::LineEditor).to receive(:readline).with("\e[1m\e[34mIs this legacy blue? \e[0m", anything).and_return("yes") shell.ask "Is this legacy blue?", [:blue, true] end end describe "#say" do it "set the color if specified and tty?" do out = capture(:stdout) do shell.say "Wow! Now we have colors!", :green end expect(out.chomp).to eq("\e[32mWow! Now we have colors!\e[0m") end it "does not set the color if output is not a tty" do out = capture(:stdout) do expect($stdout).to receive(:tty?).and_return(false) shell.say "Wow! Now we have colors!", :green end expect(out.chomp).to eq("Wow! Now we have colors!") end it "does not set the color if NO_COLOR is set" do allow(ENV).to receive(:[]).with("NO_COLOR").and_return("") out = capture(:stdout) do shell.say "Wow! Now we have colors!", :green end expect(out.chomp).to eq("Wow! Now we have colors!") end it "does not use a new line even with colors" do out = capture(:stdout) do shell.say "Wow! Now we have colors! ", :green end expect(out.chomp).to eq("\e[32mWow! Now we have colors! \e[0m") end it "handles an Array of colors" do out = capture(:stdout) do shell.say "Wow! Now we have colors *and* background colors", [:green, :on_red, :bold] end expect(out.chomp).to eq("\e[32m\e[41m\e[1mWow! Now we have colors *and* background colors\e[0m") end it "supports the legacy color syntax" do out = capture(:stdout) do shell.say "Wow! This still works?", [:blue, true] end expect(out.chomp).to eq("\e[1m\e[34mWow! This still works?\e[0m") end end describe "#say_status" do it "uses color to say status" do out = capture(:stdout) do shell.say_status :conflict, "README", :red end expect(out.chomp).to eq("\e[1m\e[31m conflict\e[0m README") end end describe "#set_color" do it "colors a string with a foreground color" do red = shell.set_color "hi!", :red expect(red).to eq("\e[31mhi!\e[0m") end it "colors a string with a background color" do on_red = shell.set_color "hi!", :white, :on_red expect(on_red).to eq("\e[37m\e[41mhi!\e[0m") end it "colors a string with a bold color" do bold = shell.set_color "hi!", :white, true expect(bold).to eq("\e[1m\e[37mhi!\e[0m") bold = shell.set_color "hi!", :white, :bold expect(bold).to eq("\e[37m\e[1mhi!\e[0m") bold = shell.set_color "hi!", :white, :on_red, :bold expect(bold).to eq("\e[37m\e[41m\e[1mhi!\e[0m") end it "does nothing when there are no colors" do colorless = shell.set_color "hi!", nil expect(colorless).to eq("hi!") colorless = shell.set_color "hi!" expect(colorless).to eq("hi!") end it "does nothing when the terminal does not support color" do allow($stdout).to receive(:tty?).and_return(false) colorless = shell.set_color "hi!", :white expect(colorless).to eq("hi!") end it "does nothing when the terminal has the NO_COLOR environment variable set" do allow(ENV).to receive(:[]).with("NO_COLOR").and_return("") allow($stdout).to receive(:tty?).and_return(true) colorless = shell.set_color "hi!", :white expect(colorless).to eq("hi!") end end describe "#file_collision" do describe "when a block is given" do it "invokes the diff command" do allow($stdout).to receive(:print) allow($stdout).to receive(:tty?).and_return(true) expect(Thor::LineEditor).to receive(:readline).and_return("d", "n") output = capture(:stdout) { shell.file_collision("spec/fixtures/doc/README") { "README\nEND\n" } } expect(output).to match(/\e\[31m\- __start__\e\[0m/) expect(output).to match(/^ README/) expect(output).to match(/\e\[32m\+ END\e\[0m/) end end end end thor-1.0.1/spec/shell/html_spec.rb000066400000000000000000000027611357617015300170470ustar00rootroot00000000000000require "helper" describe Thor::Shell::HTML do def shell @shell ||= Thor::Shell::HTML.new end describe "#say" do it "sets the color if specified" do out = capture(:stdout) { shell.say "Wow! Now we have colors!", :green } expect(out.chomp).to eq('Wow! Now we have colors!') end it "sets bold if specified" do out = capture(:stdout) { shell.say "Wow! Now we have colors *and* bold!", [:green, :bold] } expect(out.chomp).to eq('Wow! Now we have colors *and* bold!') end it "does not use a new line even with colors" do out = capture(:stdout) { shell.say "Wow! Now we have colors! ", :green } expect(out.chomp).to eq('Wow! Now we have colors! ') end end describe "#say_status" do it "uses color to say status" do expect($stdout).to receive(:print).with(" conflict README\n") shell.say_status :conflict, "README", :red end end describe "#set_color" do it "escapes HTML content when unsing the default colors" do expect(shell.set_color("", :blue)).to eq "<htmlcontent>" end it "escapes HTML content when not using the default colors" do expect(shell.set_color("", [:nocolor])).to eq "<htmlcontent>" end end end thor-1.0.1/spec/shell_spec.rb000066400000000000000000000023411357617015300160750ustar00rootroot00000000000000require "helper" describe Thor::Shell do def shell @shell ||= Thor::Base.shell.new end describe "#initialize" do it "sets shell value" do base = MyCounter.new [1, 2], {}, :shell => shell expect(base.shell).to eq(shell) end it "sets the base value on the shell if an accessor is available" do base = MyCounter.new [1, 2], {}, :shell => shell expect(shell.base).to eq(base) end end describe "#shell" do it "returns the shell in use" do expect(MyCounter.new([1, 2]).shell).to be_kind_of(Thor::Base.shell) end it "uses $THOR_SHELL" do class Thor::Shell::TestShell < Thor::Shell::Basic; end expect(Thor::Base.shell).to eq(shell.class) ENV["THOR_SHELL"] = "TestShell" Thor::Base.shell = nil expect(Thor::Base.shell).to eq(Thor::Shell::TestShell) ENV["THOR_SHELL"] = "" Thor::Base.shell = shell.class expect(Thor::Base.shell).to eq(shell.class) end end describe "with_padding" do it "uses padding for inside block outputs" do base = MyCounter.new([1, 2]) base.with_padding do expect(capture(:stdout) { base.say_status :padding, "cool" }.strip).to eq("padding cool") end end end end thor-1.0.1/spec/subcommand_spec.rb000066400000000000000000000051751357617015300171260ustar00rootroot00000000000000require "helper" describe Thor do describe "#subcommand" do it "maps a given subcommand to another Thor subclass" do barn_help = capture(:stdout) { Scripts::MyDefaults.start(%w(barn)) } expect(barn_help).to include("barn help [COMMAND] # Describe subcommands or one specific subcommand") end it "passes commands to subcommand classes" do expect(capture(:stdout) { Scripts::MyDefaults.start(%w(barn open)) }.strip).to eq("Open sesame!") end it "passes arguments to subcommand classes" do expect(capture(:stdout) { Scripts::MyDefaults.start(%w(barn open shotgun)) }.strip).to eq("That's going to leave a mark.") end it "ignores unknown options (the subcommand class will handle them)" do expect(capture(:stdout) { Scripts::MyDefaults.start(%w(barn paint blue --coats 4)) }.strip).to eq("4 coats of blue paint") end it "passes parsed options to subcommands" do output = capture(:stdout) { TestSubcommands::Parent.start(%w(sub print_opt --opt output)) } expect(output).to eq("output") end it "accepts the help switch and calls the help command on the subcommand" do output = capture(:stdout) { TestSubcommands::Parent.start(%w(sub print_opt --help)) } sub_help = capture(:stdout) { TestSubcommands::Parent.start(%w(sub help print_opt)) } expect(output).to eq(sub_help) end it "accepts the help short switch and calls the help command on the subcommand" do output = capture(:stdout) { TestSubcommands::Parent.start(%w(sub print_opt -h)) } sub_help = capture(:stdout) { TestSubcommands::Parent.start(%w(sub help print_opt)) } expect(output).to eq(sub_help) end it "the help command on the subcommand and after it should result in the same output" do output = capture(:stdout) { TestSubcommands::Parent.start(%w(sub help)) } sub_help = capture(:stdout) { TestSubcommands::Parent.start(%w(help sub)) } expect(output).to eq(sub_help) end end context "subcommand with an arg" do module SubcommandTest1 class Child1 < Thor desc "foo NAME", "Fooo" def foo(name) puts "#{name} was given" end end class Parent < Thor desc "child1", "child1 description" subcommand "child1", Child1 def self.exit_on_failure? false end end end it "shows subcommand name and method name" do sub_help = capture(:stderr) { SubcommandTest1::Parent.start(%w(child1 foo)) } expect(sub_help).to eq ['ERROR: "thor child1 foo" was called with no arguments', 'Usage: "thor child1 foo NAME"', ""].join("\n") end end end thor-1.0.1/spec/thor_spec.rb000066400000000000000000000651601357617015300157520ustar00rootroot00000000000000require "helper" describe Thor do describe "#method_option" do it "sets options to the next method to be invoked" do args = %w(foo bar --force) _, options = MyScript.start(args) expect(options).to eq("force" => true) end describe ":lazy_default" do it "is absent when option is not specified" do _, options = MyScript.start(%w(with_optional)) expect(options).to eq({}) end it "sets a default that can be overridden for strings" do _, options = MyScript.start(%w(with_optional --lazy)) expect(options).to eq("lazy" => "yes") _, options = MyScript.start(%w(with_optional --lazy yesyes!)) expect(options).to eq("lazy" => "yesyes!") end it "sets a default that can be overridden for numerics" do _, options = MyScript.start(%w(with_optional --lazy-numeric)) expect(options).to eq("lazy_numeric" => 42) _, options = MyScript.start(%w(with_optional --lazy-numeric 20000)) expect(options).to eq("lazy_numeric" => 20_000) end it "sets a default that can be overridden for arrays" do _, options = MyScript.start(%w(with_optional --lazy-array)) expect(options).to eq("lazy_array" => %w(eat at joes)) _, options = MyScript.start(%w(with_optional --lazy-array hello there)) expect(options).to eq("lazy_array" => %w(hello there)) end it "sets a default that can be overridden for hashes" do _, options = MyScript.start(%w(with_optional --lazy-hash)) expect(options).to eq("lazy_hash" => {"swedish" => "meatballs"}) _, options = MyScript.start(%w(with_optional --lazy-hash polish:sausage)) expect(options).to eq("lazy_hash" => {"polish" => "sausage"}) end end describe "when :for is supplied" do it "updates an already defined command" do _, options = MyChildScript.start(%w(animal horse --other=fish)) expect(options[:other]).to eq("fish") end describe "and the target is on the parent class" do it "updates an already defined command" do args = %w(example_default_command my_param --new-option=verified) options = Scripts::MyScript.start(args) expect(options[:new_option]).to eq("verified") end it "adds a command to the command list if the updated command is on the parent class" do expect(Scripts::MyScript.commands["example_default_command"]).to be end it "clones the parent command" do expect(Scripts::MyScript.commands["example_default_command"]).not_to eq(MyChildScript.commands["example_default_command"]) end end end end describe "#default_command" do it "sets a default command" do expect(MyScript.default_command).to eq("example_default_command") end it "invokes the default command if no command is specified" do expect(MyScript.start([])).to eq("default command") end it "invokes the default command if no command is specified even if switches are given" do expect(MyScript.start(%w(--with option))).to eq("with" => "option") end it "inherits the default command from parent" do expect(MyChildScript.default_command).to eq("example_default_command") end end describe "#stop_on_unknown_option!" do my_script = Class.new(Thor) do class_option "verbose", :type => :boolean class_option "mode", :type => :string stop_on_unknown_option! :exec desc "exec", "Run a command" def exec(*args) [options, args] end desc "boring", "An ordinary command" def boring(*args) [options, args] end end it "passes remaining args to command when it encounters a non-option" do expect(my_script.start(%w(exec command --verbose))).to eq [{}, %w(command --verbose)] end it "passes remaining args to command when it encounters an unknown option" do expect(my_script.start(%w(exec --foo command --bar))).to eq [{}, %w(--foo command --bar)] end it "still accepts options that are given before non-options" do expect(my_script.start(%w(exec --verbose command --foo))).to eq [{"verbose" => true}, %w(command --foo)] end it "still accepts options that require a value" do expect(my_script.start(%w(exec --mode rashly command))).to eq [{"mode" => "rashly"}, %w(command)] end it "still passes everything after -- to command" do expect(my_script.start(%w(exec -- --verbose))).to eq [{}, %w(--verbose)] end it "still passes everything after -- to command, complex" do expect(my_script.start(%w[exec command --mode z again -- --verbose more])).to eq [{}, %w[command --mode z again -- --verbose more]] end it "does not affect ordinary commands" do expect(my_script.start(%w(boring command --verbose))).to eq [{"verbose" => true}, %w(command)] end context "when provided with multiple command names" do klass = Class.new(Thor) do stop_on_unknown_option! :foo, :bar end it "affects all specified commands" do expect(klass.stop_on_unknown_option?(double(:name => "foo"))).to be true expect(klass.stop_on_unknown_option?(double(:name => "bar"))).to be true expect(klass.stop_on_unknown_option?(double(:name => "baz"))).to be false end end context "when invoked several times" do klass = Class.new(Thor) do stop_on_unknown_option! :foo stop_on_unknown_option! :bar end it "affects all specified commands" do expect(klass.stop_on_unknown_option?(double(:name => "foo"))).to be true expect(klass.stop_on_unknown_option?(double(:name => "bar"))).to be true expect(klass.stop_on_unknown_option?(double(:name => "baz"))).to be false end end it "doesn't break new" do expect(my_script.new).to be_a(Thor) end context "along with check_unknown_options!" do my_script2 = Class.new(Thor) do class_option "verbose", :type => :boolean class_option "mode", :type => :string check_unknown_options! stop_on_unknown_option! :exec desc "exec", "Run a command" def exec(*args) [options, args] end def self.exit_on_failure? false end end it "passes remaining args to command when it encounters a non-option" do expect(my_script2.start(%w[exec command --verbose])).to eq [{}, %w[command --verbose]] end it "does not accept if first non-option looks like an option, but only refuses that invalid option" do expect(capture(:stderr) do my_script2.start(%w[exec --foo command --bar]) end.strip).to eq("Unknown switches \"--foo\"") end it "still accepts options that are given before non-options" do expect(my_script2.start(%w[exec --verbose command])).to eq [{"verbose" => true}, %w[command]] end it "still accepts when non-options are given after real options and argument" do expect(my_script2.start(%w[exec --verbose command --foo])).to eq [{"verbose" => true}, %w[command --foo]] end it "does not accept when non-option looks like an option and is after real options" do expect(capture(:stderr) do my_script2.start(%w[exec --verbose --foo]) end.strip).to eq("Unknown switches \"--foo\"") end it "still accepts options that require a value" do expect(my_script2.start(%w[exec --mode rashly command])).to eq [{"mode" => "rashly"}, %w[command]] end it "still passes everything after -- to command" do expect(my_script2.start(%w[exec -- --verbose])).to eq [{}, %w[--verbose]] end it "still passes everything after -- to command, complex" do expect(my_script2.start(%w[exec command --mode z again -- --verbose more])).to eq [{}, %w[command --mode z again -- --verbose more]] end end end describe "#check_unknown_options!" do my_script = Class.new(Thor) do class_option "verbose", :type => :boolean class_option "mode", :type => :string check_unknown_options! desc "checked", "a command with checked" def checked(*args) [options, args] end def self.exit_on_failure? false end end it "still accept options and arguments" do expect(my_script.start(%w[checked command --verbose])).to eq [{"verbose" => true}, %w[command]] end it "still accepts options that are given before arguments" do expect(my_script.start(%w[checked --verbose command])).to eq [{"verbose" => true}, %w[command]] end it "does not accept if non-option that looks like an option is before the arguments" do expect(capture(:stderr) do my_script.start(%w[checked --foo command --bar]) end.strip).to eq("Unknown switches \"--foo\", \"--bar\"") end it "does not accept if non-option that looks like an option is after an argument" do expect(capture(:stderr) do my_script.start(%w[checked command --foo --bar]) end.strip).to eq("Unknown switches \"--foo\", \"--bar\"") end it "does not accept when non-option that looks like an option is after real options" do expect(capture(:stderr) do my_script.start(%w[checked --verbose --foo]) end.strip).to eq("Unknown switches \"--foo\"") end it "does not accept when non-option that looks like an option is before real options" do expect(capture(:stderr) do my_script.start(%w[checked --foo --verbose]) end.strip).to eq("Unknown switches \"--foo\"") end it "still accepts options that require a value" do expect(my_script.start(%w[checked --mode rashly command])).to eq [{"mode" => "rashly"}, %w[command]] end it "still passes everything after -- to command" do expect(my_script.start(%w[checked -- --verbose])).to eq [{}, %w[--verbose]] end it "still passes everything after -- to command, complex" do expect(my_script.start(%w[checked command --mode z again -- --verbose more])).to eq [{"mode" => "z"}, %w[command again --verbose more]] end end describe "#disable_required_check!" do my_script = Class.new(Thor) do class_option "foo", :required => true disable_required_check! :boring desc "exec", "Run a command" def exec(*args) [options, args] end desc "boring", "An ordinary command" def boring(*args) [options, args] end def self.exit_on_failure? false end end it "does not check the required option in the given command" do expect(my_script.start(%w(boring command))).to eq [{}, %w(command)] end it "does check the required option of the remaining command" do content = capture(:stderr) { my_script.start(%w(exec command)) } expect(content).to eq "No value provided for required options '--foo'\n" end it "does affects help by default" do expect(my_script.disable_required_check?(double(:name => "help"))).to be true end context "when provided with multiple command names" do klass = Class.new(Thor) do disable_required_check! :foo, :bar end it "affects all specified commands" do expect(klass.disable_required_check?(double(:name => "help"))).to be true expect(klass.disable_required_check?(double(:name => "foo"))).to be true expect(klass.disable_required_check?(double(:name => "bar"))).to be true expect(klass.disable_required_check?(double(:name => "baz"))).to be false end end context "when invoked several times" do klass = Class.new(Thor) do disable_required_check! :foo disable_required_check! :bar end it "affects all specified commands" do expect(klass.disable_required_check?(double(:name => "help"))).to be true expect(klass.disable_required_check?(double(:name => "foo"))).to be true expect(klass.disable_required_check?(double(:name => "bar"))).to be true expect(klass.disable_required_check?(double(:name => "baz"))).to be false end end end describe "#map" do it "calls the alias of a method if one is provided" do expect(MyScript.start(%w(-T fish))).to eq(%w(fish)) end it "calls the alias of a method if several are provided via #map" do expect(MyScript.start(%w(-f fish))).to eq(["fish", {}]) expect(MyScript.start(%w(--foo fish))).to eq(["fish", {}]) end it "inherits all mappings from parent" do expect(MyChildScript.default_command).to eq("example_default_command") end end describe "#package_name" do it "provides a proper description for a command when the package_name is assigned" do content = capture(:stdout) { PackageNameScript.start(%w(help)) } expect(content).to match(/Baboon commands:/m) end # TODO: remove this, might be redundant, just wanted to prove full coverage it "provides a proper description for a command when the package_name is NOT assigned" do content = capture(:stdout) { MyScript.start(%w(help)) } expect(content).to match(/Commands:/m) end end describe "#desc" do it "provides description for a command" do content = capture(:stdout) { MyScript.start(%w(help)) } expect(content).to match(/thor my_script:zoo\s+# zoo around/m) end it "provides no namespace if $thor_runner is false" do begin $thor_runner = false content = capture(:stdout) { MyScript.start(%w(help)) } expect(content).to match(/thor zoo\s+# zoo around/m) ensure $thor_runner = true end end describe "when :for is supplied" do it "overwrites a previous defined command" do expect(capture(:stdout) { MyChildScript.start(%w(help)) }).to match(/animal KIND \s+# fish around/m) end end describe "when :hide is supplied" do it "does not show the command in help" do expect(capture(:stdout) { MyScript.start(%w(help)) }).not_to match(/this is hidden/m) end it "but the command is still invokable, does not show the command in help" do expect(MyScript.start(%w(hidden yesyes))).to eq(%w(yesyes)) end end end describe "#method_options" do it "sets default options if called before an initializer" do options = MyChildScript.class_options expect(options[:force].type).to eq(:boolean) expect(options[:param].type).to eq(:numeric) end it "overwrites default options if called on the method scope" do args = %w(zoo --force --param feathers) options = MyChildScript.start(args) expect(options).to eq("force" => true, "param" => "feathers") end it "allows default options to be merged with method options" do args = %w(animal bird --force --param 1.0 --other tweets) arg, options = MyChildScript.start(args) expect(arg).to eq("bird") expect(options).to eq("force" => true, "param" => 1.0, "other" => "tweets") end end describe "#start" do it "calls a no-param method when no params are passed" do expect(MyScript.start(%w(zoo))).to eq(true) end it "calls a single-param method when a single param is passed" do expect(MyScript.start(%w(animal fish))).to eq(%w(fish)) end it "does not set options in attributes" do expect(MyScript.start(%w(with_optional --all))).to eq([nil, {"all" => true}, []]) end it "raises an error if the wrong number of params are provided" do arity_asserter = lambda do |args, msg| stderr = capture(:stderr) { Scripts::Arities.start(args) } expect(stderr.strip).to eq(msg) end arity_asserter.call %w(zero_args one), 'ERROR: "thor zero_args" was called with arguments ["one"] Usage: "thor scripts:arities:zero_args"' arity_asserter.call %w(one_arg), 'ERROR: "thor one_arg" was called with no arguments Usage: "thor scripts:arities:one_arg ARG"' arity_asserter.call %w(one_arg one two), 'ERROR: "thor one_arg" was called with arguments ["one", "two"] Usage: "thor scripts:arities:one_arg ARG"' arity_asserter.call %w(one_arg one two), 'ERROR: "thor one_arg" was called with arguments ["one", "two"] Usage: "thor scripts:arities:one_arg ARG"' arity_asserter.call %w(two_args one), 'ERROR: "thor two_args" was called with arguments ["one"] Usage: "thor scripts:arities:two_args ARG1 ARG2"' arity_asserter.call %w(optional_arg one two), 'ERROR: "thor optional_arg" was called with arguments ["one", "two"] Usage: "thor scripts:arities:optional_arg [ARG]"' arity_asserter.call %w(multiple_usages), 'ERROR: "thor multiple_usages" was called with no arguments Usage: "thor scripts:arities:multiple_usages ARG --foo" "thor scripts:arities:multiple_usages ARG --bar"' end it "raises an error if the invoked command does not exist" do expect(capture(:stderr) { Amazing.start(%w(animal)) }.strip).to eq('Could not find command "animal" in "amazing" namespace.') end it "calls method_missing if an unknown method is passed in" do expect(MyScript.start(%w(unk hello))).to eq([:unk, %w(hello)]) end it "does not call a private method no matter what" do expect(capture(:stderr) { MyScript.start(%w(what)) }.strip).to eq('Could not find command "what" in "my_script" namespace.') end it "uses command default options" do options = MyChildScript.start(%w(animal fish)).last expect(options).to eq("other" => "method default") end it "raises when an exception happens within the command call" do expect { MyScript.start(%w(call_myself_with_wrong_arity)) }.to raise_error(ArgumentError) end context "when the user enters an unambiguous substring of a command" do it "invokes a command" do expect(MyScript.start(%w(z))).to eq(MyScript.start(%w(zoo))) end it "invokes a command, even when there's an alias it resolves to the same command" do expect(MyScript.start(%w(hi arg))).to eq(MyScript.start(%w(hidden arg))) end it "invokes an alias" do expect(MyScript.start(%w(animal_pri))).to eq(MyScript.start(%w(zoo))) end end context "when the user enters an ambiguous substring of a command" do it "raises an exception and displays a message that explains the ambiguity" do shell = Thor::Base.shell.new expect(shell).to receive(:error).with("Ambiguous command call matches [call_myself_with_wrong_arity, call_unexistent_method]") MyScript.start(%w(call), :shell => shell) end it "raises an exception when there is an alias" do shell = Thor::Base.shell.new expect(shell).to receive(:error).with("Ambiguous command f matches [foo, fu]") MyScript.start(%w(f), :shell => shell) end end end describe "#help" do def shell @shell ||= Thor::Base.shell.new end describe "on general" do before do @content = capture(:stdout) { MyScript.help(shell) } end it "provides useful help info for the help method itself" do expect(@content).to match(/help \[COMMAND\]\s+# Describe available commands/) end it "provides useful help info for a method with params" do expect(@content).to match(/animal TYPE\s+# horse around/) end it "uses the maximum terminal size to show commands" do expect(@shell).to receive(:terminal_width).and_return(80) content = capture(:stdout) { MyScript.help(shell) } expect(content).to match(/aaa\.\.\.$/) end it "provides description for commands from classes in the same namespace" do expect(@content).to match(/baz\s+# do some bazing/) end it "shows superclass commands" do content = capture(:stdout) { MyChildScript.help(shell) } expect(content).to match(/foo BAR \s+# do some fooing/) end it "shows class options information" do content = capture(:stdout) { MyChildScript.help(shell) } expect(content).to match(/Options\:/) expect(content).to match(/\[\-\-param=N\]/) end it "injects class arguments into default usage" do content = capture(:stdout) { Scripts::MyScript.help(shell) } expect(content).to match(/zoo ACCESSOR \-\-param\=PARAM/) end end describe "for a specific command" do it "provides full help info when talking about a specific command" do expect(capture(:stdout) { MyScript.command_help(shell, "foo") }).to eq(<<-END) Usage: thor my_script:foo BAR Options: [--force] # Force to do some fooing do some fooing This is more info! Everyone likes more info! END end it "provides full help info when talking about a specific command with multiple usages" do expect(capture(:stdout) { MyScript.command_help(shell, "baz") }).to eq(<<-END) Usage: thor my_script:baz THING thor my_script:baz --all Options: [--all=ALL] # Do bazing for all the things super cool END end it "raises an error if the command can't be found" do expect do MyScript.command_help(shell, "unknown") end.to raise_error(Thor::UndefinedCommandError, 'Could not find command "unknown" in "my_script" namespace.') end it "normalizes names before claiming they don't exist" do expect(capture(:stdout) { MyScript.command_help(shell, "name-with-dashes") }).to match(/thor my_script:name-with-dashes/) end it "uses the long description if it exists" do expect(capture(:stdout) { MyScript.command_help(shell, "long_description") }).to eq(<<-HELP) Usage: thor my_script:long_description Description: This is a really really really long description. Here you go. So very long. It even has two paragraphs. HELP end it "doesn't assign the long description to the next command without one" do expect(capture(:stdout) do MyScript.command_help(shell, "name_with_dashes") end).not_to match(/so very long/i) end end describe "instance method" do it "calls the class method" do expect(capture(:stdout) { MyScript.start(%w(help)) }).to match(/Commands:/) end it "calls the class method" do expect(capture(:stdout) { MyScript.start(%w(help foo)) }).to match(/Usage:/) end end context "with required class_options" do let(:klass) do Class.new(Thor) do class_option :foo, :required => true desc "bar", "do something" def bar; end end end it "shows the command help" do content = capture(:stdout) { klass.start(%w(help)) } expect(content).to match(/Commands:/) end end end describe "subcommands" do it "triggers a subcommand help when passed --help" do parent = Class.new(Thor) child = Class.new(Thor) parent.desc "child", "child subcommand" parent.subcommand "child", child parent.desc "dummy", "dummy" expect(child).to receive(:help).with(anything, anything) parent.start ["child", "--help"] end end describe "when creating commands" do it "prints a warning if a public method is created without description or usage" do expect(capture(:stdout) do klass = Class.new(Thor) klass.class_eval "def hello_from_thor; end" end).to match(/\[WARNING\] Attempted to create command "hello_from_thor" without usage or description/) end it "does not print if overwriting a previous command" do expect(capture(:stdout) do klass = Class.new(Thor) klass.class_eval "def help; end" end).to be_empty end end describe "edge-cases" do it "can handle boolean options followed by arguments" do klass = Class.new(Thor) do method_option :loud, :type => :boolean desc "hi NAME", "say hi to name" def hi(name) name = name.upcase if options[:loud] "Hi #{name}" end end expect(klass.start(%w(hi jose))).to eq("Hi jose") expect(klass.start(%w(hi jose --loud))).to eq("Hi JOSE") expect(klass.start(%w(hi --loud jose))).to eq("Hi JOSE") end it "passes through unknown options" do klass = Class.new(Thor) do desc "unknown", "passing unknown options" def unknown(*args) args end end expect(klass.start(%w(unknown foo --bar baz bat --bam))).to eq(%w(foo --bar baz bat --bam)) expect(klass.start(%w(unknown --bar baz))).to eq(%w(--bar baz)) end it "does not pass through unknown options with strict args" do klass = Class.new(Thor) do strict_args_position! desc "unknown", "passing unknown options" def unknown(*args) args end end expect(klass.start(%w(unknown --bar baz))).to eq([]) expect(klass.start(%w(unknown foo --bar baz))).to eq(%w(foo)) end it "strict args works in the inheritance chain" do parent = Class.new(Thor) do strict_args_position! end klass = Class.new(parent) do desc "unknown", "passing unknown options" def unknown(*args) args end end expect(klass.start(%w(unknown --bar baz))).to eq([]) expect(klass.start(%w(unknown foo --bar baz))).to eq(%w(foo)) end it "issues a deprecation warning on incompatible types by default" do expect do Class.new(Thor) do option "bar", :type => :numeric, :default => "foo" end end.to output(/^Deprecation warning/).to_stderr end it "allows incompatible types if allow_incompatible_default_type! is called" do expect do Class.new(Thor) do allow_incompatible_default_type! option "bar", :type => :numeric, :default => "foo" end end.not_to output.to_stderr end it "allows incompatible types if `check_default_type: false` is given" do expect do Class.new(Thor) do option "bar", :type => :numeric, :default => "foo", :check_default_type => false end end.not_to output.to_stderr end it "checks the default type when check_default_type! is called" do expect do Class.new(Thor) do check_default_type! option "bar", :type => :numeric, :default => "foo" end end.to raise_error(ArgumentError, "Expected numeric default value for '--bar'; got \"foo\" (string)") end it "send as a command name" do expect(MyScript.start(%w(send))).to eq(true) end end context "without an exit_on_failure? method" do my_script = Class.new(Thor) do desc "no arg", "do nothing" def no_arg end end it "outputs a deprecation warning on error" do expect do my_script.start(%w[no_arg one]) end.to output(/^Deprecation.*exit_on_failure/).to_stderr end end end thor-1.0.1/spec/util_spec.rb000066400000000000000000000160051357617015300157450ustar00rootroot00000000000000require "helper" module Thor::Util def self.clear_user_home! @@user_home = nil end end describe Thor::Util do describe "#find_by_namespace" do it "returns 'default' if no namespace is given" do expect(Thor::Util.find_by_namespace("")).to eq(Scripts::MyDefaults) end it "adds 'default' if namespace starts with :" do expect(Thor::Util.find_by_namespace(":child")).to eq(Scripts::ChildDefault) end it "returns nil if the namespace can't be found" do expect(Thor::Util.find_by_namespace("thor:core_ext:hash_with_indifferent_access")).to be nil end it "returns a class if it matches the namespace" do expect(Thor::Util.find_by_namespace("app:broken:counter")).to eq(BrokenCounter) end it "matches classes default namespace" do expect(Thor::Util.find_by_namespace("scripts:my_script")).to eq(Scripts::MyScript) end end describe "#namespace_from_thor_class" do it "replaces constant nesting with command namespacing" do expect(Thor::Util.namespace_from_thor_class("Foo::Bar::Baz")).to eq("foo:bar:baz") end it "snake-cases component strings" do expect(Thor::Util.namespace_from_thor_class("FooBar::BarBaz::BazBoom")).to eq("foo_bar:bar_baz:baz_boom") end it "accepts class and module objects" do expect(Thor::Util.namespace_from_thor_class(Thor::CoreExt::HashWithIndifferentAccess)).to eq("thor:core_ext:hash_with_indifferent_access") expect(Thor::Util.namespace_from_thor_class(Thor::Util)).to eq("thor:util") end it "removes Thor::Sandbox namespace" do expect(Thor::Util.namespace_from_thor_class("Thor::Sandbox::Package")).to eq("package") end end describe "#namespaces_in_content" do it "returns an array of names of constants defined in the string" do list = Thor::Util.namespaces_in_content("class Foo; class Bar < Thor; end; end; class Baz; class Bat; end; end") expect(list).to include("foo:bar") expect(list).not_to include("bar:bat") end it "doesn't put the newly-defined constants in the enclosing namespace" do Thor::Util.namespaces_in_content("class Blat; end") expect(defined?(Blat)).not_to be expect(defined?(Thor::Sandbox::Blat)).to be end end describe "#snake_case" do it "preserves no-cap strings" do expect(Thor::Util.snake_case("foo")).to eq("foo") expect(Thor::Util.snake_case("foo_bar")).to eq("foo_bar") end it "downcases all-caps strings" do expect(Thor::Util.snake_case("FOO")).to eq("foo") expect(Thor::Util.snake_case("FOO_BAR")).to eq("foo_bar") end it "downcases initial-cap strings" do expect(Thor::Util.snake_case("Foo")).to eq("foo") end it "replaces camel-casing with underscores" do expect(Thor::Util.snake_case("FooBarBaz")).to eq("foo_bar_baz") expect(Thor::Util.snake_case("Foo_BarBaz")).to eq("foo_bar_baz") end it "places underscores between multiple capitals" do expect(Thor::Util.snake_case("ABClass")).to eq("a_b_class") end end describe "#find_class_and_command_by_namespace" do it "returns a Thor::Group class if full namespace matches" do expect(Thor::Util.find_class_and_command_by_namespace("my_counter")).to eq([MyCounter, nil]) end it "returns a Thor class if full namespace matches" do expect(Thor::Util.find_class_and_command_by_namespace("thor")).to eq([Thor, nil]) end it "returns a Thor class and the command name" do expect(Thor::Util.find_class_and_command_by_namespace("thor:help")).to eq([Thor, "help"]) end it "falls back in the namespace:command look up even if a full namespace does not match" do Thor.const_set(:Help, Module.new) expect(Thor::Util.find_class_and_command_by_namespace("thor:help")).to eq([Thor, "help"]) Thor.send :remove_const, :Help end it "falls back on the default namespace class if nothing else matches" do expect(Thor::Util.find_class_and_command_by_namespace("test")).to eq([Scripts::MyDefaults, "test"]) end end describe "#thor_classes_in" do it "returns thor classes inside the given class" do expect(Thor::Util.thor_classes_in(MyScript)).to eq([MyScript::AnotherScript]) expect(Thor::Util.thor_classes_in(MyScript::AnotherScript)).to be_empty end end describe "#user_home" do before do allow(ENV).to receive(:[]) Thor::Util.clear_user_home! end it "returns the user path if no variable is set on the environment" do expect(Thor::Util.user_home).to eq(File.expand_path("~")) end it "returns the *nix system path if file cannot be expanded and separator does not exist" do expect(File).to receive(:expand_path).with("~").and_raise(RuntimeError) previous_value = File::ALT_SEPARATOR capture(:stderr) { File.const_set(:ALT_SEPARATOR, false) } expect(Thor::Util.user_home).to eq("/") capture(:stderr) { File.const_set(:ALT_SEPARATOR, previous_value) } end it "returns the windows system path if file cannot be expanded and a separator exists" do expect(File).to receive(:expand_path).with("~").and_raise(RuntimeError) previous_value = File::ALT_SEPARATOR capture(:stderr) { File.const_set(:ALT_SEPARATOR, true) } expect(Thor::Util.user_home).to eq("C:/") capture(:stderr) { File.const_set(:ALT_SEPARATOR, previous_value) } end it "returns HOME/.thor if set" do allow(ENV).to receive(:[]).with("HOME").and_return("/home/user/") expect(Thor::Util.user_home).to eq("/home/user/") end it "returns path with HOMEDRIVE and HOMEPATH if set" do allow(ENV).to receive(:[]).with("HOMEDRIVE").and_return("D:/") allow(ENV).to receive(:[]).with("HOMEPATH").and_return("Documents and Settings/James") expect(Thor::Util.user_home).to eq("D:/Documents and Settings/James") end it "returns APPDATA/.thor if set" do allow(ENV).to receive(:[]).with("APPDATA").and_return("/home/user/") expect(Thor::Util.user_home).to eq("/home/user/") end end describe "#thor_root_glob" do before do allow(ENV).to receive(:[]) Thor::Util.clear_user_home! end it "escapes globs in path" do allow(ENV).to receive(:[]).with("HOME").and_return("/home/user{1}/") expect(Dir).to receive(:[]).with('/home/user\\{1\\}/.thor/*').and_return([]) expect(Thor::Util.thor_root_glob).to eq([]) end end describe "#globs_for" do it "escapes globs in path" do expect(Thor::Util.globs_for("/home/apps{1}")).to eq([ '/home/apps\\{1\\}/Thorfile', '/home/apps\\{1\\}/*.thor', '/home/apps\\{1\\}/tasks/*.thor', '/home/apps\\{1\\}/lib/tasks/*.thor' ]) end end describe "#escape_globs" do it "escapes ? * { } [ ] glob characters" do expect(Thor::Util.escape_globs("apps?")).to eq('apps\\?') expect(Thor::Util.escape_globs("apps*")).to eq('apps\\*') expect(Thor::Util.escape_globs("apps {1}")).to eq('apps \\{1\\}') expect(Thor::Util.escape_globs("apps [1]")).to eq('apps \\[1\\]') end end end thor-1.0.1/thor.gemspec000066400000000000000000000022241357617015300150160ustar00rootroot00000000000000# coding: utf-8 lib = File.expand_path("../lib/", __FILE__) $LOAD_PATH.unshift lib unless $LOAD_PATH.include?(lib) require "thor/version" Gem::Specification.new do |spec| spec.add_development_dependency "bundler", ">= 1.0", "< 3" spec.authors = ["Yehuda Katz", "José Valim"] spec.description = "Thor is a toolkit for building powerful command-line interfaces." spec.email = "ruby-thor@googlegroups.com" spec.executables = %w(thor) spec.files = %w(.document thor.gemspec) + Dir["*.md", "bin/*", "lib/**/*.rb"] spec.homepage = "http://whatisthor.com/" spec.licenses = %w(MIT) spec.name = "thor" spec.metadata = { "bug_tracker_uri" => "https://github.com/erikhuda/thor/issues", "changelog_uri" => "https://github.com/erikhuda/thor/blob/master/CHANGELOG.md", "documentation_uri" => "http://whatisthor.com/", "source_code_uri" => "https://github.com/erikhuda/thor/tree/v#{Thor::VERSION}", "wiki_uri" => "https://github.com/erikhuda/thor/wiki" } spec.require_paths = %w(lib) spec.required_ruby_version = ">= 2.0.0" spec.required_rubygems_version = ">= 1.3.5" spec.summary = spec.description spec.version = Thor::VERSION end