ruby-nuggets 0.9.1 → 0.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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