nodaire 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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