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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d9c70a78b831024d450ba7e9cc01ab5c9db7f9d3b70ad4dca16bb6634cc43364
4
- data.tar.gz: 5ff998283f79e831fee47f4f8f4a987a85a53a18ba239c178d56a5d01def7c1c
3
+ metadata.gz: 57dbe680f2640d6fff1c929cc8b953a7bcb99bd173d207193231a752e50dfdfa
4
+ data.tar.gz: b90ac2968b5ee9d26391228d1f1460a763bd409f879881991113e3286c68e542
5
5
  SHA512:
6
- metadata.gz: 3bd4a7c1ca7584c970b1be353f3d82783c808c784d8408543dd6b7de024dce4fa7068e982aaf0dce5faa66f4860c0945ca9362fa473ca1501326143b1da7596c
7
- data.tar.gz: be8a98f18ec3cbe88bf4b55eef958fb511b8938737c33b77cf81e71265000e5831147788d3796beb0017f1dd4d3859a8daec832e55f6bd574e0cd2525b09eaeb
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
+ [![Gem Version](https://badge.fury.io/rb/nodaire.svg)](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
- - [Indental](https://wiki.xxiivv.com/#indental) (planned)
13
+ ## Examples
10
14
 
11
- - [__Tablatal__](https://wiki.xxiivv.com/#tablatal)
15
+ ### Indental
12
16
 
13
- ## Examples
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
- > Nodaire::Tablatal.parse(input, preserve_keys: true)
33
- # [
34
- # { 'NAME' => 'Erica', 'AGE' => '12', 'COLOR' => 'Opal' },
35
- # { 'NAME' => 'Alex', 'AGE' => '23', 'COLOR' => 'Cyan' },
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
- > Nodaire::Tablatal.to_csv(input, preserve_keys: true)
71
+ > tablatal.to_csv
41
72
  # NAME,AGE,COLOR
42
73
  # Erica,12,Opal
43
74
  # Alex,23,Cyan
@@ -1,3 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'formats/indental'
3
4
  require_relative 'formats/tablatal'
@@ -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 and return an array of hashes.
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).rows
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 and return a string in CSV format.
28
+ # Parse a string in Tablatal format.
29
+ #
30
+ # Raises an exception if there are errors.
20
31
  #
21
- def self.to_csv(string, preserve_keys: false)
22
- Parser.new(string, preserve_keys: preserve_keys).to_csv
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 :rows
9
+ attr_reader :data, :errors
17
10
 
18
- def initialize(string, preserve_keys: false)
19
- lines = (string || '').strip.split("\n")
20
- .reject { |line| line.match(/^\s*(;.*)?$/) }
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
- @keys = make_keys(lines.shift.scan(/(\S+\s*)/).flatten, preserve_keys)
24
- @rows = lines.map { |line| make_line(line) }.compact
25
- end
15
+ @errors = []
26
16
 
27
- def to_csv
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 make_keys(segs, preserve_keys)
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
- start = keys.empty? ? 0 : keys.last.range.last
54
- keys.push Key.new(name: key, range: start...(len && start + len))
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
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Nodaire
4
4
  module Version
5
- STRING = '0.1.0'
6
- DATE = '2019-08-15'
5
+ STRING = '0.2.0'
6
+ DATE = '2019-08-17'
7
7
  end
8
8
  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.1.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-15 00:00:00.000000000 Z
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