summaryrefslogtreecommitdiffstats
path: root/lib/puppet/interface/option.rb
blob: 2abcd40339ae534dc711297037afb2f40c0697ba (plain)
1
2
3
4
5
6
7
8
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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
require 'puppet/interface'

class Puppet::Interface::Option
  def initialize(parent, *declaration, &block)
    @parent   = parent
    @optparse = []

    # Collect and sort the arguments in the declaration.
    dups = {}
    declaration.each do |item|
      if item.is_a? String and item.to_s =~ /^-/ then
        unless item =~ /^-[a-z]\b/ or item =~ /^--[^-]/ then
          raise ArgumentError, "#{item.inspect}: long options need two dashes (--)"
        end
        @optparse << item

        # Duplicate checking...
        name = optparse_to_name(item)
        if dup = dups[name] then
          raise ArgumentError, "#{item.inspect}: duplicates existing alias #{dup.inspect} in #{@parent}"
        else
          dups[name] = item
        end
      else
        raise ArgumentError, "#{item.inspect} is not valid for an option argument"
      end
    end

    if @optparse.empty? then
      raise ArgumentError, "No option declarations found while building"
    end

    # Now, infer the name from the options; we prefer the first long option as
    # the name, rather than just the first option.
    @name = optparse_to_name(@optparse.find do |a| a =~ /^--/ end || @optparse.first)
    @aliases = @optparse.map { |o| optparse_to_name(o) }

    # Do we take an argument?  If so, are we consistent about it, because
    # incoherence here makes our life super-difficult, and we can more easily
    # relax this rule later if we find a valid use case for it. --daniel 2011-03-30
    @argument = @optparse.any? { |o| o =~ /[ =]/ }
    if @argument and not @optparse.all? { |o| o =~ /[ =]/ } then
      raise ArgumentError, "Option #{@name} is inconsistent about taking an argument"
    end

    # Is our argument optional?  The rules about consistency apply here, also,
    # just like they do to taking arguments at all. --daniel 2011-03-30
    @optional_argument = @optparse.any? { |o| o.include? "[" }
    if @optional_argument and not @optparse.all? { |o| o.include? "[" } then
      raise ArgumentError, "Option #{@name} is inconsistent about the argument being optional"
    end
  end

  # to_s and optparse_to_name are roughly mirrored, because they are used to
  # transform options to name symbols, and vice-versa.  This isn't a full
  # bidirectional transformation though. --daniel 2011-04-07
  def to_s
    @name.to_s.tr('_', '-')
  end

  def optparse_to_name(declaration)
    unless found = declaration.match(/^-+(?:\[no-\])?([^ =]+)/) then
      raise ArgumentError, "Can't find a name in the declaration #{declaration.inspect}"
    end
    name = found.captures.first.tr('-', '_')
    raise "#{name.inspect} is an invalid option name" unless name.to_s =~ /^[a-z]\w*$/
    name.to_sym
  end


  def takes_argument?
    !!@argument
  end
  def optional_argument?
    !!@optional_argument
  end


  attr_reader   :parent, :name, :aliases, :optparse
  attr_accessor :desc

  attr_accessor :before_action
  def before_action=(proc)
    proc.is_a? Proc or raise ArgumentError, "before action hook for #{self} is a #{proc.class.name.inspect}, not a proc"
    @before_action =
      @parent.__send__(:__decorate, :before, __decoration_name(:before), proc)
  end

  attr_accessor :after_action
  def after_action=(proc)
    proc.is_a? Proc or raise ArgumentError, "after action hook for #{self} is a #{proc.class.name.inspect}, not a proc"
    @after_action =
      @parent.__send__(:__decorate, :after, __decoration_name(:after), proc)
  end

  def __decoration_name(type)
    if @parent.is_a? Puppet::Interface::Action then
      :"option #{name} from #{parent.name} #{type} decoration"
    else
      :"option #{name} #{type} decoration"
    end
  end
end