cmdparse-3.0.7/0000755000004100000410000000000013775147450013364 5ustar www-datawww-datacmdparse-3.0.7/COPYING0000644000004100000410000000216613775147450014424 0ustar www-datawww-datacmdparse - advanced command line parser supporting commands Copyright (C) 2004-2020 Thomas Leitner Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. cmdparse-3.0.7/README.md0000644000004100000410000000272713775147450014653 0ustar www-datawww-data**cmdparse** - an advanced command line parser using optparse which has support for commands Copyright (C) 2004-2020 Thomas Leitner ## Description Some programs use a "command style" command line. Examples for such programs are the "gem" program from Rubygems and the "svn" program from Subversion. The standard Ruby distribution has no library to create programs that use such a command line interface. This library, cmdparse, can be used to create such a command line interface. Internally it uses optparse to parse options and it provides a nice API for specifying commands. See for detailed information, an extensive tutorial and the API reference! ## Documentation You can build the documentation by invoking $ rake doc This builds the whole documentation and needs webgen >=1.4.0 (https://webgen.gettalong.org) for building. ## Example Usage There is an example of how to use cmdparse in the `example/net.rb` file. A detailed walkthrough of what each part does can be found on . ## License MIT - see COPYING. ## Dependencies none ## Installation The preferred way of installing cmdparse is via RubyGems: $ gem install cmdparse If you don't want to use RubyGems, use these commands: $ ruby setup.rb config $ ruby setup.rb setup $ ruby setup.rb install ## Contact Author: Thomas Leitner * Web: * e-Mail: cmdparse-3.0.7/cmdparse.gemspec0000644000004100000410000000372713775147450016540 0ustar www-datawww-data######################################################### # This file has been automatically generated by gem2tgz # ######################################################### # -*- encoding: utf-8 -*- # stub: cmdparse 3.0.7 ruby lib Gem::Specification.new do |s| s.name = "cmdparse".freeze s.version = "3.0.7" s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version= s.require_paths = ["lib".freeze] s.authors = ["Thomas Leitner".freeze] s.date = "2020-12-08" s.description = " cmdparse provides classes for parsing (possibly nested) commands on the command line;\n command line options themselves are parsed using optparse.\n".freeze s.email = "t_leitner@gmx.at".freeze s.files = ["COPYING".freeze, "README.md".freeze, "Rakefile".freeze, "VERSION".freeze, "doc/api.api".freeze, "doc/api.template".freeze, "doc/default.scss".freeze, "doc/default.template".freeze, "doc/images/bg01.png".freeze, "doc/images/bg02.png".freeze, "doc/index.page".freeze, "doc/installation.page".freeze, "doc/metainfo".freeze, "doc/news.page".freeze, "doc/sitemap.sitemap".freeze, "doc/tutorial.page".freeze, "doc/virtual".freeze, "example/net.rb".freeze, "lib/cmdparse.rb".freeze, "setup.rb".freeze, "webgen.config".freeze] s.homepage = "https://cmdparse.gettalong.org".freeze s.licenses = ["MIT".freeze] s.rdoc_options = ["--line-numbers".freeze, "--main".freeze, "CmdParse::CommandParser".freeze] s.required_ruby_version = Gem::Requirement.new(">= 2.0.0".freeze) s.rubygems_version = "2.5.2.1".freeze s.summary = "Advanced command line parser supporting commands".freeze if s.respond_to? :specification_version then s.specification_version = 4 if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then s.add_development_dependency(%q.freeze, ["~> 1.4"]) else s.add_dependency(%q.freeze, ["~> 1.4"]) end else s.add_dependency(%q.freeze, ["~> 1.4"]) end end cmdparse-3.0.7/Rakefile0000644000004100000410000000652313775147450015037 0ustar www-datawww-data# -*- ruby -*- # #-- # cmdparse: advanced command line parser supporting commands # Copyright (C) 2004-2015 Thomas Leitner # # This file is part of cmdparse which is licensed under the MIT. #++ # require 'rubygems/package_task' require 'rake/clean' require 'rake/packagetask' require 'rdoc/task' # General actions ############################################################## $:.unshift 'lib' require 'cmdparse' PKG_NAME = "cmdparse" PKG_VERSION = CmdParse::VERSION PKG_FULLNAME = PKG_NAME + "-" + PKG_VERSION begin require 'webgen/page' rescue LoadError end # End user tasks ################################################################ # The default task is run if rake is given no explicit arguments. task :default do puts "Select task to execute:" sh "rake -T" end desc "Installs the package #{PKG_NAME} using setup.rb" task :install do ruby "setup.rb config" ruby "setup.rb setup" ruby "setup.rb install" end task :clean do ruby "setup.rb clean" end desc "Build the whole user documentation (website and api)" task :doc if defined?(Webgen) CLOBBER << "htmldoc" CLOBBER << "webgen-tmp" desc "Builds the documentation website" task :htmldoc do sh "webgen" end task :doc => :htmldoc end # Developer tasks ############################################################## namespace :dev do PKG_FILES = FileList.new( [ 'setup.rb', 'COPYING', 'README.md', 'Rakefile', 'example/net.rb', 'VERSION', 'lib/**/*.rb', 'doc/**/*', 'webgen.config' ]) CLOBBER << "VERSION" file 'VERSION' do puts "Generating VERSION file" File.open('VERSION', 'w+') {|file| file.write(PKG_VERSION + "\n")} end Rake::PackageTask.new('cmdparse', PKG_VERSION) do |pkg| pkg.need_tar = true pkg.need_zip = true pkg.package_files = PKG_FILES end spec = Gem::Specification.new do |s| #### Basic information s.name = PKG_NAME s.version = PKG_VERSION s.summary = "Advanced command line parser supporting commands" s.description = <<-EOF cmdparse provides classes for parsing (possibly nested) commands on the command line; command line options themselves are parsed using optparse. EOF s.license = 'MIT' #### Dependencies, requirements and files s.files = PKG_FILES.to_a s.require_path = 'lib' s.required_ruby_version = ">= 2.0.0" s.add_development_dependency "webgen", "~> 1.4" #### Documentation s.rdoc_options = ['--line-numbers', '--main', 'CmdParse::CommandParser'] #### Author and project details s.author = "Thomas Leitner" s.email = "t_leitner@gmx.at" s.homepage = "https://cmdparse.gettalong.org" end Gem::PackageTask.new(spec) do |pkg| pkg.need_zip = true pkg.need_tar = true end desc "Upload the release to Rubygems" task :publish_files => [:package] do sh "gem push pkg/cmdparse-#{PKG_VERSION}.gem" end if defined?(Webgen) desc "Release cmdparse version " + PKG_VERSION task :release => [:clobber, :package, :publish_files, :doc] do puts "Upload htmldoc/ to the webserver" end end end task :clobber => ['dev:clobber'] cmdparse-3.0.7/lib/0000755000004100000410000000000013775147450014132 5ustar www-datawww-datacmdparse-3.0.7/lib/cmdparse.rb0000644000004100000410000007610613775147450016267 0ustar www-datawww-data# #-- # cmdparse: advanced command line parser supporting commands # Copyright (C) 2004-2020 Thomas Leitner # # This file is part of cmdparse which is licensed under the MIT. #++ # require 'optparse' OptionParser::Officious.delete('version') OptionParser::Officious.delete('help') # Extension for OptionParser objects to allow access to some internals. class OptionParser #:nodoc: # Access the option list stack. attr_reader :stack # Returns +true+ if at least one local option is defined. # # The zeroth stack element is not respected when doing the query because it contains either the # OptionParser::DefaultList or a CmdParse::MultiList with the global options of the # CmdParse::CommandParser. def options_defined? stack[1..-1].each do |list| list.each_option do |switch| return true if switch.kind_of?(OptionParser::Switch) && (switch.short || switch.long) end end false end # Returns +true+ if a banner has been set. def banner? !@banner.nil? end end # Namespace module for cmdparse. # # See CmdParse::CommandParser and CmdParse::Command for the two important classes. module CmdParse # The version of this cmdparse implemention VERSION = '3.0.7'.freeze # Base class for all cmdparse errors. class ParseError < StandardError # Sets the error reason for the subclass. def self.reason(reason) @reason = reason end # Returns the error reason or 'CmdParse error' if it has not been set. def self.get_reason @reason ||= 'CmdParse error' end # Returns the reason plus the original message. def message str = super self.class.get_reason + (str.empty? ? "" : ": #{str}") end end # This error is thrown when an invalid command is encountered. class InvalidCommandError < ParseError reason 'Invalid command' end # This error is thrown when an invalid argument is encountered. class InvalidArgumentError < ParseError reason 'Invalid argument' end # This error is thrown when an invalid option is encountered. class InvalidOptionError < ParseError reason 'Invalid option' end # This error is thrown when no command was given and no default command was specified. class NoCommandGivenError < ParseError reason 'No command given' def initialize #:nodoc: super('') end end # This error is thrown when a command is added to another command which does not support commands. class TakesNoCommandError < ParseError reason 'This command takes no other commands' end # This error is thrown when not enough arguments are provided for the command. class NotEnoughArgumentsError < ParseError reason 'Not enough arguments provided, minimum is' end # This error is thrown when too many arguments are provided for the command. class TooManyArgumentsError < ParseError reason 'Too many arguments provided, maximum is' end # Command Hash - will return partial key matches as well if there is a single non-ambigous # matching key class CommandHash < Hash #:nodoc: def key?(name) #:nodoc: !self[name].nil? end def [](cmd_name) #:nodoc: super || begin possible = keys.select {|key| key[0, cmd_name.length] == cmd_name } fetch(possible[0]) if possible.size == 1 end end end # Container for multiple OptionParser::List objects. # # This is needed for providing what's equivalent to stacked OptionParser instances and the global # options implementation. class MultiList #:nodoc: def initialize(list) #:nodoc: @list = list end def summarize(*args, &block) #:nodoc: # We don't want summary information of the global options to automatically appear. end [:accept, :reject, :prepend, :append].each do |mname| module_eval <<-EOF def #{mname}(*args, &block) @list[-1].#{mname}(*args, &block) end EOF end [:search, :complete, :each_option, :add_banner, :compsys].each do |mname| module_eval <<-EOF def #{mname}(*args, &block) #:nodoc: @list.reverse_each {|list| list.#{mname}(*args, &block)} end EOF end def get_candidates(id, &b) @list.reverse_each {|list| list.get_candidates(id, &b)} end end # === Base class for commands # # This class implements all needed methods so that it can be used by the CommandParser class. # # Commands can either be created by sub-classing or on the fly when using the #add_command method. # The latter allows for a more terse specification of a command while the sub-class approach # allows to customize all aspects of a command by overriding methods. # # Basic example for sub-classing: # # class TestCommand < CmdParse::Command # def initialize # super('test', takes_commands: false) # options.on('-m', '--my-opt', 'My option') { 'Do something' } # end # end # # parser = CmdParse::CommandParser.new # parser.add_command(TestCommand.new) # parser.parse # # Basic example for on the fly creation: # # parser = CmdParse::CommandParser.new # parser.add_command('test') do |cmd| # takes_commands(false) # options.on('-m', '--my-opt', 'My option') { 'Do something' } # end # parser.parse # # === Basic Properties # # The only thing that is mandatory to set for a Command is its #name. If the command does not take # any sub-commands, then additionally an #action block needs to be specified or the #execute # method overridden. # # However, there are several other methods that can be used to configure the behavior of a # command: # # #takes_commands:: For specifying whether sub-commands are allowed. # #options:: For specifying command specific options. # #add_command:: For specifying sub-commands if the command takes them. # # === Help Related Methods # # Many of this class' methods are related to providing useful help output. While the most common # methods can directly be invoked to set or retrieve information, many other methods compute the # needed information dynamically and therefore need to be overridden to customize their return # value. # # #short_desc:: # For a short description of the command (getter/setter). # #long_desc:: # For a detailed description of the command (getter/setter). # #argument_desc:: # For describing command arguments (setter). # #help, #help_banner, #help_short_desc, #help_long_desc, #help_commands, #help_arguments, #help_options:: # For outputting the general command help or individual sections of the command help (getter). # #usage, #usage_options, #usage_arguments, #usage_commands:: # For outputting the usage line or individual parts of it (getter). # # === Built-in Commands # # cmdparse ships with two built-in commands: # * HelpCommand (for showing help messages) and # * VersionCommand (for showing version information). class Command # The name of the command. attr_reader :name # Returns the name of the default sub-command or +nil+ if there isn't any. attr_reader :default_command # Sets or returns the super-command of this command. The super-command is either a Command # instance for normal commands or a CommandParser instance for the main command (ie. # CommandParser#main_command). attr_accessor :super_command # Returns the mapping of command name to command for all sub-commands of this command. attr_reader :commands # A data store (initially an empty Hash) that can be used for storing anything. For example, it # can be used to store option values. cmdparse itself doesn't do anything with it. attr_accessor :data # Initializes the command called +name+. # # Options: # # takes_commands:: Specifies whether this command can take sub-commands. def initialize(name, takes_commands: true) @name = name.freeze @options = OptionParser.new @commands = CommandHash.new @default_command = nil @action = nil @argument_desc ||= {} @data = {} takes_commands(takes_commands) end # Sets whether this command can take sub-command. # # The argument +val+ needs to be +true+ or +false+. def takes_commands(val) if !val && !commands.empty? raise Error, "Can't change takes_commands to false because there are already sub-commands" else @takes_commands = val end end alias takes_commands= takes_commands # Return +true+ if this command can take sub-commands. def takes_commands? @takes_commands end # :call-seq: # command.options {|opts| ...} -> opts # command.options -> opts # # Yields the OptionParser instance that is used for parsing the options of this command (if a # block is given) and returns it. def options #:yields: options yield(@options) if block_given? @options end # :call-seq: # command.add_command(other_command, default: false) {|cmd| ... } -> command # command.add_command('other', default: false) {|cmd| ...} -> command # # Adds a command to the command list. # # The argument +command+ can either be a Command object or a String in which case a new Command # object is created. In both cases the Command object is yielded. # # If the optional argument +default+ is +true+, then the command is used when no other # sub-command is specified on the command line. # # If this command takes no other commands, an error is raised. def add_command(command, default: false) # :yields: command_object raise TakesNoCommandError.new(name) unless takes_commands? command = Command.new(command) if command.kind_of?(String) command.super_command = self @commands[command.name] = command @default_command = command.name if default command.fire_hook_after_add yield(command) if block_given? self end # :call-seq: # command.command_chain -> [top_level_command, super_command, ..., command] # # Returns the command chain, i.e. a list containing this command and all of its super-commands, # starting at the top level command. def command_chain cmds = [] cmd = self while !cmd.nil? && !cmd.super_command.kind_of?(CommandParser) cmds.unshift(cmd) cmd = cmd.super_command end cmds end # Returns the associated CommandParser instance for this command or +nil+ if no command parser # is associated. def command_parser cmd = super_command cmd = cmd.super_command while !cmd.nil? && !cmd.kind_of?(CommandParser) cmd end # Sets the given +block+ as the action block that is used on when executing this command. # # If a sub-class is created for specifying a command, then the #execute method should be # overridden instead of setting an action block. # # See also: #execute def action(&block) @action = block end # Invokes the action block with the parsed arguments. # # This method is called by the CommandParser instance if this command was specified on the # command line to be executed. # # Sub-classes can either specify an action block or directly override this method (the latter is # preferred). def execute(*args) @action.call(*args) end # Sets the short description of the command if an argument is given. Always returns the short # description. # # The short description is ideally shorter than 60 characters. def short_desc(*val) @short_desc = val[0] unless val.empty? @short_desc end alias short_desc= short_desc # Sets the detailed description of the command if an argument is given. Always returns the # detailed description. # # This may be a single string or an array of strings for multiline description. Each string # is ideally shorter than 76 characters. def long_desc(*val) @long_desc = val.flatten unless val.empty? @long_desc end alias long_desc= long_desc # :call-seq: # cmd.argument_desc(name => desc, ...) # # Sets the descriptions for one or more arguments using name-description pairs. # # The used names should correspond to the names used in #usage_arguments. def argument_desc(hash) @argument_desc.update(hash) end # Returns the number of arguments required for the execution of the command, i.e. the number of # arguments the #action block or the #execute method takes. # # If the returned number is negative, it means that the minimum number of arguments is -n-1. # # See: Method#arity, Proc#arity def arity (@action || method(:execute)).arity end # Returns +true+ if the command can take one or more arguments. def takes_arguments? arity.abs > 0 end # Returns a string containing the help message for the command. def help output = '' output << help_banner output << help_short_desc output << help_long_desc output << help_commands output << help_arguments output << help_options('Options (take precedence over global options)', options) output << help_options('Global Options', command_parser.global_options) end # Returns the banner (including the usage line) of the command. # # The usage line is command specific but the rest is the same for all commands and can be set # via +command_parser.main_options.banner+. def help_banner output = '' if command_parser.main_options.banner? output << format(command_parser.main_options.banner, indent: 0) << "\n\n" end output << format(usage, indent: 7) << "\n\n" end # Returns the usage line for the command. # # The usage line is automatically generated from the available information. If this is not # suitable, override this method to provide a command specific usage line. # # Typical usage lines looks like the following: # # Usage: program [options] command [options] {sub_command1 | sub_command2} # Usage: program [options] command [options] ARG1 [ARG2] [REST...] # # See: #usage_options, #usage_arguments, #usage_commands def usage tmp = "Usage: #{command_parser.main_options.program_name}" tmp << command_parser.main_command.usage_options tmp << command_chain.map {|cmd| " #{cmd.name}#{cmd.usage_options}"}.join('') if takes_commands? tmp << " #{usage_commands}" elsif takes_arguments? tmp << " #{usage_arguments}" end tmp end # Returns a string describing the options of the command for use in the usage line. # # If there are any options, the resulting string also includes a leading space! # # A typical return value would look like the following: # # [options] # # See: #usage def usage_options (options.options_defined? ? ' [options]' : '') end # Returns a string describing the arguments for the command for use in the usage line. # # By default the names of the action block or #execute method arguments are used (done via # Ruby's reflection API). If this is not wanted, override this method. # # A typical return value would look like the following: # # ARG1 [ARG2] [REST...] # # See: #usage, #argument_desc def usage_arguments (@action || method(:execute)).parameters.map do |type, name| case type when :req then name.to_s when :opt then "[#{name}]" when :rest then "[#{name}...]" end end.join(" ").upcase end # Returns a string describing the sub-commands of the commands for use in the usage line. # # Override this method for providing a command specific specialization. # # A typical return value would look like the following: # # {command | other_command | another_command } def usage_commands (commands.empty? ? '' : "{#{commands.keys.sort.join(" | ")}}") end # Returns the formatted short description. # # For the output format see #cond_format_help_section def help_short_desc cond_format_help_section("Summary", "#{name} - #{short_desc}", condition: short_desc && !short_desc.empty?) end # Returns the formatted detailed description. # # For the output format see #cond_format_help_section def help_long_desc cond_format_help_section("Description", [long_desc].flatten, condition: long_desc && !long_desc.empty?) end # Returns the formatted sub-commands of this command. # # For the output format see #cond_format_help_section def help_commands describe_commands = lambda do |command, level = 0| command.commands.sort.collect do |name, cmd| str = " " * level << name << (name == command.default_command ? " (*)" : '') str = str.ljust(command_parser.help_desc_indent) << cmd.short_desc.to_s str = format(str, width: command_parser.help_line_width - command_parser.help_indent, indent: command_parser.help_desc_indent) str << "\n" << (cmd.takes_commands? ? describe_commands.call(cmd, level + 1) : "") end.join('') end cond_format_help_section("Available commands", describe_commands.call(self), condition: takes_commands?, preformatted: true) end # Returns the formatted arguments of this command. # # For the output format see #cond_format_help_section def help_arguments desc = @argument_desc.map {|k, v| k.to_s.ljust(command_parser.help_desc_indent) << v.to_s} cond_format_help_section('Arguments', desc, condition: !@argument_desc.empty?) end # Returns the formatted option descriptions for the given OptionParser instance. # # The section title needs to be specified with the +title+ argument. # # For the output format see #cond_format_help_section def help_options(title, options) summary = '' summary_width = command_parser.main_options.summary_width options.summarize([], summary_width, summary_width - 1, '') do |line| summary << format(line, width: command_parser.help_line_width - command_parser.help_indent, indent: summary_width + 1, indent_first_line: false) << "\n" end cond_format_help_section(title, summary, condition: !summary.empty?, preformatted: true) end # This hook method is called when the command (or one of its super-commands) is added to another # Command instance that has an associated command parser (#see command_parser). # # It can be used, for example, to add global options. def on_after_add end # For sorting commands by name. def <=>(other) name <=> other.name end protected # Conditionally formats a help section. # # Returns either the formatted help section if the condition is +true+ or an empty string # otherwise. # # The help section starts with a title and the given lines are indented to easily distinguish # different sections. # # A typical help section would look like the following: # # Summary: # help - Provide help for individual commands # # Options: # # condition:: The formatted help section is only returned if the condition is +true+. # # indent:: Whether the lines should be indented with CommandParser#help_indent spaces. # # preformatted:: Assume that the given lines are already correctly formatted and don't try to # reformat them. def cond_format_help_section(title, *lines, condition: true, indent: true, preformatted: false) if condition out = "#{title}:\n" lines = lines.flatten.join("\n").split(/\n/) if preformatted lines.map! {|l| ' ' * command_parser.help_indent << l} if indent out << lines.join("\n") else out << format(lines.join("\n"), indent: (indent ? command_parser.help_indent : 0), indent_first_line: true) end out << "\n\n" else '' end end # Returns the text in +content+ formatted so that no line is longer than +width+ characters. # # Options: # # width:: The maximum width of a line. If not specified, the CommandParser#help_line_width value # is used. # # indent:: This option specifies the amount of spaces prepended to each line. If not specified, # the CommandParser#help_indent value is used. # # indent_first_line:: If this option is +true+, then the first line is also indented. def format(content, width: command_parser.help_line_width, indent: command_parser.help_indent, indent_first_line: false) content = (content || '').dup line_length = width - indent first_line_pattern = other_lines_pattern = /\A.{1,#{line_length}}\z|\A.{1,#{line_length}}[ \n]/m (first_line_pattern = /\A.{1,#{width}}\z|\A.{1,#{width}}[ \n]/m) unless indent_first_line pattern = first_line_pattern content.split(/\n\n/).map do |paragraph| lines = [] until paragraph.empty? unless (str = paragraph.slice!(pattern)) and (str = str.sub(/[ \n]\z/, '')) str = paragraph.slice!(0, line_length) end lines << (lines.empty? && !indent_first_line ? '' : ' ' * indent) + str.tr("\n", ' ') pattern = other_lines_pattern end lines.join("\n") end.join("\n\n") end def fire_hook_after_add #:nodoc: return unless command_parser @options.stack[0] = MultiList.new(command_parser.global_options.stack) on_after_add @commands.each_value {|cmd| cmd.fire_hook_after_add} end end # The default help Command. # # It adds the options "-h" and "--help" to the CommandParser#global_options. # # When the command is specified on the command line (or one of the above mentioned options), it # shows the main help or individual command help. class HelpCommand < Command def initialize #:nodoc: super('help', takes_commands: false) short_desc('Provide help for individual commands') long_desc('This command prints the program help if no arguments are given. If one or ' \ 'more command names are given as arguments, these arguments are interpreted ' \ 'as a hierachy of commands and the help for the right most command is show.') argument_desc(COMMAND: 'The name of a command or sub-command') end def on_after_add #:nodoc: command_parser.global_options.on_tail("-h", "--help", "Show help") do execute(*command_parser.current_command.command_chain.map(&:name)) exit end end def usage_arguments #:nodoc: "[COMMAND COMMAND...]" end def execute(*args) #:nodoc: if !args.empty? cmd = command_parser.main_command arg = args.shift while !arg.nil? && cmd.commands.key?(arg) cmd = cmd.commands[arg] arg = args.shift end if arg.nil? puts cmd.help else raise InvalidArgumentError, args.unshift(arg).join(' ') end else puts command_parser.main_command.help end end end # The default version command. # # It adds the options "-v" and "--version" to the CommandParser#main_options but this can be # changed in ::new. # # When the command is specified on the command line (or one of the above mentioned options), it # shows the version of the program configured by the settings # # * command_parser.main_options.program_name # * command_parser.main_options.version class VersionCommand < Command # Create a new version command. # # Options: # # add_switches:: Specifies whether the '-v' and '--version' switches should be added to the # CommandParser#main_options def initialize(add_switches: true) super('version', takes_commands: false) short_desc("Show the version of the program") @add_switches = add_switches end def on_after_add #:nodoc: command_parser.main_options.on_tail("--version", "-v", "Show the version of the program") do execute end if @add_switches end def execute #:nodoc: version = command_parser.main_options.version version = version.join('.') if version.kind_of?(Array) puts command_parser.main_options.banner + "\n" if command_parser.main_options.banner? puts "#{command_parser.main_options.program_name} #{version}" exit end end # === Main Class for Creating a Command Based CLI Program # # This class can directly be used (or sub-classed, if need be) to create a command based CLI # program. # # The CLI program itself is represented by the #main_command, a Command instance (as are all # commands and sub-commands). This main command can either hold sub-commands (the normal use case) # which represent the programs top level commands or take no commands in which case it acts # similar to a simple OptionParser based program (albeit with better help functionality). # # Parsing the command line for commands is done by this class, option parsing is delegated to the # battle tested OptionParser of the Ruby standard library. # # === Usage # # After initialization some optional information is expected to be set on the Command#options of # the #main_command: # # banner:: A banner that appears in the help output before anything else. # program_name:: The name of the program. If not set, this value is computed from $0. # version:: The version string of the program. # # In addition to the main command's options instance (which represents the top level options that # need to be specified before any command name), there is also a #global_options instance which # represents options that can be specified anywhere on the command line. # # Top level commands can be added to the main command by using the #add_command method. # # Once everything is set up, the #parse method is used for parsing the command line. class CommandParser # The top level command representing the program itself. attr_reader :main_command # The command that is being executed. Only available during parsing of the command line # arguments. attr_reader :current_command # A data store (initially an empty Hash) that can be used for storing anything. For example, it # can be used to store global option values. cmdparse itself doesn't do anything with it. attr_accessor :data # Should exceptions be handled gracefully? I.e. by printing error message and the help screen? # # See ::new for possible values. attr_reader :handle_exceptions # The maximum width of the help lines. attr_accessor :help_line_width # The amount of spaces to indent the content of help sections. attr_accessor :help_indent # The indentation used for, among other things, command descriptions. attr_accessor :help_desc_indent # Creates a new CommandParser object. # # Options: # # handle_exceptions:: Set to +true+ if exceptions should be handled gracefully by showing the # error and a help message, or to +false+ if exception should not be handled # at all. If this options is set to :no_help, the exception is handled but # no help message is shown. # # takes_commands:: Specifies whether the main program takes any commands. def initialize(handle_exceptions: false, takes_commands: true) @global_options = OptionParser.new @main_command = Command.new('main', takes_commands: takes_commands) @main_command.super_command = self @main_command.options.stack[0] = MultiList.new(@global_options.stack) @handle_exceptions = handle_exceptions @help_line_width = 80 @help_indent = 4 @help_desc_indent = 18 @data = {} end # :call-seq: # cmdparse.main_options -> OptionParser instance # cmdparse.main_options {|opts| ...} -> opts (OptionParser instance) # # Yields the main options (that are only available directly after the program name) if a block # is given and returns them. # # The main options are also used for setting the program name, version and banner. def main_options yield(@main_command.options) if block_given? @main_command.options end # :call-seq: # cmdparse.global_options -> OptionParser instance # cmdparse.gloabl_options {|opts| ...} -> opts (OptionParser instance) # # Yields the global options if a block is given and returns them. # # The global options are those options that can be used on the top level and with any # command. def global_options yield(@global_options) if block_given? @global_options end # Adds a top level command. # # See Command#add_command for detailed invocation information. def add_command(*args, **kws, &block) @main_command.add_command(*args, **kws, &block) end # Parses the command line arguments. # # If a block is given, the current hierarchy level and the name of the current command is # yielded after the option parsing is done but before a command is executed. def parse(argv = ARGV) # :yields: level, command_name level = 0 @current_command = @main_command while true argv = if @current_command.takes_commands? || ENV.include?('POSIXLY_CORRECT') @current_command.options.order(argv) else @current_command.options.permute(argv) end yield(level, @current_command.name) if block_given? if @current_command.takes_commands? cmd_name = argv.shift || @current_command.default_command if cmd_name.nil? raise NoCommandGivenError.new elsif !@current_command.commands.key?(cmd_name) raise InvalidCommandError.new(cmd_name) end @current_command = @current_command.commands[cmd_name] level += 1 else original_n = @current_command.arity n = (original_n < 0 ? -original_n - 1 : original_n) if argv.size < n raise NotEnoughArgumentsError.new("#{n} - #{@current_command.usage_arguments}") elsif argv.size > n && original_n > 0 raise TooManyArgumentsError.new("#{n} - #{@current_command.usage_arguments}") end argv.slice!(n..-1) unless original_n < 0 @current_command.execute(*argv) break end end rescue ParseError, OptionParser::ParseError => e raise unless @handle_exceptions puts "Error while parsing command line:\n " + e.message if @handle_exceptions != :no_help && @main_command.commands.key?('help') puts @main_command.commands['help'].execute(*@current_command.command_chain.map(&:name)) end exit(64) # FreeBSD standard exit error for "command was used incorrectly" rescue Interrupt exit(128 + 2) rescue Errno::EPIPE # Behave well when used in a pipe ensure @current_command = nil end end end cmdparse-3.0.7/doc/0000755000004100000410000000000013775147450014131 5ustar www-datawww-datacmdparse-3.0.7/doc/tutorial.page0000644000004100000410000003001713775147450016633 0ustar www-datawww-data--- title: Tutorial in_menu: true sort_info: 6 --- ## Tutorial The complete code for this example can be found in the file `example/net.rb` of the `cmdparse` package (or online at [cmdparse's Github repository][netrb]! **Tl;dr:** In this tutorial we will create a small `net` program which can add, delete and list IP addresses as well as show 'network statistics'. By doing this we show how easy it is to use the `cmdparse` library for creating a command based program. The last part shows how our created program can be invoked and the built-in help facility of `cmdparse`. Note that the shown code fragments do not comprise the whole program. So, depending on what you like, just look at the code fragments and the explanations or open the example file alongside this tutorial. Later use the `example/net.rb` file for running the program yourself and testing different command line arguments. ### Require statements First we create a new new file and add the necessary `require` statements: ~~~ ruby {extract: {lines: !ruby/range 4..4}} ~~~ When requiring `cmdparse`, the `optparse` library from Ruby's standard library is also automatically required which is used for doing the option parsing part. ### The Basic Command Parser Object Next we will define our command parser object through the [CmdParse::CommandParser] class. Objects of this class are used for defining the top level commands of a program as well as the global options and other information like the name of the program or its version. ~~~ ruby {extract: {lines: !ruby/range 26..29}} ~~~ We use the `handle_exceptions` argument of the constructor so that exceptions are automatically handled gracefully, i.e. by showing an appropriate error message. If we used `true` as value, the help screen would be shown in addition to the error message. The next lines set the name of the program, its version and a banner that is shown on all help messages. All this information is set on the [main options][CmdParse::CommandParser#main_options]. Setting this information on the global options or any other `OptionParser` instance has no effect! ### Specifying Options An integral part of any CLI program are options that can be set when invoking the program. A command based CLI program has several kinds of options: * The [main options][CmdParse::CommandParser#main_options] which can only be used directly after the program name itself. An example for such an option would be a `--version` switch that only makes sense at the top level. * The [command specific options][CmdParse::Command#options] which can only be used after the command name and before any sub-command name. * The [global options][CmdParse::CommandParser#global_options] which can be used directly after the program name as well as after any command. Therefore global options are normally used for things that affect all commands, like a global verbosity setting. All these options are specified using the Ruby standard library `optparse` and its `OptionParser` class. The `OptionParser` implementation is battle tested, easy to use and allows great flexibility. We go back to our example now and define a global option: ~~~ ruby {extract: {lines: !ruby/range 30..34}} ~~~ The [data attribute][CmdParse::CommandParser#data] on the command parse object (or any command object) can be used to store arbitrary information. Here we use it to store the verbosity level so that it can easily be used later by any command. We could have used a global variable for this but storing such information with the command parser object usually makes for a better design. Note that the data attribute is only really useful when the [CmdParse::CommandParser] is not sub-classed. ### Parsing the Command Line Arguments We have set up everything that is needed for a basic command based program. The last step is to tell the program to use our newly defined command parser object to process the command line arguments: ~~~ ruby {extract: {lines: !ruby/range 84..84}} ~~~ The [parse method][CmdParse::CommandParser#parse] parses the given array of arguments (or `ARGV` if no array is specified). All the command line arguments are parsed and the given command executed. The program could now be executed but it won't be useful as we did not specify any commands yet. ### Defining commands After performing the basic setup, we need to add some commands so that our program actually does something useful. First, we will add two built-in commands, namely the `help` and the `version` command: ~~~ ruby {extract: {lines: !ruby/range 35..36}} ~~~ That was easy! Now you can execute the program and specify the commands `help` or `version`. You will also find that the `help` command is automatically invoked when you don't specify any command. This is because we used the `default: true` argument when adding the `help` command which sets the added command as the default command that should be used if no explicit command name is given. The next step is to create the needed commands for our program. There are several different ways of doing this. ~~~ ruby {extract: {lines: !ruby/range 40..42}} ~~~ One way is to create an instance of [CmdParse::Command], update it with all needed properties and then add it to the command parser object or another command. Since the `ipaddr` command takes other commands and doesn't do anything by itself, no action is defined for it. Also notice that we have redefined the default command to be the `ipaddr` command. The last added command with the argument `default: true` will be the default command. ~~~ ruby {extract: {lines: !ruby/range 45..52}} ~~~ Another way would be to take advantage of the fact that the [add_command method][CmdParse::Command#add_command] creates a [CmdParse::Command] object when a name is passed as argument instead of a command object itself and that the added command is always yielded if a block is given. Using this block we can now easily customize the command. Since the `ipaddr add` command does not take any commands, we need to define an [action block][CmdParse::Command#action] that gets called if the command should be executed. By using `*ips` we advertise to `cmdparse` that this command takes an arbitrary number of IP addresses as arguments. Note that this is also automatically reflected in the usage line for the command! We add the `ipaddr del` and `ipaddr list` commands in a similar manner and set the `list` command to be the default sub-command for the `ipaddr` command. ~~~ ruby {extract: {lines: !ruby/range 6..24}} {extract: {lines: !ruby/range 37..37}} ~~~ The last way for creating a command is to sub-class the [CmdParse::Command] class and do all customization there. If this is done, it is recommended to override the `#execute` method instead of setting an action block. We can also see that the execute method takes one or two arguments and that these arguments are also properly documented. ### Running the Program <%= (context[:args] = nil; context.render_block('execution')) %> Now that we have completed our program we can finally run it! Below are some sample invocations with their respective output and some explanations. <%= (context[:args] = ""; context.render_block('execution')) %> When called with no arguments, the default command is executed. The default top level command is `ipaddr` and its default command is `list` which shows the added IP addresses. So far, no IP addresses are stored - we should change that. <%= (context[:args] = "ip add 192.168.0.1"; context.render_block('execution')) %> Now we have added one IP address. You might have noticed that we used `ip` instead of `ipaddr`. Since partial command matching is automatically done, the shortest unambiguous name for a command can be used. As there is no other command starting with `ip` (or even with the letter `i`), it is sufficient to write the above to select the `ipaddr` command. Now lets add some more IPs but with some informational output. <%= (context[:args] = "i a 192.168.0.2 192.168.0.4 192.168.0.3 -v"; context.render_block('execution')) %> This time we added three IP addresses and by using the global option `-v` we got some informational output, too. Let's display which IP addresses are currently stored. <%= (context[:args] = "ipaddr list"; context.render_block('execution')) %> So we have four IPs stored. However, we really only need three so we delete one. <%= (context[:args] = "ip -v del 192.16.8.0.4 "; context.render_block('execution')) %> That's much better! But we all are getting sick of this and don't want any IP addresses stored anymore. <%= (context[:args] = "ip -a del"; context.render_block('execution')) %> Alas, I mistyped that last command. The option `-a` is a command specific option of `ipaddr del` and therefore not recognized by `ipaddr`. <%= (context[:args] = ["ip del -av", '']; context.render_block('execution')) %> After deleting all IP addresses none are shown anymore - perfect! Now we want to see the "network statistics" part of our program. What was the command name again? Let's get some help! <%= (context[:args] = ["help"]; context.render_block('execution')) %> Ah, yes, the name was `stat`, now we remember! And there are some interesting things in the help output worth pointing out: * The usage line shows us that we can define top level option (in addition to the global options) and also nicely lists the available commands. * The asterisks in the section for the commands show us the default commands. * We have a top level option `-v` for showing the version as well as a global option `-v` for setting the verbosity level. As mentioned in the help output, the top level option (or a command specific option) always takes precedence over global options. To make the last point clear, we run the command with the `-v` option. <%= (context[:args] = ["-v"]; context.render_block('execution')) %> This shows us the version information as expected instead of invoking the default command. Back to the network statistics. Now we now the command name but we have forgotten how to use the command itself. The `help` command comes to our rescue again! <%= (context[:args] = ["help stat"]; context.render_block('execution')) %> There are again some things to point out: * We get a short summary and a more detailed description of the command. The short description is the one shown in the general help overview. The detailed description is normally longer than this and fully explains the command. * The arguments for the command are also described. When looking at the usage line, you can see that the `M` argument is optional, but the `N` argument isn't (indicated by the brackets). This means that we need at least one argument. If we provide only one argument, it is used for `N`. And if we provide two arguments, they are used for `M` and `N` (in this order). How does `cmdparse` know that `M` is optional? It has inferred this (as well as the names themselves) by looking at the signature of the execute method of the command! Now we know everything to invoke the command. <%= (context[:args] = ["stat 5", "stat 3 5"]; context.render_block('execution')) %> ### Final Words Our `net` program is certainly only useful for this tutorial, however, it nicely showcases many of the features of `cmdparse` and how easy `cmdparse` is to use. If you haven't done so by now, [install](installation.html) the `cmdparse` library, [download][netrb] our sample `net` program and experiment a bit with it. The [API documentation](/api/) is quite extensive and will answer all remaining questions. And if it doesn't, you can contact [me](mailto:t_leitner@gmx.at). [netrb]: https://github.com/gettalong/cmdparse/blob/master/example/net.rb --- name:execution pipeline:ruby if context[:args].nil? require 'fileutils' FileUtils.rm(context.website.directory + '/dumpnet', :force => true) context.content = '' else args = [context[:args]].flatten result = args.map do |arg| ["$ ruby example/net.rb #{arg}", h(`ruby -I#{context.website.directory}/lib #{context.website.directory}/example/net.rb #{arg}`).chomp] end.flatten.push("$").delete_if {|l| l.empty? }.join("\n") context.content = "
#{result}\n
" end cmdparse-3.0.7/doc/default.scss0000644000004100000410000015644513775147450016471 0ustar www-datawww-data@charset 'UTF-8'; $logo-color: #3A974D; /* Striped by HTML5 UP html5up.net | @n33co Free for personal and commercial use under the CCA 3.0 license (html5up.net/license) */ /***************************************/ /***************************************/ /* Original file: skel.css */ /***************************************/ /***************************************/ /* Resets (http://meyerweb.com/eric/tools/css/reset/ | v2.0 | 20110126 | License: none (public domain)) */ html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,embed,figure,figcaption,footer,header,hgroup,menu,nav,output,ruby,section,summary,time,mark,audio,video{margin:0;padding:0;border:0;font-size:100%;font:inherit;vertical-align:baseline;}article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{display:block;}body{line-height:1;}ol,ul{list-style:none;}blockquote,q{quotes:none;}blockquote:before,blockquote:after,q:before,q:after{content:'';content:none;}table{border-collapse:collapse;border-spacing:0;}body{-webkit-text-size-adjust:none} /* Box Model */ *, *:before, *:after { -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; } /* Container */ .container { margin-left: auto; margin-right: auto; /* width: (containers) */ width: 1200px; } /* Modifiers */ /* 125% */ .container.\31 25\25 { width: 100%; /* max-width: (containers * 1.25) */ max-width: 1500px; /* min-width: (containers) */ min-width: 1200px; } /* 75% */ .container.\37 5\25 { /* width: (containers * 0.75) */ width: 900px; } /* 50% */ .container.\35 0\25 { /* width: (containers * 0.50) */ width: 600px; } /* 25% */ .container.\32 5\25 { /* width: (containers * 0.25) */ width: 300px; } /* Grid */ .row { border-bottom: solid 1px transparent; } .row > * { float: left; } .row:after, .row:before { content: ''; display: block; clear: both; height: 0; } .row.uniform > * > :first-child { margin-top: 0; } .row.uniform > * > :last-child { margin-bottom: 0; } /* Gutters */ /* Normal */ .row > * { /* padding: (gutters.horizontal) 0 0 (gutters.vertical) */ padding: 40px 0 0 40px; } .row { /* margin: -(gutters.horizontal) 0 -1px -(gutters.vertical) */ margin: -40px 0 -1px -40px; } .row.uniform > * { /* padding: (gutters.vertical) 0 0 (gutters.vertical) */ padding: 40px 0 0 40px; } .row.uniform { /* margin: -(gutters.vertical) 0 -1px -(gutters.vertical) */ margin: -40px 0 -1px -40px; } /* 200% */ .row.\32 00\25 > * { /* padding: (gutters.horizontal) 0 0 (gutters.vertical) */ padding: 80px 0 0 80px; } .row.\32 00\25 { /* margin: -(gutters.horizontal) 0 -1px -(gutters.vertical) */ margin: -80px 0 -1px -80px; } .row.uniform.\32 00\25 > * { /* padding: (gutters.vertical) 0 0 (gutters.vertical) */ padding: 80px 0 0 80px; } .row.uniform.\32 00\25 { /* margin: -(gutters.vertical) 0 -1px -(gutters.vertical) */ margin: -80px 0 -1px -80px; } /* 150% */ .row.\31 50\25 > * { /* padding: (gutters.horizontal) 0 0 (gutters.vertical) */ padding: 60px 0 0 60px; } .row.\31 50\25 { /* margin: -(gutters.horizontal) 0 -1px -(gutters.vertical) */ margin: -60px 0 -1px -60px; } .row.uniform.\31 50\25 > * { /* padding: (gutters.vertical) 0 0 (gutters.vertical) */ padding: 60px 0 0 60px; } .row.uniform.\31 50\25 { /* margin: -(gutters.vertical) 0 -1px -(gutters.vertical) */ margin: -60px 0 -1px -60px; } /* 50% */ .row.\35 0\25 > * { /* padding: (gutters.horizontal) 0 0 (gutters.vertical) */ padding: 20px 0 0 20px; } .row.\35 0\25 { /* margin: -(gutters.horizontal) 0 -1px -(gutters.vertical) */ margin: -20px 0 -1px -20px; } .row.uniform.\35 0\25 > * { /* padding: (gutters.vertical) 0 0 (gutters.vertical) */ padding: 20px 0 0 20px; } .row.uniform.\35 0\25 { /* margin: -(gutters.vertical) 0 -1px -(gutters.vertical) */ margin: -20px 0 -1px -20px; } /* 25% */ .row.\32 5\25 > * { /* padding: (gutters.horizontal) 0 0 (gutters.vertical) */ padding: 10px 0 0 10px; } .row.\32 5\25 { /* margin: -(gutters.horizontal) 0 -1px -(gutters.vertical) */ margin: -10px 0 -1px -10px; } .row.uniform.\32 5\25 > * { /* padding: (gutters.vertical) 0 0 (gutters.vertical) */ padding: 10px 0 0 10px; } .row.uniform.\32 5\25 { /* margin: -(gutters.vertical) 0 -1px -(gutters.vertical) */ margin: -10px 0 -1px -10px; } /* 0% */ .row.\30 \25 > * { padding: 0; } .row.\30 \25 { margin: 0 0 -1px 0; } /* Cells */ .\31 2u, .\31 2u\24 { width: 100%; clear: none; margin-left: 0; } .\31 1u, .\31 1u\24 { width: 91.6666666667%; clear: none; margin-left: 0; } .\31 0u, .\31 0u\24 { width: 83.3333333333%; clear: none; margin-left: 0; } .\39 u, .\39 u\24 { width: 75%; clear: none; margin-left: 0; } .\38 u, .\38 u\24 { width: 66.6666666667%; clear: none; margin-left: 0; } .\37 u, .\37 u\24 { width: 58.3333333333%; clear: none; margin-left: 0; } .\36 u, .\36 u\24 { width: 50%; clear: none; margin-left: 0; } .\35 u, .\35 u\24 { width: 41.6666666667%; clear: none; margin-left: 0; } .\34 u, .\34 u\24 { width: 33.3333333333%; clear: none; margin-left: 0; } .\33 u, .\33 u\24 { width: 25%; clear: none; margin-left: 0; } .\32 u, .\32 u\24 { width: 16.6666666667%; clear: none; margin-left: 0; } .\31 u, .\31 u\24 { width: 8.3333333333%; clear: none; margin-left: 0; } .\31 2u\24 + *, .\31 1u\24 + *, .\31 0u\24 + *, .\39 u\24 + *, .\38 u\24 + *, .\37 u\24 + *, .\36 u\24 + *, .\35 u\24 + *, .\34 u\24 + *, .\33 u\24 + *, .\32 u\24 + *, .\31 u\24 + * { clear: left; } .\-11u { margin-left: 91.6666666667% } .\-10u { margin-left: 83.3333333333% } .\-9u { margin-left: 75% } .\-8u { margin-left: 66.6666666667% } .\-7u { margin-left: 58.3333333333% } .\-6u { margin-left: 50% } .\-5u { margin-left: 41.6666666667% } .\-4u { margin-left: 33.3333333333% } .\-3u { margin-left: 25% } .\-2u { margin-left: 16.6666666667% } .\-1u { margin-left: 8.3333333333% } /***************************************/ /***************************************/ /* Original file: style.css */ /***************************************/ /***************************************/ @font-face { font-family: 'Source Sans Pro'; font-style: normal; font-weight: 400; src: local('Source Sans Pro Regular'), local('SourceSansPro-Regular'), url('//static.gettalong.org/fonts/source-sans-pro-v11-latin-ext_latin-regular.woff2') format('woff2'), url('//static.gettalong.org/fonts/source-sans-pro-v11-latin-ext_latin-regular.woff') format('woff'), url('//static.gettalong.org/fonts/source-sans-pro-v11-latin-ext_latin-regular.ttf') format('truetype'); } @font-face { font-family: 'Source Sans Pro'; font-style: italic; font-weight: 400; src: local('Source Sans Pro Italic'), local('SourceSansPro-Italic'), url('//static.gettalong.org/fonts/source-sans-pro-v11-latin-ext_latin-italic.woff2') format('woff2'), url('//static.gettalong.org/fonts/source-sans-pro-v11-latin-ext_latin-italic.woff') format('woff'), url('//static.gettalong.org/fonts/source-sans-pro-v11-latin-ext_latin-italic.ttf') format('truetype'); } @font-face { font-family: 'Source Sans Pro'; font-style: normal; font-weight: 700; src: local('Source Sans Pro Bold'), local('SourceSansPro-Bold'), url('//static.gettalong.org/fonts/source-sans-pro-v11-latin-ext_latin-700.woff2') format('woff2'), url('//static.gettalong.org/fonts/source-sans-pro-v11-latin-ext_latin-700.woff') format('woff'), url('//static.gettalong.org/fonts/source-sans-pro-v11-latin-ext_latin-700.ttf') format('truetype'); } @font-face { font-family: 'Open Sans Condensed'; font-style: normal; font-weight: 300; src: local('Open Sans Condensed Light'), local('OpenSansCondensed-Light'), url('//static.gettalong.org/fonts/open-sans-condensed-v12-latin-ext_latin-300.woff2') format('woff2'), url('//static.gettalong.org/fonts/open-sans-condensed-v12-latin-ext_latin-300.woff') format('woff'), url('//static.gettalong.org/fonts/open-sans-condensed-v12-latin-ext_latin-300.ttf') format('truetype'); } @font-face { font-family: 'Open Sans Condensed'; font-style: normal; font-weight: 700; src: local('Open Sans Condensed Bold'), local('OpenSansCondensed-Bold'), url('//static.gettalong.org/fonts/open-sans-condensed-v12-latin-ext_latin-700.woff2') format('woff2'), url('//static.gettalong.org/fonts/open-sans-condensed-v12-latin-ext_latin-700.woff') format('woff'), url('//static.gettalong.org/fonts/open-sans-condensed-v12-latin-ext_latin-700.ttf') format('truetype'); } /*********************************************************************************/ /* Basic */ /*********************************************************************************/ body { background-color: #e8e8e8; background-image: url('images/bg02.png'); } body.is-loading * { -moz-transition: none !important; -webkit-transition: none !important; -o-transition: none !important; -ms-transition: none !important; transition: none !important; -moz-animation: none !important; -webkit-animation: none !important; -o-animation: none !important; -ms-animation: none !important; animation: none !important; } body,input,textarea,select { font-family: 'Source Sans Pro', sans-serif; font-weight: 400; color: #565656; } a { color: $logo-color; } strong, b { font-weight: 700; color: #232323; } h2,h3,h4,h5,h6 { font-family: 'Open Sans Condensed', sans-serif; font-weight: 700; color: #232323; margin-top: 1em; } h2:first-child, h3:first-child, h4:first-child, h5:first-child, h6:first-child { margin-top: 0; } h2 a, h4 a, h5 a, h6 a { text-decoration: none; color: inherit; } blockquote { border-left: solid 5px #ddd; padding: 1em 0 1em 2em; font-style: italic; } em, i { font-style: italic; } hr { border: 0; border-top: solid 1px #ddd; padding: 1.5em 0 0 0; margin: 1.75em 0 0 0; } sub { position: relative; top: 0.5em; font-size: 0.8em; } sup { position: relative; top: -0.5em; font-size: 0.8em; } br.clear { clear: both; } p, ul, ol, dl, table { margin-bottom: 1em; } /* Table */ table { width: 100%; } table.default { } table.default tbody tr:nth-child(2n+2) { background: #f4f4f4; } table.default td { padding: 0.5em 1em 0.5em 1em; } table.default th { text-align: left; font-weight: 700; padding: 0.75em 1em 0.75em 1em; } table.default thead { border-bottom: solid 1px #ddd; } table.default tfoot { border-top: solid 1px #ddd; background: #eee; } table.default tbody { } /* Form */ form { } form label { font-family: 'Open Sans Condensed', sans-serif; font-weight: 700; color: #232323; margin: 0 0 0.75em 0; } form input[type="search"], form input[type="text"], form input[type="email"], form input[type="password"], form select, form textarea { display: block; border-radius: 0.4em; -webkit-appearance: none; border: solid 1px #ddd; padding: 0.5em 0.75em; width: 100%; line-height: 1.25em; } form input[type="text"]:focus, form input[type="email"]:focus, form input[type="password"]:focus, form select:focus, form textarea:focus { box-shadow: 0px 0px 2px 2px #c94663; } form ::-webkit-input-placeholder { color: #aaa; } form :-moz-placeholder { color: #aaa; } form ::-moz-placeholder { color: #aaa; } form :-ms-input-placeholder { color: #aaa; } /* Section/Article */ header { margin: 0 0 2em 0; } header > p { font-family: 'Open Sans Condensed', sans-serif; font-weight: 300; display: block; margin-top: 1em; color: #999; } section, article { margin-bottom: 3em; } section > :last-child, article > :last-child { margin-bottom: 0; } section > .inner > :last-child, article > .inner > :last-child { margin-bottom: 0; } section:last-child, article:last-child { margin-bottom: 0; } /* Image */ .image { display: inline-block; } .image img { display: block; width: 100%; } .image.fit { display: block; width: 100%; } .image.featured { display: block; width: 100%; margin: 0 0 2em 0; } .image.left { float: left; margin: 0 2em 2em 0; } .image.centered { display: block; margin: 0 0 2em 0; } .image.centered img { margin: 0 auto; width: auto; } /* Button */ input[type="button"], input[type="submit"], input[type="reset"], .button { cursor: pointer; background-color: #c94663; background-image: url('images/bg01.png'); border-radius: 0.4em; text-align: center; box-shadow: inset 0px 0px 0px 1px rgba(255,255,255,0.15); color: #fff; font-family: 'Open Sans Condensed', sans-serif; font-weight: 700; text-decoration: none; -moz-transition: background-color .25s ease-in-out, color .25s ease-in-out; -webkit-transition: background-color .25s ease-in-out, color .25s ease-in-out; -o-transition: background-color .25s ease-in-out, color .25s ease-in-out; -ms-transition: background-color .25s ease-in-out, color .25s ease-in-out; transition: background-color .25s ease-in-out, color .25s ease-in-out; text-shadow: -1px -1px 0px rgba(0,0,0,0.5); } input[type="button"]:disabled, input[type="submit"]:disabled, input[type="reset"]:disabled, input[type="button"].disabled, input[type="submit"].disabled, input[type="reset"].disabled, .button.disabled { opacity: 0.35; cursor: default; } input[type="button"]:hover, input[type="submit"]:hover, input[type="reset"]:hover, .button:hover { background-color: #d95673; } input[type="button"]:active, input[type="submit"]:active, input[type="reset"]:active, .button:active { background-color: #b93653; } input[type="button"].alt, input[type="submit"].alt, input[type="reset"].alt, .button.alt { background-color: #364050; } input[type="button"].alt:hover, input[type="submit"].alt:hover, input[type="reset"].alt:hover, .button.alt:hover { background-color: #465060; } input[type="button"].alt:active, input[type="submit"].alt:active, input[type="reset"].alt:active, .button.alt:active { background-color: #263040; } /* List */ #content ul { list-style: disc; padding-left: 1.25em; } #content ul li { padding-left: 0.5em; } #content ol { list-style: decimal; padding-left: 1.25em; } #content ul li { padding-left: 0.25em; } /* Pagination */ .pagination { } .pagination .pages { display: inline-block; font-family: 'Open Sans Condensed', sans-serif; font-weight: 700; } .pagination .pages span { display: inline-block; width: 1.5em; text-align: center; margin: 0 0.4em 0 0; } .pagination .pages a { cursor: pointer; display: inline-block; text-align: center; text-decoration: none; color: inherit; background-color: #e8e8e8; background-image: url('images/bg02.png'); color: #565656; margin: 0 0.4em 0 0; border-radius: 0.4em; -moz-transition: background-color .25s ease-in-out; -webkit-transition: background-color .25s ease-in-out; -o-transition: background-color .25s ease-in-out; -ms-transition: background-color .25s ease-in-out; transition: background-color .25s ease-in-out; box-shadow: inset 0 0.075em 0.25em 0 rgba(0,0,0,0.1); } .pagination .pages a:hover { background-color: #dadada; } .pagination .pages a.active { background-color: #364050; color: #fff; box-shadow: none; text-shadow: -1px -1px 0px rgba(0,0,0,0.5); } /* Box */ .box { } .box.post { position: relative; } .box.post .info { } .box.post .info .stats { margin: 0; cursor: default; } .box.post .info .stats a { font-size: 0.8em; text-decoration: none; color: #232323; font-weight: 700; line-height: 1em; } .box.recent-posts, .box.recent-comments { } .box.recent-posts li, .box.recent-comments li { border-top: solid 1px rgba(0,0,0,0.25); box-shadow: inset 0px 1px 0px 0px rgba(255,255,255,0.075); padding: 0.5em 0 0.5em 0; } .box.recent-posts li:first-child, .box.recent-comments li:first-child { border-top: 0; box-shadow: none; padding-top: 0; } .box.recent-posts li:last-child, .box.recent-comments li:last-child { padding-bottom: 0; } .box.search { } .box.search form { position: relative; } .box.search form input { position: relative; padding-right: 34px; } .box.search form:before { display:inline-block; font-family: FontAwesome; font-size: 18px; text-decoration: none; font-style: normal; font-weight: normal; -webkit-font-smoothing:antialiased; -moz-osx-font-smoothing:grayscale; content: '\f002'; filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1); -webkit-transform:scale(-1, 1); -moz-transform:scale(-1, 1); -ms-transform:scale(-1, 1); -o-transform:scale(-1, 1); transform:scale(-1, 1); position: absolute; right: 0.5em; top: 0.25em; z-index: 1; text-shadow: none; color: #c8ccce; } .box.text-style1 { } .box.text-style1 .inner { position: relative; background-color: #272E39; background-color: rgba(0,0,0,0.15); border-radius: 0.4em; padding: 1.25em; box-shadow: 0 0 0 1px rgba(255,255,255,0.05), inset 0 0 0.25em 0 rgba(0,0,0,0.25); } /*********************************************************************************/ /* Icons */ /*********************************************************************************/ .icon { position: relative; text-decoration: none; } .icon:before { -moz-osx-font-smoothing: grayscale; -webkit-font-smoothing: antialiased; font-family: FontAwesome; font-style: normal; font-weight: normal; text-transform: none !important; } .icon > .label { display: none; } /*********************************************************************************/ /* Wrapper */ /*********************************************************************************/ #wrapper { position: relative; } /*********************************************************************************/ /* Nav */ /*********************************************************************************/ #nav { } #nav > ul > li > ul { /* display: none; */ } #nav ul { margin: 0; } #nav > ul > li { border-top: solid 1px rgba(0,0,0,0.25); box-shadow: inset 0px 1px 0px 0px rgba(255,255,255,0.075); padding: 0.5em 0 0.5em 0; } #nav > ul > li:first-child { border: 0; box-shadow: none; padding-top: 0; } #nav > ul > li:last-child { padding-bottom: 0; } #nav li a { display: block; padding: 0.4em 1em 0.4em 1em; text-decoration: none; border-radius: 0.4em; outline: 0; } #nav li.current a { background-color: #272E39 !important; background-color: rgba(0,0,0,0.15) !important; box-shadow: 0 0 0 1px rgba(255,255,255,0.05), inset 0 0 0.25em 0 rgba(0,0,0,0.25); font-weight: 700; color: #fff; } #nav li:hover a { background-color: rgba(255,255,255,0.1); color: #fff; } /*********************************************************************************/ /* Sidebar */ /*********************************************************************************/ #sidebar { color: #aaa; color: rgba(255,255,255,0.55); text-shadow: -1px -1px 0px rgba(0,0,0,0.5); } #sidebar form input, #sidebar form select, #sidebar form textarea { border: 0; } #sidebar section, #sidebar nav { position: relative; } #sidebar section:before, #sidebar nav:before { content: ''; border-top: solid 1px; border-bottom: solid 1px; border-color: #171E29; border-color: rgba(0,0,0,0.25); box-shadow: inset 0px 1px 0px 0px rgba(255,255,255,0.075), 0px 1px 0px 0px rgba(255,255,255,0.075); display: block; } #sidebar strong, #sidebar b { color: #fff; } #sidebar a { color: #d4dde0; } #sidebar h2, #sidebar h3, #sidebar h4, #sidebar h5, #sidebar h6 { color: #fff; } #sidebar header { margin: 0 0 1.25em 0; } #sidebar section, #sidebar nav { margin: 1em 0 0 0; font-size: 0.9em; } #sidebar section:before, #sidebar nav:before { height: 0.5em; margin: 0 0 1em 0; } /*********************************************************************************/ /* Content */ /*********************************************************************************/ #content { position: relative; background-color: #fff; background-image: url('images/bg02.png'); } #content > .inner { position: relative; z-index: 1; } /*********************************************************************************/ /* Copyright */ /*********************************************************************************/ #copyright { } #copyright p { opacity: 0.75; } #copyright a { color: inherit !important; } #copyright:before { content: ''; border-top: solid 1px; border-bottom: solid 1px; border-color: #171E29; border-color: rgba(0,0,0,0.25); box-shadow: inset 0px 1px 0px 0px rgba(255,255,255,0.075), 0px 1px 0px 0px rgba(255,255,255,0.075); display: block; } /*********************************************************************************/ /* Mobile UI */ /*********************************************************************************/ #titleBar { background: #364050 url('images/bg01.png'); backface-visibility: hidden; z-index: 10004; position: fixed; display: none; width: 100%; height: 44px; top: 0px; left: 0px; } #titleBar .title { display: block; color: #fff; line-height: 44px; font-size: 1.25em; font-family: 'Open Sans Condensed', sans-serif; font-weight: 700; letter-spacing: 0.1em; text-shadow: -1px -1px 0px rgba(0,0,0,0.5); box-shadow: 0px 1px 4px 0px rgba(0,0,0,0.25); text-align: center; } #titleBar .toggle { width: 80px; height: 60px; position: absolute; left: 0; top: 0; } #titleBar .toggle:before { content: '<<>>'; font-weight: bold; display: block; position: absolute; left: 7px; top: 7px; font-size: 14px; width: 50px; height: 30px; line-height: 30px; background: $logo-color url('images/bg01.png'); border-radius: 5px; color: #fff; text-align: center; box-shadow: inset 0px 0px 0px 1px rgba(255,255,255,0.15), 0 0.025em 0.15em 0em rgba(0,0,0,0.25); text-shadow: -1px -1px 0px rgba(0,0,0,0.5); } #titleBar .toggle:active { } #titleBar .toggle:active:before { background-color: lighten($logo-color, 10%); } #sidePanel { background: #364050 url('images/bg01.png'); } /***************************************/ /***************************************/ /* Original file: desktop.css */ /***************************************/ /***************************************/ @media (min-width: 737px) { /*********************************************************************************/ /* Basic */ /*********************************************************************************/ body { height: 100%; } body,input,textarea,select { line-height: 1.75em; } h2 { font-size: 1.5em; } h3 { font-size: 1.2em; } h4, h5, h6 { font-size: 1em; } /* Button */ input[type="button"], input[type="submit"], input[type="reset"], .button { display: inline-block; padding: 0.5em 2em 0.5em 2em; } input[type="button"].small, input[type="submit"].small, input[type="reset"].small, .button.small { font-size: 0.85em; padding: 0.35em 1.5em 0.35em 1.5em; } input[type="button"].big, input[type="submit"].big, input[type="reset"].big, .button.big { font-size: 1.25em; padding: 0.75em 2em 0.75em 2em; } input[type="button"].huge, input[type="submit"].huge, input[type="reset"].huge, .button.huge { font-size: 1.5em; padding: 0.75em 2em 0.75em 2em; } /* Pagination */ .pagination { padding: 3em 0 0 0; } .pagination .pages { } .pagination .previous { margin-right: 0.6em; } .pagination .next { margin-left: 0.2em; } .pagination .pages a { width: 2.75em; height: 2.75em; line-height: 2.75em; } .pagination .button { height: 2.75em; padding-top: 0; padding-bottom: 0; line-height: 2.75em; } /* Box */ .box { } .box.post { } .box.post .info { } .box.post .info .stats { } .box.post .info .stats li a { border-radius: 0.4em; opacity: 0.5; -moz-transition: opacity .25s ease-in-out, background-color .25s ease-in-out; -webkit-transition: opacity .25s ease-in-out, background-color .25s ease-in-out; -o-transition: opacity .25s ease-in-out, background-color .25s ease-in-out; -ms-transition: opacity .25s ease-in-out, background-color .25s ease-in-out; transition: opacity .25s ease-in-out, background-color .25s ease-in-out; } .box.post .info .stats li:hover a { background-color: #f4f4f4; opacity: 1.0; } /*********************************************************************************/ /* Logo */ /*********************************************************************************/ #logo { font-family: 'Open Sans Condensed', sans-serif; font-weight: 700; font-size: 2em; letter-spacing: 0.1em; } #logo { display: block; background-color: $logo-color; background-image: url('images/bg01.png'); padding: 0.50em 0.25em; border-radius: 0.2em; text-align: center; box-shadow: inset 0px 0px 0px 1px rgba(255,255,255,0.15), 0 0.025em 0.15em 0em rgba(0,0,0,0.25); color: #fff; } #logo a { color: #fff; text-decoration: none; } #logo span { font-size: 50%; } /*********************************************************************************/ /* Content */ /*********************************************************************************/ #content { box-shadow: 0 0 0.25em 0em rgba(0,0,0,0.25); } #content > .inner { /* This sets an upper limit on your page content to prevent it from looking insane on really wide displays. It's currently set to the width of the sample post images, but you can change it to whatever you like (or remove it entirely). */ max-width: 1038px; padding-bottom: 2em; } /*********************************************************************************/ /* Sidebar */ /*********************************************************************************/ #sidebar { position: absolute; top: 0; padding: 1em 1.35em 1em 1.15em; width: 275px; /* = whatever you want */ background: #364050 url('images/bg01.png'); box-shadow: inset -0.1em 0em 0.35em 0em rgba(0,0,0,0.15); } body.left-sidebar #sidebar { left: 0; } body.right-sidebar #sidebar { right: 0; } /*********************************************************************************/ /* Copyright */ /*********************************************************************************/ #copyright { margin: 1em 0 0 0; text-align: center; } #copyright p { font-size: 0.8em; line-height: 2em; } #copyright:before { height: 0.5em; margin: 0 0 1em 0; } } /***************************************/ /***************************************/ /* Custom modifications */ /***************************************/ /***************************************/ pre, code { font-family: monospace; } pre { line-height: 1.25em; background: #efefef; color: #000; border: 1px solid #565656; border-top-color: #cbcbcb; border-left-color: #a5a5a5; border-right-color: #a5a5a5; border-radius: 0.4em; padding: 1em; margin: 0px; margin-bottom: 1em; overflow: auto; font-size: 90%; } .CodeRay span.insert, .CodeRay span.change, .CodeRay span.delete { width: auto; } /* http://www.andrewthorp.com/posts/github-theme-for-coderay */ .CodeRay { background-color: #efefef; font-family: Monaco, "Courier New", "DejaVu Sans Mono", "Bitstream Vera Sans Mono", monospace; color: #000; } div.CodeRay { } span.CodeRay { white-space: pre; border: 0px; padding: 2px } table.CodeRay { border-collapse: collapse; width: 100%; padding: 2px } table.CodeRay td { padding: 1em 0.5em; vertical-align: top; } .CodeRay .line-numbers, .CodeRay .no { background-color: #ECECEC; color: #AAA; text-align: right; } .CodeRay .line-numbers a { color: #AAA; } .CodeRay .line-numbers tt { font-weight: bold } .CodeRay .line-numbers .highlighted { color: red } .CodeRay .line { display: block; float: left; width: 100%; } .CodeRay span.line-numbers { padding: 0px 4px } .CodeRay .code { width: 100% } ol.CodeRay { font-size: 10pt } ol.CodeRay li { white-space: pre } .CodeRay .code pre { overflow: auto } .CodeRay .debug { color:white ! important; background:blue ! important; } .CodeRay .annotation { color:#007 } .CodeRay .attribute-name { color:#f08 } .CodeRay .attribute-value { color:#700 } .CodeRay .binary { color:#509; font-weight:bold } .CodeRay .comment { color:#998; font-style: italic;} .CodeRay .char { color:#04D } .CodeRay .char .content { color:#04D } .CodeRay .char .delimiter { color:#039 } .CodeRay .class { color:#458; font-weight:bold } .CodeRay .complex { color:#A08; font-weight:bold } .CodeRay .constant { color:teal; } .CodeRay .color { color:#0A0 } .CodeRay .class-variable { color:#369 } .CodeRay .decorator { color:#B0B; } .CodeRay .definition { color:#099; font-weight:bold } .CodeRay .directive { color:#088; font-weight:bold } .CodeRay .delimiter { color:black } .CodeRay .doc { color:#970 } .CodeRay .doctype { color:#34b } .CodeRay .doc-string { color:#D42; font-weight:bold } .CodeRay .escape { color:#666; font-weight:bold } .CodeRay .entity { color:#800; font-weight:bold } .CodeRay .error { color:#F00; background-color:#FAA } .CodeRay .exception { color:#C00; font-weight:bold } .CodeRay .filename { color:#099; } .CodeRay .function { color:#900; font-weight:bold } .CodeRay .global-variable { color:teal; font-weight:bold } .CodeRay .hex { color:#058; font-weight:bold } .CodeRay .integer { color:#099; } .CodeRay .include { color:#B44; font-weight:bold } .CodeRay .inline { color: black } .CodeRay .inline .inline { background: #ccc } .CodeRay .inline .inline .inline { background: #bbb } .CodeRay .inline .inline-delimiter { color: #D14; } .CodeRay .inline-delimiter { color: #D14; } .CodeRay .important { color:#f00; } .CodeRay .interpreted { color:#B2B; font-weight:bold } .CodeRay .instance-variable { color:teal } .CodeRay .label { color:#970; font-weight:bold } .CodeRay .local-variable { color:#963 } .CodeRay .octal { color:#40E; font-weight:bold } .CodeRay .operator { } .CodeRay .predefined-constant { font-weight:bold } .CodeRay .predefined { color:#369; font-weight:bold } .CodeRay .preprocessor { color:#579; } .CodeRay .pseudo-class { color:#00C; font-weight:bold } .CodeRay .predefined-type { color:#074; font-weight:bold } .CodeRay .reserved, .keyword { color:#000; font-weight:bold } .CodeRay .key { color: #808; } .CodeRay .key .delimiter { color: #606; } .CodeRay .key .char { color: #80f; } .CodeRay .value { color: #088; } .CodeRay .regexp { background-color:#fff0ff } .CodeRay .regexp .content { color:#808 } .CodeRay .regexp .delimiter { color:#404 } .CodeRay .regexp .modifier { color:#C2C } .CodeRay .regexp .function { color:#404; font-weight: bold } .CodeRay .string { color: #D20; } .CodeRay .string .string { } .CodeRay .string .string .string { background-color:#ffd0d0 } .CodeRay .string .content { color: #D14; } .CodeRay .string .char { color: #D14; } .CodeRay .string .delimiter { color: #D14; } .CodeRay .shell { color:#D14 } .CodeRay .shell .content { } .CodeRay .shell .delimiter { color:#D14 } .CodeRay .symbol { color:#990073 } .CodeRay .symbol .content { color:#A60 } .CodeRay .symbol .delimiter { color:#630 } .CodeRay .tag { color:#070 } .CodeRay .tag-special { color:#D70; font-weight:bold } .CodeRay .type { color:#339; font-weight:bold } .CodeRay .variable { color:#036 } .CodeRay .insert { background: #afa; } .CodeRay .delete { background: #faa; } .CodeRay .change { color: #aaf; background: #007; } .CodeRay .head { color: #f8f; background: #505 } .CodeRay .insert .insert { color: #080; font-weight:bold } .CodeRay .delete .delete { color: #800; font-weight:bold } .CodeRay .change .change { color: #66f; } .CodeRay .head .head { color: #f4f; } /***************************************/ /***************************************/ /* Original file: style-narrow.css */ /***************************************/ /***************************************/ @media (min-width: 737px) { /*********************************************************************************/ /* Basic */ /*********************************************************************************/ body { min-width: 1000px; } body,input,textarea,select { font-size: 12pt; } /* Box */ .box { } .box.post { } .box.post h2 { font-size: 2.50em; line-height: 1.25em; } .box.post h3 { font-size: 1.50em; line-height: 1.25em; } .box.post header { padding: 1.5em 0 0 0; margin: 0 0 3em 0; } .box.post header > p { font-size: 1.75em; line-height: 1.5em; position: relative; top: -0.75em; margin-bottom: -0.75em; } .box.post .info { width: 100%; padding: 1em 0 0 0; position: relative; top: -2em; } .box.post .info .date { display: inline; } .box.post .info .stats { display: inline; border-left: solid 1px #ddd; margin-left: 1em; padding-left: 1em; } .box.post .info .stats li { display: inline-block; margin-left: 0.25em; } .box.post .info .stats li a { display: inline-block; padding-right: 0.5em; } .box.post .info .stats li a:before { position: relative; width: 1.75em; text-align: center; margin-right: 0.35em; opacity: 0.35; } /*********************************************************************************/ /* Content */ /*********************************************************************************/ #content { padding: 2em 3em 6em 3em; } body.left-sidebar #content { margin-left: 275px; /* 14em; /* = sidebar width */ } body.right-sidebar #content { margin-right: 275px; /* 14em; /* = sidebar width */ } } /***************************************/ /***************************************/ /* Original file: style-narrower.css */ /***************************************/ /***************************************/ @media (min-width: 737px) and (max-width: 1000px) { /*********************************************************************************/ /* Basic */ /*********************************************************************************/ body { min-width: 641px; } body,input,textarea,select { font-size: 14pt; } /*********************************************************************************/ /* Logo */ /*********************************************************************************/ #logo { display: none; } /*********************************************************************************/ /* Nav */ /*********************************************************************************/ #nav { margin-top: 0 !important; } #nav:before { display: none !important; } #nav li:hover a { background: none; } /*********************************************************************************/ /* Content */ /*********************************************************************************/ #content { margin-top: 44px; } body.left-sidebar #content { margin-left: 0; } body.right-sidebar #content { margin-right: 0; } /*********************************************************************************/ /* Sidebar */ /*********************************************************************************/ #sidebar { width: auto; background: none; padding: 30px 20px 30px 20px; } } /***************************************/ /***************************************/ /* Original file: style-mobile.css */ /***************************************/ /***************************************/ @media (max-width: 736px) { /*********************************************************************************/ /* Basic */ /*********************************************************************************/ body,input,textarea,select { line-height: 1.75em; font-size: 11pt; letter-spacing: 0; } h2, h3, h4, h5, h6 { font-size: 1.5em; } /* Section/Article */ section, article { clear: both; } /* Button */ input[type="button"], input[type="submit"], input[type="reset"], .button { display: block; width: 100%; font-size: 1.25em; padding: 0.75em 0 0.75em 0; margin: 0.5em 0 0.5em 0; } /* Pagination */ .pagination { } .pagination .pages { display: none; } /* Box */ .box { } .box.post { padding-bottom: 5em; margin-bottom: 6em; } .box.post header { } .box.post header > p { font-size: 1em; font-family: 'Source Sans Pro', sans-serif; line-height: 1.5em; } .box.post .info { width: 100%; position: absolute; bottom: 0; left: 0; border-top: solid 1px #ddd; padding-top: 0.5em; } .box.post .info .date { display: inline; } .box.post .info .date .year { display: none; } .box.post .info .date .month span { display: none; } .box.post .info .stats { display: inline; border-left: solid 1px #ddd; margin-left: 0.75em; padding-left: 0.75em; } .box.post .info .stats li { display: inline-block; margin-right: 0.85em; } .box.post .info .stats li a { display: inline-block; opacity: 0.5; } .box.post .info .stats li a:before { position: relative; top: 0.1em; margin-right: 0.5em; opacity: 0.35; } .box.post .info .stats li a:active { opacity: 1.0; } /*********************************************************************************/ /* Logo */ /*********************************************************************************/ #logo { display: none; } /*********************************************************************************/ /* Nav */ /*********************************************************************************/ #nav { margin-top: 0 !important; } #nav:before { display: none !important; } #nav li:hover a { background: none; } /*********************************************************************************/ /* Content */ /*********************************************************************************/ #content { margin-top: 44px; padding: 3em 20px 2em 20px; } /*********************************************************************************/ /* Sidebar */ /*********************************************************************************/ #sidebar { padding: 20px 12px 20px 10px; box-shadow: inset -1px 0 0 0 rgba(255,255,255,0.15), inset -0.1em 0 0.5em 0 rgba(0,0,0,0.25); } /*********************************************************************************/ /* Copyright */ /*********************************************************************************/ #copyright { margin: 1em 0 0 0; text-align: center; } #copyright p { font-size: 0.9em; line-height: 2em; } #copyright:before { height: 0.5em; margin: 0 0 1em 0; } } /***************************************/ /***************************************/ /* Original file: style-hiddensidebar.css */ /***************************************/ /***************************************/ @media (max-width: 1000px) { #titleBar { display: block; } #sidebar { background: #364050 url('images/bg01.png'); width: 275px; position: absolute; top: 0px; left: 0px; z-index: 0; display: none; } #titleBar { z-index: 2; } #content { z-index: 1; } #sidebar-hide { display: none; } #titleBar:target ~ #sidebar { display: initial; } #titleBar:target ~ #content { left: 275px; } #titleBar:target > #sidebar-hide { display: initial; } } cmdparse-3.0.7/doc/api.api0000644000004100000410000000023613775147450015376 0ustar www-datawww-data--- rdoc_options: lib/ --main 'CmdParse::CommandParser' --title 'cmdparse' prefix_link_defs: false output_structure: hierarchical api_template: /api.template cmdparse-3.0.7/doc/api.template0000644000004100000410000000166713775147450016451 0ustar www-datawww-data--- name:content pipeline:erb <% rdoc_object = context.node.node_info[:rdoc_object] api = context.node.node_info[:api] %>

<%= context.render_block(:name => 'content', :chain => [context.website.tree['/templates/api.template'], context.content_node]) %> cmdparse-3.0.7/doc/news.page0000644000004100000410000001053613775147450015750 0ustar www-datawww-data--- title: News in_menu: true sort_info: 8 --- ## News ### 2020-12-08 cmdparse 3.0.7 released! Changes: * Behave well when interrupted via Control+C and when used in a pipe that gets closed (thanks to postmodern) ### 2020-05-02 cmdparse 3.0.6 released! Changes: * Fix OptParse integration with regards to error handling (thanks to Damien Robert) ### 2020-01-31 cmdparse 3.0.5 released! Changes: * Fix warning on Ruby 2.7 (thanks to Damien Robert) ### 2018-12-22 cmdparse 3.0.4 released! Changes: * Fix bug in help output generation when a word is longer than the available space - thanks to Damien Robert for the bug report and pull request ### 2017-01-13 cmdparse 3.0.3 released! Changes: * Fix bug introduced during refactoring that affected help output (continuation lines were missing) - thanks to Andrew Talbot for reporting the bug ### 2016-12-28 cmdparse 3.0.2 released! Changes: * Raise an error if too many arguments are provided for a command * Better error message if not enough arguments are provided for a command ### 2015-03-14 cmdparse 3.0.1 released! Changes: * Commands in usage line are now shown sorted * Long lines with embedded new lines are now correctly formatted * The built-in version command can now be told to not add the `-v` and `--version` switches ### 2015-03-12 cmdparse 3.0.0 released! This version is not compatible with previous versions of cmdparse and requires at least Ruby 2.0. However, updating code to use the new API is straightforward and the new version offers more features! Changes: * License changed to MIT! * Top level options and global options are now separated * Easier on the fly definition of commands * Better help output (automatic formatting of long lines -- even for option descriptions --, automatic argument name detection, ...) * Keyword arguments are now used for better code readability * The `CmdParse::VERSION` constant is now a simple version string instead of an array * Better API documentation and expanded tutorial * Better website design and integration of the API documentation into the website ### 2014-04-05 cmdparse 2.0.6 released! There were no codewise changes but the used infrastructure and tools have been updated to newer versions. Also the license information has been added to the gem specification and the tests are now included in the distribution. ### 2012-06-09 cmdparse 2.0.5 released! Changes: * Fixed backwards incompatible change ### 2012-06-07 cmdparse 2.0.4 released! Changes: * Only some minor changes regarding the help output for commands which is nicer now. ### 2006-06-17 cmdparse 2.0.2 released! Changes: * Included two patches from Assaph Mehr: * partial command matching can now be used (see the [tutorial page](tutorial.html)) * now a banner for the help and version commands can be specified ### 2006-04-05 cmdparse 2.0.1 released! Changes: * Just a bug fix release for those using cmdparse with Rubygems. By issuing the command `require_gem 'cmdparse'` the cmdparse library gets automagically loaded. * Minor documentation updates ### 2005-08-16 cmdparse 2.0.0 released! This version is not compatible to previous versions of cmdparse as there have been major changes in the API. However, updating your code to use the new API is very easy! Changes: * Commands can now have subcommands which can have subcommands which can have subcommands... * No need to implement a whole new class for simple commands anymore * Default option parser library is `optparse`, however, any option parser library can be used after writing a small wrapper ### 2005-07-05 cmdparse 1.0.5 released! Changes: * added possibility to parse global options, command and local options separately ### 2005-06-16 cmdparse 1.0.4 released! Changes: * fix for older ruby versions * fixed bug where exception was not caught ### 2005-06-09 cmdparse 1.0.3 released! Changes: * added optional graceful exception handling ### 2005-04-21 cmdparse 1.0.2 released! Changes: * splitted parsing of the arguments and executing the command into two methods ### 2005-04-13 cmdparse 1.0.1 released! Changes: * Improved HelpCommand: the global options -h and --help take an optional command name now * Possibility to define a default command which is used when no command was specified * A `NoCommandGivenError` is thrown when no command on the CLI and no default command was specified cmdparse-3.0.7/doc/index.page0000644000004100000410000000615313775147450016103 0ustar www-datawww-data--- title: Home in_menu: true routed_title: cmdparse sort_info: 1 --- ## cmdparse ... is an advanced Ruby command line parser supporting nested commands. It allows the creation of "command style" programs, like `git` or `svn`, that perform different functions depending on which command is invoked. Additionally, nesting of commands, i.e. commands that take commands themselves, is also possible. For option parsing, the battle-tested Ruby standard library `optparse` is used. A typical command line for a program which uses commands looks like this: $ net --verbose ipaddr add 192.168.0.1 193.150.0.1 ## Features * Commands can have sub-commands * Built-in commands for showing help and the version of the program * Automatic discovery of names and number of required/optional command arguments * Truly global options that can be used on any command * No need to learn a special DSL * Use POROs (plain old Ruby objects) for creating commands or create them on the fly * All `OptionParser` goodies available for option parsing * Easy to use ## Quickstart Here are two short code samples which show how to use cmdparse. A complete example application can be found in the [tutorial](tutorial.html), also see the API documentation of [CmdParse::CommandParser] and [CmdParse::Command]. A sample CLI program where commands are defined using classes: ~~~ ruby require 'cmdparse' class TestCmd < CmdParse::Command def initialize super('test') short_desc('Short description of command') add_command(TestSubCmd.new) end end class TestSubCmd < CmdParse::Command def initialize super('sub', takes_commands: false) options.on('-x', '--example', 'Example option') { puts 'example' } end def execute(name, *opt) puts "Hello #{name}, options: #{opt.join(', ')}" end end parser = CmdParse::CommandParser.new(handle_exceptions: :no_help) parser.add_command(CmdParse::HelpCommand.new) parser.add_command(TestCmd.new) parser.parse ~~~ The same program but with the commands defined on the fly: ~~~ ruby <%= context.render_block('sample') %> ~~~ Sample output for different invocations: <% invocations = ['', 'help', 'test sub', 'test sub -h', 'test sub Thomas', 'test sub -x Thomas opt1 opt2'] tmpfile = context.website.tmpdir('sample.rb', true) File.write(tmpfile, context.render_block('sample')) result = invocations.map do |args| ["$ sample.rb #{args}", h(`ruby -I#{context.website.directory}/lib #{tmpfile} #{args}`)] end.flatten.join("\n") %>
<%= result %>
## Author * **Thomas Leitner** * Web: * e-Mail: --- name:sample pipeline: require 'cmdparse' parser = CmdParse::CommandParser.new(handle_exceptions: :no_help) parser.add_command(CmdParse::HelpCommand.new) parser.add_command('test') do |test_cmd| test_cmd.short_desc('Short description of command') test_cmd.add_command('sub') do |sub_cmd| sub_cmd.takes_commands(false) sub_cmd.options.on('-x', '--example', 'Example option') { puts 'example' } sub_cmd.action do |name, *opt| puts "Hello #{name}, options: #{opt.join(', ')}" end end end parser.parse cmdparse-3.0.7/doc/sitemap.sitemap0000644000004100000410000000010113775147450017147 0ustar www-datawww-data--- entries: alcn: /**/*.html default_change_freq: monthly --- cmdparse-3.0.7/doc/images/0000755000004100000410000000000013775147450015376 5ustar www-datawww-datacmdparse-3.0.7/doc/images/bg01.png0000644000004100000410000001423113775147450016636 0ustar www-datawww-dataPNG  IHDR1|PLTE(oj tRNS )" IDAT;{۸(`iKV(⤣(Ktt N:xy13sy9/5 y_Lz sޣ0']75)EB._ D7a cTb֟r> WPqokVýƜ etM[N[11P˴zTG EVN YO2,F#B$8%4uK̒#3<174%9ȶE8Tգ͟PyIMӛk +蕁QÞ/R~̩M1 ~6%{VWhLJC_-|דb"@PZ(ٱ/h4n4haF4]Zt?UR͟lAʐ[ҊKIQvΉ(gp4"׬*W(0* BJ@$1 ;Psl" 9~2 1E8z]iP }ӽԖ|S7-hIsݘ 靹CvzxPPKO%E<>0hsOI9X1mF_ZNa'ZsrXG$1bؚOjQ&'Nrz/g]~=ϤaVS'L} F=W( r2\Vc.K炥׻]Ŷd0 WzggCT47k2ް&,S06KJ,wr\RwK(zГQy/#CNXJ%a mJQuA#{|>P+p%\ ďfՑ_oy2E})ԓBEn>^T[ ,~Vv8#!)I$m˱M-y})0iO'ޘw3 ot̿YK9(P.r^qtMCsrm!yFM·<KE1痤6r(=V>gϭ?ye ѺX-eﳴO(9}vͥBlY,rau\іє<ODMrRg7!n4k!(Ʌͭ4GcC%6!VytzJQpE m &Pv}۰52ZZ\dESŚ(lrqa_r^Nn˟s1alg~@#qmj].PBݎ&IfSg\y)P)H\6+1'1zo2:3ڙGǬ4o`oXnoLqEB'7=M\dToB ⳚX2ŒZӟ Ss?mMǨf^3 Q9>=^Qdd#S =sۑ 35BĈKn'[6ŗ/t gm ǵ|2e=y{O2 %7OM|[Y-~ g$a)J j#>)ft|Ӆ#p`ϥDM{,tRk姐hGqQSK!V}.2>Ww n}4`dph)m<A~¼8vOG*|ʦO9#O=[2.&UثE3'$2f֫FY;ܨlk,)o -L"%!aD0MH#h!!Zkag52Ϭ}:ưS| !r5'D!0^w^^҄Kq)<m:v[VXvce6@]!kTR L5B'i( :\| 'r;6|(6?LU-gvt&=G e R6V6v.u5%R_P.NɎ5rlD{`ZQB<оC6l-5!G۬ TPgy1;ݨ(c ҌRh\⻝tI@I0#jQ⽎-?YvzA\nFgOGgZťiܻ(-S6{fn2muK4'_UrgC0B^~gIt+mWb([`FvbI#mrBAGN&Rm.mܬז/|{&z%!Ndҋ)=diX\*ė3.J4Ȓ4uZ(b˹kh|qFZ219X&~ &ulH%(ڌ>4@gXZgn8ѦE?KhA"(pdG{ J RKCk6,6|xt ,9p9ioz&+ҘU7>|hS ]v3z78>/~O( qb*In-^^ sYGfV v=UU/`> Go#˅zht֞;}$['qtr8J#shQ [{"!߲iΕ7L66L9Gy b/KId܋O|7AE6ׇhXۍH.]q{G*FK%#7cn{XO<*BAwr5[oJvG:JRIzDq1oA;5rJT>oB9mJKǚԇUr7WN շ= P/ĨL`B~6`9a ҝ܊KJ!7q91=~BlBNThbzrmۑF.y õ`-7h=J؈%Pꎮ/ 憡k2TUl/hߗO5|1$Yz96Qib2ye9(kO1m gOLLt:ɯcVGUD :Hl̄1kCT,HXY93H$f >[Y$Ė*ݓQh"e=evmT<`,]bWm$ 90,FKG \6dCІ"/h(HbZu}q9Gfhޛ:?aYz\&_%M?\> 0D9=.R'$Ѿonh bmK_f}q,(`6M&4/%U$4%[iʋD!]( ApY{18|  zYܳ}ˆB93twGpчU~Qm‚[]0qIq`73P>)B3khYXZ ^,B쐣_hp*7ڷ@fC6g]wt8iz鮴h S/c{<G0 kjd# I)--(ʐE0E G㓠'?f#ȈqiB5h_N,(OA2%nP+>i19 ~N/_x#T'} mYܗ V2bg$\Zsf5# Vrcʘx,F2w {mU$xmcdQ X-6{cJ5|c{&euP G .\GD5߀awM'2 ,*wyw oӳ d!B` QIENDB`cmdparse-3.0.7/doc/images/bg02.png0000644000004100000410000000671413775147450016646 0ustar www-datawww-dataPNG  IHDRPX PLTE57tRNS kIDATkWb9y;;n4稫5f^V7OhM?$-:\# oJXMl1;.Z(~y^M<9b5q1ϻ79,W(}jM8J 19# ٠2q]f KJ{LS,aOohHRF~faol׾o Dz1 _l~r5Pɑd` bDz/֡+YGHCyoWZꑄrLsȦEB3(tv'T)]:rB7[4D.FcNꌨ^htjj+^ WasCءF6)P>-hsoFVF:/.Vo^1GgY RBi+ ?&0Jx#$@CbM{KDuWi%xsMᦉ.R$-Gi#b,Q}Z2@72!^h/q%'29u+!umNty@"̍AXe-P LRg`kϸ B *CA%r?zj xuiT!CHfѷ®r^mJbTYe @/;1ձ2.>1$-8 3*薺Z~ŧ 6܏( E<x uc1*h~!I\ WdBi]{GQZEz'ILu=a #(Z*Mkg )xއCJkI蕅֮}fpG] л#v'pv\PjGAk`H\I+ExDri & Ñx 4d&{-1\2c̯= [ktw`QRC;mMHVJ#_NԒRLbmK%p_Nz9a qkx"d̫l˰#FltVs/ԀbZ#P)*FHS򑤜3k &L.z ($zD5I`91[Hyθe3+}mG c }zuKӑR*Fn=Ε=^Ỹ_' "5F|x!%}>lw}3wy=`4VQvګ;0)0 9#'P)%0\Tr9r¾R\zgYDLv?dw92ضKX"0Z_k뺿*;o#5`@N/J=CmnZK`ZGĢ+Q ՑABK a/^Aqbbn+;ۗsl:6 &E Ovp k^SrL''";cq,fZ'U@جT`$@XS;Yte&bweS+k+8e QQlW;Iką"ZS7xBX4p I+ßI|v@>0 {'ިG3%+jNjϥPc 3>QZ_M ٰY Sp"\gcn$ W>3-pUڙGzZ)cwbÂ3Јz[u0ZF8,m:!s#'HZ5O z0\egHITޟԄږ83C`Rxv%N&QKq*aj:X@t5J p!2rBrZc` =9P}^;Mac8jUO$A@#wSxeq(">q5չ(I3pȰy(CN S`㡴M,Ș $G0c}7,+jK+Ƥ҃ >R?Q s %rN6G|* ( 8UF k#k!IUdCU%(f+&k#Dg0S չ@r]iW %&$G0*Am i 1KhCdwJ\u>܉r vz-xy@e‡^ .F }*qz9B,茠T_oq!Egc7 t݆[zVMudCzn!CZW7"\ PޘDCBi٩bb#$yhF࿜דHH4+HͿj'UB>Yָ90*jiðMHS6 \jhRP*՝Η\FG ps+6DB6F'8ɘ:¡WA)Xt 3X~Y`$&BإLB0cʼniiF3U@ 7d֗m.H9 Ӌy'l#l2jfH"phu$z1f6TC1l^fOUmF7 W)Du)dP_--:\GxpiPik}aě1]..qKiXY\1*UPxnH?l[~W `|ך>Gל !WČB=u+ R봶$BU[dQ.x8^p\X{(s(VB GIENDB`cmdparse-3.0.7/doc/installation.page0000644000004100000410000000127613775147450017476 0ustar www-datawww-data--- title: Installation in_menu: true sort_info: 4 --- ## Download & Installation The newest version of cmdparse can be downloaded from [Rubygems] or the [cmdparse Github page][github]! The preferred way of installing cmdparse is via RubyGems: $ gem install cmdparse If you do not have RubyGems installed, but Rake, you can use the following command inside the cmdparse directory: $ rake install If you have neither RubyGems nor Rake, use these commands: $ ruby setup.rb config $ ruby setup.rb setup $ ruby setup.rb install [Rubygems]: http://rubygems.org/gems/cmdparse [github]: http://github.com/gettalong/cmdparse ### Dependencies This library has *no* dependencies. cmdparse-3.0.7/doc/virtual0000644000004100000410000000007113775147450015540 0ustar www-datawww-dataapi/index.html: dest_path: CmdParse/CommandParser.html cmdparse-3.0.7/doc/metainfo0000644000004100000410000000050313775147450015654 0ustar www-datawww-data--- paths [/**/*.js, /**/*.css]: modified_at_in_dest_path: true /**/*.scss: modified_at_in_dest_path: true --- alcn /api/: title: API in_menu: true sort_info: 10 /api/**/*.html: link: css: /stylesheets/api.css in_menu: true /api/**/index*.html: in_menu: false /**/*.css: pipeline: scss,cssminify cmdparse-3.0.7/doc/default.template0000644000004100000410000000551513775147450017320 0ustar www-datawww-data cmdparse: {title:}
cmdparse
cmdparse-3.0.7/example/0000755000004100000410000000000013775147450015017 5ustar www-datawww-datacmdparse-3.0.7/example/net.rb0000644000004100000410000000513213775147450016133 0ustar www-datawww-data#!/usr/bin/env ruby # if something is changed here -> change line numbers in doc/tutorial.page require 'cmdparse' class NetStatCommand < CmdParse::Command def initialize super('stat', takes_commands: false) short_desc("Show network statistics") long_desc("This command shows very useful 'network' statistics - eye catching!!!") argument_desc(M: 'start row number', N: 'end row number') end def execute(m = 1, n) puts "Showing network statistics" if command_parser.data[:verbose] puts m.to_i.upto(n.to_i) do |row| puts " "*(20 - row).abs + "#"*(row*2 - 1).abs end puts end end parser = CmdParse::CommandParser.new(handle_exceptions: :no_help) parser.main_options.program_name = "net" parser.main_options.version = "0.1.1" parser.main_options.banner = "This is net, a s[ai]mple network analytics program" parser.global_options do |opt| opt.on("-v", "--verbose", "Be verbose when outputting info") do parser.data[:verbose] = true end end parser.add_command(CmdParse::HelpCommand.new, default: true) parser.add_command(CmdParse::VersionCommand.new) parser.add_command(NetStatCommand.new) # ipaddr ipaddr = CmdParse::Command.new('ipaddr') ipaddr.short_desc = "Manage IP addresses" parser.add_command(ipaddr, default: true) # ipaddr add ipaddr.add_command('add') do |cmd| cmd.takes_commands(false) cmd.short_desc("Add an IP address") cmd.action do |*ips| puts "Adding ip addresses: #{ips.join(', ')}" if parser.data[:verbose] parser.data[:ipaddrs] += ips end end # ipaddr del del = CmdParse::Command.new('del', takes_commands: false) del.short_desc = "Delete an IP address" del.options.on('-a', '--all', 'Delete all IPs') { del.data[:delete_all] = true } del.action do |*ips| if del.data[:delete_all] puts "All IP adresses deleted!" if parser.data[:verbose] parser.data[:ipaddrs] = [] else puts "Deleting ip addresses: #{ips.join(', ')}" if parser.data[:verbose] ips.each {|ip| parser.data[:ipaddrs].delete(ip) } end end ipaddr.add_command(del) # ipaddr list list = CmdParse::Command.new('list', takes_commands: false) list.short_desc = "Lists all IP addresses" list.action do puts "Listing ip addresses:" if parser.data[:verbose] puts parser.data[:ipaddrs].join("\n") unless parser.data[:ipaddrs].empty? end ipaddr.add_command(list, default: true) parser.data[:ipaddrs] = if File.exists?('dumpnet') Marshal.load(File.read('dumpnet', mode: 'rb')) else [] end parser.parse File.write('dumpnet', Marshal.dump(parser.data[:ipaddrs]), mode: 'wb+') cmdparse-3.0.7/setup.rb0000644000004100000410000010650413775147450015057 0ustar www-datawww-data# # setup.rb # # Copyright (c) 2000-2005 Minero Aoki # # This program is free software. # You can distribute/modify this program under the terms of # the GNU LGPL, Lesser General Public License version 2.1. # unless Enumerable.method_defined?(:map) # Ruby 1.4.6 module Enumerable alias map collect end end unless File.respond_to?(:read) # Ruby 1.6 def File.read(fname) open(fname) {|f| return f.read } end end unless Errno.const_defined?(:ENOTEMPTY) # Windows? module Errno class ENOTEMPTY # We do not raise this exception, implementation is not needed. end end end def File.binread(fname) open(fname, 'rb') {|f| return f.read } end # for corrupted Windows' stat(2) def File.dir?(path) File.directory?((path[-1,1] == '/') ? path : path + '/') end class ConfigTable include Enumerable def initialize(rbconfig) @rbconfig = rbconfig @items = [] @table = {} # options @install_prefix = nil @config_opt = nil @verbose = true @no_harm = false end attr_accessor :install_prefix attr_accessor :config_opt attr_writer :verbose def verbose? @verbose end attr_writer :no_harm def no_harm? @no_harm end def [](key) lookup(key).resolve(self) end def []=(key, val) lookup(key).set val end def names @items.map {|i| i.name } end def each(&block) @items.each(&block) end def key?(name) @table.key?(name) end def lookup(name) @table[name] or setup_rb_error "no such config item: #{name}" end def add(item) @items.push item @table[item.name] = item end def remove(name) item = lookup(name) @items.delete_if {|i| i.name == name } @table.delete_if {|name, i| i.name == name } item end def load_script(path, inst = nil) if File.file?(path) MetaConfigEnvironment.new(self, inst).instance_eval File.read(path), path end end def savefile '.config' end def load_savefile begin File.foreach(savefile()) do |line| k, v = *line.split(/=/, 2) self[k] = v.strip end rescue Errno::ENOENT setup_rb_error $!.message + "\n#{File.basename($0)} config first" end end def save @items.each {|i| i.value } File.open(savefile(), 'w') {|f| @items.each do |i| f.printf "%s=%s\n", i.name, i.value if i.value? and i.value end } end def load_standard_entries standard_entries(@rbconfig).each do |ent| add ent end end def standard_entries(rbconfig) c = rbconfig rubypath = File.join(c['bindir'], c['ruby_install_name'] + c['EXEEXT']) major = c['MAJOR'].to_i minor = c['MINOR'].to_i teeny = c['TEENY'].to_i version = "#{major}.#{minor}" # ruby ver. >= 1.4.4? newpath_p = ((major >= 2) or ((major == 1) and ((minor >= 5) or ((minor == 4) and (teeny >= 4))))) if c['rubylibdir'] # V > 1.6.3 libruby = "#{c['prefix']}/lib/ruby" librubyver = c['rubylibdir'] librubyverarch = c['archdir'] siteruby = c['sitedir'] siterubyver = c['sitelibdir'] siterubyverarch = c['sitearchdir'] elsif newpath_p # 1.4.4 <= V <= 1.6.3 libruby = "#{c['prefix']}/lib/ruby" librubyver = "#{c['prefix']}/lib/ruby/#{version}" librubyverarch = "#{c['prefix']}/lib/ruby/#{version}/#{c['arch']}" siteruby = c['sitedir'] siterubyver = "$siteruby/#{version}" siterubyverarch = "$siterubyver/#{c['arch']}" else # V < 1.4.4 libruby = "#{c['prefix']}/lib/ruby" librubyver = "#{c['prefix']}/lib/ruby/#{version}" librubyverarch = "#{c['prefix']}/lib/ruby/#{version}/#{c['arch']}" siteruby = "#{c['prefix']}/lib/ruby/#{version}/site_ruby" siterubyver = siteruby siterubyverarch = "$siterubyver/#{c['arch']}" end parameterize = lambda {|path| path.sub(/\A#{Regexp.quote(c['prefix'])}/, '$prefix') } if arg = c['configure_args'].split.detect {|arg| /--with-make-prog=/ =~ arg } makeprog = arg.sub(/'/, '').split(/=/, 2)[1] else makeprog = 'make' end [ ExecItem.new('installdirs', 'std/site/home', 'std: install under libruby; site: install under site_ruby; home: install under $HOME')\ {|val, table| case val when 'std' table['rbdir'] = '$librubyver' table['sodir'] = '$librubyverarch' when 'site' table['rbdir'] = '$siterubyver' table['sodir'] = '$siterubyverarch' when 'home' setup_rb_error '$HOME was not set' unless ENV['HOME'] table['prefix'] = ENV['HOME'] table['rbdir'] = '$libdir/ruby' table['sodir'] = '$libdir/ruby' end }, PathItem.new('prefix', 'path', c['prefix'], 'path prefix of target environment'), PathItem.new('bindir', 'path', parameterize.call(c['bindir']), 'the directory for commands'), PathItem.new('libdir', 'path', parameterize.call(c['libdir']), 'the directory for libraries'), PathItem.new('datadir', 'path', parameterize.call(c['datadir']), 'the directory for shared data'), PathItem.new('mandir', 'path', parameterize.call(c['mandir']), 'the directory for man pages'), PathItem.new('sysconfdir', 'path', parameterize.call(c['sysconfdir']), 'the directory for system configuration files'), PathItem.new('localstatedir', 'path', parameterize.call(c['localstatedir']), 'the directory for local state data'), PathItem.new('libruby', 'path', libruby, 'the directory for ruby libraries'), PathItem.new('librubyver', 'path', librubyver, 'the directory for standard ruby libraries'), PathItem.new('librubyverarch', 'path', librubyverarch, 'the directory for standard ruby extensions'), PathItem.new('siteruby', 'path', siteruby, 'the directory for version-independent aux ruby libraries'), PathItem.new('siterubyver', 'path', siterubyver, 'the directory for aux ruby libraries'), PathItem.new('siterubyverarch', 'path', siterubyverarch, 'the directory for aux ruby binaries'), PathItem.new('rbdir', 'path', '$siterubyver', 'the directory for ruby scripts'), PathItem.new('sodir', 'path', '$siterubyverarch', 'the directory for ruby extentions'), PathItem.new('rubypath', 'path', rubypath, 'the path to set to #! line'), ProgramItem.new('rubyprog', 'name', rubypath, 'the ruby program using for installation'), ProgramItem.new('makeprog', 'name', makeprog, 'the make program to compile ruby extentions'), SelectItem.new('shebang', 'all/ruby/never', 'ruby', 'shebang line (#!) editing mode'), BoolItem.new('without-ext', 'yes/no', 'no', 'does not compile/install ruby extentions') ] end private :standard_entries def load_multipackage_entries multipackage_entries().each do |ent| add ent end end def multipackage_entries [ PackageSelectionItem.new('with', 'name,name...', '', 'ALL', 'package names that you want to install'), PackageSelectionItem.new('without', 'name,name...', '', 'NONE', 'package names that you do not want to install') ] end private :multipackage_entries ALIASES = { 'std-ruby' => 'librubyver', 'stdruby' => 'librubyver', 'rubylibdir' => 'librubyver', 'archdir' => 'librubyverarch', 'site-ruby-common' => 'siteruby', # For backward compatibility 'site-ruby' => 'siterubyver', # For backward compatibility 'bin-dir' => 'bindir', 'bin-dir' => 'bindir', 'rb-dir' => 'rbdir', 'so-dir' => 'sodir', 'data-dir' => 'datadir', 'ruby-path' => 'rubypath', 'ruby-prog' => 'rubyprog', 'ruby' => 'rubyprog', 'make-prog' => 'makeprog', 'make' => 'makeprog' } def fixup ALIASES.each do |ali, name| @table[ali] = @table[name] end @items.freeze @table.freeze @options_re = /\A--(#{@table.keys.join('|')})(?:=(.*))?\z/ end def parse_opt(opt) m = @options_re.match(opt) or setup_rb_error "config: unknown option #{opt}" m.to_a[1,2] end def dllext @rbconfig['DLEXT'] end def value_config?(name) lookup(name).value? end class Item def initialize(name, template, default, desc) @name = name.freeze @template = template @value = default @default = default @description = desc end attr_reader :name attr_reader :description attr_accessor :default alias help_default default def help_opt "--#{@name}=#{@template}" end def value? true end def value @value end def resolve(table) @value.gsub(%r<\$([^/]+)>) { table[$1] } end def set(val) @value = check(val) end private def check(val) setup_rb_error "config: --#{name} requires argument" unless val val end end class BoolItem < Item def config_type 'bool' end def help_opt "--#{@name}" end private def check(val) return 'yes' unless val case val when /\Ay(es)?\z/i, /\At(rue)?\z/i then 'yes' when /\An(o)?\z/i, /\Af(alse)\z/i then 'no' else setup_rb_error "config: --#{@name} accepts only yes/no for argument" end end end class PathItem < Item def config_type 'path' end private def check(path) setup_rb_error "config: --#{@name} requires argument" unless path path[0,1] == '$' ? path : File.expand_path(path) end end class ProgramItem < Item def config_type 'program' end end class SelectItem < Item def initialize(name, selection, default, desc) super @ok = selection.split('/') end def config_type 'select' end private def check(val) unless @ok.include?(val.strip) setup_rb_error "config: use --#{@name}=#{@template} (#{val})" end val.strip end end class ExecItem < Item def initialize(name, selection, desc, &block) super name, selection, nil, desc @ok = selection.split('/') @action = block end def config_type 'exec' end def value? false end def resolve(table) setup_rb_error "$#{name()} wrongly used as option value" end undef set def evaluate(val, table) v = val.strip.downcase unless @ok.include?(v) setup_rb_error "invalid option --#{@name}=#{val} (use #{@template})" end @action.call v, table end end class PackageSelectionItem < Item def initialize(name, template, default, help_default, desc) super name, template, default, desc @help_default = help_default end attr_reader :help_default def config_type 'package' end private def check(val) unless File.dir?("packages/#{val}") setup_rb_error "config: no such package: #{val}" end val end end class MetaConfigEnvironment def initialize(config, installer) @config = config @installer = installer end def config_names @config.names end def config?(name) @config.key?(name) end def bool_config?(name) @config.lookup(name).config_type == 'bool' end def path_config?(name) @config.lookup(name).config_type == 'path' end def value_config?(name) @config.lookup(name).config_type != 'exec' end def add_config(item) @config.add item end def add_bool_config(name, default, desc) @config.add BoolItem.new(name, 'yes/no', default ? 'yes' : 'no', desc) end def add_path_config(name, default, desc) @config.add PathItem.new(name, 'path', default, desc) end def set_config_default(name, default) @config.lookup(name).default = default end def remove_config(name) @config.remove(name) end # For only multipackage def packages raise '[setup.rb fatal] multi-package metaconfig API packages() called for single-package; contact application package vendor' unless @installer @installer.packages end # For only multipackage def declare_packages(list) raise '[setup.rb fatal] multi-package metaconfig API declare_packages() called for single-package; contact application package vendor' unless @installer @installer.packages = list end end end # class ConfigTable # This module requires: #verbose?, #no_harm? module FileOperations def mkdir_p(dirname, prefix = nil) dirname = prefix + File.expand_path(dirname) if prefix $stderr.puts "mkdir -p #{dirname}" if verbose? return if no_harm? # Does not check '/', it's too abnormal. dirs = File.expand_path(dirname).split(%r<(?=/)>) if /\A[a-z]:\z/i =~ dirs[0] disk = dirs.shift dirs[0] = disk + dirs[0] end dirs.each_index do |idx| path = dirs[0..idx].join('') Dir.mkdir path unless File.dir?(path) end end def rm_f(path) $stderr.puts "rm -f #{path}" if verbose? return if no_harm? force_remove_file path end def rm_rf(path) $stderr.puts "rm -rf #{path}" if verbose? return if no_harm? remove_tree path end def remove_tree(path) if File.symlink?(path) remove_file path elsif File.dir?(path) remove_tree0 path else force_remove_file path end end def remove_tree0(path) Dir.foreach(path) do |ent| next if ent == '.' next if ent == '..' entpath = "#{path}/#{ent}" if File.symlink?(entpath) remove_file entpath elsif File.dir?(entpath) remove_tree0 entpath else force_remove_file entpath end end begin Dir.rmdir path rescue Errno::ENOTEMPTY # directory may not be empty end end def move_file(src, dest) force_remove_file dest begin File.rename src, dest rescue File.open(dest, 'wb') {|f| f.write File.binread(src) } File.chmod File.stat(src).mode, dest File.unlink src end end def force_remove_file(path) begin remove_file path rescue end end def remove_file(path) File.chmod 0777, path File.unlink path end def install(from, dest, mode, prefix = nil) $stderr.puts "install #{from} #{dest}" if verbose? return if no_harm? realdest = prefix ? prefix + File.expand_path(dest) : dest realdest = File.join(realdest, File.basename(from)) if File.dir?(realdest) str = File.binread(from) if diff?(str, realdest) verbose_off { rm_f realdest if File.exist?(realdest) } File.open(realdest, 'wb') {|f| f.write str } File.chmod mode, realdest File.open("#{objdir_root()}/InstalledFiles", 'a') {|f| if prefix f.puts realdest.sub(prefix, '') else f.puts realdest end } end end def diff?(new_content, path) return true unless File.exist?(path) new_content != File.binread(path) end def command(*args) $stderr.puts args.join(' ') if verbose? system(*args) or raise RuntimeError, "system(#{args.map{|a| a.inspect }.join(' ')}) failed" end def ruby(*args) command config('rubyprog'), *args end def make(task = nil) command(*[config('makeprog'), task].compact) end def extdir?(dir) File.exist?("#{dir}/MANIFEST") or File.exist?("#{dir}/extconf.rb") end def files_of(dir) Dir.open(dir) {|d| return d.select {|ent| File.file?("#{dir}/#{ent}") } } end DIR_REJECT = %w( . .. CVS SCCS RCS CVS.adm .svn ) def directories_of(dir) Dir.open(dir) {|d| return d.select {|ent| File.dir?("#{dir}/#{ent}") } - DIR_REJECT } end end # This module requires: #srcdir_root, #objdir_root, #relpath module HookScriptAPI def get_config(key) @config[key] end alias config get_config # obsolete: use metaconfig to change configuration def set_config(key, val) @config[key] = val end # # srcdir/objdir (works only in the package directory) # def curr_srcdir "#{srcdir_root()}/#{relpath()}" end def curr_objdir "#{objdir_root()}/#{relpath()}" end def srcfile(path) "#{curr_srcdir()}/#{path}" end def srcexist?(path) File.exist?(srcfile(path)) end def srcdirectory?(path) File.dir?(srcfile(path)) end def srcfile?(path) File.file?(srcfile(path)) end def srcentries(path = '.') Dir.open("#{curr_srcdir()}/#{path}") {|d| return d.to_a - %w(. ..) } end def srcfiles(path = '.') srcentries(path).select {|fname| File.file?(File.join(curr_srcdir(), path, fname)) } end def srcdirectories(path = '.') srcentries(path).select {|fname| File.dir?(File.join(curr_srcdir(), path, fname)) } end end class ToplevelInstaller Version = '3.4.1' Copyright = 'Copyright (c) 2000-2005 Minero Aoki' TASKS = [ [ 'all', 'do config, setup, then install' ], [ 'config', 'saves your configurations' ], [ 'show', 'shows current configuration' ], [ 'setup', 'compiles ruby extentions and others' ], [ 'install', 'installs files' ], [ 'test', 'run all tests in test/' ], [ 'clean', "does `make clean' for each extention" ], [ 'distclean',"does `make distclean' for each extention" ] ] def ToplevelInstaller.invoke config = ConfigTable.new(load_rbconfig()) config.load_standard_entries config.load_multipackage_entries if multipackage? config.fixup klass = (multipackage?() ? ToplevelInstallerMulti : ToplevelInstaller) klass.new(File.dirname($0), config).invoke end def ToplevelInstaller.multipackage? File.dir?(File.dirname($0) + '/packages') end def ToplevelInstaller.load_rbconfig if arg = ARGV.detect {|arg| /\A--rbconfig=/ =~ arg } ARGV.delete(arg) load File.expand_path(arg.split(/=/, 2)[1]) $".push 'rbconfig.rb' else require 'rbconfig' end ::RbConfig::CONFIG end def initialize(ardir_root, config) @ardir = File.expand_path(ardir_root) @config = config # cache @valid_task_re = nil end def config(key) @config[key] end def inspect "#<#{self.class} #{__id__()}>" end def invoke run_metaconfigs case task = parsearg_global() when nil, 'all' parsearg_config init_installers exec_config exec_setup exec_install else case task when 'config', 'test' ; when 'clean', 'distclean' @config.load_savefile if File.exist?(@config.savefile) else @config.load_savefile end __send__ "parsearg_#{task}" init_installers __send__ "exec_#{task}" end end def run_metaconfigs @config.load_script "#{@ardir}/metaconfig" end def init_installers @installer = Installer.new(@config, @ardir, File.expand_path('.')) end # # Hook Script API bases # def srcdir_root @ardir end def objdir_root '.' end def relpath '.' end # # Option Parsing # def parsearg_global while arg = ARGV.shift case arg when /\A\w+\z/ setup_rb_error "invalid task: #{arg}" unless valid_task?(arg) return arg when '-q', '--quiet' @config.verbose = false when '--verbose' @config.verbose = true when '--help' print_usage $stdout exit 0 when '--version' puts "#{File.basename($0)} version #{Version}" exit 0 when '--copyright' puts Copyright exit 0 else setup_rb_error "unknown global option '#{arg}'" end end nil end def valid_task?(t) valid_task_re() =~ t end def valid_task_re @valid_task_re ||= /\A(?:#{TASKS.map {|task,desc| task }.join('|')})\z/ end def parsearg_no_options unless ARGV.empty? task = caller(0).first.slice(%r<`parsearg_(\w+)'>, 1) setup_rb_error "#{task}: unknown options: #{ARGV.join(' ')}" end end alias parsearg_show parsearg_no_options alias parsearg_setup parsearg_no_options alias parsearg_test parsearg_no_options alias parsearg_clean parsearg_no_options alias parsearg_distclean parsearg_no_options def parsearg_config evalopt = [] set = [] @config.config_opt = [] while i = ARGV.shift if /\A--?\z/ =~ i @config.config_opt = ARGV.dup break end name, value = *@config.parse_opt(i) if @config.value_config?(name) @config[name] = value else evalopt.push [name, value] end set.push name end evalopt.each do |name, value| @config.lookup(name).evaluate value, @config end # Check if configuration is valid set.each do |n| @config[n] if @config.value_config?(n) end end def parsearg_install @config.no_harm = false @config.install_prefix = '' while a = ARGV.shift case a when '--no-harm' @config.no_harm = true when /\A--prefix=/ path = a.split(/=/, 2)[1] path = File.expand_path(path) unless path[0,1] == '/' @config.install_prefix = path else setup_rb_error "install: unknown option #{a}" end end end def print_usage(out) out.puts 'Typical Installation Procedure:' out.puts " $ ruby #{File.basename $0} config" out.puts " $ ruby #{File.basename $0} setup" out.puts " # ruby #{File.basename $0} install (may require root privilege)" out.puts out.puts 'Detailed Usage:' out.puts " ruby #{File.basename $0} " out.puts " ruby #{File.basename $0} [] []" fmt = " %-24s %s\n" out.puts out.puts 'Global options:' out.printf fmt, '-q,--quiet', 'suppress message outputs' out.printf fmt, ' --verbose', 'output messages verbosely' out.printf fmt, ' --help', 'print this message' out.printf fmt, ' --version', 'print version and quit' out.printf fmt, ' --copyright', 'print copyright and quit' out.puts out.puts 'Tasks:' TASKS.each do |name, desc| out.printf fmt, name, desc end fmt = " %-24s %s [%s]\n" out.puts out.puts 'Options for CONFIG or ALL:' @config.each do |item| out.printf fmt, item.help_opt, item.description, item.help_default end out.printf fmt, '--rbconfig=path', 'rbconfig.rb to load',"running ruby's" out.puts out.puts 'Options for INSTALL:' out.printf fmt, '--no-harm', 'only display what to do if given', 'off' out.printf fmt, '--prefix=path', 'install path prefix', '' out.puts end # # Task Handlers # def exec_config @installer.exec_config @config.save # must be final end def exec_setup @installer.exec_setup end def exec_install @installer.exec_install end def exec_test @installer.exec_test end def exec_show @config.each do |i| printf "%-20s %s\n", i.name, i.value if i.value? end end def exec_clean @installer.exec_clean end def exec_distclean @installer.exec_distclean end end # class ToplevelInstaller class ToplevelInstallerMulti < ToplevelInstaller include FileOperations def initialize(ardir_root, config) super @packages = directories_of("#{@ardir}/packages") raise 'no package exists' if @packages.empty? @root_installer = Installer.new(@config, @ardir, File.expand_path('.')) end def run_metaconfigs @config.load_script "#{@ardir}/metaconfig", self @packages.each do |name| @config.load_script "#{@ardir}/packages/#{name}/metaconfig" end end attr_reader :packages def packages=(list) raise 'package list is empty' if list.empty? list.each do |name| raise "directory packages/#{name} does not exist"\ unless File.dir?("#{@ardir}/packages/#{name}") end @packages = list end def init_installers @installers = {} @packages.each do |pack| @installers[pack] = Installer.new(@config, "#{@ardir}/packages/#{pack}", "packages/#{pack}") end with = extract_selection(config('with')) without = extract_selection(config('without')) @selected = @installers.keys.select {|name| (with.empty? or with.include?(name)) \ and not without.include?(name) } end def extract_selection(list) a = list.split(/,/) a.each do |name| setup_rb_error "no such package: #{name}" unless @installers.key?(name) end a end def print_usage(f) super f.puts 'Inluded packages:' f.puts ' ' + @packages.sort.join(' ') f.puts end # # Task Handlers # def exec_config run_hook 'pre-config' each_selected_installers {|inst| inst.exec_config } run_hook 'post-config' @config.save # must be final end def exec_setup run_hook 'pre-setup' each_selected_installers {|inst| inst.exec_setup } run_hook 'post-setup' end def exec_install run_hook 'pre-install' each_selected_installers {|inst| inst.exec_install } run_hook 'post-install' end def exec_test run_hook 'pre-test' each_selected_installers {|inst| inst.exec_test } run_hook 'post-test' end def exec_clean rm_f @config.savefile run_hook 'pre-clean' each_selected_installers {|inst| inst.exec_clean } run_hook 'post-clean' end def exec_distclean rm_f @config.savefile run_hook 'pre-distclean' each_selected_installers {|inst| inst.exec_distclean } run_hook 'post-distclean' end # # lib # def each_selected_installers Dir.mkdir 'packages' unless File.dir?('packages') @selected.each do |pack| $stderr.puts "Processing the package `#{pack}' ..." if verbose? Dir.mkdir "packages/#{pack}" unless File.dir?("packages/#{pack}") Dir.chdir "packages/#{pack}" yield @installers[pack] Dir.chdir '../..' end end def run_hook(id) @root_installer.run_hook id end # module FileOperations requires this def verbose? @config.verbose? end # module FileOperations requires this def no_harm? @config.no_harm? end end # class ToplevelInstallerMulti class Installer FILETYPES = %w( bin lib ext data conf man ) include FileOperations include HookScriptAPI def initialize(config, srcroot, objroot) @config = config @srcdir = File.expand_path(srcroot) @objdir = File.expand_path(objroot) @currdir = '.' end def inspect "#<#{self.class} #{File.basename(@srcdir)}>" end def noop(rel) end # # Hook Script API base methods # def srcdir_root @srcdir end def objdir_root @objdir end def relpath @currdir end # # Config Access # # module FileOperations requires this def verbose? @config.verbose? end # module FileOperations requires this def no_harm? @config.no_harm? end def verbose_off begin save, @config.verbose = @config.verbose?, false yield ensure @config.verbose = save end end # # TASK config # def exec_config exec_task_traverse 'config' end alias config_dir_bin noop alias config_dir_lib noop def config_dir_ext(rel) extconf if extdir?(curr_srcdir()) end alias config_dir_data noop alias config_dir_conf noop alias config_dir_man noop def extconf ruby "#{curr_srcdir()}/extconf.rb", *@config.config_opt end # # TASK setup # def exec_setup exec_task_traverse 'setup' end def setup_dir_bin(rel) files_of(curr_srcdir()).each do |fname| update_shebang_line "#{curr_srcdir()}/#{fname}" end end alias setup_dir_lib noop def setup_dir_ext(rel) make if extdir?(curr_srcdir()) end alias setup_dir_data noop alias setup_dir_conf noop alias setup_dir_man noop def update_shebang_line(path) return if no_harm? return if config('shebang') == 'never' old = Shebang.load(path) if old $stderr.puts "warning: #{path}: Shebang line includes too many args. It is not portable and your program may not work." if old.args.size > 1 new = new_shebang(old) return if new.to_s == old.to_s else return unless config('shebang') == 'all' new = Shebang.new(config('rubypath')) end $stderr.puts "updating shebang: #{File.basename(path)}" if verbose? open_atomic_writer(path) {|output| File.open(path, 'rb') {|f| f.gets if old # discard output.puts new.to_s output.print f.read } } end def new_shebang(old) if /\Aruby/ =~ File.basename(old.cmd) Shebang.new(config('rubypath'), old.args) elsif File.basename(old.cmd) == 'env' and old.args.first == 'ruby' Shebang.new(config('rubypath'), old.args[1..-1]) else return old unless config('shebang') == 'all' Shebang.new(config('rubypath')) end end def open_atomic_writer(path, &block) tmpfile = File.basename(path) + '.tmp' begin File.open(tmpfile, 'wb', &block) File.rename tmpfile, File.basename(path) ensure File.unlink tmpfile if File.exist?(tmpfile) end end class Shebang def Shebang.load(path) line = nil File.open(path) {|f| line = f.gets } return nil unless /\A#!/ =~ line parse(line) end def Shebang.parse(line) cmd, *args = *line.strip.sub(/\A\#!/, '').split(' ') new(cmd, args) end def initialize(cmd, args = []) @cmd = cmd @args = args end attr_reader :cmd attr_reader :args def to_s "#! #{@cmd}" + (@args.empty? ? '' : " #{@args.join(' ')}") end end # # TASK install # def exec_install rm_f 'InstalledFiles' exec_task_traverse 'install' end def install_dir_bin(rel) install_files targetfiles(), "#{config('bindir')}/#{rel}", 0755 end def install_dir_lib(rel) install_files libfiles(), "#{config('rbdir')}/#{rel}", 0644 end def install_dir_ext(rel) return unless extdir?(curr_srcdir()) install_files rubyextentions('.'), "#{config('sodir')}/#{File.dirname(rel)}", 0555 end def install_dir_data(rel) install_files targetfiles(), "#{config('datadir')}/#{rel}", 0644 end def install_dir_conf(rel) # FIXME: should not remove current config files # (rename previous file to .old/.org) install_files targetfiles(), "#{config('sysconfdir')}/#{rel}", 0644 end def install_dir_man(rel) install_files targetfiles(), "#{config('mandir')}/#{rel}", 0644 end def install_files(list, dest, mode) mkdir_p dest, @config.install_prefix list.each do |fname| install fname, dest, mode, @config.install_prefix end end def libfiles glob_reject(%w(*.y *.output), targetfiles()) end def rubyextentions(dir) ents = glob_select("*.#{@config.dllext}", targetfiles()) if ents.empty? setup_rb_error "no ruby extention exists: 'ruby #{$0} setup' first" end ents end def targetfiles mapdir(existfiles() - hookfiles()) end def mapdir(ents) ents.map {|ent| if File.exist?(ent) then ent # objdir else "#{curr_srcdir()}/#{ent}" # srcdir end } end # picked up many entries from cvs-1.11.1/src/ignore.c JUNK_FILES = %w( core RCSLOG tags TAGS .make.state .nse_depinfo #* .#* cvslog.* ,* .del-* *.olb *~ *.old *.bak *.BAK *.orig *.rej _$* *$ *.org *.in .* ) def existfiles glob_reject(JUNK_FILES, (files_of(curr_srcdir()) | files_of('.'))) end def hookfiles %w( pre-%s post-%s pre-%s.rb post-%s.rb ).map {|fmt| %w( config setup install clean ).map {|t| sprintf(fmt, t) } }.flatten end def glob_select(pat, ents) re = globs2re([pat]) ents.select {|ent| re =~ ent } end def glob_reject(pats, ents) re = globs2re(pats) ents.reject {|ent| re =~ ent } end GLOB2REGEX = { '.' => '\.', '$' => '\$', '#' => '\#', '*' => '.*' } def globs2re(pats) /\A(?:#{ pats.map {|pat| pat.gsub(/[\.\$\#\*]/) {|ch| GLOB2REGEX[ch] } }.join('|') })\z/ end # # TASK test # TESTDIR = 'test' def exec_test unless File.directory?('test') $stderr.puts 'no test in this package' if verbose? return end $stderr.puts 'Running tests...' if verbose? begin require 'test/unit' rescue LoadError setup_rb_error 'test/unit cannot loaded. You need Ruby 1.8 or later to invoke this task.' end runner = Test::Unit::AutoRunner.new(true) runner.to_run << TESTDIR runner.run end # # TASK clean # def exec_clean exec_task_traverse 'clean' rm_f @config.savefile rm_f 'InstalledFiles' end alias clean_dir_bin noop alias clean_dir_lib noop alias clean_dir_data noop alias clean_dir_conf noop alias clean_dir_man noop def clean_dir_ext(rel) return unless extdir?(curr_srcdir()) make 'clean' if File.file?('Makefile') end # # TASK distclean # def exec_distclean exec_task_traverse 'distclean' rm_f @config.savefile rm_f 'InstalledFiles' end alias distclean_dir_bin noop alias distclean_dir_lib noop def distclean_dir_ext(rel) return unless extdir?(curr_srcdir()) make 'distclean' if File.file?('Makefile') end alias distclean_dir_data noop alias distclean_dir_conf noop alias distclean_dir_man noop # # Traversing # def exec_task_traverse(task) run_hook "pre-#{task}" FILETYPES.each do |type| if type == 'ext' and config('without-ext') == 'yes' $stderr.puts 'skipping ext/* by user option' if verbose? next end traverse task, type, "#{task}_dir_#{type}" end run_hook "post-#{task}" end def traverse(task, rel, mid) dive_into(rel) { run_hook "pre-#{task}" __send__ mid, rel.sub(%r[\A.*?(?:/|\z)], '') directories_of(curr_srcdir()).each do |d| traverse task, "#{rel}/#{d}", mid end run_hook "post-#{task}" } end def dive_into(rel) return unless File.dir?("#{@srcdir}/#{rel}") dir = File.basename(rel) Dir.mkdir dir unless File.dir?(dir) prevdir = Dir.pwd Dir.chdir dir $stderr.puts '---> ' + rel if verbose? @currdir = rel yield Dir.chdir prevdir $stderr.puts '<--- ' + rel if verbose? @currdir = File.dirname(rel) end def run_hook(id) path = [ "#{curr_srcdir()}/#{id}", "#{curr_srcdir()}/#{id}.rb" ].detect {|cand| File.file?(cand) } return unless path begin instance_eval File.read(path), path, 1 rescue raise if $DEBUG setup_rb_error "hook #{path} failed:\n" + $!.message end end end # class Installer class SetupError < StandardError; end def setup_rb_error(msg) raise SetupError, msg end if $0 == __FILE__ begin ToplevelInstaller.invoke rescue SetupError raise if $DEBUG $stderr.puts $!.message $stderr.puts "Try 'ruby #{$0} --help' for detailed usage." exit 1 end end cmdparse-3.0.7/VERSION0000644000004100000410000000000613775147450014430 0ustar www-datawww-data3.0.7 cmdparse-3.0.7/webgen.config0000644000004100000410000000166613775147450016033 0ustar www-datawww-data# -*- ruby -*- website.config['website.base_url'] = 'https://cmdparse.gettalong.org/' website.config['sources'] =[['/', :file_system, 'doc']] website.config['destination'] = [:file_system, 'htmldoc'] website.config['website.tmpdir'] = 'webgen-tmp' website.config['content_processor.kramdown.options'] = { syntax_highlighter: 'coderay', syntax_highlighter_opts: {css: 'class', line_numbers: nil} } website.config['content_processor.sass.options'] = {:style => :compressed} website.config.define_option('tag.extract.file', 'example/net.rb') website.config.define_option('tag.extract.lines', nil) website.ext.tag.register('extract', :config_prefix => 'tag.extract', :mandatory => ['lines']) do |tag, body, context| file = context[:config]['tag.extract.file'] lines = context[:config]['tag.extract.lines'] File.readlines(File.join(context.website.directory, file)).unshift('unused line')[lines].join("").rstrip end