Class: Hammer::Option

Inherits:
Object
  • Object
show all
Defined in:
lib/hammer/option.rb

Overview

Single option/flag definition.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name, **opts) ⇒ Option

Returns a new instance of Option.

Raises:

  • (Hammer::Error)


9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# File 'lib/hammer/option.rb', line 9

def initialize(name, **opts)
  bad = opts.keys - ALLOWED_KEYS
  unless bad.empty?
    raise Hammer::Error,
          "unknown opt parameter(s) for :#{name}: #{bad.join(', ')}. " \
          "allowed: #{ALLOWED_KEYS.join(', ')}"
  end
  @name        = name.to_sym
  @type        = opts[:type] || :string
  @default     = opts[:default]
  @desc        = opts[:desc]
  @aliases     = Array(opts[:alias]).map { |a| Option.normalize_alias(a) }
  @required    = opts[:req] ? true : false
  # Custom usage placeholder, e.g. `placeholder: 't/f'` -> `--log t/f`.
  # Falls back to uppercased name (`--log LOG`) when nil.
  @placeholder = opts[:placeholder]
  # `positional: false` means "flag form only" - the option never claims a
  # bare positional. For commands that forward their positionals somewhere
  # else (`llm wrap --lines 3 -- claude`), the default fill would otherwise
  # swallow the first one.
  @positional  = opts.key?(:positional) ? !!opts[:positional] : true

  # Reserve -h / --help so every command supports them uniformly.
  if RESERVED_FLAGS.include?(switch)
    raise Hammer::Error, "opt :#{name} produces reserved flag #{switch.inspect} (used for help)"
  end
  clash = @aliases.find { |a| RESERVED_FLAGS.include?(a) }
  raise Hammer::Error, "alias #{clash.inspect} is reserved for help" if clash
end

Instance Attribute Details

#aliasesObject (readonly)

Returns the value of attribute aliases.



4
5
6
# File 'lib/hammer/option.rb', line 4

def aliases
  @aliases
end

#defaultObject (readonly)

Returns the value of attribute default.



4
5
6
# File 'lib/hammer/option.rb', line 4

def default
  @default
end

#descObject (readonly)

Returns the value of attribute desc.



4
5
6
# File 'lib/hammer/option.rb', line 4

def desc
  @desc
end

#nameObject (readonly)

Returns the value of attribute name.



4
5
6
# File 'lib/hammer/option.rb', line 4

def name
  @name
end

#placeholderObject (readonly)

Returns the value of attribute placeholder.



4
5
6
# File 'lib/hammer/option.rb', line 4

def placeholder
  @placeholder
end

#requiredObject (readonly)

Returns the value of attribute required.



4
5
6
# File 'lib/hammer/option.rb', line 4

def required
  @required
end

#typeObject (readonly)

Returns the value of attribute type.



4
5
6
# File 'lib/hammer/option.rb', line 4

def type
  @type
end

Class Method Details

.normalize_alias(value) ⇒ Object

:p -> -p, :dry_run -> --dry-run. Strings starting with - pass through untouched.



41
42
43
44
45
# File 'lib/hammer/option.rb', line 41

def self.normalize_alias(value)
  s = value.to_s
  return s if s.start_with?('-')
  s.length == 1 ? "-#{s}" : "--#{s.tr('_', '-')}"
end

Instance Method Details

#annotationObject

"(default: dev)" or "(required)"



92
93
94
95
96
# File 'lib/hammer/option.rb', line 92

def annotation
  return '(required)' if required
  return '' if default.nil?
  "(default: #{default.inspect})"
end

#boolean?Boolean

Returns:

  • (Boolean)


47
48
49
# File 'lib/hammer/option.rb', line 47

def boolean?
  type == :boolean
end

#cast(value) ⇒ Object



55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/hammer/option.rb', line 55

def cast(value)
  case type
  when :boolean then !!value && value != 'false' && value != '0'
  when :integer then Integer(value)
  when :float   then Float(value)
  when :array   then value.is_a?(Array) ? value : value.to_s.split(',')
  when :json
    # Hash/Array pass through. Strings may be inline JSON, @file, or "-"
    # (stdin resolved later by Hammer::Input.prepare_json!). "@" and
    # inline JSON are parsed here so opts[:x] is already a Hash in
    # the handler when the flag is used.
    return value if value.is_a?(Hash) || value.is_a?(Array)
    s = value.to_s
    return s if s == '-' || s.empty?
    Hammer::Input.resolve_json_value(s, {}, source: switch)
  else value.to_s
  end
rescue ArgumentError, TypeError
  raise Hammer::Parser::Error, "invalid #{type} value for --#{name}: #{value.inspect}"
end

#skip_positional_fill?Boolean

JSON bodies are flags / stdin / @file — never bare positionals. Anything declared positional: false opts out the same way.

Returns:

  • (Boolean)


78
79
80
# File 'lib/hammer/option.rb', line 78

def skip_positional_fill?
  type == :json || !@positional
end

#switchObject



51
52
53
# File 'lib/hammer/option.rb', line 51

def switch
  "--#{name.to_s.tr('_', '-')}"
end

#to_hObject

Structured form for JSON export (h:json). The GUI maps type to a form widget; default/required/desc decorate it. Mirrors the data behind usage.



101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/hammer/option.rb', line 101

def to_h
  {
    name:        name.to_s,
    type:        type.to_s,
    default:     default,
    required:    required,
    desc:        desc,
    placeholder: placeholder,
    switch:      switch,
    aliases:     aliases,
    usage:       usage.strip
  }
end

#usageObject



82
83
84
85
86
87
88
89
# File 'lib/hammer/option.rb', line 82

def usage
  flag = switch
  flag += " #{placeholder || name.to_s.upcase}" unless boolean?
  line = aliases.empty? ? flag : "#{aliases.join(', ')}, #{flag}"
  suffix = annotation
  line = "#{line.ljust(28)} #{desc}" if desc
  suffix.empty? ? line : "#{line} #{suffix}"
end