scanner.rb

David Lutterkort, 04/08/2009 09:40 pm

Download (2.7 kB)

 
1
require 'strscan'
2

    
3
COMMANDS = {
4
  "set" => [ :path, :string ],
5
  "rm" => [ :path ],
6
  "clear" => [ :path ],
7
  "insert" => [ :string, :string, :path ]
8
}
9

    
10
COMMANDS["ins"] = COMMANDS["insert"]
11
COMMANDS["remove"] = COMMANDS["rm"]
12

    
13
# In reality, the value of the context attribute
14
CONTEXT = "/context/"
15

    
16
def parse_commands(data)
17
  if data.is_a?(String)
18
    s = data
19
    data = []
20
    s.each_line { |line| data << line }
21
  end
22
  args = []
23
  data.each do |line|
24
    sc = StringScanner.new(line)
25
    cmd = sc.scan(/\w+/)
26
    formals = COMMANDS[cmd]
27
    raise Exception, "Unknown command #{cmd}" unless formals
28
    args << cmd
29
    narg = 0
30
    formals.each do |f|
31
      sc.skip(/\s+/)
32
      narg += 1
33
      if f == :path
34
        start = sc.pos
35
        nbracket = 0
36
        begin
37
          sc.skip(/[^\]\[\s]+/)
38
          ch = sc.getch
39
          nbracket += 1 if ch == "["
40
          nbracket -= 1 if ch == "]"
41
          raise Exception, "unmatched [" if nbracket < 0
42
        end until nbracket == 0 && (sc.eos? || ch =~ /\s/)
43
        len = sc.pos - start
44
        len -= 1 unless sc.eos?
45
        unless p = sc.string[start, len]
46
          raise Exception, "missing path argument #{narg} for #{cmd}"
47
        end
48
        if p[0,1] != "$" && p[0,1] != "/"
49
          args << CONTEXT + p
50
        else
51
          args << p
52
        end
53
      elsif f == :string
54
        delim = sc.peek(1)
55
        if delim == "'" || delim == "\""
56
          sc.getch
57
          args << sc.scan(/([^\\#{delim}]|(\\.))*/)
58
          sc.getch
59
        else
60
          args << sc.scan(/[^\s]+/)
61
        end
62
        unless args[-1]
63
          raise Exception, "missing string argument #{narg} for #{cmd}"
64
        end
65
      end
66
    end
67
  end
68
  return args
69
end
70

    
71
TESTS = [ ]
72
TESTS << [ "rm */*[module='pam_console.so']",
73
           ["rm", "/context/*/*[module='pam_console.so']"] ]
74
TESTS << [ "ins 42 before /files/etc/hosts/*/ipaddr[ . = '127.0.0.1' ]",
75
            ["ins", "42", "before",
76
             "/files/etc/hosts/*/ipaddr[ . = '127.0.0.1' ]"] ]
77
TESTS << [ "set /files/etc/*/*[ipaddr = '127.0.0.1'] \"foo bar\"",
78
           ["set", "/files/etc/*/*[ipaddr = '127.0.0.1']", "foo bar"] ]
79
TESTS << [ "clear pam.d/*/*[module = 'system-auth'][type = 'account']",
80
           ["clear",
81
            "/context/pam.d/*/*[module = 'system-auth'][type = 'account']"] ]
82
TESTS << [ "set /foo 'hello \"there\"'" , ["set", "/foo", "hello \"there\""] ]
83
TESTS << [ "set /foo \"''\\\"''\"", [ "set", "/foo", "''\\\"''" ] ]
84

    
85
TESTS.each do |t|
86
  exp = t[1]
87
  begin
88
    act = parse_commands(t[0])
89
    if exp != act
90
      puts "Error parsing #{t[0]}"
91
      puts "  expected: #{exp.inspect}"
92
      puts "  actual:   #{act.inspect}"
93
    end
94
  rescue Exception => e
95
    puts "Exception parsing #{t[0]}"
96
    puts "  #{e.message}"
97
  end
98
end