ruby-nuggets 0.9.1 → 0.9.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,130 @@
1
+ #--
2
+ ###############################################################################
3
+ # #
4
+ # A component of ruby-nuggets, some extensions to the Ruby programming #
5
+ # language. #
6
+ # #
7
+ # Copyright (C) 2007-2013 Jens Wille #
8
+ # #
9
+ # Authors: #
10
+ # Jens Wille <jens.wille@gmail.com> #
11
+ # #
12
+ # ruby-nuggets is free software; you can redistribute it and/or modify it #
13
+ # under the terms of the GNU Affero General Public License as published by #
14
+ # the Free Software Foundation; either version 3 of the License, or (at your #
15
+ # option) any later version. #
16
+ # #
17
+ # ruby-nuggets is distributed in the hope that it will be useful, but WITHOUT #
18
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
19
+ # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License #
20
+ # for more details. #
21
+ # #
22
+ # You should have received a copy of the GNU Affero General Public License #
23
+ # along with ruby-nuggets. If not, see <http://www.gnu.org/licenses/>. #
24
+ # #
25
+ ###############################################################################
26
+ #++
27
+
28
+ module Nuggets
29
+ module Midos
30
+
31
+ class Parser
32
+
33
+ # Record separator
34
+ DEFAULT_RS = '&&&'
35
+
36
+ # Field separator
37
+ DEFAULT_FS = ':'
38
+
39
+ # Value separator
40
+ DEFAULT_VS = ' | '
41
+
42
+ # Line break indicator
43
+ DEFAULT_NL = '^'
44
+
45
+ def self.parse(input, *args, &block)
46
+ parser = new(*args).parse(input, &block)
47
+ block_given? ? parser : parser.records
48
+ end
49
+
50
+ def initialize(options = {})
51
+ @key = options[:key]
52
+
53
+ @rs = options[:rs] || DEFAULT_RS
54
+ @fs = options[:fs] || DEFAULT_FS
55
+ @vs = options[:vs] || DEFAULT_VS
56
+ @vs = /\s*#{Regexp.escape(@vs)}\s*/ unless @vs.is_a?(Regexp)
57
+ @nl = options[:nl] || DEFAULT_NL
58
+
59
+ reset
60
+ end
61
+
62
+ attr_reader :rs, :fs, :vs, :key, :records
63
+
64
+ def reset
65
+ @records = {}
66
+ @auto_id = auto_id
67
+ end
68
+
69
+ def parse(input, &block)
70
+ unless block
71
+ records, block = @records, amend_block { |id, record|
72
+ records[id] = record
73
+ }
74
+ end
75
+
76
+ rs, fs, vs, nl, key, auto_id, id, record =
77
+ @rs, @fs, @vs, @nl, @key, @auto_id, nil, {}
78
+
79
+ input.each { |line|
80
+ line = line.chomp
81
+
82
+ if line == rs
83
+ block[key ? id : auto_id.call, record]
84
+ id, record = nil, {}
85
+ else
86
+ k, v = line.split(fs, 2)
87
+
88
+ if k && v
89
+ if k == key
90
+ id = v
91
+ else
92
+ v.gsub!(nl, "\n")
93
+ v = v.split(vs) if v.index(vs)
94
+ end
95
+
96
+ record[k] = v
97
+ end
98
+ end
99
+ }
100
+
101
+ self
102
+ end
103
+
104
+ private
105
+
106
+ def auto_id(n = 0)
107
+ lambda { n += 1 }
108
+ end
109
+
110
+ def amend_block(&block)
111
+ return block unless $VERBOSE && k = @key
112
+
113
+ r, i = block.binding.eval('_ = records, input')
114
+
115
+ l = i.respond_to?(:lineno)
116
+ s = i.respond_to?(:path) ? i.path : Object.instance_method(:inspect).bind(i).call
117
+
118
+ lambda { |id, *args|
119
+ if (r ||= block.binding.eval('records')).has_key?(id)
120
+ warn "Duplicate record in #{s}#{":#{i.lineno}" if l}: »#{k}:#{id}«"
121
+ end
122
+
123
+ block[id, *args]
124
+ }
125
+ end
126
+
127
+ end
128
+
129
+ end
130
+ end
@@ -0,0 +1,209 @@
1
+ #--
2
+ ###############################################################################
3
+ # #
4
+ # A component of ruby-nuggets, some extensions to the Ruby programming #
5
+ # language. #
6
+ # #
7
+ # Copyright (C) 2007-2013 Jens Wille #
8
+ # #
9
+ # Authors: #
10
+ # Jens Wille <jens.wille@gmail.com> #
11
+ # #
12
+ # ruby-nuggets is free software; you can redistribute it and/or modify it #
13
+ # under the terms of the GNU Affero General Public License as published by #
14
+ # the Free Software Foundation; either version 3 of the License, or (at your #
15
+ # option) any later version. #
16
+ # #
17
+ # ruby-nuggets is distributed in the hope that it will be useful, but WITHOUT #
18
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
19
+ # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License #
20
+ # for more details. #
21
+ # #
22
+ # You should have received a copy of the GNU Affero General Public License #
23
+ # along with ruby-nuggets. If not, see <http://www.gnu.org/licenses/>. #
24
+ # #
25
+ ###############################################################################
26
+ #++
27
+
28
+ require 'strscan'
29
+
30
+ module Nuggets
31
+ module MySQL
32
+
33
+ class Parser
34
+
35
+ DEFAULT_NAME = '__DEFAULT__'
36
+ DEFAULT_TABLE = '__DEFAULT__'
37
+
38
+ USE_RE = /\AUSE\s+`(.+?)`/i
39
+ CREATE_TABLE_RE = /\ACREATE\s+TABLE\s+`(.+?)`/i
40
+ TABLE_COLUMN_RE = /\A\s+`(.+?)`/
41
+ FINISH_TABLE_RE = /\A\).*;\Z/
42
+ INSERT_VALUES_RE = /\AINSERT\s+INTO\s+`(.+?)`\s+(?:\((.+?)\)\s+)?VALUES\s*(.*);\Z/i
43
+ CLEAN_COLUMNS_RE = /[\s`]+/
44
+
45
+ def self.parse(input, &block)
46
+ parser = new.parse(input, &block)
47
+ block_given? ? parser : parser.tables
48
+ end
49
+
50
+ def initialize
51
+ reset
52
+ end
53
+
54
+ def reset
55
+ @name = DEFAULT_NAME
56
+ @table = DEFAULT_TABLE
57
+ @tables = {}
58
+ @columns = Hash.new { |h, k| h[k] = [] }
59
+ @value_parser = ValueParser.new
60
+ end
61
+
62
+ attr_reader :tables
63
+
64
+ def parse(input, &block)
65
+ unless block
66
+ tables, block = @tables, lambda { |_, name, table, columns, values|
67
+ ((tables[name] ||= {})[table] ||= []) << fields = {}
68
+
69
+ values.each_with_index { |value, index|
70
+ if column = columns[index]
71
+ fields[column] = value
72
+ end
73
+ }
74
+ }
75
+ end
76
+
77
+ name, table, columns, value_parser, block_given =
78
+ @name, @table, @columns, @value_parser, block_given?
79
+
80
+ input.each { |line|
81
+ case line
82
+ when USE_RE
83
+ name = $1
84
+ yield :use, name if block_given
85
+ when CREATE_TABLE_RE
86
+ table = $1
87
+ when TABLE_COLUMN_RE
88
+ columns[table] << $1 if table
89
+ when FINISH_TABLE_RE
90
+ yield :table, name, table, columns[table] if block_given
91
+ table = nil
92
+ when INSERT_VALUES_RE
93
+ _table, _columns, _values = $1, $2, $3
94
+
95
+ _columns = _columns.nil? ? columns[_table] :
96
+ _columns.gsub(CLEAN_COLUMNS_RE, '').split(',')
97
+
98
+ value_parser.parse(_values) { |values|
99
+ block[:insert, name, _table, _columns, values]
100
+ } unless _columns.empty?
101
+ end
102
+ }
103
+
104
+ @name, @table = name, table
105
+
106
+ self
107
+ end
108
+
109
+ end
110
+
111
+ class ValueParser
112
+
113
+ AST = Struct.new(:value)
114
+
115
+ def self.parse(input)
116
+ new.parse(input)
117
+ end
118
+
119
+ def parse(input)
120
+ @input = StringScanner.new(input)
121
+
122
+ rows, block_given = [], block_given?
123
+
124
+ while result = parse_row
125
+ row = result.value
126
+ block_given ? yield(row) : rows << row
127
+ break unless @input.scan(/,\s*/)
128
+ end
129
+
130
+ @input.scan(/;/) # optional
131
+
132
+ error('Unexpected data') unless @input.eos?
133
+
134
+ rows unless block_given
135
+ end
136
+
137
+ def parse_row
138
+ return unless @input.scan(/\(/)
139
+
140
+ row = []
141
+
142
+ while result = parse_value
143
+ row << result.value
144
+ break unless @input.scan(/,\s*/)
145
+ end
146
+
147
+ error('Unclosed row') unless @input.scan(/\)/)
148
+
149
+ AST.new(row)
150
+ end
151
+
152
+ def parse_value
153
+ parse_string ||
154
+ parse_number ||
155
+ parse_keyword
156
+ end
157
+
158
+ def parse_string
159
+ return unless @input.scan(/'/)
160
+
161
+ string = ''
162
+
163
+ while contents = parse_string_content || parse_string_escape
164
+ string << contents.value
165
+ end
166
+
167
+ error('Unclosed string') unless @input.scan(/'/)
168
+
169
+ AST.new(string)
170
+ end
171
+
172
+ def parse_string_content
173
+ if @input.scan(/[^\\']+/)
174
+ AST.new(@input.matched)
175
+ end
176
+ end
177
+
178
+ def parse_string_escape
179
+ if @input.scan(/\\[abtnvfr]/)
180
+ AST.new(eval(%Q{"#{@input.matched}"}))
181
+ elsif @input.scan(/\\.|''/)
182
+ AST.new(@input.matched[-1, 1])
183
+ end
184
+ end
185
+
186
+ def parse_number
187
+ if @input.scan(/-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?/)
188
+ AST.new(eval(@input.matched))
189
+ end
190
+ end
191
+
192
+ def parse_keyword
193
+ if @input.scan(/null/i)
194
+ AST.new(nil)
195
+ end
196
+ end
197
+
198
+ def error(message)
199
+ if @input.eos?
200
+ raise "Unexpected end of input (#{message})."
201
+ else
202
+ raise "#{message} at #{$.}:#{@input.pos}: #{@input.peek(16).inspect}"
203
+ end
204
+ end
205
+
206
+ end
207
+
208
+ end
209
+ end
@@ -0,0 +1,92 @@
1
+ #--
2
+ ###############################################################################
3
+ # #
4
+ # A component of ruby-nuggets, some extensions to the Ruby programming #
5
+ # language. #
6
+ # #
7
+ # Copyright (C) 2007-2011 Jens Wille #
8
+ # #
9
+ # Authors: #
10
+ # Jens Wille <jens.wille@gmail.com> #
11
+ # #
12
+ # ruby-nuggets is free software; you can redistribute it and/or modify it #
13
+ # under the terms of the GNU Affero General Public License as published by #
14
+ # the Free Software Foundation; either version 3 of the License, or (at your #
15
+ # option) any later version. #
16
+ # #
17
+ # ruby-nuggets is distributed in the hope that it will be useful, but WITHOUT #
18
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
19
+ # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License #
20
+ # for more details. #
21
+ # #
22
+ # You should have received a copy of the GNU Affero General Public License #
23
+ # along with ruby-nuggets. If not, see <http://www.gnu.org/licenses/>. #
24
+ # #
25
+ ###############################################################################
26
+ #++
27
+
28
+ require 'set'
29
+
30
+ module Nuggets
31
+ module Pluggable
32
+
33
+ def self.load_plugins_for(*klasses)
34
+ klasses.map { |klass| klass.extend(self).load_plugins }
35
+ end
36
+
37
+ def load_plugins(name = plugin_filename)
38
+ load_path_plugins(name)
39
+ load_gem_plugins(name)
40
+ load_env_plugins(name)
41
+
42
+ loaded_plugins.to_a
43
+ end
44
+
45
+ def loaded_plugins
46
+ @loaded_plugins ||= Set.new
47
+ end
48
+
49
+ def plugin_filename
50
+ @plugin_filename ||= "#{name.
51
+ gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2').
52
+ gsub(/([a-z\d])([A-Z])/, '\1_\2').
53
+ gsub(/::/, '/').tr('-', '_').downcase}_plugin.rb"
54
+ end
55
+
56
+ attr_writer :plugin_filename
57
+
58
+ private
59
+
60
+ def load_path_plugins(name)
61
+ $LOAD_PATH.dup.each { |path|
62
+ plugin = ::File.expand_path(name, path)
63
+ load_plugin_file(plugin) if ::File.file?(plugin)
64
+ }
65
+ end
66
+
67
+ def load_gem_plugins(name)
68
+ ::Gem::Specification.latest_specs.each { |spec|
69
+ load_plugin_files(spec.matches_for_glob(name))
70
+ } if ::Object.const_defined?(:Gem)
71
+ end
72
+
73
+ def load_env_plugins(name)
74
+ path = ::ENV["#{name.chomp(ext = ::File.extname(name)).upcase}_PATH"]
75
+ path.split(::File::PATH_SEPARATOR).each { |dir|
76
+ load_plugin_files(::Dir["#{dir}/*#{ext}"])
77
+ } if path
78
+ end
79
+
80
+ def load_plugin_file(plugin)
81
+ load plugin if loaded_plugins.add?(plugin)
82
+ rescue ::Exception => err
83
+ raise unless loaded_plugins.delete?(plugin)
84
+ warn "Error loading #{name} plugin: #{plugin}: #{err} (#{err.class})"
85
+ end
86
+
87
+ def load_plugin_files(plugins)
88
+ plugins.each { |plugin| load_plugin_file(::File.expand_path(plugin)) }
89
+ end
90
+
91
+ end
92
+ end