nodaire 0.1.0 → 0.2.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 +44 -13
- data/lib/nodaire/formats.rb +1 -0
- data/lib/nodaire/formats/indental.rb +58 -0
- data/lib/nodaire/formats/tablatal.rb +45 -5
- data/lib/nodaire/parsers/indental_parser.rb +103 -0
- data/lib/nodaire/parsers/tablatal_parser.rb +37 -31
- data/lib/nodaire/version.rb +2 -2
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 57dbe680f2640d6fff1c929cc8b953a7bcb99bd173d207193231a752e50dfdfa
|
4
|
+
data.tar.gz: b90ac2968b5ee9d26391228d1f1460a763bd409f879881991113e3286c68e542
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c5606db3b3c3f5574024476681b5c5053d23274107dd88faa92211bade14d35acd3e9d5f1adc088681d6174ebe24ce66f2165138a864aa5d56efe89dfb20246d
|
7
|
+
data.tar.gz: 0bb05b71c821062ce5bc5314491a3fa11b7b80f0aedef704c66a0fc7d972676a71e64da4fc72a21665e09acb3338e8396b29ce71454550cf740d02987b0b163d
|
data/README.md
CHANGED
@@ -2,15 +2,47 @@
|
|
2
2
|
|
3
3
|
Ruby parsers for text file formats. Work in progress.
|
4
4
|
|
5
|
+
[](https://rubygems.org/gems/nodaire)
|
6
|
+
|
5
7
|
## File formats
|
6
8
|
|
7
|
-
- [Oscean](https://wiki.xxiivv.com/#oscean) by Devine Lu Linvega
|
9
|
+
- [Oscean](https://wiki.xxiivv.com/#oscean) file formats by Devine Lu Linvega:
|
10
|
+
- [__Indental__](https://wiki.xxiivv.com/#indental) (.ndtl)
|
11
|
+
- [__Tablatal__](https://wiki.xxiivv.com/#tablatal) (.tbtl)
|
8
12
|
|
9
|
-
|
13
|
+
## Examples
|
10
14
|
|
11
|
-
|
15
|
+
### Indental
|
12
16
|
|
13
|
-
|
17
|
+
```ruby
|
18
|
+
> input = <<~NDTL
|
19
|
+
NAME
|
20
|
+
KEY : VALUE
|
21
|
+
LIST
|
22
|
+
ITEM1
|
23
|
+
ITEM2
|
24
|
+
NDTL
|
25
|
+
|
26
|
+
> indental = Nodaire::Indental.parse(input)
|
27
|
+
|
28
|
+
> indental.data
|
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"]}}
|
43
|
+
```
|
44
|
+
|
45
|
+
### Tablatal
|
14
46
|
|
15
47
|
```ruby
|
16
48
|
> input = <<~TBTL
|
@@ -21,7 +53,9 @@ Ruby parsers for text file formats. Work in progress.
|
|
21
53
|
Ruca 45 Grey
|
22
54
|
TBTL
|
23
55
|
|
24
|
-
> Nodaire::Tablatal.parse(input)
|
56
|
+
> tablatal = Nodaire::Tablatal.parse(input)
|
57
|
+
|
58
|
+
> tablatal.data
|
25
59
|
# [
|
26
60
|
# { name: 'Erica', age: '12', color: 'Opal' },
|
27
61
|
# { name: 'Alex', age: '23', color: 'Cyan' },
|
@@ -29,15 +63,12 @@ Ruby parsers for text file formats. Work in progress.
|
|
29
63
|
# { name: 'Ruca', age: '45', color: 'Grey' },
|
30
64
|
# ]
|
31
65
|
|
32
|
-
>
|
33
|
-
#
|
34
|
-
|
35
|
-
|
36
|
-
# { 'NAME' => 'Nike', 'AGE' => '34', 'COLOR' => 'Red' },
|
37
|
-
# { 'NAME' => 'Ruca', 'AGE' => '45', 'COLOR' => 'Grey' },
|
38
|
-
# ]
|
66
|
+
> tablatal.valid?
|
67
|
+
# true
|
68
|
+
|
69
|
+
> tablatal = Nodaire::Tablatal.parse(input, preserve_keys: true)
|
39
70
|
|
40
|
-
>
|
71
|
+
> tablatal.to_csv
|
41
72
|
# NAME,AGE,COLOR
|
42
73
|
# Erica,12,Opal
|
43
74
|
# Alex,23,Cyan
|
data/lib/nodaire/formats.rb
CHANGED
@@ -0,0 +1,58 @@
|
|
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,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'csv'
|
4
|
+
|
3
5
|
require_relative '../parsers/tablatal_parser'
|
4
6
|
|
5
7
|
##
|
@@ -8,17 +10,55 @@ require_relative '../parsers/tablatal_parser'
|
|
8
10
|
# Tablatal is (c) Devine Lu Linvega (MIT License).
|
9
11
|
#
|
10
12
|
class Nodaire::Tablatal
|
13
|
+
attr_reader :data, :keys, :errors
|
14
|
+
alias_method :to_a, :data
|
15
|
+
|
11
16
|
##
|
12
|
-
# Parse a string in Tablatal format
|
17
|
+
# Parse a string in Tablatal format.
|
18
|
+
#
|
19
|
+
# Ignores or attempts to work around errors.
|
13
20
|
#
|
14
21
|
def self.parse(string, preserve_keys: false)
|
15
|
-
Parser.new(string, preserve_keys: preserve_keys)
|
22
|
+
parser = Parser.new(string, false, preserve_keys: preserve_keys)
|
23
|
+
|
24
|
+
new(parser)
|
16
25
|
end
|
17
26
|
|
18
27
|
##
|
19
|
-
# Parse a string in Tablatal format
|
28
|
+
# Parse a string in Tablatal format.
|
29
|
+
#
|
30
|
+
# Raises an exception if there are errors.
|
20
31
|
#
|
21
|
-
def self.
|
22
|
-
Parser.new(string, preserve_keys: preserve_keys)
|
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
|
23
63
|
end
|
24
64
|
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../errors'
|
4
|
+
|
5
|
+
class Nodaire::Indental
|
6
|
+
class ParserError < Nodaire::ParserError; end
|
7
|
+
|
8
|
+
class Parser
|
9
|
+
attr_reader :data, :errors
|
10
|
+
|
11
|
+
def initialize(string, strict, options = {})
|
12
|
+
@strict = strict
|
13
|
+
@preserve_keys = options.fetch(:preserve_keys, false)
|
14
|
+
|
15
|
+
@data = {}
|
16
|
+
@errors = []
|
17
|
+
|
18
|
+
parse! string
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def parse!(string)
|
24
|
+
(string || '')
|
25
|
+
.split("\n").each_with_index
|
26
|
+
.reject { |line, _| line.match(/^\s*(;.*)?$/) }
|
27
|
+
.each { |line, idx| parse_line! line, idx + 1 }
|
28
|
+
end
|
29
|
+
|
30
|
+
def parse_line!(line, num)
|
31
|
+
case line.match(/^\s*/)[0].size
|
32
|
+
when 0 then parse_category! line.strip, num
|
33
|
+
when 2 then parse_key_or_list! line.strip, num
|
34
|
+
when 4 then parse_list_item! line.strip, num
|
35
|
+
else oops! 'Unexpected indent', num
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def parse_category!(cat, num)
|
40
|
+
cat = symbolize_key(cat)
|
41
|
+
if data.include?(cat)
|
42
|
+
@cat_id = nil
|
43
|
+
@list_id = nil
|
44
|
+
oops! 'Duplicate category', num
|
45
|
+
else
|
46
|
+
@data[cat] = {}
|
47
|
+
@cat_id = cat
|
48
|
+
@list_id = nil
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def parse_key_or_list!(line, num)
|
53
|
+
return oops!('No category specified', num) unless @cat_id
|
54
|
+
|
55
|
+
if line.include?(' : ')
|
56
|
+
key, value = line.split(' : ', 2)
|
57
|
+
parse_key_value!(key.strip, value.strip, num)
|
58
|
+
else
|
59
|
+
parse_list!(line, num)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def parse_key_value!(key, value, num)
|
64
|
+
key = symbolize_key(key)
|
65
|
+
if @data[@cat_id].include?(key)
|
66
|
+
@list_id = nil
|
67
|
+
oops! 'Duplicate key', num
|
68
|
+
else
|
69
|
+
@data[@cat_id][key] = value.strip
|
70
|
+
@list_id = nil
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def parse_list!(key, num)
|
75
|
+
key = symbolize_key(key)
|
76
|
+
if @data[@cat_id].include?(key)
|
77
|
+
@list_id = nil
|
78
|
+
oops! 'Duplicate key', num
|
79
|
+
else
|
80
|
+
@data[@cat_id][key] = []
|
81
|
+
@list_id = key
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def parse_list_item!(item, num)
|
86
|
+
if @list_id.nil?
|
87
|
+
oops! 'No list specified', num
|
88
|
+
else
|
89
|
+
@data[@cat_id][@list_id] << item
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def oops!(message, line_num)
|
94
|
+
message = "#{message} on line #{line_num}"
|
95
|
+
@errors << message
|
96
|
+
raise ParserError, message if @strict
|
97
|
+
end
|
98
|
+
|
99
|
+
def symbolize_key(key)
|
100
|
+
@preserve_keys ? key : key.downcase.split.join('_').to_sym
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -1,37 +1,20 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'csv'
|
4
|
-
|
5
3
|
require_relative '../errors'
|
6
4
|
|
7
5
|
class Nodaire::Tablatal
|
8
6
|
class ParserError < Nodaire::ParserError; end
|
9
7
|
|
10
|
-
##
|
11
|
-
# Parser for the Tablatal file format.
|
12
|
-
#
|
13
|
-
# Tablatal is (c) Devine Lu Linvega (MIT License).
|
14
|
-
#
|
15
8
|
class Parser
|
16
|
-
attr_reader :
|
9
|
+
attr_reader :data, :errors
|
17
10
|
|
18
|
-
def initialize(string,
|
19
|
-
|
20
|
-
|
21
|
-
return if lines.empty?
|
11
|
+
def initialize(string, strict, options = {})
|
12
|
+
@strict = strict
|
13
|
+
@preserve_keys = options.fetch(:preserve_keys, false)
|
22
14
|
|
23
|
-
@
|
24
|
-
@rows = lines.map { |line| make_line(line) }.compact
|
25
|
-
end
|
15
|
+
@errors = []
|
26
16
|
|
27
|
-
|
28
|
-
key_symbols = keys
|
29
|
-
CSV.generate do |csv|
|
30
|
-
csv << key_symbols
|
31
|
-
rows.each do |row|
|
32
|
-
csv << key_symbols.map { |key| row[key] }
|
33
|
-
end
|
34
|
-
end
|
17
|
+
parse! string
|
35
18
|
end
|
36
19
|
|
37
20
|
def keys
|
@@ -42,22 +25,45 @@ class Nodaire::Tablatal
|
|
42
25
|
|
43
26
|
Key = Struct.new(:name, :range, keyword_init: true)
|
44
27
|
|
45
|
-
def
|
28
|
+
def parse!(string)
|
29
|
+
lines = (string || '').strip.split("\n")
|
30
|
+
.reject { |line| line.match(/^\s*(;.*)?$/) }
|
31
|
+
return if lines.empty?
|
32
|
+
|
33
|
+
@keys = make_keys(lines.shift.scan(/(\S+\s*)/).flatten)
|
34
|
+
@data = lines.map { |line, num| make_line(line, num) }.compact
|
35
|
+
end
|
36
|
+
|
37
|
+
def make_keys(segs)
|
46
38
|
[].tap do |keys|
|
39
|
+
start = 0
|
47
40
|
segs.each_with_index do |seg, idx|
|
48
|
-
key = seg.strip
|
49
|
-
key = key.downcase.to_sym unless preserve_keys
|
50
|
-
raise ParserError, 'Duplicate keys' if keys.any? { |k| key == k.name }
|
51
|
-
|
41
|
+
key = symbolize_key(seg.strip)
|
52
42
|
len = seg.size if idx < segs.size - 1
|
53
|
-
|
54
|
-
keys.
|
43
|
+
|
44
|
+
if keys.any? { |k| key == k.name }
|
45
|
+
oops! "Duplicate key #{key}", 1
|
46
|
+
else
|
47
|
+
keys << Key.new(name: key, range: start...(len && start + len))
|
48
|
+
end
|
49
|
+
|
50
|
+
start += len if len
|
55
51
|
end
|
56
52
|
end
|
57
53
|
end
|
58
54
|
|
59
|
-
def make_line(line)
|
55
|
+
def make_line(line, num)
|
60
56
|
@keys.map { |key| [key.name, (line[key.range] || '').strip] }.to_h
|
61
57
|
end
|
58
|
+
|
59
|
+
def oops!(message, line_num)
|
60
|
+
message = "#{message} on line #{line_num}"
|
61
|
+
@errors << message
|
62
|
+
raise ParserError, message if @strict
|
63
|
+
end
|
64
|
+
|
65
|
+
def symbolize_key(key)
|
66
|
+
@preserve_keys ? key : key.downcase.to_sym
|
67
|
+
end
|
62
68
|
end
|
63
69
|
end
|
data/lib/nodaire/version.rb
CHANGED
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.2.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-17 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
|
@@ -21,7 +21,9 @@ files:
|
|
21
21
|
- lib/nodaire.rb
|
22
22
|
- lib/nodaire/errors.rb
|
23
23
|
- lib/nodaire/formats.rb
|
24
|
+
- lib/nodaire/formats/indental.rb
|
24
25
|
- lib/nodaire/formats/tablatal.rb
|
26
|
+
- lib/nodaire/parsers/indental_parser.rb
|
25
27
|
- lib/nodaire/parsers/tablatal_parser.rb
|
26
28
|
- lib/nodaire/version.rb
|
27
29
|
homepage: https://github.com/ljcooke/nodaire
|