Class: Clive::Parser

Inherits:
Object
  • Object
show all
Defined in:
lib/clive/parser.rb

Defined Under Namespace

Classes: MissingArgumentError, MissingOptionError

Constant Summary collapse

DEFAULTS =
{
  :state => ::Clive::StructHash
}

Instance Method Summary collapse

Constructor Details

#initialize(base, config) ⇒ Parser

Returns a new instance of Parser.

Parameters:

Options Hash (config):

  • :state (.new, #[], #[]=, #alias)

    What class the state should be


22
23
24
25
# File 'lib/clive/parser.rb', line 22

def initialize(base, config)
  @base = base
  @config = DEFAULTS.merge(config)
end

Instance Method Details

#parse(argv, pre_state) ⇒ Object

The parser should work how you expect. It allows you to put global options before and after a command section (if it exists, which it doesn't), so you have something like.

app [global] [command] [global]

Where the [global] sections are made of options and arguments and

command

is made of

command
options/args

Only one command can be run, if you attempt to use two the other will be caught as an argument.

Parameters:

  • argv (Array)

    The input to parse from the command line, usually ARGV.

  • pre_state (Hash)

    A pre-populated state to be used.


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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
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
159
160
# File 'lib/clive/parser.rb', line 47

def parse(argv, pre_state)
  @argv = argv
  @i    = 0

  @state = @config[:state].new(pre_state)
  @state.store :args, []

  # Pull out 'help' command immediately if found
  if @argv[0] == 'help'
    if @argv[1]
      if @base.has?(@argv[1])
        command = @base.find(@argv[1])
        command.run_block({})
        puts command.help
      else
        puts "Error: command #{@argv[1]} could not be found. Try `help` to see the available commands."
      end
    else
      puts @base.help
    end
    Kernel.exit 0
  end

  until ended?
    # does +curr+ exist? (and also check that if it is a command a command hasn't been run yet
    if @base.has?(curr) && ((@base.find(curr).kind_of?(Command) && !command_ran?) || @base.find(curr).kind_of?(Option))

      found = @base.find(curr)

      # is it a command?
      if found.kind_of?(Command)
        @command_ran = true
        @state.store found.names, found.run_block(@config[:state].new)

        inc
        args = []

        until ended?
          # it's a no- option
          if curr[0..4] == '--no-' && found.has?("--#{curr[5..-1]}") && found.find("--#{curr[5..-1]}").config[:boolean] == true
            found.find("--#{curr[5..-1]}").run @state, [false], found

          # it's one (or more) short options
          elsif curr[0..0] == '-' && curr.size > 2 && found.has?("-#{curr[1..1]}")
            currs = curr[1..-1].split('').map {|i| "-#{i}" }

            currs.each do |c|
              opt = found.find(c)
              raise MissingOptionError.new(c) unless opt

              if c == currs.last
                run_option opt, found
              else
                # can't take any arguments as an option is next to it
                if opt.args.min > 0
                  raise MissingArgumentError.new(opt, [], opt.args)
                else
                  opt.run @state, [true], found
                end
              end
            end

          elsif found.has?(curr)
            run_option found.find(curr), found

          else
            break unless found.args.possible?(args + [curr])
            args << curr
          end
          inc
        end
        dec

        found.run @state, validate_arguments(found, args), found

      # otherwise it is an option
      else
        run_option found
      end

    # it's a no- option
    elsif curr[0..4] == '--no-' && @base.has?("--#{curr[5..-1]}") && @base.find("--#{curr[5..-1]}").config[:boolean] == true
      @base.find("--#{curr[5..-1]}").run @state, [false]

    # it's one (or more) short options
    elsif curr[0..0] == '-' && curr.size > 2 && @base.has?("-#{curr[1..1]}")
      currs = curr[1..-1].split('').map {|i| "-#{i}" }

      currs.each do |c|
        opt = @base.find(c)
        raise MissingOptionError.new(c) unless opt

        if c == currs.last
          run_option opt
        else
          # can't take any arguments as an option is next to it
          if opt.args.min > 0
            raise MissingArgumentError.new(opt, [], opt.args)
          else
            opt.run @state, [true]
          end
        end
      end

    # otherwise it is an argument
    else
      @state.args << curr
    end

    inc
  end

  @state
end