Class: Hammer

Inherits:
Object
  • Object
show all
Includes:
Shell
Defined in:
lib/lux-hammer.rb,
lib/hammer/shell.rb,
lib/hammer/loader.rb,
lib/hammer/option.rb,
lib/hammer/parser.rb,
lib/hammer/builder.rb,
lib/hammer/command.rb,
lib/hammer/command_builder.rb

Overview

Thor-inspired tiny CLI builder.

Class DSL:

class MyCli < Hammer
  define :build do
    desc    'Build the project'
    example 'build -v --env=prod'
    opt :verbose, type: :boolean, alias: :v
    opt :env,     type: :string,  default: 'dev'
    proc do |opts|
      say.green "building #{opts[:env]} args=#{opts[:args].inspect}"
    end
  end
end

MyCli.start(ARGV)

Block DSL is identical, just inside ‘Hammer.run`:

Hammer.run(ARGV) do
  define :hello do
    desc 'Greet someone'
    opt :loud, type: :boolean, alias: :l
    proc do |opts|
      msg = "hello #{opts[:args].first || 'world'}"
      msg = msg.upcase if opts[:loud]
      say msg, :cyan
    end
  end
end

Defined Under Namespace

Modules: Shell Classes: Builder, Command, CommandBuilder, Loader, Option, Parser

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Shell

ask, choose, choose_numbered, color!, color?, error, paint, print_error, say, sh, yes?

Class Method Details

.alt(*names) ⇒ Object



74
# File 'lib/lux-hammer.rb', line 74

def alt(*names)      ; @pending_alts.concat(names) end

.ancestor_chainObject

Root -> … -> self. Used to gather ‘before` hooks for a command.



207
208
209
210
211
212
213
214
215
# File 'lib/lux-hammer.rb', line 207

def ancestor_chain
  chain = []
  klass = self
  while klass
    chain.unshift klass
    klass = klass.parent
  end
  chain
end

.before(&block) ⇒ Object

Register a hook to run before every command in this class (root or namespace). Hooks receive the command’s ‘opts` hash. All hooks run outer -> inner, once per top-level `start` (prereqs don’t re-trigger).

before { |opts| Dotenv.load }
namespace :db do
  before { hammer :env }
  define :migrate do ... end
end


194
195
196
# File 'lib/lux-hammer.rb', line 194

def before(&block)
  before_hooks << block
end

.before_hooksObject



198
199
200
# File 'lib/lux-hammer.rb', line 198

def before_hooks
  @before_hooks
end

.cli(argv = ARGV) ⇒ Object

Entry point for the ‘hammer` binary. Walks up from CWD until it finds a Hammerfile, evaluates it as the block DSL, then dispatches ARGV against the resulting CLI.



589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
# File 'lib/lux-hammer.rb', line 589

def self.cli(argv = ARGV)
  path = find_hammerfile(Dir.pwd)
  unless path
    Shell.print_error "no Hammerfile found in #{Dir.pwd} or any parent directory"

    # Heuristic: *.rb files referencing `Hammer.` are likely inline CLIs
    # the user could promote into a Hammerfile.
    excludes = %w[.git node_modules tmp vendor coverage dist build]
               .map { |d| "--exclude-dir=#{d}" }.join(' ')
    candidates = `grep -rl --include='*.rb' #{excludes} 'Hammer\\.' . 2>/dev/null`
                 .lines.map(&:strip).reject(&:empty?)
    unless candidates.empty?
      Shell.say "possible CLI implementation(s) - files referencing `Hammer.`:", :yellow
      candidates.first(10).each { |f| Shell.say "  #{f.sub(%r{\A\./}, '')}" }
      Shell.say ''
    end

    Shell.say "create one - example:"
    puts
    Shell.say <<~RUBY
      define :hello do
        desc 'say hello'
        proc do |opts|
          say.green "hello \#{opts[:args].first || 'world'}"
        end
      end
    RUBY
    exit 1
  end

  klass = Class.new(Hammer)
  # Resolve before chdir so paths like `bin/foo` stay relative to the
  # cwd the user actually invoked from. `program_name` memoizes.
  klass.program_name

  # chdir into the Hammerfile's directory for the entire run so commands
  # operate on the project root (Rake-style).
  Dir.chdir(File.dirname(path))
  Builder.new(klass).instance_eval(File.read(path), path)
  klass.start(argv)
end

.commandsObject



224
225
226
# File 'lib/lux-hammer.rb', line 224

def commands
  @commands ||= {}
end

.default_program_nameObject

Program name shown in help/usage: the invocation path relative to cwd if the script lives inside it (e.g. ‘bin/foo` when invoked from the project root), otherwise the basename (e.g. `lux` for a globally installed bin in PATH).



112
113
114
115
116
117
118
119
120
121
# File 'lib/lux-hammer.rb', line 112

def default_program_name
  prog = $PROGRAM_NAME
  return File.basename(prog) unless prog.include?('/')
  # Resolve symlinks on both sides so e.g. macOS `/tmp` -> `/private/tmp`
  # doesn't cause a false miss when comparing prefixes.
  abs = File.realpath(prog) rescue File.expand_path(prog)
  cwd = File.realpath(Dir.pwd) rescue Dir.pwd
  return abs[(cwd.length + 1)..] if abs.start_with?("#{cwd}/")
  File.basename(prog)
end

.define(name, &block) ⇒ Object

Define a command. Block runs in a CommandBuilder context and must return a Proc as its last expression. That proc is the handler and receives a single ‘opts` hash with symbol keys; positional ARGV lives at `opts`.



127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# File 'lib/lux-hammer.rb', line 127

def define(name, &block)
  cmd = Command.new(name: name.to_s)
  handler = CommandBuilder.new(cmd).instance_eval(&block)
  unless handler.is_a?(Proc)
    raise Error, <<~MSG
      define(:#{name}) block must end with a `proc do |opts| ... end`.
      The proc's return value is what becomes the command handler.

      Example:

        define :#{name} do
          desc    'what it does'
          example '#{name} foo --env=prod'
          opt :env, default: 'dev'

          proc do |opts|
            # your code here - opts[:env], opts[:args], ...
          end
        end
    MSG
  end
  cmd.handler = handler
  commands[cmd.name] = cmd

  # `define` ignores pending class-level state, but clear it so a
  # later `def` doesn't accidentally consume stale metadata.
  @pending_desc = nil
  @pending_examples = []
  @pending_options  = []
  @pending_alts     = []
  @pending_needs    = []
end

.desc(text) ⇒ Object

—– class-level DSL for ‘def`-style commands ——————— Set pending metadata that the next `def` will consume.

class MyCli < Hammer
  desc 'Build'
  opt :env, default: 'dev'
  def build(opts)
    say "building #{opts[:env]}"
  end
end


71
# File 'lib/lux-hammer.rb', line 71

def desc(text)       ; @pending_desc = text.to_s.rstrip end

.each_command(prefix = nil, &block) ⇒ Object

Yield [full_colon_path, Command] for every command in this class and all nested namespaces.



366
367
368
369
370
371
372
373
374
375
# File 'lib/lux-hammer.rb', line 366

def each_command(prefix = nil, &block)
  commands.each_value do |c|
    full = prefix ? "#{prefix}:#{c.name}" : c.name
    yield full, c
  end
  namespaces.each do |ns_name, sub|
    sub_prefix = prefix ? "#{prefix}:#{ns_name}" : ns_name
    sub.each_command(sub_prefix, &block)
  end
end

.emit_rows(rows, width) ⇒ Object



490
491
492
493
494
495
# File 'lib/lux-hammer.rb', line 490

def emit_rows(rows, width)
  rows.each do |full, c|
    label = label_for(full, c)
    Shell.say "  #{program_name} #{label.ljust(width)}  # #{c.brief}"
  end
end

.example(text) ⇒ Object



72
# File 'lib/lux-hammer.rb', line 72

def example(text)    ; @pending_examples << text end

.find_command(name) ⇒ Object

Find a command by canonical name or alt within this class.



317
318
319
# File 'lib/lux-hammer.rb', line 317

def find_command(name)
  commands[name.to_s] || commands.values.find { |c| c.matches?(name) }
end

.find_hammerfile(start) ⇒ Object

Walk up the directory tree looking for a Hammerfile.



632
633
634
635
636
637
638
639
640
641
# File 'lib/lux-hammer.rb', line 632

def self.find_hammerfile(start)
  dir = File.expand_path(start)
  loop do
    candidate = File.join(dir, 'Hammerfile')
    return candidate if File.file?(candidate)
    parent = File.dirname(dir)
    return nil if parent == dir
    dir = parent
  end
end

.hammer(name, *args, **opts) ⇒ Object

Programmatic dispatch by name. Useful for scripting and tests.

MyCli.hammer :build                       -> start(["build"])
MyCli.hammer 'db:users:list'              -> start(["db:users:list"])
MyCli.hammer :eval, 'puts 42'             -> start(["eval", "puts 42"])
MyCli.hammer :build, env: 'prod'          -> start(["build", "--env=prod"])
MyCli.hammer :build, verbose: true        -> start(["build", "--verbose"])
MyCli.hammer :build, no_cache: true       -> start(["build", "--no-cache"])
MyCli.hammer :build, cache: false         -> skipped (no-op)

Symbols are single-segment names; pass a string with colons for namespaced paths. Trailing positionals become positional ARGV. Underscores in option keys become dashes in flags.



354
355
356
357
358
359
360
361
362
# File 'lib/lux-hammer.rb', line 354

def hammer(name, *args, **opts)
  argv = [name.to_s, *args.map(&:to_s)]
  opts.each do |k, v|
    next if v == false
    flag = "--#{k.to_s.tr('_', '-')}"
    argv << (v == true ? flag : "#{flag}=#{v}")
  end
  start(argv)
end

.help_requested?(argv) ⇒ Boolean

Returns:

  • (Boolean)


426
427
428
429
430
# File 'lib/lux-hammer.rb', line 426

def help_requested?(argv)
  stop = argv.index('--')
  scan = stop ? argv[0...stop] : argv
  scan.include?('-h') || scan.include?('--help')
end

.inherited(sub) ⇒ Object



46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/lux-hammer.rb', line 46

def inherited(sub)
  super
  sub.instance_variable_set(:@commands, {})
  sub.instance_variable_set(:@namespaces, {})
  sub.instance_variable_set(:@before_hooks, [])
  sub.instance_variable_set(:@parent, nil)
  sub.instance_variable_set(:@program_name, nil)
  sub.instance_variable_set(:@pending_desc, nil)
  sub.instance_variable_set(:@pending_examples, [])
  sub.instance_variable_set(:@pending_options, [])
  sub.instance_variable_set(:@pending_alts, [])
  sub.instance_variable_set(:@pending_needs, [])
end

.label_for(full, cmd) ⇒ Object

“db:migrate” or “db:migrate (alt: m)”



510
511
512
# File 'lib/lux-hammer.rb', line 510

def label_for(full, cmd)
  cmd.alts.empty? ? full : "#{full} (alt: #{cmd.alts.join(', ')})"
end

.load(*paths, **kwargs) ⇒ Object

Load Hammerfile fragments and register their commands on this class. Rake-style: split a CLI across multiple files.

load                        # auto-discover *_hammer.rb under caller dir
load auto: true             # same
load 'tasks/db_hammer.rb'   # one file
load 'tasks/*_hammer.rb'    # glob

Paths resolve relative to the file calling ‘load`. See `Hammer::Loader` for the full implementation.



248
249
250
251
252
253
254
255
# File 'lib/lux-hammer.rb', line 248

def load(*paths, **kwargs)
  if self == Hammer
    raise Error, 'use `load` from inside a Hammerfile / Hammer.run block / Hammer subclass body, ' \
                 'or call SubClass.load - Hammer.load itself has no target'
  end
  anchor = Loader.caller_anchor(caller_locations(1, 1).first)
  loader.load(anchor, paths, kwargs)
end

.loaderObject

Per-target Loader instance. Owns the dedup cache, so re-entrant ‘load` from inside a fragment is safe and idempotent.



234
235
236
# File 'lib/lux-hammer.rb', line 234

def loader
  @loader ||= Loader.new(self)
end

.method_added(method_name) ⇒ Object



77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/lux-hammer.rb', line 77

def method_added(method_name)
  super
  return unless @pending_desc

  cmd = Command.new(name: method_name.to_s, desc: @pending_desc)
  @pending_examples.each { |e| cmd.add_example(e) }
  @pending_options.each  { |o| cmd.add_option(o) }
  @pending_alts.each     { |n| cmd.add_alt(n) }
  @pending_needs.each    { |n| cmd.add_need(n) }

  # If the method takes no args, call it without opts. Otherwise pass
  # opts. So both `def build` and `def build(opts)` work.
  m = method_name
  arity = instance_method(method_name).arity
  cmd.handler = arity.zero? ? proc { send(m) } : proc { |opts| send(m, opts) }
  commands[cmd.name] = cmd

  @pending_desc = nil
  @pending_examples = []
  @pending_options  = []
  @pending_alts     = []
  @pending_needs    = []
end

.namespace(name, &block) ⇒ Object

Open a namespace (group of commands). Everything inside the block (define, nested namespace, …) belongs to that namespace, evaluated against an anonymous Hammer subclass.

namespace :db do
  define :migrate do ... end
  namespace :users do ... end
end


168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
# File 'lib/lux-hammer.rb', line 168

def namespace(name, &block)
  sub = Class.new(Hammer)
  # Track the top-level CLI class so cross-invocation
  # (`hammer 'ns:cmd'`) from inside a namespaced command dispatches
  # against the full tree, not just the current namespace.
  sub.instance_variable_set(:@root, root)
  # Parent link, so `before` hooks defined further up the namespace
  # tree can be collected and run outer -> inner before a command.
  sub.instance_variable_set(:@parent, self)
  # Share the parent's resolved program_name so help banners show
  # "myapp ns:cmd" with the same prefix everywhere - and so the value
  # captured pre-chdir (see `Hammer.cli`) survives into nested classes.
  sub.instance_variable_set(:@program_name, program_name)
  sub.class_eval(&block) if block
  @namespaces[name.to_s] = sub
end

.namespacesObject



228
229
230
# File 'lib/lux-hammer.rb', line 228

def namespaces
  @namespaces ||= {}
end

.needs(*names) ⇒ Object



75
# File 'lib/lux-hammer.rb', line 75

def needs(*names)    ; @pending_needs.concat(names) end

.opt(name, **o) ⇒ Object



73
# File 'lib/lux-hammer.rb', line 73

def opt(name, **o)   ; @pending_options << Option.new(name, **o) end

.parentObject



202
203
204
# File 'lib/lux-hammer.rb', line 202

def parent
  @parent
end


528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
# File 'lib/lux-hammer.rb', line 528

def print_command_help(cmd, full = nil)
  full ||= cmd.name
  Shell.say "Usage: #{program_name} #{full}#{usage_signature(cmd)}", :cyan
  cmd.desc.each_line do |line|
    stripped = line.chomp
    Shell.say(stripped.empty? ? '' : "  #{stripped}")
  end unless cmd.desc.empty?
  Shell.say "  alias: #{cmd.alts.join(', ')}" unless cmd.alts.empty?
  unless cmd.options.empty?
    Shell.say ''
    Shell.say 'Options:', :yellow
    cmd.options.each { |o| Shell.say "  #{o.usage}" }
  end
  unless cmd.examples.empty?
    Shell.say ''
    Shell.say 'Examples:', :yellow
    cmd.examples.each { |e| Shell.say "  #{program_name} #{e}" }
  end
end


462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
# File 'lib/lux-hammer.rb', line 462

def print_command_list(klass, prefix = nil)
  rows = []
  # Commands without a `desc` are hidden from listings but still
  # dispatchable + `hammer`-callable - useful for private helpers
  # invoked from `before` hooks or other commands (e.g. `:env`, `:app`).
  klass.each_command(prefix) { |full, c| rows << [full, c] unless c.desc.empty? }
  return if rows.empty?

  # group by "section" = everything between the view prefix and the
  # leaf name. Bare leaves go in :root.
  groups = rows.group_by { |full, _| section_for(full, prefix) }
  width  = rows.map { |full, c| label_for(full, c).length }.max
  first  = true

  if (rooted = groups.delete(:root))
    Shell.say 'Commands:', :yellow
    emit_rows(rooted.sort_by { |full, _| full }, width)
    first = false
  end

  groups.each do |section, items|
    Shell.say unless first
    first = false
    Shell.say "#{section}:", :yellow
    emit_rows(items.sort_by { |full, _| full }, width)
  end
end


457
458
459
460
# File 'lib/lux-hammer.rb', line 457

def print_footer
  Shell.say ''
  Shell.say "powered by hammer - #{HOMEPAGE}", :gray
end


432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
# File 'lib/lux-hammer.rb', line 432

def print_help(target = nil)
  if target
    cmd, _ = resolve(target)
    return print_command_help(cmd, target) if cmd
    ns = resolve_namespace(target)
    return print_namespace_help(target, ns) if ns
    Shell.print_error("unknown: #{target}")
    return
  end

  Shell.say "Usage: #{program_name} COMMAND [ARGS]", :cyan
  Shell.say ''
  print_command_list(self)
  print_footer
end


448
449
450
451
452
453
# File 'lib/lux-hammer.rb', line 448

def print_namespace_help(prefix, ns)
  Shell.say "Usage: #{program_name} #{prefix}:COMMAND [ARGS]", :cyan
  Shell.say ''
  print_command_list(ns, prefix)
  print_footer
end

.program_nameObject

Resolved lazily on first read and memoized, so callers that need the cwd-relative form (see ‘default_program_name`) can warm the cache before chdir-ing elsewhere.



104
105
106
# File 'lib/lux-hammer.rb', line 104

def program_name
  @program_name ||= default_program_name
end

.resolve(path) ⇒ Object

Walk “ns1:ns2:cmd” -> [command, owning_class]. Returns [nil, nil] if any segment is missing or the final segment isn’t a command.



323
324
325
326
327
328
329
330
331
# File 'lib/lux-hammer.rb', line 323

def resolve(path)
  parts = path.to_s.split(':')
  klass = self
  parts[0..-2].each do |ns|
    klass = klass.namespaces[ns] or return [nil, nil]
  end
  cmd = klass.find_command(parts.last)
  cmd ? [cmd, klass] : [nil, nil]
end

.resolve_namespace(path) ⇒ Object

Walk “ns1:ns2” -> namespace class, or nil if any segment missing.



334
335
336
337
338
339
# File 'lib/lux-hammer.rb', line 334

def resolve_namespace(path)
  parts = path.to_s.split(':')
  klass = self
  parts.each { |ns| klass = klass.namespaces[ns] or return nil }
  klass
end

.rootObject

Topmost class in this CLI tree. For user-defined ‘class MyCli < Hammer` or `Class.new(Hammer)` it’s self; for namespace subclasses it’s whichever class opened the namespace.



220
221
222
# File 'lib/lux-hammer.rb', line 220

def root
  @root || self
end

.run(argv = ARGV, &block) ⇒ Object

Define and run a CLI inline. Inside the block use ‘define :name do … end`, `namespace`, and `load`.

Without a block: load ./Hammerfile if it exists, otherwise auto-discover *_hammer.rb under Dir.pwd, then dispatch ARGV.



571
572
573
574
575
576
577
578
579
580
581
582
583
584
# File 'lib/lux-hammer.rb', line 571

def self.run(argv = ARGV, &block)
  klass = Class.new(Hammer)
  if block
    Builder.new(klass).instance_eval(&block)
  else
    hf = File.join(Dir.pwd, 'Hammerfile')
    if File.file?(hf)
      Builder.new(klass).instance_eval(File.read(hf), hf)
    else
      klass.loader.load(Dir.pwd, [], auto: true)
    end
  end
  klass.start(argv)
end

.run_before_hooks(instance, opts) ⇒ Object

Fire ‘before` hooks from root down through the namespace chain. Each class’s hooks fire at most once per top-level ‘start`, so prereqs dispatched via `needs` won’t re-trigger them.



402
403
404
405
406
407
408
409
# File 'lib/lux-hammer.rb', line 402

def run_before_hooks(instance, opts)
  ran = Thread.current[:hammer_before_ran] ||= {}
  ancestor_chain.each do |klass|
    next if ran[klass.object_id]
    ran[klass.object_id] = true
    klass.before_hooks.each { |hook| instance.instance_exec(opts, &hook) }
  end
end

.run_command(cmd, argv, full: nil) ⇒ Object



377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
# File 'lib/lux-hammer.rb', line 377

def run_command(cmd, argv, full: nil)
  # -h / --help is reserved on every command. Anywhere before a `--`
  # stop-marker, it short-circuits to per-command help.
  return print_command_help(cmd, full) if help_requested?(argv)

  positional, opts = Parser.new(cmd.options).parse(argv)
  opts[:args] = positional
  instance = new
  run_before_hooks(instance, opts)
  run_needs(cmd)
  instance.instance_exec(opts, &cmd.handler)
rescue Parser::Error => e
  Shell.print_error(e.message)
  print_command_help(cmd, full)
  exit 1
rescue Hammer::Error => e
  # Raised by `error 'msg'` inside a handler - controlled exit, no
  # backtrace, no per-command help spam.
  Shell.print_error(e.message)
  exit 1
end

.run_needs(cmd) ⇒ Object

Dispatch a command’s declared ‘needs` through the root class, with per-invocation dedupe. Prereqs run with default options (no argv).



413
414
415
416
417
418
419
420
421
422
423
424
# File 'lib/lux-hammer.rb', line 413

def run_needs(cmd)
  return if cmd.needs.empty?
  ran = Thread.current[:hammer_needs_ran] ||= {}
  cmd.needs.each do |path|
    key = path.to_s
    next if ran[key]
    ran[key] = true
    target, = root.resolve(key)
    raise Error, "needs: unknown command '#{key}' in #{cmd.name}" unless target
    root.start([key])
  end
end

.section_for(full, prefix) ⇒ Object

‘db’ for ‘db:migrate’ or ‘db:users:list’ viewed from root; ‘users’ for ‘db:users:list’ viewed from ‘db’; :root if the command sits at the view’s top level. Only the first segment under the view groups, so deeper paths fold into their top-level section.



501
502
503
504
505
506
507
# File 'lib/lux-hammer.rb', line 501

def section_for(full, prefix)
  segs = full.split(':')[0..-2]
  if prefix && !prefix.empty?
    segs = segs[prefix.split(':').size..] || []
  end
  segs.empty? ? :root : segs.first
end

.start(argv = ARGV) ⇒ Object

Entry point. Parses ARGV, finds the right command, runs it. Command names are Rake-style colon paths: “build”, “db:migrate”, “db:users:list”.

Rake-style chained dispatch: ‘hammer build + deploy + notify`. A bare `+` argv token separates commands; `++` escapes to a literal `+` positional. Quoted shell args (`–foo=“a + b”`) arrive as a single token and are not split.



265
266
267
268
269
270
271
272
273
274
275
276
277
278
# File 'lib/lux-hammer.rb', line 265

def start(argv = ARGV)
  # Track prereqs fired during this top-level invocation so a `needs`
  # chain runs each prereq at most once. Nested `start` calls (e.g.
  # `needs` -> `hammer` -> `start`, or a `+` chain) share the set;
  # the outermost call owns its lifetime.
  outer = Thread.current[:hammer_needs_ran].nil?
  Thread.current[:hammer_needs_ran] ||= {}
  Thread.current[:hammer_before_ran] ||= {}

  split_chain(argv).each { |seg| dispatch(seg) }
ensure
  Thread.current[:hammer_needs_ran] = nil if outer
  Thread.current[:hammer_before_ran] = nil if outer
end

.usage_signature(cmd) ⇒ Object

“ URL [ENV] [OPTIONS]” - shows the positional-fill names for declared non-boolean opts (required bare, optional bracketed), plus a generic [OPTIONS] tail if any flags exist.



517
518
519
520
521
522
523
524
525
526
# File 'lib/lux-hammer.rb', line 517

def usage_signature(cmd)
  pos = cmd.options.reject(&:boolean?).map { |o|
    name = o.name.to_s.upcase
    o.required ? name : "[#{name}]"
  }
  out = pos.join(' ')
  out = "#{out} ".lstrip unless out.empty?
  out += '[OPTIONS]' unless cmd.options.empty?
  out.empty? ? '' : " #{out}"
end

Instance Method Details

#hammer(name, *args, **opts) ⇒ Object

Inside a command’s ‘proc do |opts| … end`, call sibling commands:

define :deploy do
  proc do |opts|
    hammer :build
    hammer 'db:migrate', pretend: true
  end
end

Dispatches from the root class so colon paths resolve against the full tree even when called from inside a namespaced command.



560
561
562
# File 'lib/lux-hammer.rb', line 560

def hammer(name, *args, **opts)
  self.class.root.hammer(name, *args, **opts)
end