nodaire 0.2.0 → 0.3.0
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.
- checksums.yaml +4 -4
- data/README.md +55 -48
- data/lib/nodaire.rb +4 -9
- data/lib/nodaire/api/indental.rb +103 -0
- data/lib/nodaire/api/tablatal.rb +108 -0
- data/lib/nodaire/base.rb +11 -0
- data/lib/nodaire/errors.rb +3 -0
- data/lib/nodaire/indental.rb +4 -0
- data/lib/nodaire/parsers/indental_parser.rb +63 -37
- data/lib/nodaire/parsers/parser.rb +28 -0
- data/lib/nodaire/parsers/tablatal_parser.rb +27 -22
- data/lib/nodaire/tablatal.rb +4 -0
- metadata +17 -14
- data/lib/nodaire/formats.rb +0 -4
- data/lib/nodaire/formats/indental.rb +0 -58
- data/lib/nodaire/formats/tablatal.rb +0 -64
- data/lib/nodaire/version.rb +0 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e9b7c87d8f3775b1d06a7cf203296efcf40bf95904ba9ed7e8a1f09d0d946e21
|
4
|
+
data.tar.gz: 2185bc3eea0363808665f4dbc6f6a04c7cb483544c92698dfd00200eee61673f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ac327bc203d061ee6bef827b3d43b5c3c6e8cd6f754c5e4cce9bf63dac8996452de348de057218c27da87170d45db9e8ca81f3d52ac25df8106136944940d867
|
7
|
+
data.tar.gz: '06508af2ec0ea395bc4b45c59c77d0b41490c6abf8cbc7ff565e86195b855e6bf24c4bcb2243d73a3da17f2e1b780ec04c50618709b41b0f098680503b148677'
|
data/README.md
CHANGED
@@ -1,84 +1,91 @@
|
|
1
|
-
# Nodaire
|
1
|
+
# Nodaire [](https://rubygems.org/gems/nodaire) [](https://travis-ci.org/slisne/nodaire)
|
2
2
|
|
3
|
-
|
3
|
+
Nodaire is a collection of text file parsers.
|
4
|
+
It supports Ruby 2.5.0 or greater.
|
4
5
|
|
5
|
-
|
6
|
+
__Note__: This is a new gem, and the interface is not yet stable.
|
7
|
+
Expect breaking API changes before v1.0.0 is released.
|
6
8
|
|
7
9
|
## File formats
|
8
10
|
|
9
|
-
|
10
|
-
- [__Indental__](https://wiki.xxiivv.com/#indental) (.ndtl)
|
11
|
-
- [__Tablatal__](https://wiki.xxiivv.com/#tablatal) (.tbtl)
|
11
|
+
Nodaire currently supports the following text file formats:
|
12
12
|
|
13
|
-
|
13
|
+
- __Indental__ — <https://wiki.xxiivv.com/#indental>
|
14
|
+
- __Tablatal__ — <https://wiki.xxiivv.com/#tablatal>
|
15
|
+
|
16
|
+
## Install
|
17
|
+
|
18
|
+
Install `nodaire` from [RubyGems](https://rubygems.org/gems/nodaire):
|
19
|
+
|
20
|
+
```sh
|
21
|
+
gem install nodaire
|
22
|
+
```
|
23
|
+
|
24
|
+
## Documentation
|
25
|
+
|
26
|
+
[Code documentation](https://slisne.github.io/nodaire/) is available.
|
27
|
+
|
28
|
+
Keep reading below for examples of how to use Nodaire.
|
29
|
+
|
30
|
+
## Usage examples
|
14
31
|
|
15
32
|
### Indental
|
16
33
|
|
17
34
|
```ruby
|
18
|
-
|
35
|
+
require 'nodaire/indental'
|
36
|
+
|
37
|
+
doc = Nodaire::Indental.parse! <<~NDTL
|
19
38
|
NAME
|
20
39
|
KEY : VALUE
|
21
40
|
LIST
|
22
41
|
ITEM1
|
23
42
|
ITEM2
|
24
|
-
|
43
|
+
NDTL
|
25
44
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
# {
|
30
|
-
# name: {
|
31
|
-
# key: 'VALUE',
|
32
|
-
# list: ['ITEM1', 'ITEM2'],
|
33
|
-
# },
|
34
|
-
# }
|
35
|
-
|
36
|
-
> indental.valid?
|
37
|
-
# true
|
38
|
-
|
39
|
-
> indental = Nodaire::Indental.parse(input, preserve_keys: true)
|
40
|
-
|
41
|
-
> indental.to_json
|
42
|
-
# {"NAME":{"KEY":"VALUE","LIST":["ITEM1","ITEM2"]}}
|
45
|
+
doc.valid? # true
|
46
|
+
doc.categories # ["NAME"]
|
47
|
+
doc.to_h # {"NAME"=>{"KEY"=>"VALUE", "LIST"=>["ITEM1", "ITEM2"]}}
|
48
|
+
doc.to_json # '{"NAME":{"KEY":"VALUE","LIST":["ITEM1","ITEM2"]}}'
|
43
49
|
```
|
44
50
|
|
45
51
|
### Tablatal
|
46
52
|
|
47
53
|
```ruby
|
48
|
-
|
54
|
+
require 'nodaire/tablatal'
|
55
|
+
|
56
|
+
doc = Nodaire::Tablatal.parse! <<~TBTL
|
49
57
|
NAME AGE COLOR
|
50
58
|
Erica 12 Opal
|
51
59
|
Alex 23 Cyan
|
52
60
|
Nike 34 Red
|
53
61
|
Ruca 45 Grey
|
54
|
-
|
62
|
+
TBTL
|
55
63
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
#
|
60
|
-
|
61
|
-
# { name: 'Alex', age: '23', color: 'Cyan' },
|
62
|
-
# { name: 'Nike', age: '34', color: 'Red' },
|
63
|
-
# { name: 'Ruca', age: '45', color: 'Grey' },
|
64
|
-
# ]
|
64
|
+
doc.valid? # true
|
65
|
+
doc.keys # ["NAME", "AGE", "COLOR"]
|
66
|
+
doc.to_a.last # {"NAME"=>"Ruca", "AGE"=>"45", "COLOR"=>"Grey"}
|
67
|
+
doc.to_csv # "NAME,AGE,COLOR\nErica,12,Opal\nAlex,23,..."
|
68
|
+
```
|
65
69
|
|
66
|
-
|
67
|
-
# true
|
70
|
+
## Development
|
68
71
|
|
69
|
-
|
72
|
+
To run the latest source code, check out the
|
73
|
+
[Git repository](https://github.com/slisne/nodaire):
|
70
74
|
|
71
|
-
|
72
|
-
|
73
|
-
# Erica,12,Opal
|
74
|
-
# Alex,23,Cyan
|
75
|
-
# Nike,34,Red
|
76
|
-
# Ruca,45,Grey
|
75
|
+
```sh
|
76
|
+
git clone https://github.com/slisne/nodaire.git
|
77
77
|
```
|
78
78
|
|
79
|
-
|
79
|
+
Install the dependencies using Bundler:
|
80
80
|
|
81
|
+
```sh
|
82
|
+
gem install bundler
|
83
|
+
bundle install
|
81
84
|
```
|
82
|
-
|
85
|
+
|
86
|
+
Analyse the code and run unit tests using Bundler:
|
87
|
+
|
88
|
+
```sh
|
89
|
+
bundle exec rubocop
|
83
90
|
bundle exec rspec
|
84
91
|
```
|
data/lib/nodaire.rb
CHANGED
@@ -1,12 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
# Nodaire is a collection of text file parsers.
|
5
|
-
#
|
6
|
-
module Nodaire
|
7
|
-
end
|
8
|
-
|
9
|
-
require_relative 'nodaire/formats'
|
10
|
-
|
3
|
+
require_relative 'nodaire/base'
|
11
4
|
require_relative 'nodaire/errors'
|
12
|
-
|
5
|
+
|
6
|
+
require_relative 'nodaire/indental'
|
7
|
+
require_relative 'nodaire/tablatal'
|
@@ -0,0 +1,103 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
require_relative '../parsers/indental_parser'
|
6
|
+
|
7
|
+
##
|
8
|
+
# Interface for documents in _Indental_ format.
|
9
|
+
#
|
10
|
+
# _Indental_ is a 'dictionary-type database format' by Devine Lu Linvega.
|
11
|
+
# See: https://wiki.xxiivv.com/#indental
|
12
|
+
#
|
13
|
+
# require 'nodaire/indental'
|
14
|
+
#
|
15
|
+
# doc = Nodaire::Indental.parse! <<~NDTL
|
16
|
+
# NAME
|
17
|
+
# KEY : VALUE
|
18
|
+
# LIST
|
19
|
+
# ITEM1
|
20
|
+
# ITEM2
|
21
|
+
# NDTL
|
22
|
+
#
|
23
|
+
# doc.valid? # true
|
24
|
+
# doc.categories # ["NAME"]
|
25
|
+
# doc.to_h # {"NAME"=>{"KEY"=>"VALUE", "LIST"=>["ITEM1", "ITEM2"]}}
|
26
|
+
# doc.to_json # '{"NAME":{"KEY":"VALUE","LIST":["ITEM1","ITEM2"]}}'
|
27
|
+
#
|
28
|
+
# @since 0.2.0
|
29
|
+
#
|
30
|
+
class Nodaire::Indental
|
31
|
+
# A hash containing the data parsed from the source.
|
32
|
+
# @return [Hash]
|
33
|
+
attr_reader :data
|
34
|
+
# An array of category names.
|
35
|
+
# @since 0.3.0
|
36
|
+
# @return [Array<String>]
|
37
|
+
attr_reader :categories
|
38
|
+
# An array of error messages.
|
39
|
+
# @return [Array<String>]
|
40
|
+
attr_reader :errors
|
41
|
+
|
42
|
+
alias_method :to_h, :data
|
43
|
+
|
44
|
+
##
|
45
|
+
# Parse the document +source+.
|
46
|
+
#
|
47
|
+
# @param [String] source The document source to parse.
|
48
|
+
# @param [Boolean] symbolize_names
|
49
|
+
# If true, normalize category and key names and convert them to
|
50
|
+
# lowercase symbols.
|
51
|
+
#
|
52
|
+
# @return [Indental]
|
53
|
+
#
|
54
|
+
def self.parse(source, symbolize_names: false)
|
55
|
+
parser = Parser.new(source, false, symbolize_names: symbolize_names)
|
56
|
+
|
57
|
+
new(parser)
|
58
|
+
end
|
59
|
+
|
60
|
+
##
|
61
|
+
# Parse the document +source+, raising an exception if a parser error occurs.
|
62
|
+
#
|
63
|
+
# @param [String] source The document source to parse.
|
64
|
+
# @param [boolean] symbolize_names
|
65
|
+
# If true, normalize category and key names and convert them to
|
66
|
+
# lowercase symbols.
|
67
|
+
#
|
68
|
+
# @raise [ParserError]
|
69
|
+
# @return [Indental]
|
70
|
+
#
|
71
|
+
def self.parse!(source, symbolize_names: false)
|
72
|
+
parser = Parser.new(source, true, symbolize_names: symbolize_names)
|
73
|
+
|
74
|
+
new(parser)
|
75
|
+
end
|
76
|
+
|
77
|
+
##
|
78
|
+
# A boolean indicating whether the source was parsed without errors.
|
79
|
+
#
|
80
|
+
# @return [Boolean]
|
81
|
+
#
|
82
|
+
def valid?
|
83
|
+
@errors.empty?
|
84
|
+
end
|
85
|
+
|
86
|
+
##
|
87
|
+
# Convert the document to JSON.
|
88
|
+
#
|
89
|
+
# @return [String]
|
90
|
+
#
|
91
|
+
def to_json(*_args)
|
92
|
+
JSON.generate(data)
|
93
|
+
end
|
94
|
+
|
95
|
+
private
|
96
|
+
|
97
|
+
def initialize(parser)
|
98
|
+
@data = parser.data
|
99
|
+
@errors = parser.errors
|
100
|
+
|
101
|
+
@categories = data.keys
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'csv'
|
4
|
+
|
5
|
+
require_relative '../parsers/tablatal_parser'
|
6
|
+
|
7
|
+
##
|
8
|
+
# Interface for documents in _Tablatal_ format.
|
9
|
+
#
|
10
|
+
# _Tablatal_ is a 'list-type database format' by Devine Lu Linvega.
|
11
|
+
# See: https://wiki.xxiivv.com/#tablatal
|
12
|
+
#
|
13
|
+
# require 'nodaire/tablatal'
|
14
|
+
#
|
15
|
+
# doc = Nodaire::Tablatal.parse! <<~TBTL
|
16
|
+
# NAME AGE COLOR
|
17
|
+
# Erica 12 Opal
|
18
|
+
# Alex 23 Cyan
|
19
|
+
# Nike 34 Red
|
20
|
+
# Ruca 45 Grey
|
21
|
+
# TBTL
|
22
|
+
#
|
23
|
+
# doc.valid? # true
|
24
|
+
# doc.keys # ["NAME", "AGE", "COLOR"]
|
25
|
+
# doc.to_a.last # {"NAME"=>"Ruca", "AGE"=>"45", "COLOR"=>"Grey"}
|
26
|
+
# doc.to_csv # "NAME,AGE,COLOR\nErica,12,Opal\nAlex,23,..."
|
27
|
+
#
|
28
|
+
# @since 0.1.0
|
29
|
+
#
|
30
|
+
class Nodaire::Tablatal
|
31
|
+
# An array of hashes containing the data parsed from the source.
|
32
|
+
# @return [Array<Hash>]
|
33
|
+
attr_reader :data
|
34
|
+
# An array of keys parsed from the source header line.
|
35
|
+
# @return [Array]
|
36
|
+
attr_reader :keys
|
37
|
+
# An array of error message strings.
|
38
|
+
# @since 0.2.0
|
39
|
+
# @return [Array<String>]
|
40
|
+
attr_reader :errors
|
41
|
+
|
42
|
+
alias_method :to_a, :data
|
43
|
+
|
44
|
+
##
|
45
|
+
# Parse the document +source+.
|
46
|
+
#
|
47
|
+
# @param [String] source The document source to parse.
|
48
|
+
# @param [Boolean] symbolize_names
|
49
|
+
# If true, normalize key names and convert them to lowercase symbols.
|
50
|
+
#
|
51
|
+
# @since 0.2.0
|
52
|
+
# @return [Tablatal]
|
53
|
+
#
|
54
|
+
def self.parse(source, symbolize_names: false)
|
55
|
+
parser = Parser.new(source, false, symbolize_names: symbolize_names)
|
56
|
+
|
57
|
+
new(parser)
|
58
|
+
end
|
59
|
+
|
60
|
+
##
|
61
|
+
# Parse the document +source+, raising an exception if a parser error occurs.
|
62
|
+
#
|
63
|
+
# @param [String] source The document source to parse.
|
64
|
+
# @param [Boolean] symbolize_names
|
65
|
+
# If true, normalize key names and convert them to lowercase symbols.
|
66
|
+
#
|
67
|
+
# @since 0.2.0
|
68
|
+
# @raise [ParserError]
|
69
|
+
# @return [Tablatal]
|
70
|
+
#
|
71
|
+
def self.parse!(source, symbolize_names: false)
|
72
|
+
parser = Parser.new(source, true, symbolize_names: symbolize_names)
|
73
|
+
|
74
|
+
new(parser)
|
75
|
+
end
|
76
|
+
|
77
|
+
##
|
78
|
+
# A boolean indicating whether the source was parsed without errors.
|
79
|
+
#
|
80
|
+
# @since 0.2.0
|
81
|
+
# @return [Boolean]
|
82
|
+
#
|
83
|
+
def valid?
|
84
|
+
@errors.empty?
|
85
|
+
end
|
86
|
+
|
87
|
+
##
|
88
|
+
# Convert the document to CSV.
|
89
|
+
#
|
90
|
+
# @return [String]
|
91
|
+
#
|
92
|
+
def to_csv
|
93
|
+
CSV.generate do |csv|
|
94
|
+
csv << keys
|
95
|
+
data.each do |row|
|
96
|
+
csv << keys.map { |key| row[key] }
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
private
|
102
|
+
|
103
|
+
def initialize(parser)
|
104
|
+
@data = parser.data
|
105
|
+
@keys = parser.keys
|
106
|
+
@errors = parser.errors
|
107
|
+
end
|
108
|
+
end
|
data/lib/nodaire/base.rb
ADDED
data/lib/nodaire/errors.rb
CHANGED
@@ -1,30 +1,47 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative '
|
3
|
+
require_relative 'parser'
|
4
4
|
|
5
5
|
class Nodaire::Indental
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
attr_reader :data, :errors
|
6
|
+
# @private
|
7
|
+
class Parser < Nodaire::Parser
|
8
|
+
attr_reader :data
|
10
9
|
|
11
10
|
def initialize(string, strict, options = {})
|
12
|
-
|
13
|
-
@preserve_keys = options.fetch(:preserve_keys, false)
|
11
|
+
super(strict, options)
|
14
12
|
|
13
|
+
@symbolize_names = option(:symbolize_names, false)
|
15
14
|
@data = {}
|
16
|
-
@
|
15
|
+
@category_ids = {}
|
16
|
+
@category = nil
|
17
17
|
|
18
18
|
parse! string
|
19
19
|
end
|
20
20
|
|
21
21
|
private
|
22
22
|
|
23
|
+
Category = Struct.new(:name, :key_ids, :list_id, keyword_init: true)
|
24
|
+
|
25
|
+
attr_accessor :category
|
26
|
+
attr_reader :symbolize_names, :category_ids
|
27
|
+
|
23
28
|
def parse!(string)
|
29
|
+
lines = lines_to_parse(string)
|
30
|
+
lines = lines[1...-1] if js_wrapper?(lines)
|
31
|
+
lines.each { |line, num| parse_line! line, num }
|
32
|
+
end
|
33
|
+
|
34
|
+
def lines_to_parse(string)
|
24
35
|
(string || '')
|
25
36
|
.split("\n").each_with_index
|
26
37
|
.reject { |line, _| line.match(/^\s*(;.*)?$/) }
|
27
|
-
.
|
38
|
+
.map { |line, idx| [line, idx + 1] }
|
39
|
+
end
|
40
|
+
|
41
|
+
def js_wrapper?(lines)
|
42
|
+
!lines.empty? &&
|
43
|
+
lines.first[0].match(/=\s*`\s*$/) &&
|
44
|
+
lines.last[0].strip == '`'
|
28
45
|
end
|
29
46
|
|
30
47
|
def parse_line!(line, num)
|
@@ -37,67 +54,76 @@ class Nodaire::Indental
|
|
37
54
|
end
|
38
55
|
|
39
56
|
def parse_category!(cat, num)
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
@list_id = nil
|
57
|
+
id = normalize_sym(cat)
|
58
|
+
|
59
|
+
if category_ids.include?(id)
|
44
60
|
oops! 'Duplicate category', num
|
61
|
+
self.category = nil
|
45
62
|
else
|
46
|
-
|
47
|
-
|
48
|
-
|
63
|
+
self.category = Category.new(
|
64
|
+
name: symbolize_names ? id : normalize_text(cat),
|
65
|
+
key_ids: {},
|
66
|
+
list_id: nil
|
67
|
+
)
|
68
|
+
data[category.name] = {}
|
69
|
+
category_ids[id] = category.name
|
49
70
|
end
|
50
71
|
end
|
51
72
|
|
52
73
|
def parse_key_or_list!(line, num)
|
53
|
-
return oops!('No category specified', num)
|
74
|
+
return oops!('No category specified', num) if category.nil?
|
54
75
|
|
55
76
|
if line.include?(' : ')
|
56
77
|
key, value = line.split(' : ', 2)
|
57
|
-
parse_key_value!(key
|
78
|
+
parse_key_value!(key, value, num)
|
58
79
|
else
|
59
80
|
parse_list!(line, num)
|
60
81
|
end
|
61
82
|
end
|
62
83
|
|
63
84
|
def parse_key_value!(key, value, num)
|
64
|
-
|
65
|
-
|
66
|
-
|
85
|
+
id = normalize_sym(key)
|
86
|
+
key_name = symbolize_names ? id : normalize_text(key)
|
87
|
+
|
88
|
+
if category.key_ids.include?(id)
|
67
89
|
oops! 'Duplicate key', num
|
68
90
|
else
|
69
|
-
|
70
|
-
|
91
|
+
data[category.name][key_name] = normalize_text(value)
|
92
|
+
category.key_ids[id] = key_name
|
71
93
|
end
|
94
|
+
|
95
|
+
category.list_id = nil
|
72
96
|
end
|
73
97
|
|
74
98
|
def parse_list!(key, num)
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
99
|
+
id = normalize_sym(key)
|
100
|
+
list_name = symbolize_names ? id : normalize_text(key)
|
101
|
+
|
102
|
+
if category.key_ids.include?(id)
|
103
|
+
oops! 'Duplicate key for list', num
|
104
|
+
category.list_id = nil
|
79
105
|
else
|
80
|
-
|
81
|
-
|
106
|
+
data[category.name][list_name] = []
|
107
|
+
category.key_ids[id] = list_name
|
108
|
+
category.list_id = id
|
82
109
|
end
|
83
110
|
end
|
84
111
|
|
85
112
|
def parse_list_item!(item, num)
|
86
|
-
if
|
113
|
+
if category.nil? || category.list_id.nil?
|
87
114
|
oops! 'No list specified', num
|
88
115
|
else
|
89
|
-
|
116
|
+
list_name = category.key_ids[category.list_id]
|
117
|
+
data[category.name][list_name] << normalize_text(item)
|
90
118
|
end
|
91
119
|
end
|
92
120
|
|
93
|
-
def
|
94
|
-
|
95
|
-
@errors << message
|
96
|
-
raise ParserError, message if @strict
|
121
|
+
def normalize_text(string)
|
122
|
+
string.split.join(' ')
|
97
123
|
end
|
98
124
|
|
99
|
-
def
|
100
|
-
|
125
|
+
def normalize_sym(key)
|
126
|
+
key.downcase.gsub(/[\s_-]+/, ' ').split.join('_').to_sym
|
101
127
|
end
|
102
128
|
end
|
103
129
|
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../errors'
|
4
|
+
|
5
|
+
# @private
|
6
|
+
class Nodaire::Parser
|
7
|
+
attr_reader :errors, :options
|
8
|
+
|
9
|
+
def initialize(strict, options = {})
|
10
|
+
@strict = strict
|
11
|
+
@options = options
|
12
|
+
@errors = []
|
13
|
+
end
|
14
|
+
|
15
|
+
def strict?
|
16
|
+
@strict
|
17
|
+
end
|
18
|
+
|
19
|
+
def option(name, default = nil)
|
20
|
+
@options.fetch(name, default)
|
21
|
+
end
|
22
|
+
|
23
|
+
def oops!(message, line_num)
|
24
|
+
message = "#{message} on line #{line_num}" unless line_num.nil?
|
25
|
+
errors << message
|
26
|
+
raise Nodaire::ParserError, message if strict?
|
27
|
+
end
|
28
|
+
end
|
@@ -1,28 +1,32 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative '
|
3
|
+
require_relative 'parser'
|
4
4
|
|
5
5
|
class Nodaire::Tablatal
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
attr_reader :data, :errors
|
6
|
+
# @private
|
7
|
+
class Parser < Nodaire::Parser
|
8
|
+
attr_reader :data
|
10
9
|
|
11
10
|
def initialize(string, strict, options = {})
|
12
|
-
|
13
|
-
|
11
|
+
super(strict, options)
|
12
|
+
|
13
|
+
@symbolize_names = option(:symbolize_names, false)
|
14
14
|
|
15
|
-
@
|
15
|
+
@data = []
|
16
|
+
@keys = []
|
17
|
+
@key_ids = {}
|
16
18
|
|
17
19
|
parse! string
|
18
20
|
end
|
19
21
|
|
20
22
|
def keys
|
21
|
-
@keys.map(&:name)
|
23
|
+
@keys.map(&:name)
|
22
24
|
end
|
23
25
|
|
24
26
|
private
|
25
27
|
|
28
|
+
attr_reader :symbolize_names, :key_ids
|
29
|
+
|
26
30
|
Key = Struct.new(:name, :range, keyword_init: true)
|
27
31
|
|
28
32
|
def parse!(string)
|
@@ -31,20 +35,23 @@ class Nodaire::Tablatal
|
|
31
35
|
return if lines.empty?
|
32
36
|
|
33
37
|
@keys = make_keys(lines.shift.scan(/(\S+\s*)/).flatten)
|
34
|
-
@data = lines.map { |line
|
38
|
+
@data = lines.map { |line| make_line(line) }.compact
|
35
39
|
end
|
36
40
|
|
37
41
|
def make_keys(segs)
|
38
42
|
[].tap do |keys|
|
39
43
|
start = 0
|
40
44
|
segs.each_with_index do |seg, idx|
|
41
|
-
key = symbolize_key(seg.strip)
|
42
45
|
len = seg.size if idx < segs.size - 1
|
46
|
+
id = normalize_sym(seg)
|
47
|
+
key_name = symbolize_names ? id : normalize_text(seg)
|
43
48
|
|
44
|
-
if
|
45
|
-
oops! "Duplicate key #{
|
49
|
+
if key_ids.include?(id)
|
50
|
+
oops! "Duplicate key #{key_name}", 1
|
46
51
|
else
|
47
|
-
|
52
|
+
range_end = len ? start + len - 1 : -1
|
53
|
+
key_ids[id] = key_name
|
54
|
+
keys << Key.new(name: key_name, range: start..range_end)
|
48
55
|
end
|
49
56
|
|
50
57
|
start += len if len
|
@@ -52,18 +59,16 @@ class Nodaire::Tablatal
|
|
52
59
|
end
|
53
60
|
end
|
54
61
|
|
55
|
-
def make_line(line
|
56
|
-
@keys.map { |key| [key.name, (line[key.range]
|
62
|
+
def make_line(line)
|
63
|
+
@keys.map { |key| [key.name, normalize_text(line[key.range])] }.to_h
|
57
64
|
end
|
58
65
|
|
59
|
-
def
|
60
|
-
|
61
|
-
@errors << message
|
62
|
-
raise ParserError, message if @strict
|
66
|
+
def normalize_text(string)
|
67
|
+
string ? string.split.join(' ') : ''
|
63
68
|
end
|
64
69
|
|
65
|
-
def
|
66
|
-
|
70
|
+
def normalize_sym(key)
|
71
|
+
key.downcase.gsub(/[_-]+/, ' ').split.join('_').to_sym
|
67
72
|
end
|
68
73
|
end
|
69
74
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: nodaire
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Liam Cooke
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-08-
|
11
|
+
date: 2019-08-18 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Nodaire is a collection of text file parsers.
|
14
14
|
email: nodaire@liamcooke.com
|
@@ -19,22 +19,24 @@ files:
|
|
19
19
|
- LICENSE
|
20
20
|
- README.md
|
21
21
|
- lib/nodaire.rb
|
22
|
+
- lib/nodaire/api/indental.rb
|
23
|
+
- lib/nodaire/api/tablatal.rb
|
24
|
+
- lib/nodaire/base.rb
|
22
25
|
- lib/nodaire/errors.rb
|
23
|
-
- lib/nodaire/
|
24
|
-
- lib/nodaire/formats/indental.rb
|
25
|
-
- lib/nodaire/formats/tablatal.rb
|
26
|
+
- lib/nodaire/indental.rb
|
26
27
|
- lib/nodaire/parsers/indental_parser.rb
|
28
|
+
- lib/nodaire/parsers/parser.rb
|
27
29
|
- lib/nodaire/parsers/tablatal_parser.rb
|
28
|
-
- lib/nodaire/
|
29
|
-
homepage: https://github.com/
|
30
|
+
- lib/nodaire/tablatal.rb
|
31
|
+
homepage: https://github.com/slisne/nodaire
|
30
32
|
licenses:
|
31
33
|
- MIT
|
32
34
|
metadata:
|
33
|
-
bug_tracker_uri: https://github.com/
|
34
|
-
changelog_uri: https://github.com/
|
35
|
-
documentation_uri: https://github.
|
36
|
-
homepage_uri: https://github.com/
|
37
|
-
source_code_uri: https://github.com/
|
35
|
+
bug_tracker_uri: https://github.com/slisne/nodaire/issues
|
36
|
+
changelog_uri: https://github.com/slisne/nodaire/blob/master/CHANGELOG.md
|
37
|
+
documentation_uri: https://slisne.github.io/nodaire/
|
38
|
+
homepage_uri: https://github.com/slisne/nodaire
|
39
|
+
source_code_uri: https://github.com/slisne/nodaire
|
38
40
|
post_install_message:
|
39
41
|
rdoc_options: []
|
40
42
|
require_paths:
|
@@ -43,14 +45,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
43
45
|
requirements:
|
44
46
|
- - ">="
|
45
47
|
- !ruby/object:Gem::Version
|
46
|
-
version:
|
48
|
+
version: 2.5.0
|
47
49
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
48
50
|
requirements:
|
49
51
|
- - ">="
|
50
52
|
- !ruby/object:Gem::Version
|
51
53
|
version: '0'
|
52
54
|
requirements: []
|
53
|
-
|
55
|
+
rubyforge_project:
|
56
|
+
rubygems_version: 2.7.3
|
54
57
|
signing_key:
|
55
58
|
specification_version: 4
|
56
59
|
summary: Text file parsers.
|
data/lib/nodaire/formats.rb
DELETED
@@ -1,58 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'json'
|
4
|
-
|
5
|
-
require_relative '../parsers/indental_parser'
|
6
|
-
|
7
|
-
##
|
8
|
-
# Interface for the Indental file format.
|
9
|
-
#
|
10
|
-
# Indental is (c) Devine Lu Linvega (MIT License).
|
11
|
-
#
|
12
|
-
class Nodaire::Indental
|
13
|
-
attr_reader :data, :errors
|
14
|
-
alias_method :to_h, :data
|
15
|
-
|
16
|
-
##
|
17
|
-
# Parse a string in Indental format.
|
18
|
-
#
|
19
|
-
# Ignores or attempts to work around errors.
|
20
|
-
#
|
21
|
-
def self.parse(string, preserve_keys: false)
|
22
|
-
parser = Parser.new(string, false, preserve_keys: preserve_keys)
|
23
|
-
|
24
|
-
new(parser)
|
25
|
-
end
|
26
|
-
|
27
|
-
##
|
28
|
-
# Parse a string in Indental format.
|
29
|
-
#
|
30
|
-
# Raises an exception if there are errors.
|
31
|
-
#
|
32
|
-
def self.parse!(string, preserve_keys: false)
|
33
|
-
parser = Parser.new(string, true, preserve_keys: preserve_keys)
|
34
|
-
|
35
|
-
new(parser)
|
36
|
-
end
|
37
|
-
|
38
|
-
##
|
39
|
-
# Returns whether the input was parsed without errors.
|
40
|
-
#
|
41
|
-
def valid?
|
42
|
-
@errors.empty?
|
43
|
-
end
|
44
|
-
|
45
|
-
##
|
46
|
-
# Return a string in JSON format.
|
47
|
-
#
|
48
|
-
def to_json
|
49
|
-
JSON.generate(data)
|
50
|
-
end
|
51
|
-
|
52
|
-
private
|
53
|
-
|
54
|
-
def initialize(parser)
|
55
|
-
@data = parser.data
|
56
|
-
@errors = parser.errors
|
57
|
-
end
|
58
|
-
end
|
@@ -1,64 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'csv'
|
4
|
-
|
5
|
-
require_relative '../parsers/tablatal_parser'
|
6
|
-
|
7
|
-
##
|
8
|
-
# Interface for the Tablatal file format.
|
9
|
-
#
|
10
|
-
# Tablatal is (c) Devine Lu Linvega (MIT License).
|
11
|
-
#
|
12
|
-
class Nodaire::Tablatal
|
13
|
-
attr_reader :data, :keys, :errors
|
14
|
-
alias_method :to_a, :data
|
15
|
-
|
16
|
-
##
|
17
|
-
# Parse a string in Tablatal format.
|
18
|
-
#
|
19
|
-
# Ignores or attempts to work around errors.
|
20
|
-
#
|
21
|
-
def self.parse(string, preserve_keys: false)
|
22
|
-
parser = Parser.new(string, false, preserve_keys: preserve_keys)
|
23
|
-
|
24
|
-
new(parser)
|
25
|
-
end
|
26
|
-
|
27
|
-
##
|
28
|
-
# Parse a string in Tablatal format.
|
29
|
-
#
|
30
|
-
# Raises an exception if there are errors.
|
31
|
-
#
|
32
|
-
def self.parse!(string, preserve_keys: false)
|
33
|
-
parser = Parser.new(string, true, preserve_keys: preserve_keys)
|
34
|
-
|
35
|
-
new(parser)
|
36
|
-
end
|
37
|
-
|
38
|
-
##
|
39
|
-
# Returns whether the input was parsed without errors.
|
40
|
-
#
|
41
|
-
def valid?
|
42
|
-
@errors.empty?
|
43
|
-
end
|
44
|
-
|
45
|
-
##
|
46
|
-
# Return a string in CSV format.
|
47
|
-
#
|
48
|
-
def to_csv
|
49
|
-
CSV.generate do |csv|
|
50
|
-
csv << keys
|
51
|
-
data.each do |row|
|
52
|
-
csv << keys.map { |key| row[key] }
|
53
|
-
end
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
private
|
58
|
-
|
59
|
-
def initialize(parser)
|
60
|
-
@data = parser.data
|
61
|
-
@keys = parser.keys
|
62
|
-
@errors = parser.errors
|
63
|
-
end
|
64
|
-
end
|