bismas 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/COPYING +663 -0
- data/ChangeLog +11 -0
- data/README +41 -0
- data/Rakefile +23 -0
- data/bin/bismas +30 -0
- data/lib/bismas/base.rb +73 -0
- data/lib/bismas/cli.rb +139 -0
- data/lib/bismas/mapping.rb +96 -0
- data/lib/bismas/parser.rb +167 -0
- data/lib/bismas/reader.rb +90 -0
- data/lib/bismas/version.rb +27 -0
- data/lib/bismas/writer.rb +105 -0
- data/lib/bismas.rb +146 -0
- data/spec/bismas/parser_spec.rb +18 -0
- data/spec/bismas/reader_spec.rb +22 -0
- data/spec/data/test.dat +0 -0
- data/spec/spec_helper.rb +15 -0
- metadata +153 -0
data/ChangeLog
ADDED
data/README
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
= bismas - A Ruby client for BISMAS databases
|
2
|
+
|
3
|
+
== VERSION
|
4
|
+
|
5
|
+
This documentation refers to bismas version 0.1.0.
|
6
|
+
|
7
|
+
|
8
|
+
== DESCRIPTION
|
9
|
+
|
10
|
+
Access BISMAS databases from Ruby.
|
11
|
+
|
12
|
+
|
13
|
+
== LINKS
|
14
|
+
|
15
|
+
Documentation:: https://blackwinter.github.com/bismas
|
16
|
+
Source code:: https://github.com/blackwinter/bismas
|
17
|
+
RubyGem:: https://rubygems.org/gems/bismas
|
18
|
+
Travis CI:: https://travis-ci.org/blackwinter/bismas
|
19
|
+
|
20
|
+
|
21
|
+
== AUTHORS
|
22
|
+
|
23
|
+
* Jens Wille <mailto:jens.wille@gmail.com>
|
24
|
+
|
25
|
+
|
26
|
+
== LICENSE AND COPYRIGHT
|
27
|
+
|
28
|
+
Copyright (C) 2015 Jens Wille
|
29
|
+
|
30
|
+
bismas is free software: you can redistribute it and/or modify it
|
31
|
+
under the terms of the GNU Affero General Public License as published by
|
32
|
+
the Free Software Foundation, either version 3 of the License, or (at your
|
33
|
+
option) any later version.
|
34
|
+
|
35
|
+
bismas is distributed in the hope that it will be useful, but
|
36
|
+
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
37
|
+
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
38
|
+
License for more details.
|
39
|
+
|
40
|
+
You should have received a copy of the GNU Affero General Public License
|
41
|
+
along with bismas. If not, see <http://www.gnu.org/licenses/>.
|
data/Rakefile
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require_relative 'lib/bismas/version'
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'hen'
|
5
|
+
|
6
|
+
Hen.lay! {{
|
7
|
+
gem: {
|
8
|
+
name: %q{bismas},
|
9
|
+
version: Bismas::VERSION,
|
10
|
+
summary: %q{A Ruby client for BISMAS databases.},
|
11
|
+
description: %q{Access BISMAS databases from Ruby.},
|
12
|
+
author: %q{Jens Wille},
|
13
|
+
email: %q{jens.wille@gmail.com},
|
14
|
+
license: %q{AGPL-3.0},
|
15
|
+
homepage: :blackwinter,
|
16
|
+
dependencies: { cyclops: '~> 0.2', nuggets: '~> 1.4' },
|
17
|
+
|
18
|
+
required_ruby_version: '>= 2.0'
|
19
|
+
}
|
20
|
+
}}
|
21
|
+
rescue LoadError => err
|
22
|
+
warn "Please install the `hen' gem. (#{err})"
|
23
|
+
end
|
data/bin/bismas
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
#! /usr/bin/env ruby
|
2
|
+
|
3
|
+
#--
|
4
|
+
###############################################################################
|
5
|
+
# #
|
6
|
+
# bismas -- A Ruby client for BISMAS databases #
|
7
|
+
# #
|
8
|
+
# Copyright (C) 2015 Jens Wille #
|
9
|
+
# #
|
10
|
+
# Authors: #
|
11
|
+
# Jens Wille <jens.wille@gmail.com> #
|
12
|
+
# #
|
13
|
+
# bismas is free software; you can redistribute it and/or modify it #
|
14
|
+
# under the terms of the GNU Affero General Public License as published by #
|
15
|
+
# the Free Software Foundation; either version 3 of the License, or (at your #
|
16
|
+
# option) any later version. #
|
17
|
+
# #
|
18
|
+
# bismas is distributed in the hope that it will be useful, but #
|
19
|
+
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY #
|
20
|
+
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public #
|
21
|
+
# License for more details. #
|
22
|
+
# #
|
23
|
+
# You should have received a copy of the GNU Affero General Public License #
|
24
|
+
# along with bismas. If not, see <http://www.gnu.org/licenses/>. #
|
25
|
+
# #
|
26
|
+
###############################################################################
|
27
|
+
#++
|
28
|
+
|
29
|
+
require 'bismas/cli'
|
30
|
+
Bismas::CLI.execute
|
data/lib/bismas/base.rb
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
#--
|
2
|
+
###############################################################################
|
3
|
+
# #
|
4
|
+
# bismas -- A Ruby client for BISMAS databases #
|
5
|
+
# #
|
6
|
+
# Copyright (C) 2015 Jens Wille #
|
7
|
+
# #
|
8
|
+
# Authors: #
|
9
|
+
# Jens Wille <jens.wille@gmail.com> #
|
10
|
+
# #
|
11
|
+
# bismas is free software; you can redistribute it and/or modify it #
|
12
|
+
# under the terms of the GNU Affero General Public License as published by #
|
13
|
+
# the Free Software Foundation; either version 3 of the License, or (at your #
|
14
|
+
# option) any later version. #
|
15
|
+
# #
|
16
|
+
# bismas is distributed in the hope that it will be useful, but #
|
17
|
+
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY #
|
18
|
+
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public #
|
19
|
+
# License for more details. #
|
20
|
+
# #
|
21
|
+
# You should have received a copy of the GNU Affero General Public License #
|
22
|
+
# along with bismas. If not, see <http://www.gnu.org/licenses/>. #
|
23
|
+
# #
|
24
|
+
###############################################################################
|
25
|
+
#++
|
26
|
+
|
27
|
+
require 'nuggets/file/open_file'
|
28
|
+
require 'nuggets/array/extract_options'
|
29
|
+
|
30
|
+
module Bismas
|
31
|
+
|
32
|
+
class Base
|
33
|
+
|
34
|
+
class << self
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def file_method(method, mode, file, options = {}, *args, &block)
|
39
|
+
Bismas.amend_encoding(options)
|
40
|
+
|
41
|
+
File.open_file(file, options, mode) { |io|
|
42
|
+
args.unshift(options.merge(io: io))
|
43
|
+
method ? send(method, *args, &block) : block[new(*args)]
|
44
|
+
}
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
def initialize(options = {}, &block)
|
50
|
+
self.key = options[:key]
|
51
|
+
self.io = options.fetch(:io, self.class::DEFAULT_IO)
|
52
|
+
|
53
|
+
@auto_id_block = options.fetch(:auto_id, block)
|
54
|
+
@options = options
|
55
|
+
|
56
|
+
reset
|
57
|
+
end
|
58
|
+
|
59
|
+
attr_accessor :key, :io, :auto_id
|
60
|
+
|
61
|
+
def reset
|
62
|
+
@auto_id = @auto_id_block ? @auto_id_block.call : default_auto_id
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def default_auto_id(n = 0)
|
68
|
+
lambda { n += 1 }
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
data/lib/bismas/cli.rb
ADDED
@@ -0,0 +1,139 @@
|
|
1
|
+
#--
|
2
|
+
###############################################################################
|
3
|
+
# #
|
4
|
+
# bismas -- A Ruby client for BISMAS databases #
|
5
|
+
# #
|
6
|
+
# Copyright (C) 2015 Jens Wille #
|
7
|
+
# #
|
8
|
+
# Authors: #
|
9
|
+
# Jens Wille <jens.wille@gmail.com> #
|
10
|
+
# #
|
11
|
+
# bismas is free software; you can redistribute it and/or modify it #
|
12
|
+
# under the terms of the GNU Affero General Public License as published by #
|
13
|
+
# the Free Software Foundation; either version 3 of the License, or (at your #
|
14
|
+
# option) any later version. #
|
15
|
+
# #
|
16
|
+
# bismas is distributed in the hope that it will be useful, but #
|
17
|
+
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY #
|
18
|
+
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public #
|
19
|
+
# License for more details. #
|
20
|
+
# #
|
21
|
+
# You should have received a copy of the GNU Affero General Public License #
|
22
|
+
# along with bismas. If not, see <http://www.gnu.org/licenses/>. #
|
23
|
+
# #
|
24
|
+
###############################################################################
|
25
|
+
#++
|
26
|
+
|
27
|
+
require 'cyclops'
|
28
|
+
require 'bismas'
|
29
|
+
|
30
|
+
module Bismas
|
31
|
+
|
32
|
+
class CLI < Cyclops
|
33
|
+
|
34
|
+
TYPES = %w[dat dbm]
|
35
|
+
|
36
|
+
class << self
|
37
|
+
|
38
|
+
def defaults
|
39
|
+
super.merge(
|
40
|
+
config: 'config.yaml',
|
41
|
+
input: '-',
|
42
|
+
output: '-',
|
43
|
+
type: TYPES.first,
|
44
|
+
key_format: '%s'
|
45
|
+
)
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
def run(arguments)
|
51
|
+
quit unless arguments.empty?
|
52
|
+
|
53
|
+
klass = case type = options[:type]
|
54
|
+
when 'dat'
|
55
|
+
Writer
|
56
|
+
when 'dbm'
|
57
|
+
require 'midos'
|
58
|
+
options[:output_encoding] ||= Midos::DEFAULT_ENCODING
|
59
|
+
Midos::Writer
|
60
|
+
else
|
61
|
+
quit "Unsupported type: #{type}. Must be one of: #{TYPES.join(', ')}."
|
62
|
+
end
|
63
|
+
|
64
|
+
Bismas.filter(klass, options, &method(:quit))
|
65
|
+
rescue LoadError => err
|
66
|
+
abort "Please install the `#{File.dirname(err.path)}' gem. (#{err})"
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def opts(opts)
|
72
|
+
opts.option(:input__FILE, 'Path to input file [Default: STDIN]')
|
73
|
+
|
74
|
+
opts.option(:output__FILE, 'Path to output file [Default: STDOUT]')
|
75
|
+
|
76
|
+
opts.separator
|
77
|
+
opts.separator 'Writer options:'
|
78
|
+
|
79
|
+
opts.option(:type__TYPE, "Output file type (#{TYPES.join(', ')}) [Default: #{TYPES.first}]")
|
80
|
+
|
81
|
+
opts.separator
|
82
|
+
|
83
|
+
opts.option(:output_encoding__ENCODING, :n, 'Output encoding [Default: depends on TYPE]')
|
84
|
+
|
85
|
+
opts.separator
|
86
|
+
|
87
|
+
opts.option(:output_key__KEY, :k, 'ID key of output file')
|
88
|
+
opts.option(:key_format__KEY_FORMAT, :f, 'Key format [Default: %s]')
|
89
|
+
|
90
|
+
opts.separator
|
91
|
+
|
92
|
+
opts.option(:mapping__FILE_OR_YAML, 'Path to mapping file or YAML string')
|
93
|
+
|
94
|
+
opts.separator
|
95
|
+
|
96
|
+
opts.switch(:sort, 'Sort each record')
|
97
|
+
|
98
|
+
opts.separator
|
99
|
+
|
100
|
+
opts.option(:execute__CODE, 'Code to execute for each _record_ before mapping') { |e|
|
101
|
+
options[:execute] << e
|
102
|
+
}
|
103
|
+
|
104
|
+
opts.option(:execute_mapped__CODE, :E, 'Code to execute for each _record_ after mapping') { |e|
|
105
|
+
options[:execute_mapped] << e
|
106
|
+
}
|
107
|
+
|
108
|
+
opts.separator
|
109
|
+
|
110
|
+
opts.option(:padding_length__LENGTH, :P, Integer, "Length of padding for TYPE=dat [Default: #{DEFAULT_PADDING_LENGTH}]")
|
111
|
+
|
112
|
+
opts.separator
|
113
|
+
opts.separator 'Reader options:'
|
114
|
+
|
115
|
+
opts.option(:input_encoding__ENCODING, :N, "Input encoding [Default: #{DEFAULT_ENCODING}]")
|
116
|
+
|
117
|
+
opts.separator
|
118
|
+
|
119
|
+
opts.option(:input_key__KEY, :K, 'ID key of input file')
|
120
|
+
|
121
|
+
opts.separator
|
122
|
+
|
123
|
+
opts.switch(:strict, :S, 'Turn parse warnings into errors')
|
124
|
+
|
125
|
+
opts.switch(:silent, :T, 'Silence parse warnings')
|
126
|
+
|
127
|
+
opts.separator
|
128
|
+
|
129
|
+
opts.switch(:legacy, :L, 'Use the legacy parser')
|
130
|
+
|
131
|
+
opts.separator
|
132
|
+
opts.separator 'Common options:'
|
133
|
+
|
134
|
+
opts.option(:category_length__LENGTH, :C, Integer, "Length of category for TYPE=dat [Default: #{DEFAULT_CATEGORY_LENGTH}]")
|
135
|
+
end
|
136
|
+
|
137
|
+
end
|
138
|
+
|
139
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
#--
|
2
|
+
###############################################################################
|
3
|
+
# #
|
4
|
+
# bismas -- A Ruby client for BISMAS databases #
|
5
|
+
# #
|
6
|
+
# Copyright (C) 2015 Jens Wille #
|
7
|
+
# #
|
8
|
+
# Authors: #
|
9
|
+
# Jens Wille <jens.wille@gmail.com> #
|
10
|
+
# #
|
11
|
+
# bismas is free software; you can redistribute it and/or modify it #
|
12
|
+
# under the terms of the GNU Affero General Public License as published by #
|
13
|
+
# the Free Software Foundation; either version 3 of the License, or (at your #
|
14
|
+
# option) any later version. #
|
15
|
+
# #
|
16
|
+
# bismas is distributed in the hope that it will be useful, but #
|
17
|
+
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY #
|
18
|
+
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public #
|
19
|
+
# License for more details. #
|
20
|
+
# #
|
21
|
+
# You should have received a copy of the GNU Affero General Public License #
|
22
|
+
# along with bismas. If not, see <http://www.gnu.org/licenses/>. #
|
23
|
+
# #
|
24
|
+
###############################################################################
|
25
|
+
#++
|
26
|
+
|
27
|
+
module Bismas
|
28
|
+
|
29
|
+
class Mapping
|
30
|
+
|
31
|
+
DEFAULT_MAPPING = true
|
32
|
+
|
33
|
+
LITERALS = { '~' => nil, 'false' => false, 'true' => true }
|
34
|
+
|
35
|
+
NULL = Object.new.tap { |null| def null.apply(hash); hash; end }
|
36
|
+
|
37
|
+
def self.[](mapping)
|
38
|
+
mapping ? new(mapping) : NULL
|
39
|
+
end
|
40
|
+
|
41
|
+
def initialize(mapping)
|
42
|
+
@mapping = default_hash.update(default: Array(DEFAULT_MAPPING))
|
43
|
+
|
44
|
+
mapping.each { |key, value|
|
45
|
+
value = Array(value.is_a?(String) ? range(value) : value)
|
46
|
+
|
47
|
+
!key.is_a?(String) ? @mapping[key] = value :
|
48
|
+
range(key) { |m| @mapping[m].concat(value) }
|
49
|
+
}
|
50
|
+
|
51
|
+
@mapping.each_value(&:uniq!)
|
52
|
+
end
|
53
|
+
|
54
|
+
def apply(hash, new_hash = default_hash)
|
55
|
+
hash.each { |key, value| map(key) { |new_key|
|
56
|
+
new_hash[new_key].concat(value)
|
57
|
+
} }
|
58
|
+
|
59
|
+
new_hash
|
60
|
+
end
|
61
|
+
|
62
|
+
def [](key)
|
63
|
+
map(key).to_a
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def default_hash
|
69
|
+
Hash.new { |h, k| h[k] = [] }
|
70
|
+
end
|
71
|
+
|
72
|
+
def range(list, &block)
|
73
|
+
return enum_for(__method__, list) unless block
|
74
|
+
|
75
|
+
list.split(/\s*,\s*/).each { |part|
|
76
|
+
LITERALS.key?(part) ? block[LITERALS[part]] : begin
|
77
|
+
from, to = part.split('-')
|
78
|
+
from.upto(to || from, &block)
|
79
|
+
end
|
80
|
+
}
|
81
|
+
end
|
82
|
+
|
83
|
+
def fetch(key)
|
84
|
+
@mapping.key?(key) ? @mapping[key] : @mapping[:default]
|
85
|
+
end
|
86
|
+
|
87
|
+
def map(key)
|
88
|
+
return enum_for(__method__, key) unless block_given?
|
89
|
+
|
90
|
+
fetch(key).each { |new_key|
|
91
|
+
yield new_key == true ? key : new_key if new_key }
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
@@ -0,0 +1,167 @@
|
|
1
|
+
#--
|
2
|
+
###############################################################################
|
3
|
+
# #
|
4
|
+
# bismas -- A Ruby client for BISMAS databases #
|
5
|
+
# #
|
6
|
+
# Copyright (C) 2015 Jens Wille #
|
7
|
+
# #
|
8
|
+
# Authors: #
|
9
|
+
# Jens Wille <jens.wille@gmail.com> #
|
10
|
+
# #
|
11
|
+
# bismas is free software; you can redistribute it and/or modify it #
|
12
|
+
# under the terms of the GNU Affero General Public License as published by #
|
13
|
+
# the Free Software Foundation; either version 3 of the License, or (at your #
|
14
|
+
# option) any later version. #
|
15
|
+
# #
|
16
|
+
# bismas is distributed in the hope that it will be useful, but #
|
17
|
+
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY #
|
18
|
+
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public #
|
19
|
+
# License for more details. #
|
20
|
+
# #
|
21
|
+
# You should have received a copy of the GNU Affero General Public License #
|
22
|
+
# along with bismas. If not, see <http://www.gnu.org/licenses/>. #
|
23
|
+
# #
|
24
|
+
###############################################################################
|
25
|
+
#++
|
26
|
+
|
27
|
+
require 'strscan'
|
28
|
+
|
29
|
+
module Bismas
|
30
|
+
|
31
|
+
# 0. Each _record_ is terminated by +0x0D+ +0x0A+ (<tt>CHARS[:newline]</tt>).
|
32
|
+
# 0. Each _record_ starts with +0x01+ (<tt>CHARS[:rs]</tt>) or, if it's a
|
33
|
+
# deleted _record_, with +0xFF+ (<tt>CHARS[:deleted]</tt>).
|
34
|
+
# 0. Each _field_ is terminated by +0x00+ (<tt>CHARS[:fs]</tt>).
|
35
|
+
# 0. Each _field_ starts with the _category_ "number", a run of
|
36
|
+
# +category_length+ characters except +0x00+, +0x01+, +0xDB+ or +0xFF+;
|
37
|
+
# trailing space is stripped.
|
38
|
+
# 0. The remaining characters of a _field_ form the _category_ content;
|
39
|
+
# trailing padding +0xDB+ (<tt>CHARS[:padding]</tt>) is stripped.
|
40
|
+
#
|
41
|
+
# To quote the BISMAS handbook: <i>"Konkret wird bei BISMAS jeder Datensatz
|
42
|
+
# durch ASCII(1) eingeleitet. Es folgt die erste Kategorienummer mit dem
|
43
|
+
# Kategorieinhalt. Abgeschlossen wird jede Kategorie mit ASCII(0), danach
|
44
|
+
# folgt die nächste Kategorienummer und -inhalt usw. Der gesamte Datensatz
|
45
|
+
# wird mit ASCII (13)(10) abgeschlossen."</i>
|
46
|
+
|
47
|
+
class Parser
|
48
|
+
|
49
|
+
# Legacy version of Parser that more closely mimics the behaviour of the
|
50
|
+
# original BISMAS software. Deviations from Parser:
|
51
|
+
#
|
52
|
+
# 0. Records are not required to start with +0x01+, any character will do.
|
53
|
+
# 0. Category numbers are extended to any character except +0x0D+ +0x0A+.
|
54
|
+
# [NOT IMPLEMENTED YET]
|
55
|
+
|
56
|
+
class Legacy < self
|
57
|
+
|
58
|
+
def initialize(options = {})
|
59
|
+
raise NotImplementedError, 'not implemented yet'
|
60
|
+
|
61
|
+
@category_char = '.'
|
62
|
+
super
|
63
|
+
@regex[:category] = /#{@regex[:category]}(?<!#{@chars[:newline]})/
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def match_record
|
69
|
+
@input.skip(/./)
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.parse(io, options = {}, &block)
|
75
|
+
klass = options[:legacy] ? Legacy : self
|
76
|
+
klass.new(options).parse(io, &block)
|
77
|
+
end
|
78
|
+
|
79
|
+
def initialize(options = {})
|
80
|
+
@regex = Bismas.regex(options)
|
81
|
+
|
82
|
+
@strict, @silent = options.values_at(:strict, :silent)
|
83
|
+
end
|
84
|
+
|
85
|
+
def parse(io, &block)
|
86
|
+
@input = StringScanner.new('')
|
87
|
+
|
88
|
+
io.each { |input|
|
89
|
+
@input << input
|
90
|
+
|
91
|
+
parse_record(&block) while @input.check_until(@regex[:newline])
|
92
|
+
@input.string = @input.string.byteslice(@input.pos..-1)
|
93
|
+
}
|
94
|
+
|
95
|
+
error('Unexpected data') unless @input.eos?
|
96
|
+
|
97
|
+
self
|
98
|
+
end
|
99
|
+
|
100
|
+
def parse_record
|
101
|
+
if match(:deleted)
|
102
|
+
match(:skip_line)
|
103
|
+
return
|
104
|
+
elsif !match_record
|
105
|
+
error('Malformed record', :line)
|
106
|
+
return
|
107
|
+
end
|
108
|
+
|
109
|
+
r = Hash.new { |h, k| h[k] = [] }
|
110
|
+
parse_field { |k, v| r[k] << v } until match(:newline)
|
111
|
+
|
112
|
+
block_given? ? yield(r) : r
|
113
|
+
end
|
114
|
+
|
115
|
+
def parse_field
|
116
|
+
k = match(:category, 0) and k.rstrip!
|
117
|
+
|
118
|
+
v = match(:field, 1) or error(k ?
|
119
|
+
"Unclosed field `#{k}'" : 'Unexpected data', :rest)
|
120
|
+
|
121
|
+
k ? block_given? ? yield(k, v) : [k, v] :
|
122
|
+
v.empty? ? nil : error('Malformed field', :field)
|
123
|
+
end
|
124
|
+
|
125
|
+
private
|
126
|
+
|
127
|
+
def match(key, index = nil)
|
128
|
+
res = @input.skip(@regex.fetch(key))
|
129
|
+
res && index ? @input[index] : res
|
130
|
+
end
|
131
|
+
|
132
|
+
def match_record
|
133
|
+
match(:rs)
|
134
|
+
end
|
135
|
+
|
136
|
+
def error(message, skip = nil)
|
137
|
+
err = parse_error(message)
|
138
|
+
raise err unless skip && !@strict
|
139
|
+
|
140
|
+
warn err.to_s unless @silent
|
141
|
+
match(:"skip_#{skip}")
|
142
|
+
nil
|
143
|
+
end
|
144
|
+
|
145
|
+
def parse_error(message)
|
146
|
+
ParseError.new(@input, message)
|
147
|
+
end
|
148
|
+
|
149
|
+
class ParseError < StandardError
|
150
|
+
|
151
|
+
def initialize(input, message)
|
152
|
+
@input, @message = input, message
|
153
|
+
end
|
154
|
+
|
155
|
+
def to_s
|
156
|
+
'%s at %d:%d: %s' % [@message]
|
157
|
+
.insert(*@input.eos? ?
|
158
|
+
[0, 'Unexpected end of input'] :
|
159
|
+
[-1, @input.peek(16).inspect])
|
160
|
+
.insert(1, $., @input.pos)
|
161
|
+
end
|
162
|
+
|
163
|
+
end
|
164
|
+
|
165
|
+
end
|
166
|
+
|
167
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
#--
|
2
|
+
###############################################################################
|
3
|
+
# #
|
4
|
+
# bismas -- A Ruby client for BISMAS databases #
|
5
|
+
# #
|
6
|
+
# Copyright (C) 2015 Jens Wille #
|
7
|
+
# #
|
8
|
+
# Authors: #
|
9
|
+
# Jens Wille <jens.wille@gmail.com> #
|
10
|
+
# #
|
11
|
+
# bismas is free software; you can redistribute it and/or modify it #
|
12
|
+
# under the terms of the GNU Affero General Public License as published by #
|
13
|
+
# the Free Software Foundation; either version 3 of the License, or (at your #
|
14
|
+
# option) any later version. #
|
15
|
+
# #
|
16
|
+
# bismas is distributed in the hope that it will be useful, but #
|
17
|
+
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY #
|
18
|
+
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public #
|
19
|
+
# License for more details. #
|
20
|
+
# #
|
21
|
+
# You should have received a copy of the GNU Affero General Public License #
|
22
|
+
# along with bismas. If not, see <http://www.gnu.org/licenses/>. #
|
23
|
+
# #
|
24
|
+
###############################################################################
|
25
|
+
#++
|
26
|
+
|
27
|
+
require_relative 'parser'
|
28
|
+
|
29
|
+
module Bismas
|
30
|
+
|
31
|
+
class Reader < Base
|
32
|
+
|
33
|
+
DEFAULT_IO = $stdin
|
34
|
+
|
35
|
+
class << self
|
36
|
+
|
37
|
+
def parse(*args, &block)
|
38
|
+
reader = new(args.extract_options!).parse(*args, &block)
|
39
|
+
block ? reader : reader.records
|
40
|
+
end
|
41
|
+
|
42
|
+
def parse_file(*args, &block)
|
43
|
+
file_method(:parse, 'rb', *args, &block)
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
attr_reader :records
|
49
|
+
|
50
|
+
def reset
|
51
|
+
super
|
52
|
+
@records = {}
|
53
|
+
end
|
54
|
+
|
55
|
+
def parse(io = io(), &block)
|
56
|
+
unless block
|
57
|
+
records, block = @records, amend_block { |id, record|
|
58
|
+
records[id] = record
|
59
|
+
}
|
60
|
+
end
|
61
|
+
|
62
|
+
Parser.parse(io, @options) { |record|
|
63
|
+
block[key ? record[key].join : auto_id.call, record] }
|
64
|
+
|
65
|
+
self
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def amend_block(&block)
|
71
|
+
return block unless $VERBOSE && k = @key
|
72
|
+
|
73
|
+
r, i = block.binding.eval('_ = records, io')
|
74
|
+
|
75
|
+
l = i.respond_to?(:lineno)
|
76
|
+
s = i.respond_to?(:path) ? i.path :
|
77
|
+
Object.instance_method(:inspect).bind(i).call
|
78
|
+
|
79
|
+
lambda { |id, *args|
|
80
|
+
if (r ||= block.binding.eval('records')).key?(id)
|
81
|
+
warn "Duplicate record in #{s}#{":#{i.lineno}" if l}: »#{k}:#{id}«"
|
82
|
+
end
|
83
|
+
|
84
|
+
block[id, *args]
|
85
|
+
}
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|