Class: Command::Definition

Inherits:
Object
  • Object
show all
Defined in:
lib/command/definition.rb

Constant Summary collapse

RequiredOptionArgument =
/[A-Z][A-Z_]*/
OptionArgument =
/\[#{RequiredOptionArgument}\]|#{RequiredOptionArgument}/
ShortOption =
/\A-[A-Za-z](?: #{OptionArgument})?\z/
NegationSequence =
/\[(?:no-|with-|without-)\]/
LongOption =
/\A--
  (?:
    #{NegationSequence}?[A-Za-z][A-Za-z0-9_-]* |
    [A-Za-z][A-Za-z0-9_-]*#{NegationSequence}[A-Za-z0-9][A-Za-z0-9_-]
  )
  (?:\x20#{OptionArgument})?\z
/x

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(parent = nil, default_command = nil, default_options = {}, &block) ⇒ Definition

Returns a new instance of Definition.



138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
# File 'lib/command/definition.rb', line 138

def initialize(parent=nil, default_command=nil, default_options={}, &block)
  @default_command   = default_command
  @default_options   = DecoratingHash.new(@parent && @parent.default_options).update(default_options)
  @elements          = []
  @parent            = parent
  @arguments_by_name = DecoratingHash.new(@parent && @parent.arguments_by_name)
  @options_by_name   = DecoratingHash.new(@parent && @parent.options_by_name)
  @options_by_flag   = DecoratingHash.new(@parent && @parent.options_by_flag)
  @commands_by_name  = DecoratingHash.new(@parent && @parent.commands_by_name)
  @env_by_variable   = DecoratingHash.new(@parent && @parent.env_by_variable)
  @argument_position = {}
  @text              = []
  @placeholders      = {}
  @content_for       = [@elements]
  instance_eval(&block) if block
end

Instance Attribute Details

#argument_positionObject (readonly)

Returns the value of attribute argument_position.



134
135
136
# File 'lib/command/definition.rb', line 134

def argument_position
  @argument_position
end

#argumentsObject (readonly)

Returns the value of attribute arguments.



127
128
129
# File 'lib/command/definition.rb', line 127

def arguments
  @arguments
end

#arguments_by_nameObject (readonly)

Returns the value of attribute arguments_by_name.



128
129
130
# File 'lib/command/definition.rb', line 128

def arguments_by_name
  @arguments_by_name
end

#commands_by_nameObject (readonly)

Returns the value of attribute commands_by_name.



132
133
134
# File 'lib/command/definition.rb', line 132

def commands_by_name
  @commands_by_name
end

#default_commandObject (readonly)

Returns the value of attribute default_command.



133
134
135
# File 'lib/command/definition.rb', line 133

def default_command
  @default_command
end

#default_optionsObject (readonly)

Returns the value of attribute default_options.



129
130
131
# File 'lib/command/definition.rb', line 129

def default_options
  @default_options
end

#env_by_variableObject (readonly)

Returns the value of attribute env_by_variable.



135
136
137
# File 'lib/command/definition.rb', line 135

def env_by_variable
  @env_by_variable
end

#options_by_flagObject (readonly)

Returns the value of attribute options_by_flag.



131
132
133
# File 'lib/command/definition.rb', line 131

def options_by_flag
  @options_by_flag
end

#options_by_nameObject (readonly)

Returns the value of attribute options_by_name.



130
131
132
# File 'lib/command/definition.rb', line 130

def options_by_name
  @options_by_name
end

#parentObject (readonly)

parent= must update the DecoratingHashes



136
137
138
# File 'lib/command/definition.rb', line 136

def parent
  @parent
end

Class Method Details

.create_argument(*args) ⇒ Object



23
24
25
26
27
28
29
30
31
# File 'lib/command/definition.rb', line 23

def self.create_argument(*args)
  name        = Symbol === args.first && args.shift
  usage       = args.shift
  bare        = usage[/\w+/]
  type        = Symbol === args.first && args.shift
  description = args.shift

  Argument.new(name, bare, usage, type, description)
end

.create_option(name, *args) ⇒ Object

valid arguments:

name # --> copy from parent
name, short[, long][, type][, description]
name, long[, type][, description]

short can be

  • ‘-?’ (short option without argument)

  • ‘-? REQUIRED’ (short option with required argument)

  • ‘-? [OPTIONAL]’ (short option with optional

where ? is any of A-Za-z examples:

  • ‘-a’

  • ‘-a [OPTIONAL_ARG]’

  • ‘-a REQUIRED_ARG’

long can be

  • ‘–?’ (short option without argument)

  • ‘–? REQUIRED’ (short option with required argument)

  • ‘–? [OPTIONAL]’ (short option with optional

where ? is [A-Za-z0-9]* It may contain a negation sequence, which is one of ‘[no-]’, ‘[with-]’, ‘[without-]’ examples:

  • ‘–colored’

  • ‘–[no-]colors’

  • ‘–port PORT’

  • ‘–foo [OPTIONAL_ARG]’

Only one of short and long may have the argument declared. Usually you’ll have the argument in long and only have it in short if there’s no long at all.

type can be

  • :Virtual

  • :Boolean

  • :String (default)

  • :Integer

  • :Float

  • :Octal

  • :Hex

  • :File - requires the provided path to exist and be a file

  • :Directory - requires the provided path to exist and be a directory

  • :Path

Exceptions (incomplete):

  • ArgumentError with single argument if it doesn’t identify an inheritable option

  • ArgumentError on invalid short definition

  • ArgumentError on invalid long definition

  • ArgumentError on option argument declaration in both, short and long definition

  • ArgumentError on invalid/unsupported type

Raises:

  • (ArgumentError)


82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/command/definition.rb', line 82

def self.create_option(name, *args)
  case args.first when nil, /\A-[^- ]/ then
    short = args.shift
  end
  case args.first when nil, /\A--[^- ]/ then
    long = args.shift
  end
  case args.first when nil, Symbol then
    type = args.shift
  end
  declaration = short ? [short,long].compact.join(", ") : "    #{long}"
  description = args.shift

  raise ArgumentError, "Too many arguments" unless args.empty?
  raise ArgumentError, "Invalid short declaration: #{short.inspect}" unless (short.nil? || short =~ ShortOption)
  raise ArgumentError, "Invalid long declaration: #{long.inspect}" unless (long.nil? || long =~ LongOption)
  raise ArgumentError, "Argument declaration must only be in one of short and long" if (short && long && short =~ /\s/ && long =~ /\s/)

  necessity        = :none
  extract_argument = nil
  extract_argument = short if short =~ /\s/
  extract_argument = long if long =~ /\s/
  if extract_argument then
    flag, *argument = extract_argument.split(/ /)
    extract_argument.replace(flag) # long/short should only contain the flag, not the argument declaration as well
    raise ArgumentError, "Multiple arguments for an option not yet supported" if argument.size > 1
    if argument.empty?
      necessity = :none
    elsif argument.first =~ /\A\[.*\]\z/ then
      necessity = :optional
    else
      necessity = :required
    end
  end

  negated = nil
  if long =~ NegationSequence then
    negated = long.delete('[]')
    long    = long.gsub(NegationSequence, '')
  end

  Option.new(name, short, long, negated, necessity, type, declaration, description)
end

Instance Method Details

#[](command) ⇒ Object



155
156
157
# File 'lib/command/definition.rb', line 155

def [](command)
  command ? @commands_by_name[command] : self
end

#argument(*args) ⇒ Object



216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
# File 'lib/command/definition.rb', line 216

def argument(*args)
  unless @argument_position[args.first]
    @argument_position[args.first] = @argument_position.size
  end

  if args.size == 1 then
    argument   = @arguments_by_name[args.first]
    raise ArgumentError, "No argument with name #{args.first.inspect} in any parent found." unless argument
  else
    argument    = self.class.create_argument(*args)
    @arguments_by_name[argument.name] = argument
  end
  @content_for.last << argument

  argument
end

#command(*args, &block) ⇒ Object



288
289
290
291
292
# File 'lib/command/definition.rb', line 288

def command(*args, &block)
  definition = Definition.new(self, *args, &block)
  @commands_by_name[args.first] = definition
  @content_for.last << definition
end

#content_for(placeholder) {|_self| ... } ⇒ Object

Yields:

  • (_self)

Yield Parameters:



159
160
161
162
163
164
# File 'lib/command/definition.rb', line 159

def content_for(placeholder)
  @placeholders[placeholder] ||= []
  @content_for << @placeholders[placeholder]
  yield(self)
  @content_for.pop
end

#env_option(name, variable) ⇒ Object



282
283
284
285
286
# File 'lib/command/definition.rb', line 282

def env_option(name, variable)
  env = Env.new(name, variable)
  @env_by_variable[variable] = env
  @content_for.last << env
end

#option(*args) ⇒ Object Also known as: o



246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
# File 'lib/command/definition.rb', line 246

def option(*args)
  if args.size == 1 then
    inherited_option = @options_by_name[args.first]
    raise ArgumentError, "No inherited option #{args.first.inspect}" unless inherited_option
    @content_for.last << inherited_option

    inherited_option
  else
    option = self.class.create_option(*args)

    @options_by_name[option.name]    = option
    @options_by_flag[option.short]   = option
    @options_by_flag[option.long]    = option
    @options_by_flag[option.negated] = option
    @content_for.last << option

    option
  end
end

#placeholder(identifier) ⇒ Object



278
279
280
# File 'lib/command/definition.rb', line 278

def placeholder(identifier)
  @content_for.last << identifier
end

#text(*args) ⇒ Object



267
268
269
270
271
272
273
274
275
276
# File 'lib/command/definition.rb', line 267

def text(*args)
  if args.size == 2 then
    indent, text = *args
    text = text.gsub(/^/, indent)
  else
    text = args.first
  end
  @text             << text
  @content_for.last << text
end

#usageObject



212
213
214
# File 'lib/command/definition.rb', line 212

def usage
  @content_for.last << :usage
end

#usage_text(elements = @elements) ⇒ Object



166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
# File 'lib/command/definition.rb', line 166

def usage_text(elements=@elements)
  longest_arg_bare = elements.grep(Argument).max { |a,b|
    a.bare.size <=> b.bare.size
  }
  longest_option = elements.grep(Option).max { |a,b|
    a.declaration.size <=> b.declaration.size
  }
  longest_env_name = elements.grep(Env).max { |a,b|
    a.variable.size <=> b.variable.size
  }
  longest_arg_bare = longest_arg_bare && longest_arg_bare.bare.size
  longest_option   = longest_option && longest_option.declaration.size
  longest_env_name = longest_env_name && longest_env_name.variable.size
  arguments = elements.grep(Argument)

  elements.map { |e|
    case e
      when :usage
        "Usage: #{File.basename($0)} #{arguments.map{|a|a.usage}.join(' ')}\n"
      when Symbol # placeholder
        usage_text(@placeholders[e])
      when Option
        sprintf "  %*s%s\n",
                -(longest_option+2),
                e.declaration,
                e.description
      when Env
        sprintf "* %*s%s\n",
                -longest_env_name-2,
                e.variable,
                @options_by_name[e.name].description
      when String
        e+"\n"
      when Argument
        indent = "\n     "+(" "*longest_arg_bare)
        sprintf "  %*s%s\n",
                -(longest_arg_bare+3),
                "#{e.bare}:",
                e.description.to_s.gsub(/\n/, indent)
      when Definition
      else
        "unimplemented(#{e.class})"
    end
  }.join('')
end

#virtual_argument(*args) ⇒ Object



233
234
235
236
237
238
239
240
241
242
243
244
# File 'lib/command/definition.rb', line 233

def virtual_argument(*args)
  if args.size == 1 then
    argument   = @arguments_by_name[args.first]
    raise ArgumentError, "No argument with name #{args.first.inspect} in any parent found." unless argument
  else
    argument    = self.class.create_argument(*args)
    @arguments_by_name[argument.name] = argument
  end

  @content_for.last << argument
  argument
end