nodaire 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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 [![Gem Version](https://badge.fury.io/rb/nodaire.svg)](https://rubygems.org/gems/nodaire) [![Build Status](https://travis-ci.org/slisne/nodaire.svg?branch=master)](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
|