├── .document ├── .gemtest ├── .github └── workflows │ └── ruby.yml ├── .gitignore ├── .rspec ├── .yardopts ├── ChangeLog.md ├── Gemfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── gemspec.yml ├── lib ├── rprogram.rb └── rprogram │ ├── argument.rb │ ├── exceptions.rb │ ├── exceptions │ └── program_not_found.rb │ ├── non_option.rb │ ├── option.rb │ ├── option_list.rb │ ├── program.rb │ ├── rprogram.rb │ ├── sudo.rb │ ├── sudo_task.rb │ ├── system.rb │ ├── task.rb │ └── version.rb ├── rprogram.gemspec └── spec ├── classes ├── aliased_program.rb ├── ls_program.rb ├── ls_selinux_task.rb ├── ls_task.rb └── named_program.rb ├── non_option_spec.rb ├── option_examples.rb ├── option_list_spec.rb ├── option_spec.rb ├── program_spec.rb ├── rprogram_spec.rb ├── scripts ├── echo.rb ├── fail.rb ├── print.rb └── success.rb ├── spec_helper.rb ├── system_spec.rb └── task_spec.rb /.document: -------------------------------------------------------------------------------- 1 | - 2 | ChangeLog.md 3 | LICENSE.txt 4 | -------------------------------------------------------------------------------- /.gemtest: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/postmodern/rprogram/40c33b519f10acc7ad576025881892125c38d58c/.gemtest -------------------------------------------------------------------------------- /.github/workflows/ruby.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: ['**'] 8 | 9 | jobs: 10 | tests: 11 | runs-on: ubuntu-latest 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | ruby: 16 | - 2.4 17 | - 2.5 18 | - 2.6 19 | - 2.7 20 | - 3.0 21 | - jruby 22 | name: Ruby ${{ matrix.ruby }} 23 | steps: 24 | - uses: actions/checkout@v2 25 | - name: Set up Ruby 26 | uses: ruby/setup-ruby@v1 27 | with: 28 | ruby-version: ${{ matrix.ruby }} 29 | - name: Install dependencies 30 | run: bundle install --jobs 4 --retry 3 31 | - name: Run tests 32 | run: bundle exec rake test 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /Gemfile.lock 2 | /doc/ 3 | /pkg/ 4 | /vendor/bundle/ 5 | .DS_Store 6 | .yardoc 7 | *.log 8 | *.swp 9 | *~ 10 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --colour --format documentation 2 | -------------------------------------------------------------------------------- /.yardopts: -------------------------------------------------------------------------------- 1 | --markup markdown --title 'RProgram Documentation' --protected 2 | -------------------------------------------------------------------------------- /ChangeLog.md: -------------------------------------------------------------------------------- 1 | ### 0.3.2 / 2012-07-15 2 | 3 | * Require Ruby >= 1.8.7. 4 | * Removed env as a dependency. 5 | * Added {RProgram::Argument}. 6 | * Removed orphaned `rprogram/yard.rb` file. 7 | * Style improvements. 8 | 9 | ### 0.3.1 / 2012-05-27 10 | 11 | * Replaced ore-tasks with 12 | [rubygems-tasks](https://github.com/postmodern/rubygems-tasks#readme). 13 | 14 | ### 0.3.0 / 2011-04-08 15 | 16 | * Merged `RProgram::Nameable` into {RProgram::Program}. 17 | * Merged `RProgram::Options` into {RProgram::Task}. 18 | * Renamed `RProgram::Compat` to {RProgram::System}. 19 | * Added {RProgram::System.arch}. 20 | * Added {RProgram::System.platform}. 21 | * Added {RProgram::System.windows?}. 22 | * Added {RProgram::System.ruby_1_8?}. 23 | * Added {RProgram::System.jruby?}. 24 | * Added {RProgram::System.sudo_path}. 25 | * Added {RProgram::System.sudo_path=}. 26 | * Added {RProgram::System.sudo?}. 27 | * Added {RProgram::Sudo}. 28 | * Added {RProgram::SudoTask}. 29 | * Allow passing tailing [exec-options](http://rubydoc.info/stdlib/core/1.9.2/Kernel#spawn-instance_method) 30 | to {RProgram::System.run} (only supported on Ruby 1.9). 31 | * Allow using `IO.popen` in {RProgram::System.run} if the `:popen` option 32 | is specified (only available on Ruby 1.9). 33 | * Allow specifying the environment variables in {RProgram::System.run} 34 | if the `:env` option is specified (only available on Ruby 1.9). 35 | 36 | ### 0.2.3 / 2011-03-30 37 | 38 | * Require env ~> 0.1, >= 0.1.2. 39 | * Automatically search for programs with a `.exe` suffix, when running on 40 | Windows. 41 | * `RProgram::Compat.find_program` and `RProgram::Compat.find_program_by_names` 42 | now return a `Pathname` object. 43 | 44 | ### 0.2.2 / 2011-01-22 45 | 46 | * Deprecated `RProgram::Compat.platform`. 47 | * Use `File::PATH_SEPARATOR` to separate the `PATH` environment variable 48 | in `RProgram::Compat.paths`. 49 | 50 | ### 0.2.1 / 2010-10-27 51 | 52 | * Allow the formatter block passed to {RProgram::Option} to return `nil`. 53 | 54 | ### 0.2.0 / 2010-10-03 55 | 56 | * Added `RProgram::Nameable::ClassMethods`. 57 | * Added `RProgram::Options::ClassMethods`. 58 | * Added `RProgram::Nameable::ClassMethods#path`: 59 | * {RProgram::Program.find} will default to 60 | `RProgram::Nameable::ClassMethods#path` if set. 61 | 62 | ### 0.1.8 / 2009-12-24 63 | 64 | * Allow Program to run commands under sudo: 65 | * Added `RProgram::Compat.sudo`. 66 | * Added `RProgram::Task#sudo`. 67 | * Added `RProgram::Task#sudo=`. 68 | * Added `RProgram::Task#sudo?`. 69 | * Added {RProgram::Program#sudo}. 70 | 71 | ### 0.1.7 / 2009-09-21 72 | 73 | * Require Hoe >= 2.3.3. 74 | * Require YARD >= 0.2.3.5. 75 | * Require RSpec >= 1.2.8. 76 | * Use 'hoe/signing' for signed RubyGems. 77 | * Moved to YARD based documentation. 78 | * All specs pass on JRuby 1.3.1. 79 | 80 | ### 0.1.6 / 2009-06-30 81 | 82 | * Use Hoe 2.2.0. 83 | * Removed requirement for 'open3'. 84 | * Renamed `PRogram::Compat.PATHS` to `RProgram::Compat.paths`. 85 | * Refactored {RProgram::Option#arguments}. 86 | * Removed `RProgram::Option#format`. 87 | * Refactored `RProgram::NonOption#arguments`. 88 | * Renamed `RProgram::NonOption#leading` to {RProgram::NonOption#leading?}. 89 | * Removed `RProgram::NonOption#tailing`. 90 | * Added {RProgram::NonOption#tailing?}. 91 | * Added specs. 92 | * All specs pass on Ruby 1.9.1-p0 and 1.8.6-p287. 93 | 94 | ### 0.1.5 / 2009-01-14 95 | 96 | * Use Kernel.system in {RProgram::Program#run}, instead of Open3.popen3: 97 | * popen3 is not well supported on Windows. 98 | * win32-open3 does not allow for the execution of single programs with 99 | separate command-line arguments. Instead, it merely executes a command 100 | string in command.com. This seems to allow arbitrary command injection 101 | via command-line arguments. 102 | * {RProgram::Program#run} will now return either `true` or `false`, 103 | depending on the exit status of the program. 104 | * Added some missing documentation. 105 | 106 | ### 0.1.4 / 2009-01-07 107 | 108 | * Added `lib/rprogram/rprogram.rb` to the Manifest. 109 | * Added more documentation. 110 | 111 | ### 0.1.3 / 2008-01-27 112 | 113 | * Renamed `RProgram::Program.create_from_path` to 114 | {RProgram::Program.find_with_path}. 115 | * Renamed `RProgram::Program.create_from_paths` to 116 | {RProgram::Program.find_with_paths}. 117 | * Renamed `RProgram::Program.create` to {RProgram::Program.find}. 118 | * Renamed `RProgram::Program.run_with_task` to {RProgram::Program#run_task}. 119 | 120 | ### 0.1.2 / 2008-01-18 121 | 122 | * DRYed up lib/rprogram/task. 123 | * Added {RProgram::Task.define_option}. 124 | * Added OptionList so that Option may contain sub-options. 125 | * Touched up documenation. 126 | 127 | ### 0.1.1 / 2008-01-18 128 | 129 | * Added support for the {RProgram::Option} argument separators. 130 | 131 | # 132 | # Creates arguments of the form: 133 | # 134 | # ["-opts","value1:value2:value3"] 135 | # 136 | long_option :flag => '-opts', :separator => ':' 137 | 138 | * Fixed the `lib/rprogram.rb` file. 139 | 140 | ### 0.1.0 / 2008-01-17 141 | 142 | * Removed redundent methods in {RProgram::Program}: 143 | * `RProgram::Program.find_by_name` 144 | * `RProgram::Program.find_by_names` 145 | * Added `RProgram::Program#create`. 146 | * Made {RProgram::Program} nameable by default. 147 | * Prevented arbitrary command-injection in {RProgram::Program#run}. 148 | 149 | ### 0.0.9 / 2008-01-09 150 | 151 | * Initial release. 152 | * Provides cross-platform access to the `PATH` environment variable. 153 | * Supports mapping long and short options. 154 | * Supports mapping leading and tailing non-options. 155 | * Supports custom formating of options. 156 | 157 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec 4 | 5 | group :development do 6 | gem 'rake' 7 | gem 'rubygems-tasks', '~> 0.2' 8 | gem 'rspec', '~> 3.0' 9 | gem 'kramdown' 10 | gem 'yard', '~> 0.9' 11 | end 12 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2007-2012 Hal Brodigan 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | 'Software'), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 18 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 19 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 20 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DEPRECATED: This library has been deprecated by [command_mapper](https://github.com/postmodern/command_mapper.rb). 2 | 3 | # RProgram 4 | 5 | * [Source](https://github.com/postmodern/rprogram) 6 | * [Issues](https://github.com/postmodern/rprogram/issues) 7 | * [Documentation](http://rubydoc.info/gems/rprogram/frames) 8 | 9 | ## Description 10 | 11 | RProgram is a library for creating wrappers around command-line programs. 12 | RProgram provides a Rubyful interface to programs and all their options 13 | or non-options. RProgram can also search for programs installed on a 14 | system. 15 | 16 | ## Features 17 | 18 | * Safely executes individual programs and their separate command-line 19 | arguments, to prevent command or option injection. 20 | * Supports using Ruby 1.9 [exec options](http://rubydoc.info/stdlib/core/1.9.2/Kernel#spawn-instance_method). 21 | * Supports specifying environment variables of a process 22 | (only available on Ruby 1.9). 23 | * Allows running programs with `IO.popen` (only available on Ruby 1.9). 24 | * Allows running programs under `sudo`. 25 | * Provides cross-platform access to the `PATH` environment variable. 26 | * Supports leading/tailing non-options. 27 | * Supports long-options and short-options. 28 | * Supports custom formatting of options. 29 | 30 | ## Examples 31 | 32 | First, create the class to represent the options of the program, using 33 | {RProgram::Task} as the base class: 34 | 35 | require 'rprogram/task' 36 | 37 | class MyProgTask < RProgram::Task 38 | 39 | # map in the short-options 40 | short_option :flag => '-o', :name => :output 41 | short_option :flag => '-oX', :name => :xml_output 42 | 43 | # map in long options 44 | long_option :flag => '--no-resolv', :name => :disable_resolv 45 | 46 | # long_option can infer the :name option, based on the :flag 47 | long_option :flag => '--mode' 48 | 49 | # options can also take multiple values 50 | long_option :flag => '--includes', :multiple => true 51 | 52 | # options with multiple values can have a custom separator character 53 | long_option :flag => '--ops', 54 | :multiple => true, 55 | :separator => ',' 56 | 57 | # define any non-options (aka additional arguments) 58 | non_option :tailing => true, :name => :files 59 | 60 | end 61 | 62 | Next, create the class to represent the program you wish to interface with, 63 | using {RProgram::Program} as the base class: 64 | 65 | require 'my_prog_task' 66 | 67 | require 'rprogram/program' 68 | 69 | class MyProg < RProgram::Program 70 | 71 | # identify the file-name of the program 72 | name_program 'my_prg' 73 | 74 | # add a top-level method which finds and runs your program. 75 | def self.my_run(options={},&block) 76 | self.find.my_run(options,&block) 77 | end 78 | 79 | # add a method which runs the program with MyProgTask. 80 | def my_run(options={},&block) 81 | run_task(MyProgTask.new(options,&block)) 82 | end 83 | 84 | end 85 | 86 | Finally, run your program with options or a block: 87 | 88 | MyProgram.my_run(:mode => :fast, :files => ['test1']) 89 | # => true 90 | 91 | MyProgram.my_run do |my_prog| 92 | my_prog.includes = ['one', 'two', 'three'] 93 | my_prog.mode = :safe 94 | 95 | my_prog.output = 'output.txt' 96 | my_prog.files = ['test1.txt', 'test2.txt'] 97 | end 98 | # => true 99 | 100 | ## Install 101 | 102 | $ gem install rprogram 103 | 104 | ## License 105 | 106 | Copyright (c) 2007-2012 Hal Brodigan 107 | 108 | See {file:LICENSE.txt} for license information. 109 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | 3 | require 'rubygems/tasks' 4 | Gem::Tasks.new 5 | 6 | require 'rspec/core/rake_task' 7 | RSpec::Core::RakeTask.new 8 | task :test => :spec 9 | task :default => :spec 10 | 11 | require 'yard' 12 | YARD::Rake::YardocTask.new 13 | -------------------------------------------------------------------------------- /gemspec.yml: -------------------------------------------------------------------------------- 1 | name: rprogram 2 | summary: A library for creating wrappers around command-line programs. 3 | description: 4 | RProgram is a library for creating wrappers around command-line programs. 5 | RProgram provides a Rubyful interface to programs and all their options 6 | or non-options. RProgram can also search for programs installed on a 7 | system. files without having to use YAML or define classes named like 8 | the file. 9 | 10 | license: MIT 11 | authors: Postmodern 12 | email: postmodern.mod3@gmail.com 13 | homepage: https://github.com/postmodern/rprogram#readme 14 | has_yard: true 15 | 16 | required_ruby_version: ">= 1.8.7" 17 | 18 | development_dependencies: 19 | bundler: ~> 2.0 20 | -------------------------------------------------------------------------------- /lib/rprogram.rb: -------------------------------------------------------------------------------- 1 | require 'rprogram/task' 2 | require 'rprogram/program' 3 | require 'rprogram/version' 4 | -------------------------------------------------------------------------------- /lib/rprogram/argument.rb: -------------------------------------------------------------------------------- 1 | module RProgram 2 | class Argument 3 | 4 | # 5 | # Formats a value into an Array of arguments. 6 | # 7 | # @param [Hash, Array, String] value 8 | # The value to format. 9 | # 10 | # @return [Array] 11 | # The formatted arguments. 12 | # 13 | def arguments(value) 14 | value = case value 15 | when Hash 16 | value.map do |key,sub_value| 17 | if sub_value == true then key.to_s 18 | elsif sub_value then "#{key}=#{sub_value}" 19 | end 20 | end 21 | when false, nil 22 | [] 23 | else 24 | Array(value) 25 | end 26 | 27 | return value.compact 28 | end 29 | 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/rprogram/exceptions.rb: -------------------------------------------------------------------------------- 1 | require 'rprogram/exceptions/program_not_found' 2 | -------------------------------------------------------------------------------- /lib/rprogram/exceptions/program_not_found.rb: -------------------------------------------------------------------------------- 1 | module RProgram 2 | class ProgramNotFound < RuntimeError 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /lib/rprogram/non_option.rb: -------------------------------------------------------------------------------- 1 | require 'rprogram/argument' 2 | 3 | module RProgram 4 | class NonOption < Argument 5 | 6 | # Name of the argument(s) 7 | attr_reader :name 8 | 9 | # Can the argument be specified multiple times 10 | attr_reader :multiple 11 | 12 | # 13 | # Creates a new NonOption object. 14 | # 15 | # @param [Hash] options 16 | # Additional options. 17 | # 18 | # @option options [Symbol] :name 19 | # The name of the non-option. 20 | # 21 | # @option options [true, false] :leading (true) 22 | # Implies the non-option is a leading non-option. 23 | # 24 | # @option options [false, true] :tailing (false) 25 | # Implies the non-option is a tailing non-option. 26 | # 27 | # @option options [false, true] :multiple (false) 28 | # Implies the non-option maybe given an Array of values. 29 | # 30 | def initialize(options={}) 31 | @name = options[:name] 32 | 33 | @tailing = if options[:leading] then !options[:leading] 34 | elsif options[:tailing] then options[:tailing] 35 | else true 36 | end 37 | 38 | @multiple = (options[:multiple] || false) 39 | end 40 | 41 | # 42 | # Determines whether the non-option's arguments are tailing. 43 | # 44 | # @return [true, false] 45 | # Specifies whether the non-option's arguments are tailing. 46 | # 47 | def tailing? 48 | @tailing == true 49 | end 50 | 51 | # 52 | # Determines whether the non-option's arguments are leading. 53 | # 54 | # @return [true, false] 55 | # Specifies whether the non-option's arguments are leading. 56 | # 57 | def leading? 58 | !(@tailing) 59 | end 60 | 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /lib/rprogram/option.rb: -------------------------------------------------------------------------------- 1 | require 'rprogram/argument' 2 | 3 | module RProgram 4 | class Option < Argument 5 | 6 | # Flag of the option 7 | attr_reader :flag 8 | 9 | # Is the option in equals format 10 | attr_reader :equals 11 | 12 | # Can the option be specified multiple times 13 | attr_reader :multiple 14 | 15 | # Argument separator 16 | attr_reader :separator 17 | 18 | # Does the option contain sub-options 19 | attr_reader :sub_options 20 | 21 | # 22 | # Creates a new Option object with. If a block is given it will be 23 | # used for the custom formatting of the option. If a block is not given, 24 | # the option will use the default_format when generating the arguments. 25 | # 26 | # @param [Hash] options 27 | # Additional options. 28 | # 29 | # @option options [String] :flag 30 | # The command-line flag to use. 31 | # 32 | # @option options [true, false] :equals (false) 33 | # Implies the option maybe formated as `--flag=value`. 34 | # 35 | # @option options [true, false] :multiple (false) 36 | # Specifies the option maybe given an Array of values. 37 | # 38 | # @option options [String] :separator 39 | # The separator to use for formating multiple arguments into one 40 | # `String`. Cannot be used with the `:multiple` option. 41 | # 42 | # @option options [true, false] :sub_options (false) 43 | # Specifies that the option contains sub-options. 44 | # 45 | # @yield [option, value] 46 | # If a block is given, it will be used to format each value of the 47 | # option. 48 | # 49 | # @yieldparam [Option] option 50 | # The option that is being formatted. 51 | # 52 | # @yieldparam [String, Array] value 53 | # The value to format for the option. May be an Array, if multiple 54 | # values are allowed with the option. 55 | # 56 | def initialize(options={},&block) 57 | @flag = options[:flag] 58 | 59 | @equals = (options[:equals] || false) 60 | @multiple = (options[:multiple] || false) 61 | @separator = if options[:separator] then options[:separator] 62 | elsif options[:equals] then ' ' 63 | end 64 | @sub_options = (options[:sub_options] || false) 65 | 66 | @formatter = if block 67 | block 68 | else 69 | Proc.new do |opt,value| 70 | if opt.equals 71 | ["#{opt.flag}=#{value.first}"] 72 | else 73 | [opt.flag] + value 74 | end 75 | end 76 | end 77 | end 78 | 79 | # 80 | # Formats the arguments for the option. 81 | # 82 | # @param [Hash, Array, String] value 83 | # The arguments to format. 84 | # 85 | # @return [Array] 86 | # The formatted arguments of the option. 87 | # 88 | def arguments(value) 89 | case value 90 | when true 91 | [@flag] 92 | when false, nil 93 | [] 94 | else 95 | value = super(value) 96 | 97 | if @multiple 98 | args = [] 99 | 100 | value.each do |arg| 101 | args += Array(@formatter.call(self,[arg])) 102 | end 103 | 104 | return args 105 | else 106 | value = [value.join(@separator)] if @separator 107 | 108 | return Array(@formatter.call(self,value)) 109 | end 110 | end 111 | end 112 | 113 | end 114 | end 115 | -------------------------------------------------------------------------------- /lib/rprogram/option_list.rb: -------------------------------------------------------------------------------- 1 | module RProgram 2 | class OptionList < Hash 3 | 4 | # 5 | # Creates a new OptionList object. 6 | # 7 | # @param [Hash{Symbol => String}] options 8 | # The options to start with. 9 | # 10 | def initialize(options={}) 11 | super(options) 12 | end 13 | 14 | protected 15 | 16 | # 17 | # Provides transparent access to the options within the option list. 18 | # 19 | # @example 20 | # opt_list = OptionList.new(:name => 'test') 21 | # opt_list.name 22 | # # => "test" 23 | # 24 | def method_missing(sym,*args,&block) 25 | name = sym.to_s 26 | 27 | unless block 28 | if (name =~ /=$/ && args.length == 1) 29 | return self[name.chop.to_sym] = args.first 30 | elsif args.empty? 31 | return self[sym] 32 | end 33 | end 34 | 35 | return super(sym,*args,&block) 36 | end 37 | 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /lib/rprogram/program.rb: -------------------------------------------------------------------------------- 1 | require 'rprogram/rprogram' 2 | require 'rprogram/system' 3 | require 'rprogram/task' 4 | require 'rprogram/sudo_task' 5 | require 'rprogram/exceptions/program_not_found' 6 | 7 | module RProgram 8 | class Program 9 | 10 | # Path to the program 11 | attr_reader :path 12 | 13 | # Name of the program 14 | attr_reader :name 15 | 16 | # 17 | # Creates a new Program object. 18 | # 19 | # @param [String] path 20 | # The full-path of the program. 21 | # 22 | # @yield [prog] 23 | # If a block is given, it will be passed the newly created Program 24 | # object. 25 | # 26 | # @yieldparam [Program] prog 27 | # The newly created program object. 28 | # 29 | # @raise [ProgramNotFound] 30 | # Specifies the given path was not a valid file. 31 | # 32 | # @example 33 | # Program.new('/usr/bin/ls') 34 | # 35 | def initialize(path) 36 | path = File.expand_path(path) 37 | 38 | unless File.file?(path) 39 | raise(ProgramNotFound,"program #{path.dump} does not exist") 40 | end 41 | 42 | @path = path 43 | @name = File.basename(path) 44 | 45 | yield self if block_given? 46 | end 47 | 48 | # 49 | # @return [String] 50 | # The name of the program. 51 | # 52 | def self.program_name 53 | @program_name ||= nil 54 | end 55 | 56 | # 57 | # @return [Array] 58 | # The program's aliases. 59 | # 60 | def self.program_aliases 61 | @program_aliases ||= [] 62 | end 63 | 64 | # 65 | # Combines program_name with program_aliases. 66 | # 67 | # @return [Array] 68 | # Names the program is known by. 69 | # 70 | def self.program_names 71 | ([program_name] + program_aliases).compact 72 | end 73 | 74 | # 75 | # Sets the program name for the class. 76 | # 77 | # @param [String, Symbol] name 78 | # The new program name. 79 | # 80 | # @example 81 | # name_program 'ls' 82 | # 83 | def self.name_program(name) 84 | @program_name = name.to_s 85 | end 86 | 87 | # 88 | # Sets the program aliases for the class. 89 | # 90 | # @param [Array] aliases 91 | # The new program aliases. 92 | # 93 | # @example 94 | # alias_program 'vim', 'vi' 95 | # 96 | def self.alias_program(*aliases) 97 | @program_aliases = aliases.map(&:to_s) 98 | end 99 | 100 | # 101 | # The default path of the program. 102 | # 103 | # @return [String, nil] 104 | # The path to the program. 105 | # 106 | # @since 0.2.0 107 | # 108 | def self.path 109 | @program_path 110 | end 111 | 112 | # 113 | # Sets the default path to the program. 114 | # 115 | # @param [String] new_path 116 | # The new path to the program. 117 | # 118 | # @return [String, nil] 119 | # The path to the program. 120 | # 121 | # @since 0.2.0 122 | # 123 | def self.path=(new_path) 124 | @program_path = if new_path 125 | File.expand_path(new_path) 126 | end 127 | end 128 | 129 | # 130 | # Creates a new program object. 131 | # 132 | # @param [String] path 133 | # The full-path of the program. 134 | # 135 | # @param [Array] arguments 136 | # Additional arguments to initialize the program with. 137 | # 138 | # @yield [prog] 139 | # If a block is given, it will be passed the newly created Program 140 | # object. 141 | # 142 | # @yieldparam [Program] prog 143 | # The newly created program object. 144 | # 145 | # @return [Program, nil] 146 | # Returns the newly created Program object. If the given path was 147 | # not a valid file, `nil` will be returned. 148 | # 149 | # @example 150 | # Program.find_with_path('/bin/cd') 151 | # # => Program 152 | # 153 | # @example 154 | # Program.find_with_path('/obviously/fake') 155 | # # => nil 156 | # 157 | def self.find_with_path(path,*arguments,&block) 158 | self.new(path,*arguments,&block) if File.file?(path) 159 | end 160 | 161 | # 162 | # Creates a new program object with the specified _paths_, 163 | # if a path within _paths_ is a valid file. Any given _arguments_ or 164 | # a given _block_ will be used in creating the new program. 165 | # 166 | # @param [Array] paths 167 | # The Array of paths to search for the program. 168 | # 169 | # @param [Array] arguments 170 | # Additional arguments to initialize the program with. 171 | # 172 | # @yield [prog] 173 | # If a block is given, it will be passed the newly created Program 174 | # object. 175 | # 176 | # @yieldparam [Program] prog 177 | # The newly created program object. 178 | # 179 | # @return [Program, nil] 180 | # Returns the newly created Program object. If none of the given 181 | # paths were valid files, `nil` will be returned. 182 | # 183 | # @example 184 | # Program.find_with_paths(['/bin/cd','/usr/bin/cd']) 185 | # # => Program 186 | # 187 | # @example 188 | # Program.find_with_paths(['/obviously/fake','/bla']) 189 | # # => nil 190 | # 191 | def self.find_with_paths(paths,*arguments,&block) 192 | paths.each do |path| 193 | if File.file?(path) 194 | return self.new(path,*arguments,&block) 195 | end 196 | end 197 | end 198 | 199 | # 200 | # Finds and creates the program using it's `program_names`. 201 | # 202 | # @param [Array] arguments 203 | # Additional arguments to initialize the program object with. 204 | # 205 | # @yield [prog] 206 | # If a block is given, it will be passed the newly created Program 207 | # object. 208 | # 209 | # @yieldparam [Program] prog 210 | # The newly created program object. 211 | # 212 | # @return [Program] 213 | # The newly created program object. 214 | # 215 | # @raise [ProgramNotFound] 216 | # Non of the `program_names` represented valid programs on the system. 217 | # 218 | # @example 219 | # Program.find 220 | # # => Program 221 | # 222 | # @example 223 | # MyProgram.find('stuff','here') do |prog| 224 | # # ... 225 | # end 226 | # 227 | def self.find(*arguments,&block) 228 | path = self.path 229 | path ||= System.find_program_by_names(*self.program_names) 230 | 231 | unless path 232 | names = self.program_names.map(&:dump).join(', ') 233 | 234 | raise(ProgramNotFound,"programs #{names} were not found") 235 | end 236 | 237 | return self.new(path,*arguments,&block) 238 | end 239 | 240 | # 241 | # @return [String] 242 | # The program name of the class. 243 | # 244 | def program_name 245 | self.class.program_name 246 | end 247 | 248 | # 249 | # @return [Array] 250 | # The program aliases of the class. 251 | # 252 | def program_aliases 253 | self.class.program_aliases 254 | end 255 | 256 | # 257 | # @return [Array] 258 | # The program names of the class. 259 | # 260 | def program_names 261 | self.class.program_names 262 | end 263 | 264 | # 265 | # Runs the program. 266 | # 267 | # @overload run(*arguments) 268 | # Run the program with the given arguments. 269 | # 270 | # @param [Array] arguments 271 | # Additional arguments to run the program with. 272 | # 273 | # @overload run(*arguments,options) 274 | # Run the program with the given arguments and options. 275 | # 276 | # @param [Array] arguments 277 | # Additional arguments to run the program with. 278 | # 279 | # @param [Hash] options 280 | # Additional options to execute the program with. 281 | # 282 | # @option options [Hash{String => String}] :env 283 | # Environment variables to execute the program with. 284 | # 285 | # @option options [String] :popen 286 | # Specifies to run the program using `IO.popen` with the given 287 | # IO mode. 288 | # 289 | # @return [true, false] 290 | # Specifies the exit status of the program. 291 | # 292 | # @example 293 | # echo = Program.find_by_name('echo') 294 | # echo.run('hello') 295 | # # hello 296 | # # => true 297 | # 298 | # @see http://rubydoc.info/stdlib/core/1.9.2/Kernel#spawn-instance_method 299 | # For acceptable options. 300 | # 301 | def run(*arguments) 302 | System.run(@path,*arguments) 303 | end 304 | 305 | # 306 | # Runs the program under sudo. 307 | # 308 | # @overload sudo(*arguments) 309 | # Run the program under `sudo` with the given arguments. 310 | # 311 | # @param [Array] arguments 312 | # Additional arguments to run the program with. 313 | # 314 | # @overload sudo(*arguments,options) 315 | # Run the program under `sudo` with the given arguments 316 | # and options. 317 | # 318 | # @param [Array] arguments 319 | # Additional arguments to run the program with. 320 | # 321 | # @param [Hash] options 322 | # Additional options to execute the program with. 323 | # 324 | # @option options [Hash{Symbol => Object}] :sudo 325 | # Additional `sudo` options. 326 | # 327 | # @return [Boolean] 328 | # Specifies whether the program exited successfully. 329 | # 330 | # @raise [ProgramNotFound] 331 | # Indicates that the `sudo` program could not be located. 332 | # 333 | # @since 0.1.8 334 | # 335 | # @see http://rubydoc.info/stdlib/core/1.9.2/Kernel#spawn-instance_method 336 | # For acceptable options. 337 | # 338 | # @see SudoTask 339 | # For valid `:sudo` options. 340 | # 341 | def sudo(*arguments) 342 | options = case arguments.last 343 | when Hash then arguments.pop 344 | else {} 345 | end 346 | 347 | task = SudoTask.new(options.delete(:sudo) || {}) 348 | task.command = [@path] + arguments 349 | 350 | arguments = task.arguments 351 | arguments << options unless options.empty? 352 | 353 | return System.sudo(*arguments) 354 | end 355 | 356 | # 357 | # Runs the program with the arguments from the given task. 358 | # 359 | # @param [Task, #to_a] task 360 | # The task who's arguments will be used to run the program. 361 | # 362 | # @param [Hash] options 363 | # Additional options to execute the program with. 364 | # 365 | # @return [true, false] 366 | # Specifies the exit status of the program. 367 | # 368 | # @see #run 369 | # 370 | def run_task(task,options={}) 371 | arguments = task.arguments 372 | arguments << options unless options.empty? 373 | 374 | return run(*arguments) 375 | end 376 | 377 | # 378 | # Runs the program under `sudo` with the arguments from the given task. 379 | # 380 | # @param [Task, #to_a] task 381 | # The task who's arguments will be used to run the program. 382 | # 383 | # @param [Hash] options 384 | # Spawn options for the program to be ran. 385 | # 386 | # @yield [sudo] 387 | # If a block is given, it will be passed the sudo task. 388 | # 389 | # @yieldparam [SudoTask] sudo 390 | # The sudo tasks. 391 | # 392 | # @return [true, false] 393 | # Specifies the exit status of the program. 394 | # 395 | # @see #sudo 396 | # 397 | # @since 0.3.0 398 | # 399 | def sudo_task(task,options={},&block) 400 | arguments = task.arguments 401 | arguments << options unless options.empty? 402 | 403 | return sudo(*arguments,&block) 404 | end 405 | 406 | # 407 | # Converts the program to a String. 408 | # 409 | # @return [String] 410 | # The path of the program. 411 | # 412 | # @example 413 | # Program.find_by_name('echo').to_s 414 | # # => "/usr/bin/echo" 415 | # 416 | def to_s 417 | @path.to_s 418 | end 419 | 420 | end 421 | end 422 | -------------------------------------------------------------------------------- /lib/rprogram/rprogram.rb: -------------------------------------------------------------------------------- 1 | module RProgram 2 | @debug = false 3 | 4 | # 5 | # @return [true, false] 6 | # Specifies whether debugging messages are enabled for RProgram. 7 | # Defaults to false, if not set. 8 | # 9 | def self.debug 10 | @debug 11 | end 12 | 13 | # 14 | # Enables or disables debugging messages for RProgram. 15 | # 16 | # @param [true, false] value 17 | # The new value of RProgram.debug. 18 | # 19 | # @return [true, false] 20 | # The new value of RProgram.debug. 21 | # 22 | def self.debug=(value) 23 | @debug = value 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/rprogram/sudo.rb: -------------------------------------------------------------------------------- 1 | require 'rprogram/program' 2 | 3 | module RProgram 4 | # 5 | # Represents the `sudo` executable. 6 | # 7 | # @since 0.3.0 8 | # 9 | class Sudo < Program 10 | 11 | name_program 'sudo' 12 | 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/rprogram/sudo_task.rb: -------------------------------------------------------------------------------- 1 | require 'rprogram/task' 2 | 3 | module RProgram 4 | # 5 | # Represents the options for `sudo`. 6 | # 7 | # ## Sudo options: 8 | # 9 | # * `-A` - `sudo.ask_password` 10 | # * `-b` - `sudo.background` 11 | # * `-C` - `sudo.close_from` 12 | # * `-E` - `sudo.preserve_env` 13 | # * `-e` - `sudo.edit` 14 | # * `-g` - `sudo.group` 15 | # * `-H` - `sudo.home` 16 | # * `-h` - `sudo.help` 17 | # * `-i` - `sudo.simulate_initial_login` 18 | # * `-k` - `sudo.kill` 19 | # * `-K` - `sudo.sure_kill` 20 | # * `-L` - `sudo.list_defaults` 21 | # * `-l` - `sudo.list` 22 | # * `-n` - `sudo.non_interactive` 23 | # * `-P` - `sudo.preserve_group` 24 | # * `-p` - `sudo.prompt` 25 | # * `-r` - `sudo.role` 26 | # * `-S` - `sudo.stdin` 27 | # * `-s` - `sudo.shell` 28 | # * `-t` - `sudo.type` 29 | # * `-U` - `sudo.other_user` 30 | # * `-u` - `sudo.user` 31 | # * `-V` - `sudo.version` 32 | # * `-v` - `sudo.validate` 33 | # 34 | # * `[command]` - `sudo.command` 35 | # 36 | # @since 0.3.0 37 | # 38 | class SudoTask < Task 39 | 40 | short_option :name => :ask_password, :flag => '-A' 41 | short_option :name => :background, :flag => '-b' 42 | short_option :name => :close_from, :flag => '-C' 43 | short_option :name => :preserve_env, :flag => '-E' 44 | short_option :name => :edit, :flag => '-e' 45 | short_option :name => :group, :flag => '-g' 46 | short_option :name => :home, :flag => '-H' 47 | short_option :name => :help, :flag => '-h' 48 | short_option :name => :simulate_initial_login, :flag => '-i' 49 | short_option :name => :kill, :flag => '-k' 50 | short_option :name => :sure_kill, :flag => '-K' 51 | short_option :name => :list_defaults, :flag => '-L' 52 | short_option :name => :list, :flag => '-l' 53 | short_option :name => :non_interactive, :flag => '-n' 54 | short_option :name => :preserve_group, :flag => '-P' 55 | short_option :name => :prompt, :flag => '-p' 56 | short_option :name => :role, :flag => '-r' 57 | short_option :name => :stdin, :flag => '-S' 58 | short_option :name => :shell, :flag => '-s' 59 | short_option :name => :type, :flag => '-t' 60 | short_option :name => :other_user, :flag => '-U' 61 | short_option :name => :user, :flag => '-u' 62 | short_option :name => :version, :flag => '-V' 63 | short_option :name => :validate, :flag => '-v' 64 | 65 | non_option :tailing => true, :name => :command 66 | 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /lib/rprogram/system.rb: -------------------------------------------------------------------------------- 1 | require 'rprogram/exceptions/program_not_found' 2 | require 'rprogram/rprogram' 3 | 4 | require 'pathname' 5 | 6 | module RProgram 7 | # 8 | # @since 0.3.0 9 | # 10 | module System 11 | 12 | @arch, @platform = RUBY_PLATFORM.split('-',2) 13 | @platform ||= @arch 14 | 15 | # 16 | # Determines the native architecture. 17 | # 18 | # @return [String] 19 | # The native architecture. 20 | # 21 | # @example 22 | # System.arch 23 | # # => "x86-64" 24 | # 25 | # @since 0.3.0 26 | # 27 | def self.arch 28 | @arch 29 | end 30 | 31 | # 32 | # Determines the native platform. 33 | # 34 | # @return [String] 35 | # The native platform. 36 | # 37 | # @example 38 | # System.platform 39 | # # => "linux" 40 | # 41 | def self.platform 42 | @platform 43 | end 44 | 45 | # 46 | # Determines if the platform is Windows. 47 | # 48 | # @return [Boolean] 49 | # Specifies whether the platform is Windows. 50 | # 51 | # @since 0.3.0 52 | # 53 | def self.windows? 54 | if @platform 55 | @platform.include?('mingw') || @platform.include?('mswin') 56 | else 57 | false 58 | end 59 | end 60 | 61 | # 62 | # Determines if the current Ruby VM is from the 1.8.x family. 63 | # 64 | # @return [Boolean] 65 | # Specifies if the current Ruby VM is from the 1.8.x family. 66 | # 67 | # @since 0.3.0 68 | # 69 | def self.ruby_1_8? 70 | RUBY_VERSION.start_with?('1.8.') 71 | end 72 | 73 | # 74 | # Determines if the current Ruby VM is JRuby. 75 | # 76 | # @return [Boolean] 77 | # Specifies whether the Ruby VM is JRuby. 78 | # 79 | # @since 0.3.0 80 | # 81 | def self.jruby? 82 | const_defined?(:RUBY_ENGINE) && const_get(:RUBY_ENGINE) == 'jruby' 83 | end 84 | 85 | # 86 | # The directories to search for programs. 87 | # 88 | # @return [Array] 89 | # The directories containing programs. 90 | # 91 | def self.paths 92 | @paths ||= ENV['PATH'].split(File::PATH_SEPARATOR).map do |dir| 93 | Pathname.new(dir) 94 | end 95 | end 96 | 97 | # 98 | # Finds the full-path of the program with the matching name. 99 | # 100 | # @param [String] name 101 | # The name of the program to find. 102 | # 103 | # @return [Pathname, nil] 104 | # The full-path of the desired program. 105 | # 106 | # @example 107 | # System.find_program('as') 108 | # #=> # 109 | # 110 | def self.find_program(name) 111 | # add the `.exe` suffix to the name, if running on Windows 112 | name = "#{name}.exe" if windows? 113 | 114 | paths.each do |dir| 115 | full_path = dir.join(name).expand_path 116 | 117 | return full_path if full_path.file? 118 | end 119 | 120 | return nil 121 | end 122 | 123 | # 124 | # Finds the program matching one of the matching names. 125 | # 126 | # @param [Array] names 127 | # The names of the program to use while searching for the program. 128 | # 129 | # @return [Pathname, nil] 130 | # The first full-path for the program. 131 | # 132 | # @example 133 | # System.find_program_by_names("gas","as") 134 | # # => # 135 | # 136 | def self.find_program_by_names(*names) 137 | names.each do |name| 138 | if (path = find_program(name)) 139 | return path 140 | end 141 | end 142 | 143 | return nil 144 | end 145 | 146 | # 147 | # Runs a program. 148 | # 149 | # @overload run(path,*arguments) 150 | # Run the program with the given arguments. 151 | # 152 | # @param [Pathname, String] path 153 | # The path of the program to run. 154 | # 155 | # @param [Array] arguments 156 | # Additional arguments to run the program with. 157 | # 158 | # @overload run(path,*arguments,options) 159 | # Run the program with the given arguments and options. 160 | # 161 | # @param [Pathname, String] path 162 | # The path of the program to run. 163 | # 164 | # @param [Array] arguments 165 | # Additional arguments to run the program with. 166 | # 167 | # @param [Hash] options 168 | # Additional options to execute the program with. 169 | # 170 | # @option options [Hash{String => String}] :env 171 | # Environment variables to execute the program with. 172 | # 173 | # @option options [String] :popen 174 | # Specifies to run the program using `IO.popen` with the given 175 | # IO mode. 176 | # 177 | # @return [Boolean] 178 | # Specifies whether the program exited successfully. 179 | # 180 | # @raise [RuntimeError] 181 | # Passing `:popen`, `:env` or exec options is not supported 182 | # on Ruby 1.8.x. 183 | # 184 | # @see http://rubydoc.info/stdlib/core/1.9.2/Kernel#spawn-instance_method 185 | # For acceptable options. 186 | # 187 | def self.run(*arguments) 188 | # extra tailing options and ENV variables from arguments 189 | case arguments.last 190 | when Hash 191 | options = arguments.pop 192 | env = (options.delete(:env) || {}) 193 | popen = options.delete(:popen) 194 | else 195 | options = {} 196 | env = {} 197 | end 198 | 199 | # all arguments must be Strings 200 | arguments = arguments.map(&:to_s) 201 | 202 | # print debugging information 203 | if RProgram.debug 204 | command = '' 205 | 206 | env.each do |name,value| 207 | command << "#{name}=#{value} " 208 | end 209 | 210 | command << arguments.join(' ') 211 | command << " #{options.inspect}" unless options.empty? 212 | 213 | warn ">>> #{command}" 214 | end 215 | 216 | # passing ENV variables or exec options is not supported before 1.9.1 217 | if (!options.empty? && ruby_1_8?) 218 | raise("cannot pass exec options to Kernel.system in #{RUBY_VERSION}") 219 | end 220 | 221 | if popen 222 | # IO.popen does not accept multiple arguments on Ruby 1.8.x. 223 | if ruby_1_8? 224 | raise("cannot use :popen on #{RUBY_VERSION}, please use 1.9.x") 225 | end 226 | 227 | # :popen can only be used on Unix, or on Windows with JRuby 228 | if (windows? && !jruby?) 229 | raise("cannot use :popen on Windows, unless under JRuby") 230 | end 231 | end 232 | 233 | # re-add ENV variables and exec options 234 | arguments.unshift(env) unless env.empty? 235 | arguments.push(options) unless options.empty? 236 | 237 | if popen then IO.popen(arguments,popen) 238 | else Kernel.system(*arguments) 239 | end 240 | end 241 | 242 | # 243 | # The path to the `sudo` program. 244 | # 245 | # @return [Pathname, nil] 246 | # The path to the `sudo` program. 247 | # 248 | # @since 0.3.0 249 | # 250 | def self.sudo_path 251 | @sudo ||= find_program('sudo') 252 | end 253 | 254 | # 255 | # Sets the path to the `sudo` program. 256 | # 257 | # @param [String, Pathname] path 258 | # The new path to use. 259 | # 260 | # @return [Pathanme] 261 | # The new path to the `sudo` program. 262 | # 263 | # @since 0.3.0 264 | # 265 | def self.sudo_path=(path) 266 | @sudo = Pathname.new(path) 267 | end 268 | 269 | # 270 | # Determines whether `sudo` is available on the system. 271 | # 272 | # @return [Boolean] 273 | # Specifies whether the `sudo` program is installed on the system. 274 | # 275 | # @since 0.3.0 276 | # 277 | def self.sudo? 278 | !sudo_path.nil? 279 | end 280 | 281 | # 282 | # Runs a program under sudo. 283 | # 284 | # @overload run(path,*arguments) 285 | # Run the program with the given arguments. 286 | # 287 | # @param [Pathname, String] path 288 | # The path of the program to run. 289 | # 290 | # @param [Array] arguments 291 | # Additional arguments to run the program with. 292 | # 293 | # @overload run(path,*arguments,options) 294 | # Run the program with the given arguments and options. 295 | # 296 | # @param [Pathname, String] path 297 | # The path of the program to run. 298 | # 299 | # @param [Array] arguments 300 | # Additional arguments to run the program with. 301 | # 302 | # @param [Hash] options 303 | # Additional options to execute the program with. 304 | # 305 | # @return [Boolean] 306 | # Specifies whether the program exited successfully. 307 | # 308 | # @raise [ProgramNotFound] 309 | # Indicates that the `sudo` program could not be located. 310 | # 311 | # @since 0.1.8 312 | # 313 | # @see run 314 | # 315 | def self.sudo(*arguments) 316 | unless sudo? 317 | raise(ProgramNotFound,'could not find the "sudo" program') 318 | end 319 | 320 | return run(sudo_path,*arguments) 321 | end 322 | end 323 | end 324 | -------------------------------------------------------------------------------- /lib/rprogram/task.rb: -------------------------------------------------------------------------------- 1 | require 'rprogram/option' 2 | require 'rprogram/option_list' 3 | require 'rprogram/non_option' 4 | 5 | module RProgram 6 | class Task 7 | 8 | # 9 | # Creates a new Task object. 10 | # 11 | # @param [Hash{Symbol => Object}] options 12 | # Additional task options. 13 | # 14 | # @yield [task] 15 | # If a block is given, it will be passed the newly created task. 16 | # 17 | # @yieldparam [Task] task 18 | # The newly created Task object. 19 | # 20 | # @example 21 | # Task.new(:test => 'example', :count => 2, :verbose => true) 22 | # 23 | # @example 24 | # Task.new(:help => true) do |task| 25 | # # ... 26 | # end 27 | # 28 | def initialize(options={}) 29 | @subtasks = {} 30 | @options = options 31 | 32 | yield self if block_given? 33 | end 34 | 35 | # 36 | # @return [Hash] 37 | # All defined non-options of the class. 38 | # 39 | def self.non_options 40 | @non_options ||= {} 41 | end 42 | 43 | # 44 | # Searches for the non-option with the matching name in the class 45 | # and it's ancestors. 46 | # 47 | # @param [Symbol, String] name 48 | # The name to search for. 49 | # 50 | # @return [true, false] 51 | # Specifies whether the non-option with the matching name was 52 | # defined. 53 | # 54 | def self.has_non_option?(name) 55 | name = name.to_sym 56 | 57 | ancestors.each do |base| 58 | if base < RProgram::Task 59 | return true if base.non_options.include?(name) 60 | end 61 | end 62 | 63 | return false 64 | end 65 | 66 | # 67 | # Searches for the non-option with the matching name in the class 68 | # and it's ancestors. 69 | # 70 | # @param [Symbol, String] name 71 | # The name to search for. 72 | # 73 | # @return [NonOption] 74 | # The non-option with the matching name. 75 | # 76 | def self.get_non_option(name) 77 | name = name.to_sym 78 | 79 | ancestors.each do |base| 80 | if base < RProgram::Task 81 | if base.non_options.has_key?(name) 82 | return base.non_options[name] 83 | end 84 | end 85 | end 86 | 87 | return nil 88 | end 89 | 90 | # 91 | # @return [Hash] 92 | # All defined options for the class. 93 | # 94 | def self.options 95 | @options ||= {} 96 | end 97 | 98 | # 99 | # Searches for the option with the matching name in the class and 100 | # it's ancestors. 101 | # 102 | # @param [Symbol, String] name 103 | # The name to search for. 104 | # 105 | # @return [true, false] 106 | # Specifies whether the option with the matching name was defined. 107 | # 108 | def self.has_option?(name) 109 | name = name.to_sym 110 | 111 | ancestors.each do |base| 112 | if base < RProgram::Task 113 | return true if base.options.has_key?(name) 114 | end 115 | end 116 | 117 | return false 118 | end 119 | 120 | # 121 | # Searches for the option with the matching name in the class and 122 | # it's ancestors. 123 | # 124 | # @param [Symbol, String] name 125 | # The name to search for. 126 | # 127 | # @return [Option] 128 | # The option with the matching name. 129 | # 130 | def self.get_option(name) 131 | name = name.to_sym 132 | 133 | ancestors.each do |base| 134 | if base < RProgram::Task 135 | return base.options[name] if base.options.has_key?(name) 136 | end 137 | end 138 | 139 | return nil 140 | end 141 | 142 | # 143 | # Creates a new Task object, then formats command-line arguments 144 | # using the Task object. 145 | # 146 | # @param [Hash{Symbol => Object}] options 147 | # Additional task options. 148 | # 149 | # @yield [task] 150 | # If a block is given, it will be passed the newly created task. 151 | # 152 | # @yieldparam [Task] task 153 | # The newly created Task object. 154 | # 155 | # @return [Array] 156 | # The formatted arguments from a Task object. 157 | # 158 | # @example 159 | # MyTask.arguments(:verbose => true, :count => 2) 160 | # # => [...] 161 | # 162 | # @example 163 | # MyTask.arguments do |task| 164 | # task.verbose = true 165 | # task.file = 'output.txt' 166 | # end 167 | # # => [...] 168 | # 169 | def self.arguments(options={},&block) 170 | self.new(options,&block).arguments 171 | end 172 | 173 | # 174 | # @see has_non_option? 175 | # 176 | def has_non_option?(name) 177 | self.class.has_non_option?(name) 178 | end 179 | 180 | # 181 | # @see get_non_option 182 | # 183 | def get_non_option(name) 184 | self.class.get_non_option(name) 185 | end 186 | 187 | # 188 | # @see has_option? 189 | # 190 | def has_option?(name) 191 | self.class.has_option?(name) 192 | end 193 | 194 | # 195 | # @see get_option 196 | # 197 | def get_option(name) 198 | self.class.get_option(name) 199 | end 200 | 201 | # 202 | # Generates the command-line arguments for all leading non-options. 203 | # 204 | # @return [Array] 205 | # The command-line arguments generated from all the leading 206 | # non-options of the task and it's sub-tasks. 207 | # 208 | def leading_non_options 209 | args = [] 210 | 211 | # add the task leading non-options 212 | @options.each do |name,value| 213 | non_opt = get_non_option(name) 214 | 215 | if (non_opt && non_opt.leading?) 216 | args += non_opt.arguments(value) 217 | end 218 | end 219 | 220 | # add all leading subtask non-options 221 | @subtasks.each_value do |task| 222 | args += task.leading_non_options 223 | end 224 | 225 | return args 226 | end 227 | 228 | # 229 | # Generates the command-line arguments from all options. 230 | # 231 | # @return [Array] 232 | # The command-line arguments generated from all the options of the 233 | # task and it's sub-tasks. 234 | # 235 | def options 236 | args = [] 237 | 238 | # add all subtask options 239 | @subtasks.each_value do |task| 240 | args += task.arguments 241 | end 242 | 243 | # add the task options 244 | @options.each do |name,value| 245 | opt = get_option(name) 246 | args += opt.arguments(value) if opt 247 | end 248 | 249 | return args 250 | end 251 | 252 | # 253 | # Generates the command-line arguments from all tailing non-options. 254 | # 255 | # @return [Array] 256 | # The command-line arguments generated from all the tailing 257 | # non-options of the task and it's sub-tasks. 258 | # 259 | def tailing_non_options 260 | args = [] 261 | 262 | # add all tailing subtask non-options 263 | @subtasks.each_value do |task| 264 | args += task.tailing_non_options 265 | end 266 | 267 | # add the task tailing non-options 268 | @options.each do |name,value| 269 | non_opt = get_non_option(name) 270 | 271 | if (non_opt && non_opt.tailing?) 272 | args += non_opt.arguments(value) 273 | end 274 | end 275 | 276 | return args 277 | end 278 | 279 | # 280 | # Generates the command-line arguments from the task. 281 | # 282 | # @return [Array] 283 | # The command-line arguments compiled from the leading non-options, 284 | # options and tailing non-options of the task and it's sub-tasks. 285 | # 286 | def arguments 287 | tailing_args = tailing_non_options 288 | 289 | if tailing_args.any? { |arg| arg[0,1] == '-' } 290 | tailing_args.unshift('--') 291 | end 292 | 293 | return leading_non_options + options + tailing_args 294 | end 295 | 296 | # 297 | # @see #arguments 298 | # 299 | def to_a 300 | arguments 301 | end 302 | 303 | protected 304 | 305 | # 306 | # Defines a sub-task. 307 | # 308 | # @param [String, Symbol] name 309 | # The name of the sub-task. 310 | # 311 | # @param [Task] task 312 | # The task class of the sub-task. 313 | # 314 | # @example 315 | # subtask :extra, ExtraTask 316 | # 317 | def self.subtask(name,task) 318 | name = name.to_s 319 | file = __FILE__ 320 | line = __LINE__ + 3 321 | 322 | class_eval %{ 323 | def #{name}(options={},&block) 324 | if @subtasks[#{name.dump}] 325 | @subtasks[#{name.dump}].options.merge!(options) 326 | 327 | yield(@subtasks[#{name.dump}]) if block_given? 328 | else 329 | @subtasks[#{name.dump}] = #{task}.new(options,&block) 330 | end 331 | 332 | return @subtasks[#{name.dump}] 333 | end 334 | }, file, line 335 | end 336 | 337 | # 338 | # Defines a non-option. 339 | # 340 | # @param [Hash] options 341 | # Additional options for the non-option. 342 | # 343 | # @option options [Symbol] :name 344 | # The name of the non-option. 345 | # 346 | # @option options [true, false] :leading (true) 347 | # Implies the non-option is a leading non-option. 348 | # 349 | # @option options [false, true] :tailing (false) 350 | # Implies the non-option is a tailing non-option. 351 | # 352 | # @option options [false, true] :multiple (false) 353 | # Implies the non-option maybe given an Array of values. 354 | # 355 | # @example 356 | # non_option :name => 'input_file', :tailing => true 357 | # 358 | # @example 359 | # non_option :name => 'file', :tailing => true, :multiple => true 360 | # 361 | def self.non_option(options={}) 362 | name = options[:name].to_sym 363 | 364 | self.non_options[name] = NonOption.new(options) 365 | 366 | define_method(name) do 367 | if options[:multiple] 368 | @options[name] ||= [] 369 | else 370 | @options[name] 371 | end 372 | end 373 | 374 | define_method("#{name}=") do |value| 375 | @options[name] = value 376 | end 377 | end 378 | 379 | # 380 | # Defines a long-option. 381 | # 382 | # @param [Hash] options 383 | # Additional options of the long-option. 384 | # 385 | # @option options [String] :flag 386 | # The flag to use for the option. 387 | # 388 | # @option options [Symbol] :name 389 | # The name of the option. Defaults to the flag_namify'ed form of 390 | # `options[:flag]`, if not given. 391 | # 392 | # @option options [true, false] :multiply (false) 393 | # Specifies that the option may appear multiple times in the 394 | # arguments. 395 | # 396 | # @option options [true, false] :sub_options (false) 397 | # Specifies that the option contains multiple sub-options. 398 | # 399 | # @example 400 | # long_option :flag => '--output' 401 | # 402 | # @example 403 | # long_option :flag => '-f', :name => :file 404 | # 405 | def self.long_option(options={},&block) 406 | if (options[:name].nil? && options[:flag]) 407 | options[:name] = Task.flag_namify(options[:flag]) 408 | end 409 | 410 | return define_option(options,&block) 411 | end 412 | 413 | # 414 | # Defines a short_option. 415 | # 416 | # @param [Hash] options 417 | # Additional options for the short-option. 418 | # 419 | # @option options [Symbol, String] :name 420 | # The name of the short-option. 421 | # 422 | # @option options [String] :flag 423 | # The flag to use for the short-option. 424 | # 425 | # @option options [true, false] :multiply (false) 426 | # Specifies that the option may appear multiple times in the 427 | # arguments. 428 | # 429 | # @option options [true, false] :sub_options (false) 430 | # Specifies that the option contains multiple sub-options. 431 | # 432 | # @example 433 | # short_option :flag => '-c', :name => :count 434 | # 435 | def self.short_option(options,&block) 436 | define_option(options,&block) 437 | end 438 | 439 | # 440 | # Defines an option. 441 | # 442 | # @param [Hash] options 443 | # Additional options. 444 | # 445 | # @option options [Symbol, String] :name 446 | # The name of the option. 447 | # 448 | # @option options [String] :flag 449 | # The flag to use for the option. 450 | # 451 | # @option options [true, false] :multiple (false) 452 | # Specifies that the option may appear multiple times in the 453 | # arguments. 454 | # 455 | # @option options [true, false] :sub_options (false) 456 | # Specifies that the option contains multiple sub-options. 457 | # 458 | def self.define_option(options,&block) 459 | method_name = options[:name].to_sym 460 | 461 | self.options[method_name] = Option.new(options,&block) 462 | 463 | define_method(method_name) do 464 | if options[:sub_options] 465 | @options[method_name] ||= OptionList.new 466 | elsif options[:multiple] 467 | @options[method_name] ||= [] 468 | else 469 | @options[method_name] 470 | end 471 | end 472 | 473 | define_method("#{method_name}=") do |value| 474 | if options[:sub_options] 475 | @options[method_name] = OptionList.new(value) 476 | else 477 | @options[method_name] = value 478 | end 479 | end 480 | end 481 | 482 | # 483 | # Converts a long-option flag to a Ruby method name. 484 | # 485 | # @param [String] flag 486 | # The command-line flag to convert. 487 | # 488 | # @return [String] 489 | # A method-name compatible version of the given flag. 490 | # 491 | # @example 492 | # Task.flag_namify('--output-file') 493 | # # => "output_file" 494 | # 495 | def self.flag_namify(flag) 496 | flag = flag.to_s.downcase 497 | 498 | # remove leading dashes 499 | method_name = if flag.start_with?('--') then flag[2..-1] 500 | elsif flag.start_with?('-') then flag[1..-1] 501 | else flag 502 | end 503 | 504 | # replace remaining dashes with underscores 505 | return method_name.gsub(/[-_\.\s]+/,'_') 506 | end 507 | 508 | end 509 | end 510 | -------------------------------------------------------------------------------- /lib/rprogram/version.rb: -------------------------------------------------------------------------------- 1 | module RProgram 2 | # Version of RProgram 3 | VERSION = '0.3.2' 4 | end 5 | -------------------------------------------------------------------------------- /rprogram.gemspec: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'yaml' 4 | 5 | Gem::Specification.new do |gem| 6 | gemspec = YAML.load_file('gemspec.yml') 7 | 8 | gem.name = gemspec.fetch('name') 9 | gem.version = gemspec.fetch('version') do 10 | lib_dir = File.join(File.dirname(__FILE__),'lib') 11 | $LOAD_PATH << lib_dir unless $LOAD_PATH.include?(lib_dir) 12 | 13 | require 'rprogram/version' 14 | RProgram::VERSION 15 | end 16 | 17 | gem.summary = gemspec['summary'] 18 | gem.description = gemspec['description'] 19 | gem.licenses = Array(gemspec['license']) 20 | gem.authors = Array(gemspec['authors']) 21 | gem.email = gemspec['email'] 22 | gem.homepage = gemspec['homepage'] 23 | 24 | glob = lambda { |patterns| gem.files & Dir[*patterns] } 25 | 26 | gem.files = `git ls-files`.split($/) 27 | gem.files = glob[gemspec['files']] if gemspec['files'] 28 | 29 | gem.executables = gemspec.fetch('executables') do 30 | glob['bin/*'].map { |path| File.basename(path) } 31 | end 32 | gem.default_executable = gem.executables.first if Gem::VERSION < '1.7.' 33 | 34 | gem.extensions = glob[gemspec['extensions'] || 'ext/**/extconf.rb'] 35 | gem.test_files = glob[gemspec['test_files'] || '{test/{**/}*_test.rb'] 36 | gem.extra_rdoc_files = glob[gemspec['extra_doc_files'] || '*.{txt,md}'] 37 | 38 | gem.require_paths = Array(gemspec.fetch('require_paths') { 39 | %w[ext lib].select { |dir| File.directory?(dir) } 40 | }) 41 | 42 | gem.requirements = gemspec['requirements'] 43 | gem.required_ruby_version = gemspec['required_ruby_version'] 44 | gem.required_rubygems_version = gemspec['required_rubygems_version'] 45 | gem.post_install_message = gemspec['post_install_message'] 46 | 47 | split = lambda { |string| string.split(/,\s*/) } 48 | 49 | if gemspec['dependencies'] 50 | gemspec['dependencies'].each do |name,versions| 51 | gem.add_dependency(name,split[versions]) 52 | end 53 | end 54 | 55 | if gemspec['development_dependencies'] 56 | gemspec['development_dependencies'].each do |name,versions| 57 | gem.add_development_dependency(name,split[versions]) 58 | end 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /spec/classes/aliased_program.rb: -------------------------------------------------------------------------------- 1 | require 'rprogram/program' 2 | 3 | class AliasedProgram < RProgram::Program 4 | 5 | name_program 'ls' 6 | alias_program 'dir' 7 | 8 | end 9 | -------------------------------------------------------------------------------- /spec/classes/ls_program.rb: -------------------------------------------------------------------------------- 1 | require 'rprogram/program' 2 | 3 | class LS < RProgram::Program 4 | 5 | name_program 'ls' 6 | alias_program 'dir' 7 | 8 | end 9 | -------------------------------------------------------------------------------- /spec/classes/ls_selinux_task.rb: -------------------------------------------------------------------------------- 1 | require 'classes/ls_task' 2 | 3 | class LSSELinuxTask < LSTask 4 | 5 | long_option :flag => '--lcontext', :name => :security_context 6 | 7 | end 8 | -------------------------------------------------------------------------------- /spec/classes/ls_task.rb: -------------------------------------------------------------------------------- 1 | require 'rprogram/task' 2 | 3 | class LSTask < RProgram::Task 4 | 5 | short_option :flag => '-a', :name => :all 6 | long_option :flag => '--author' 7 | long_option :flag => '--group-directories-first', :name => :group_dirs_first 8 | long_option :flag => '--hide', :multiple => true 9 | 10 | non_option :tailing => true, :multiple => true, :name => :files 11 | 12 | end 13 | -------------------------------------------------------------------------------- /spec/classes/named_program.rb: -------------------------------------------------------------------------------- 1 | require 'rprogram/program' 2 | 3 | class NamedProgram < RProgram::Program 4 | 5 | name_program 'ls' 6 | 7 | end 8 | -------------------------------------------------------------------------------- /spec/non_option_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | require 'rprogram/non_option' 4 | 5 | describe NonOption do 6 | subject { NonOption.new(:name => 'files') } 7 | 8 | it "should keep :leading and :tailing options mutually exclusive" do 9 | leading = NonOption.new(:name => 'files', :leading => true) 10 | tailing = NonOption.new(:name => 'files', :tailing => true) 11 | 12 | expect(leading).to be_leading 13 | expect(leading).not_to be_tailing 14 | 15 | expect(tailing).not_to be_leading 16 | expect(tailing).to be_tailing 17 | end 18 | 19 | it "should return an empty Array when passed nil" do 20 | expect(subject.arguments(nil)).to eq([]) 21 | end 22 | 23 | it "should return an empty Array when passed false" do 24 | expect(subject.arguments(false)).to eq([]) 25 | end 26 | 27 | it "should return an empty Array when passed []" do 28 | expect(subject.arguments([])).to eq([]) 29 | end 30 | 31 | it "should return an Array when passed a single value" do 32 | expect(subject.arguments('foo')).to eq(['foo']) 33 | end 34 | 35 | it "should return an Array when passed multiple values" do 36 | expect(subject.arguments(['foo', 'bar'])).to eq(['foo', 'bar']) 37 | end 38 | 39 | it "should return an Array when passed a Hash of keys" do 40 | expect(subject.arguments({:foo => true, :bar => false})).to eq(['foo']) 41 | end 42 | 43 | it "should return an Array when passed a Hash of values" do 44 | expect(subject.arguments({:foo => 'bar'})).to eq(['foo=bar']) 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /spec/option_examples.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | shared_examples_for 'Option' do 4 | it "should return an empty Array when passed nil" do 5 | expect(subject.arguments(nil)).to eq([]) 6 | end 7 | 8 | it "should return an empty Array when passed false" do 9 | expect(subject.arguments(false)).to eq([]) 10 | end 11 | 12 | it "should return a single flag when passed true" do 13 | expect(subject.arguments(true)).to eq(['-f']) 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /spec/option_list_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | require 'rprogram/option_list' 4 | 5 | describe OptionList do 6 | it "should behave like a Hash" do 7 | subject[:bla] = 2 8 | expect(subject[:bla]).to eq(2) 9 | end 10 | 11 | it "should provide reader and writer methods" do 12 | subject.bla = 5 13 | expect(subject.bla).to eq(5) 14 | end 15 | 16 | it "should raise a NoMethodError exception when calling other methods" do 17 | expect { 18 | subject.bla(5) 19 | }.to raise_error(NoMethodError) 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /spec/option_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'option_examples' 3 | 4 | require 'rprogram/option' 5 | 6 | describe Option do 7 | describe "custom formatting" do 8 | it "should allow the format block to return nil" do 9 | opt = described_class.new(:flag => '-f') { |opt,value| } 10 | 11 | expect(opt.arguments('bla')).to eq([]) 12 | end 13 | end 14 | 15 | describe "single flag" do 16 | subject { described_class.new(:flag => '-f') } 17 | 18 | it_should_behave_like 'Option' 19 | 20 | it "should render a single flag with an optional value" do 21 | value = 'foo' 22 | 23 | expect(subject.arguments('foo')).to eq(['-f', 'foo']) 24 | end 25 | 26 | it "should render a single flag with multiple values" do 27 | value = ['foo','bar','baz'] 28 | 29 | expect(subject.arguments(value)).to eq(['-f','foo','bar','baz']) 30 | end 31 | 32 | it "should render a single flag with a Hash of keys" do 33 | value = {:foo => true, :bar => false} 34 | 35 | expect(subject.arguments(value)).to eq(['-f','foo']) 36 | end 37 | 38 | it "should render a single flag with a Hash of keys and values" do 39 | value = {:foo => 'bar'} 40 | 41 | expect(subject.arguments(value)).to eq(['-f','foo=bar']) 42 | end 43 | end 44 | 45 | describe "equals flag" do 46 | subject do 47 | described_class.new(:equals => true, :flag => '-f') 48 | end 49 | 50 | it_should_behave_like 'Option' 51 | 52 | it "should render a single flag with a value" do 53 | value = 'foo' 54 | 55 | expect(subject.arguments('foo')).to eq(['-f=foo']) 56 | end 57 | 58 | it "should render a single flag with multiple values" do 59 | value = ['foo', 'bar', 'baz'] 60 | 61 | expect(subject.arguments(value)).to eq(['-f=foo bar baz']) 62 | end 63 | 64 | it "should render a single flag with a Hash of keys" do 65 | value = {:foo => true, :bar => false} 66 | 67 | expect(subject.arguments(value)).to eq(['-f=foo']) 68 | end 69 | end 70 | 71 | describe "multiple flags" do 72 | subject do 73 | described_class.new(:multiple => true, :flag => '-f') 74 | end 75 | 76 | it_should_behave_like 'Option' 77 | 78 | it "should render a single flag with a value" do 79 | value = 'foo' 80 | 81 | expect(subject.arguments(value)).to eq(['-f', 'foo']) 82 | end 83 | 84 | it "should render multiple flags for multiple values" do 85 | value = ['foo','bar','baz'] 86 | 87 | expect(subject.arguments(value)).to eq(['-f', 'foo', '-f', 'bar', '-f', 'baz']) 88 | end 89 | 90 | it "should render multiple flags for a Hash of keys" do 91 | value = {:foo => true, :bar => true, :baz => false} 92 | args = subject.arguments(value) 93 | 94 | expect(args & ['-f', 'foo']).to eq(['-f', 'foo']) 95 | expect(args & ['-f', 'bar']).to eq(['-f', 'bar']) 96 | end 97 | 98 | it "should render multiple flags for a Hash of values" do 99 | value = {:foo => 'bar'} 100 | 101 | expect(subject.arguments(value)).to eq(['-f', 'foo=bar']) 102 | end 103 | end 104 | 105 | describe "multiple equals flags" do 106 | subject do 107 | described_class.new(:multiple => true, :equals => true, :flag => '-f') 108 | end 109 | 110 | it_should_behave_like 'Option' 111 | 112 | it "should render a single flag with a value" do 113 | value = 'foo' 114 | 115 | expect(subject.arguments(value)).to eq(['-f=foo']) 116 | end 117 | 118 | it "should render multiple equal flags for multiple values" do 119 | value = ['foo', 'bar'] 120 | 121 | expect(subject.arguments(value)).to eq(['-f=foo', '-f=bar']) 122 | end 123 | 124 | it "should render multiple equal flags for a Hash of keys" do 125 | value = {:foo => true, :bar => true, :baz => false} 126 | args = subject.arguments(value) 127 | 128 | expect(args.include?('-f=foo')).to eq(true) 129 | expect(args.include?('-f=bar')).to eq(true) 130 | end 131 | 132 | it "should render multiple equal flags for a Hash of values" do 133 | value = {:foo => 'bar', :bar => 'baz'} 134 | args = subject.arguments(value) 135 | 136 | expect(args.include?('-f=foo=bar')).to eq(true) 137 | expect(args.include?('-f=bar=baz')).to eq(true) 138 | end 139 | end 140 | 141 | describe "separated values" do 142 | subject do 143 | described_class.new(:separator => ',', :flag => '-f') 144 | end 145 | 146 | it_should_behave_like 'Option' 147 | 148 | it "should render a single flag with a value" do 149 | value = 'foo' 150 | 151 | expect(subject.arguments('foo')).to eq(['-f', 'foo']) 152 | end 153 | 154 | it "should render a single flag with multiple values" do 155 | value = ['foo', 'bar', 'baz'] 156 | 157 | expect(subject.arguments(value)).to eq(['-f', 'foo,bar,baz']) 158 | end 159 | 160 | it "should render a single flag with a Hash of keys" do 161 | value = {:foo => true, :bar => true, :baz => false} 162 | args = subject.arguments(value) 163 | 164 | expect(args[0]).to eq('-f') 165 | 166 | sub_args = args[1].split(',') 167 | 168 | expect(sub_args.include?('foo')).to eq(true) 169 | expect(sub_args.include?('bar')).to eq(true) 170 | end 171 | 172 | it "should render a single flag with a Hash of values" do 173 | value = {:foo => 'bar', :bar => 'baz'} 174 | args = subject.arguments(value) 175 | 176 | expect(args[0]).to eq('-f') 177 | 178 | sub_args = args[1].split(',') 179 | 180 | expect(sub_args.include?('foo=bar')).to eq(true) 181 | expect(sub_args.include?('bar=baz')).to eq(true) 182 | end 183 | end 184 | end 185 | -------------------------------------------------------------------------------- /spec/program_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'classes/named_program' 3 | require 'classes/aliased_program' 4 | require 'classes/ls_program' 5 | 6 | require 'rprogram/program' 7 | 8 | describe Program do 9 | subject { described_class.new('/usr/bin/cc') } 10 | 11 | after(:all) { LS.path = nil } 12 | 13 | describe "named program" do 14 | subject { NamedProgram } 15 | 16 | it "should be able to give a class a program name" do 17 | expect(subject.program_name).to eq('ls') 18 | end 19 | 20 | it "should not have any program aliases" do 21 | expect(subject.program_aliases).to be_empty 22 | end 23 | 24 | it "should have one program name" do 25 | expect(subject.program_names).to eq(['ls']) 26 | end 27 | 28 | it "should provide an instance method for the program name" do 29 | program = subject.find 30 | 31 | expect(program.program_name).to eq('ls') 32 | end 33 | 34 | it "should provide an instance method for the program names" do 35 | program = subject.find 36 | 37 | expect(program.program_names).to eq(['ls']) 38 | end 39 | end 40 | 41 | describe "aliased program" do 42 | subject { AliasedProgram } 43 | 44 | it "should have program aliases" do 45 | expect(subject.program_aliases).to eq(['dir']) 46 | end 47 | 48 | it "should have one program name" do 49 | expect(subject.program_names).to eq(['ls', 'dir']) 50 | end 51 | 52 | it "should provide an instance method for the program aliases" do 53 | program = subject.find 54 | 55 | expect(program.program_aliases).to eq(['dir']) 56 | end 57 | 58 | it "should provide an instance method for the program names" do 59 | program = subject.find 60 | 61 | expect(program.program_names).to eq(['ls', 'dir']) 62 | end 63 | end 64 | 65 | describe "path" do 66 | subject { NamedProgram } 67 | 68 | it "should not have a path by default" do 69 | expect(subject.path).to be_nil 70 | end 71 | 72 | it "should allow setting the path" do 73 | new_path = '/bin/ls' 74 | 75 | subject.path = new_path 76 | expect(subject.path).to eq(new_path) 77 | end 78 | 79 | it "should expand paths" do 80 | subject.path = '/../bin/ls' 81 | 82 | expect(subject.path).to eq('/bin/ls') 83 | end 84 | 85 | it "should allow setting the path to nil" do 86 | subject.path = nil 87 | 88 | expect(subject.path).to be_nil 89 | end 90 | 91 | after(:all) { NamedProgram.path = nil } 92 | end 93 | 94 | it "should create a Program from a path" do 95 | expect(subject).not_to be_nil 96 | end 97 | 98 | it "should derive the program name from a path" do 99 | expect(subject.name).to eq('cc') 100 | end 101 | 102 | it "should return the program path when converted to a String" do 103 | expect(subject.to_s).to eq('/usr/bin/cc') 104 | end 105 | 106 | it "should raise an exception for invalid paths" do 107 | expect { 108 | described_class.new('/totally/doesnt/exist') 109 | }.to raise_error(ProgramNotFound) 110 | end 111 | 112 | it "should find a program from a path" do 113 | prog = described_class.find_with_path('/usr/bin/cc') 114 | 115 | expect(prog).not_to be_nil 116 | end 117 | 118 | it "should find a program from given paths" do 119 | prog = described_class.find_with_paths(['/usr/bin/ls','/bin/ls']) 120 | 121 | expect(prog).not_to be_nil 122 | end 123 | 124 | it "should be able to find a program based on the program names" do 125 | ls = LS.find 126 | 127 | expect(File.executable?(ls.path)).to eq(true) 128 | end 129 | 130 | it "should raise a ProgramNotFound exception if no path/name is valid" do 131 | expect { 132 | described_class.find 133 | }.to raise_error(ProgramNotFound) 134 | end 135 | 136 | it "should allow using a default path" do 137 | LS.path = '/bin/ls' 138 | 139 | expect(LS.find.path).to eq(LS.path) 140 | end 141 | end 142 | -------------------------------------------------------------------------------- /spec/rprogram_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | require 'rprogram/version' 4 | require 'rprogram/rprogram' 5 | 6 | describe RProgram do 7 | it "should have a VERSION constant" do 8 | expect(subject.const_defined?('VERSION')).to eq(true) 9 | end 10 | 11 | it "should have a debug mode" do 12 | subject.debug = true 13 | expect(subject.debug).to be(true) 14 | 15 | subject.debug = false 16 | expect(subject.debug).to be(false) 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /spec/scripts/echo.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | puts STDIN.readline.chomp 4 | -------------------------------------------------------------------------------- /spec/scripts/fail.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | exit -1 4 | -------------------------------------------------------------------------------- /spec/scripts/print.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | puts ARGV[0] 4 | -------------------------------------------------------------------------------- /spec/scripts/success.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | exit 0 4 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'rspec' 2 | require 'rprogram/version' 3 | 4 | include RProgram 5 | -------------------------------------------------------------------------------- /spec/system_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'tempfile' 3 | 4 | require 'rprogram/system' 5 | 6 | describe System do 7 | subject { System } 8 | 9 | it "should determine the native architecture" do 10 | expect(subject.arch).not_to be_empty 11 | end 12 | 13 | it "should determine the native platform" do 14 | expect(subject.platform).not_to be_empty 15 | end 16 | 17 | it "should have a list of directories that contain programs" do 18 | expect(subject.paths).not_to be_empty 19 | 20 | expect(subject.paths.any? { |dir| dir.directory? }).to eq(true) 21 | end 22 | 23 | it "should be able to find programs" do 24 | expect(subject.find_program('ls')).to be_executable 25 | end 26 | 27 | it "should be able to find programs by multiple names" do 28 | expect(subject.find_program_by_names('ls','dir')).to be_executable 29 | end 30 | 31 | describe "run" do 32 | let(:scripts_dir) { File.join(File.dirname(__FILE__),'scripts') } 33 | 34 | let(:fail_script) { File.join(scripts_dir,'fail.rb') } 35 | let(:success_script) { File.join(scripts_dir,'success.rb') } 36 | 37 | let(:print_script) { File.join(scripts_dir,'print.rb') } 38 | let(:echo_script) { File.join(scripts_dir,'echo.rb') } 39 | 40 | let(:data) { 'hello' } 41 | 42 | it "should return true when programs succeed" do 43 | expect(subject.run(success_script)).to eq(true) 44 | end 45 | 46 | it "should return false when programs fail" do 47 | expect(subject.run(fail_script)).to eq(false) 48 | end 49 | 50 | unless System.ruby_1_8? 51 | it "should allow passing exec options as the last argument" do 52 | output = Tempfile.new('rprogram_run_with_options') 53 | subject.run(print_script, data, :out => [output.path, 'w']) 54 | 55 | expect(output.read.chomp).to eq(data) 56 | end 57 | else 58 | it "should raise an exception when passing exec options" do 59 | expect { 60 | subject.run(print_script, data, :out => ['foo', 'w']) 61 | }.to raise_error 62 | end 63 | end 64 | 65 | 66 | unless (System.ruby_1_8? || (System.windows? && !System.jruby?)) 67 | it "should allow running programs with IO.popen" do 68 | io = subject.run(echo_script, :popen => 'w+') 69 | 70 | io.puts(data) 71 | expect(io.readline.chomp).to eq(data) 72 | end 73 | else 74 | it "should raise an exception when specifying :popen" do 75 | expect { 76 | subject.run(echo_script, :popen => 'w+') 77 | }.to raise_error 78 | end 79 | end 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /spec/task_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'classes/ls_task' 3 | require 'classes/ls_selinux_task' 4 | 5 | require 'rprogram/task' 6 | 7 | describe Task do 8 | describe "flag_namify" do 9 | subject { described_class } 10 | 11 | it "should downcase all characters" do 12 | expect(subject.flag_namify('-SHORT-option')).to eq('short_option') 13 | end 14 | 15 | it "should replace dashes with underscores" do 16 | expect(subject.flag_namify('-short-option')).to eq('short_option') 17 | end 18 | 19 | it "should replace dots with underscores" do 20 | expect(subject.flag_namify('-short.option')).to eq('short_option') 21 | end 22 | 23 | it "should replace spaces with underscores" do 24 | expect(subject.flag_namify('-short option')).to eq('short_option') 25 | end 26 | 27 | it "should replace multiple underscores with one underscore" do 28 | expect(subject.flag_namify('-short__option')).to eq('short_option') 29 | end 30 | 31 | it "should namify short options" do 32 | expect(subject.flag_namify('-short-option')).to eq('short_option') 33 | end 34 | 35 | it "should namify long options" do 36 | expect(subject.flag_namify('--long-option')).to eq('long_option') 37 | end 38 | end 39 | 40 | subject { LSTask.new } 41 | 42 | it "should have no arguments by default" do 43 | expect(subject.arguments).to be_empty 44 | end 45 | 46 | it "should define reader and writter methods for options" do 47 | expect(subject.all).to be_nil 48 | 49 | subject.all = true 50 | expect(subject.all).to eq(true) 51 | end 52 | 53 | it "should default the value of multi-options to an empty Array" do 54 | expect(subject.hide).to be_empty 55 | end 56 | 57 | it "should define reader and writter methods for non-options" do 58 | expect(subject.files).to be_empty 59 | 60 | subject.files << 'file.txt' 61 | expect(subject.files).to eq(['file.txt']) 62 | end 63 | 64 | describe "class methods" do 65 | subject { LSTask } 66 | 67 | it "should provide access to the defined options" do 68 | expect(subject.options).not_to be_empty 69 | end 70 | 71 | it "should provide access to the defined non-options" do 72 | expect(subject.non_options).not_to be_empty 73 | end 74 | 75 | it "should default the name of long options to the flag" do 76 | expect(subject.options[:author].flag).to eq('--author') 77 | end 78 | 79 | it "should allow the name of long options to be overridden" do 80 | expect(subject.options[:group_dirs_first].flag).to eq('--group-directories-first') 81 | end 82 | 83 | context "when inherited" do 84 | subject { LSSELinuxTask } 85 | 86 | it "should allow options to be inherited" do 87 | expect(subject.has_option?(:all)).to eq(true) 88 | expect(subject.has_option?(:security_context)).to eq(true) 89 | end 90 | end 91 | end 92 | end 93 | --------------------------------------------------------------------------------