diff options
Diffstat (limited to 'lib/shell')
| -rw-r--r-- | lib/shell/builtin-command.rb | 19 | ||||
| -rw-r--r-- | lib/shell/command-processor.rb | 308 | ||||
| -rw-r--r-- | lib/shell/error.rb | 1 | ||||
| -rw-r--r-- | lib/shell/filter.rb | 49 | ||||
| -rw-r--r-- | lib/shell/process-controller.rb | 30 | ||||
| -rw-r--r-- | lib/shell/shell.gemspec | 26 | ||||
| -rw-r--r-- | lib/shell/system-command.rb | 8 | ||||
| -rw-r--r-- | lib/shell/version.rb | 6 |
8 files changed, 280 insertions, 167 deletions
diff --git a/lib/shell/builtin-command.rb b/lib/shell/builtin-command.rb index b1ca5c38f6..a6a9d232ad 100644 --- a/lib/shell/builtin-command.rb +++ b/lib/shell/builtin-command.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false # # shell/builtin-command.rb - # $Release Version: 0.7 $ @@ -9,10 +10,10 @@ # # -require "shell/filter" +require_relative "filter" class Shell - class BuiltInCommand<Filter + class BuiltInCommand < Filter def wait? false end @@ -83,20 +84,6 @@ class Shell end end -# class Sort < Cat -# def initialize(sh, *filenames) -# super -# end -# -# def each(rs = nil) -# ary = [] -# super{|l| ary.push l} -# for l in ary.sort! -# yield l -# end -# end -# end - class AppendIO < BuiltInCommand def initialize(sh, io, filter) super sh diff --git a/lib/shell/command-processor.rb b/lib/shell/command-processor.rb index da7fecf1d5..82af76dd5e 100644 --- a/lib/shell/command-processor.rb +++ b/lib/shell/command-processor.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false # # shell/command-controller.rb - # $Release Version: 0.7 $ @@ -10,16 +11,19 @@ # require "e2mmap" -require "thread" -require "shell/error" -require "shell/filter" -require "shell/system-command" -require "shell/builtin-command" +require_relative "error" +require_relative "filter" +require_relative "system-command" +require_relative "builtin-command" class Shell + # In order to execute a command on your OS, you need to define it as a + # Shell method. + # + # Alternatively, you can execute any command via + # Shell::CommandProcessor#system even if it is not defined. class CommandProcessor -# include Error # # initialize of Shell and related classes. @@ -49,8 +53,9 @@ class Shell # include run file. # def self.run_config + rc = "~/.rb_shell" begin - load File.expand_path("~/.rb_shell") if ENV.key?("HOME") + load File.expand_path(rc) if ENV.key?("HOME") rescue LoadError, Errno::ENOENT rescue print "load error: #{rc}\n" @@ -76,23 +81,13 @@ class Shell @shell.expand_path(path) end + # call-seq: + # foreach(path, record_separator) -> Enumerator + # foreach(path, record_separator) { block } # - # File related commands - # Shell#foreach - # Shell#open - # Shell#unlink - # Shell#test + # See IO.foreach when +path+ is a file. # - # - - # - # CommandProcessor#foreach(path, rs) - # path: String - # rs: String - record separator - # iterator - # Same as: - # File#foreach (when path is file) - # Dir#foreach (when path is directory) - # path is relative to pwd + # See Dir.foreach when +path+ is a directory. # def foreach(path = nil, *rs) path = "." unless path @@ -105,15 +100,13 @@ class Shell end end + # call-seq: + # open(path, mode, permissions) -> Enumerator + # open(path, mode, permissions) { block } # - # CommandProcessor#open(path, mode) - # path: String - # mode: String - # return: File or Dir - # Same as: - # File#open (when path is file) - # Dir#open (when path is directory) - # mode has an effect only when path is a file + # See IO.open when +path+ is a file. + # + # See Dir.open when +path+ is a directory. # def open(path, mode = nil, perm = 0666, &b) path = expand_path(path) @@ -128,17 +121,17 @@ class Shell end f else - f = File.open(path, mode, perm, &b) + File.open(path, mode, perm, &b) end end end - # public :open + # call-seq: + # unlink(path) # - # CommandProcessor#unlink(path) - # same as: - # Dir#unlink (when path is directory) - # File#unlink (when path is file) + # See IO.unlink when +path+ is a file. + # + # See Dir.unlink when +path+ is a directory. # def unlink(path) @shell.check_point @@ -152,24 +145,21 @@ class Shell Void.new(@shell) end + # See Shell::CommandProcessor#test + alias top_level_test test + # call-seq: + # test(command, file1, file2) -> true or false + # [command, file1, file2] -> true or false # - # CommandProcessor#test(command, file1, file2) - # CommandProcessor#[command, file1, file2] - # command: char or String or Symbol - # file1: String - # file2: String(optional) - # return: Boolean - # same as: - # test() (when command is char or length 1 string or symbol) - # FileTest.command (others) - # example: + # Tests if the given +command+ exists in +file1+, or optionally +file2+. + # + # Example: # sh[?e, "foo"] # sh[:e, "foo"] # sh["e", "foo"] # sh[:exists?, "foo"] # sh["exists?", "foo"] # - alias top_level_test test def test(command, file1, file2=nil) file1 = expand_path(file1) file2 = expand_path(file2) if file2 @@ -190,6 +180,9 @@ class Shell top_level_test(command, file1) end else + unless FileTest.methods(false).include?(command.to_sym) + raise "unsupported command: #{ command }" + end if file2 FileTest.send(command, file1, file2) else @@ -198,20 +191,13 @@ class Shell end end end + # See Shell::CommandProcessor#test alias [] test + # call-seq: + # mkdir(path) # - # Dir related methods - # - # Shell#mkdir - # Shell#rmdir - # - #-- - # - # CommandProcessor#mkdir(*path) - # path: String - # same as Dir.mkdir() - # + # Same as Dir.mkdir, except multiple directories are allowed. def mkdir(*path) @shell.check_point notify("mkdir #{path.join(' ')}") @@ -232,11 +218,10 @@ class Shell Void.new(@shell) end + # call-seq: + # rmdir(path) # - # CommandProcessor#rmdir(*path) - # path: String - # same as Dir.rmdir() - # + # Same as Dir.rmdir, except multiple directories are allowed. def rmdir(*path) @shell.check_point notify("rmdir #{path.join(' ')}") @@ -247,13 +232,12 @@ class Shell Void.new(@shell) end + # call-seq: + # system(command, *options) -> SystemCommand # - # CommandProcessor#system(command, *opts) - # command: String - # opts: String - # return: SystemCommand - # Same as system() function - # example: + # Executes the given +command+ with the +options+ parameter. + # + # Example: # print sh.system("ls", "-l") # sh.system("ls", "-l") | sh.head > STDOUT # @@ -268,22 +252,26 @@ class Shell SystemCommand.new(@shell, find_system_command(command), *opts) end + # call-seq: + # rehash # - # ProcessCommand#rehash - # clear command hash table. - # + # Clears the command hash table. def rehash @system_commands = {} end - # - # ProcessCommand#transact - # - def check_point + def check_point # :nodoc: @shell.process_controller.wait_all_jobs_execution end - alias finish_all_jobs check_point + alias finish_all_jobs check_point # :nodoc: + # call-seq: + # transact { block } + # + # Executes a block as self + # + # Example: + # sh.transact { system("ls", "-l") | head > STDOUT } def transact(&block) begin @shell.instance_eval(&block) @@ -292,17 +280,27 @@ class Shell end end + # call-seq: + # out(device) { block } # - # internal commands - # + # Calls <code>device.print</code> on the result passing the _block_ to + # #transact def out(dev = STDOUT, &block) dev.print transact(&block) end + # call-seq: + # echo(*strings) -> Echo + # + # Returns a Echo object, for the given +strings+ def echo(*strings) Echo.new(@shell, *strings) end + # call-seq: + # cat(*filename) -> Cat + # + # Returns a Cat object, for the given +filenames+ def cat(*filenames) Cat.new(@shell, *filenames) end @@ -310,7 +308,10 @@ class Shell # def sort(*filenames) # Sort.new(self, *filenames) # end - + # call-seq: + # glob(pattern) -> Glob + # + # Returns a Glob filter object, with the given +pattern+ object def glob(pattern) Glob.new(@shell, pattern) end @@ -326,10 +327,18 @@ class Shell end end + # call-seq: + # tee(file) -> Tee + # + # Returns a Tee filter object, with the given +file+ command def tee(file) Tee.new(@shell, file) end + # call-seq: + # concat(*jobs) -> Concat + # + # Returns a Concat object, for the given +jobs+ def concat(*jobs) Concat.new(@shell, *jobs) end @@ -362,7 +371,12 @@ class Shell for p in @shell.system_path path = join(p, command) - if FileTest.exist?(path) + begin + st = File.stat(path) + rescue SystemCallError + next + else + next unless st.executable? and !st.directory? @system_commands[command] = path return path end @@ -371,11 +385,17 @@ class Shell Shell.Fail Error::CommandNotFound, command end + # call-seq: + # def_system_command(command, path) -> Shell::SystemCommand + # + # Defines a command, registering +path+ as a Shell method for the given + # +command+. # - # CommandProcessor.def_system_command(command, path) - # command: String - # path: String - # define 'command()' method as method. + # Shell::CommandProcessor.def_system_command "ls" + # #=> Defines ls. + # + # Shell::CommandProcessor.def_system_command "sys_sort", "sort" + # #=> Defines sys_sort as sort # def self.def_system_command(command, path = command) begin @@ -390,6 +410,10 @@ class Shell Shell.debug.kind_of?(Integer) && Shell.debug > 1) end + # call-seq: + # undef_system_command(command) -> self + # + # Undefines a command def self.undef_system_command(command) command = command.id2name if command.kind_of?(Symbol) remove_method(command) @@ -398,15 +422,20 @@ class Shell self end - # define command alias - # ex) - # def_alias_command("ls_c", "ls", "-C", "-F") - # def_alias_command("ls_c", "ls"){|*opts| ["-C", "-F", *opts]} - # @alias_map = {} + # Returns a list of aliased commands def self.alias_map @alias_map end + # call-seq: + # alias_command(alias, command, *options) -> self + # + # Creates a command alias at the given +alias+ for the given +command+, + # passing any +options+ along with it. + # + # Shell::CommandProcessor.alias_command "lsC", "ls", "-CBF", "--show-control-chars" + # Shell::CommandProcessor.alias_command("lsC", "ls"){|*opts| ["-CBF", "--show-control-chars", *opts]} + # def self.alias_command(ali, command, *opts) ali = ali.id2name if ali.kind_of?(Symbol) command = command.id2name if command.kind_of?(Symbol) @@ -436,22 +465,57 @@ class Shell self end + # call-seq: + # unalias_command(alias) -> self + # + # Unaliases the given +alias+ command. def self.unalias_command(ali) ali = ali.id2name if ali.kind_of?(Symbol) @alias_map.delete ali.intern undef_system_command(ali) end - # - # CommandProcessor.def_builtin_commands(delegation_class, command_specs) - # delegation_class: Class or Module - # command_specs: [[command_name, [argument,...]],...] - # command_name: String - # arguments: String - # FILENAME?? -> expand_path(filename??) - # *FILENAME?? -> filename??.collect{|f|expand_path(f)}.join(", ") - # define command_name(argument,...) as - # delegation_class.command_name(argument,...) + # :nodoc: + # + # Delegates File and FileTest methods into Shell, including the following + # commands: + # + # * Shell#blockdev?(file) + # * Shell#chardev?(file) + # * Shell#directory?(file) + # * Shell#executable?(file) + # * Shell#executable_real?(file) + # * Shell#exist?(file)/Shell#exists?(file) + # * Shell#file?(file) + # * Shell#grpowned?(file) + # * Shell#owned?(file) + # * Shell#pipe?(file) + # * Shell#readable?(file) + # * Shell#readable_real?(file) + # * Shell#setgid?(file) + # * Shell#setuid?(file) + # * Shell#size(file)/Shell#size?(file) + # * Shell#socket?(file) + # * Shell#sticky?(file) + # * Shell#symlink?(file) + # * Shell#writable?(file) + # * Shell#writable_real?(file) + # * Shell#zero?(file) + # * Shell#syscopy(filename_from, filename_to) + # * Shell#copy(filename_from, filename_to) + # * Shell#move(filename_from, filename_to) + # * Shell#compare(filename_from, filename_to) + # * Shell#safe_unlink(*filenames) + # * Shell#makedirs(*filenames) + # * Shell#install(filename_from, filename_to, mode) + # + # And also, there are some aliases for convenience: + # + # * Shell#cmp <- Shell#compare + # * Shell#mv <- Shell#move + # * Shell#cp <- Shell#copy + # * Shell#rm_f <- Shell#safe_unlink + # * Shell#mkpath <- Shell#makedirs # def self.def_builtin_commands(delegation_class, command_specs) for meth, args in command_specs @@ -478,15 +542,16 @@ class Shell end end + # call-seq: + # install_system_commands(prefix = "sys_") + # + # Defines all commands in the Shell.default_system_path as Shell method, + # all with given +prefix+ appended to their names. # - # CommandProcessor.install_system_commands(pre) - # pre: String - command name prefix - # defines every command which belongs in default_system_path via - # CommandProcessor.command(). It doesn't define already defined - # methods twice. By default, "pre_" is prefixes to each method - # name. Characters that may not be used in a method name are - # all converted to '_'. Definition errors are just ignored. + # Any invalid character names are converted to +_+, and errors are passed + # to Shell.notify. # + # Methods already defined are skipped. def self.install_system_commands(pre = "sys_") defined_meth = {} for m in Shell.methods @@ -511,12 +576,7 @@ class Shell end end - #---------------------------------------------------------------------- - # - # class initializing methods - - # - #---------------------------------------------------------------------- - def self.add_delegate_command_to_shell(id) + def self.add_delegate_command_to_shell(id) # :nodoc: id = id.intern if id.kind_of?(String) name = id.id2name if Shell.method_defined?(id) @@ -552,8 +612,27 @@ class Shell end], __FILE__, __LINE__) end - # - # define default builtin commands + # Delegates File methods into Shell, including the following commands: + # + # * Shell#atime(file) + # * Shell#basename(file, *opt) + # * Shell#chmod(mode, *files) + # * Shell#chown(owner, group, *file) + # * Shell#ctime(file) + # * Shell#delete(*file) + # * Shell#dirname(file) + # * Shell#ftype(file) + # * Shell#join(*file) + # * Shell#link(file_from, file_to) + # * Shell#lstat(file) + # * Shell#mtime(file) + # * Shell#readlink(file) + # * Shell#rename(file_from, file_to) + # * Shell#split(file) + # * Shell#stat(file) + # * Shell#symlink(file_from, file_to) + # * Shell#truncate(file, length) + # * Shell#utime(atime, mtime, *file) # def self.install_builtin_commands # method related File. @@ -573,7 +652,6 @@ class Shell ["mtime", ["FILENAME"]], ["readlink", ["FILENAME"]], ["rename", ["FILENAME_FROM", "FILENAME_TO"]], - # ["size", ["FILENAME"]], ["split", ["pathname"]], ["stat", ["FILENAME"]], ["symlink", ["FILENAME_O", "FILENAME_N"]], diff --git a/lib/shell/error.rb b/lib/shell/error.rb index 2701338b5a..677c424baf 100644 --- a/lib/shell/error.rb +++ b/lib/shell/error.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false # # shell/error.rb - # $Release Version: 0.7 $ diff --git a/lib/shell/filter.rb b/lib/shell/filter.rb index df41b420ea..caa976ae3e 100644 --- a/lib/shell/filter.rb +++ b/lib/shell/filter.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false # # shell/filter.rb - # $Release Version: 0.7 $ @@ -9,11 +10,12 @@ # # -class Shell +class Shell #:nodoc: + # Any result of command execution is a Filter. # - # Filter - # A method to require - # each() + # This class includes Enumerable, therefore a Filter object can use all + # Enumerable + # facilities. # class Filter include Enumerable @@ -29,6 +31,10 @@ class Shell @input = filter end + # call-seq: + # each(record_separator=nil) { block } + # + # Iterates a block for each line. def each(rs = nil) rs = @shell.record_separator unless rs if @input @@ -36,7 +42,12 @@ class Shell end end - def < (src) + # call-seq: + # < source + # + # Inputs from +source+, which is either a string of a file name or an IO + # object. + def <(src) case src when String cat = Cat.new(@shell, src) @@ -45,11 +56,16 @@ class Shell self.input = src self else - Shell.Fail Error::CantApplyMethod, "<", to.class + Shell.Fail Error::CantApplyMethod, "<", src.class end end - def > (to) + # call-seq: + # > source + # + # Outputs from +source+, which is either a string of a file name or an IO + # object. + def >(to) case to when String dst = @shell.open(to, "w") @@ -66,7 +82,12 @@ class Shell self end - def >> (to) + # call-seq: + # >> source + # + # Appends the output to +source+, which is either a string of a file name + # or an IO object. + def >>(to) begin Shell.cd(@shell.pwd).append(to, self) rescue CantApplyMethod @@ -74,7 +95,11 @@ class Shell end end - def | (filter) + # call-seq: + # | filter + # + # Processes a pipeline. + def |(filter) filter.input = self if active? @shell.process_controller.start_job filter @@ -82,7 +107,11 @@ class Shell filter end - def + (filter) + # call-seq: + # filter1 + filter2 + # + # Outputs +filter1+, and then +filter2+ using Join.new + def +(filter) Join.new(@shell, self, filter) end diff --git a/lib/shell/process-controller.rb b/lib/shell/process-controller.rb index b973539b4b..d54da68cb0 100644 --- a/lib/shell/process-controller.rb +++ b/lib/shell/process-controller.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false # # shell/process-controller.rb - # $Release Version: 0.7 $ @@ -9,19 +10,17 @@ # # require "forwardable" - -require "thread" require "sync" class Shell class ProcessController @ProcessControllers = {} - @ProcessControllersMonitor = Mutex.new - @ProcessControllersCV = ConditionVariable.new + @ProcessControllersMonitor = Thread::Mutex.new + @ProcessControllersCV = Thread::ConditionVariable.new - @BlockOutputMonitor = Mutex.new - @BlockOutputCV = ConditionVariable.new + @BlockOutputMonitor = Thread::Mutex.new + @BlockOutputCV = Thread::ConditionVariable.new class << self extend Forwardable @@ -96,8 +95,8 @@ class Shell @active_jobs = [] @jobs_sync = Sync.new - @job_monitor = Mutex.new - @job_condition = ConditionVariable.new + @job_monitor = Thread::Mutex.new + @job_condition = Thread::ConditionVariable.new end attr_reader :shell @@ -157,19 +156,16 @@ class Shell @waiting_jobs.delete command else command = @waiting_jobs.shift -# command.notify "job(%id) pre-start.", @shell.debug? return unless command end @active_jobs.push command command.start -# command.notify "job(%id) post-start.", @shell.debug? # start all jobs that input from the job for job in @waiting_jobs.dup start_job(job) if job.input == command end -# command.notify "job(%id) post2-start.", @shell.debug? end end @@ -240,8 +236,8 @@ class Shell pid = nil - pid_mutex = Mutex.new - pid_cv = ConditionVariable.new + pid_mutex = Thread::Mutex.new + pid_cv = Thread::ConditionVariable.new Thread.start do ProcessController.block_output_synchronize do @@ -254,7 +250,6 @@ class Shell pid = fork { Thread.list.each do |th| -# th.kill unless [Thread.main, Thread.current].include?(th) th.kill unless Thread.current == th end @@ -263,7 +258,7 @@ class Shell ObjectSpace.each_object(IO) do |io| if ![STDIN, STDOUT, STDERR].include?(io) - io.close unless io.closed? + io.close end end @@ -283,8 +278,6 @@ class Shell rescue Errno::ECHILD command.notify "warn: job(%id) was done already waitpid." _pid = true - # rescue - # STDERR.puts $! ensure command.notify("Job(%id): Wait to finish when Process finished.", @shell.debug?) # when the process ends, wait until the command terminates @@ -296,11 +289,8 @@ class Shell redo end -# command.notify "job(%id) pre-pre-finish.", @shell.debug? @job_monitor.synchronize do -# command.notify "job(%id) pre-finish.", @shell.debug? terminate_job(command) -# command.notify "job(%id) pre-finish2.", @shell.debug? @job_condition.signal command.notify "job(%id) finish.", @shell.debug? end diff --git a/lib/shell/shell.gemspec b/lib/shell/shell.gemspec new file mode 100644 index 0000000000..1c27670ca4 --- /dev/null +++ b/lib/shell/shell.gemspec @@ -0,0 +1,26 @@ +begin + require_relative "lib/shell/version" +rescue LoadError + # for Ruby core repository + require_relative "version" +end + +Gem::Specification.new do |spec| + spec.name = "shell" + spec.version = Shell::VERSION + spec.authors = ["Keiju ISHITSUKA"] + spec.email = ["keiju@ruby-lang.org"] + + spec.summary = %q{An idiomatic Ruby interface for common UNIX shell commands.} + spec.description = %q{An idiomatic Ruby interface for common UNIX shell commands.} + spec.homepage = "https://github.com/ruby/shell" + spec.license = "BSD-2-Clause" + + spec.files = [".gitignore", ".travis.yml", "Gemfile", "LICENSE.txt", "README.md", "Rakefile", "bin/console", "bin/setup", "lib/shell.rb", "lib/shell/builtin-command.rb", "lib/shell/command-processor.rb", "lib/shell/error.rb", "lib/shell/filter.rb", "lib/shell/process-controller.rb", "lib/shell/system-command.rb", "lib/shell/version.rb", "shell.gemspec"] + spec.bindir = "exe" + spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } + spec.require_paths = ["lib"] + + spec.add_development_dependency "bundler" + spec.add_development_dependency "rake" +end diff --git a/lib/shell/system-command.rb b/lib/shell/system-command.rb index 1a8bb1a90f..767a9ee12c 100644 --- a/lib/shell/system-command.rb +++ b/lib/shell/system-command.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false # # shell/system-command.rb - # $Release Version: 0.7 $ @@ -9,19 +10,19 @@ # # -require "shell/filter" +require_relative "filter" class Shell class SystemCommand < Filter def initialize(sh, command, *opts) if t = opts.find{|opt| !opt.kind_of?(String) && opt.class} - Shell.Fail Error::TypeError, t.class, "String" + Shell.Fail TypeError, t.class, "String" end super(sh) @command = command @opts = opts - @input_queue = Queue.new + @input_queue = Thread::Queue.new @pid = nil sh.process_controller.add_schedule(self) @@ -82,7 +83,6 @@ class Shell def start_import notify "Job(%id) start imp-pipe.", @shell.debug? - rs = @shell.record_separator unless rs _eop = true Thread.start { begin diff --git a/lib/shell/version.rb b/lib/shell/version.rb index cbdb0e5e96..f48f781b2c 100644 --- a/lib/shell/version.rb +++ b/lib/shell/version.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false # # version.rb - shell version definition file # $Release Version: 0.7$ @@ -9,7 +10,8 @@ # # -class Shell - @RELEASE_VERSION = "0.7" +class Shell # :nodoc: + VERSION = "0.7" + @RELEASE_VERSION = VERSION @LAST_UPDATE_DATE = "07/03/20" end |
