toml 0.0.2 → 0.0.3

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.
data/README.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  A sane configuration format from @mojombo. More information here: https://github.com/mojombo/toml
4
4
 
5
+ This is far superior to YAML and JSON because it doesn't suck. Really it doesn't.
6
+
5
7
  ## Usage
6
8
 
7
9
  It's simple, really.
@@ -32,4 +34,4 @@ TOML.load_file("my_file.toml")
32
34
 
33
35
  ## Contributors
34
36
 
35
- Written by Jeremy McAnally (@jm) and Dirk Gadsden (@dirk) based on TOML from Tom Preston-Werner (@mojombo).
37
+ Written by Jeremy McAnally (@jm) and Dirk Gadsden (@dirk) based on TOML from Tom Preston-Werner (@mojombo).
@@ -8,9 +8,13 @@ require 'toml/key_group'
8
8
  require 'toml/parslet'
9
9
  require 'toml/transformer'
10
10
  require 'toml/parser'
11
+ require 'toml/generator'
12
+ # Don't do monkey-patching by default. Only pulled in by TOML::Generator
13
+ # if needed (see generator.rb line 27).
14
+ # require 'toml/monkey_patch
11
15
 
12
16
  module TOML
13
- VERSION = '0.0.2'
17
+ VERSION = '0.0.3'
14
18
 
15
19
  def self.load(content)
16
20
  Parser.new(content).parsed
@@ -0,0 +1,68 @@
1
+
2
+ module TOML
3
+ class Generator
4
+ attr_reader :body, :doc
5
+
6
+ def initialize(doc)
7
+ # Ensure all the to_toml methods are injected into the base Ruby classes
8
+ # used by TOML.
9
+ self.class.inject!
10
+
11
+ @body = ""
12
+ @doc = doc
13
+
14
+ visit(@doc)
15
+
16
+ return @body
17
+ end
18
+
19
+ # Whether or not the injections have already been done.
20
+ @@injected = false
21
+ # Inject to_toml methods into the Ruby classes used by TOML (booleans,
22
+ # String, Numeric, Array). You can add to_toml methods to your own classes
23
+ # to allow them to be easily serialized by the generator (and it will shout
24
+ # if something doesn't have a to_toml method).
25
+ def self.inject!
26
+ return if @@injected
27
+ require 'toml/monkey_patch'
28
+ @@injected = true
29
+ end
30
+
31
+ def visit(hash, path = "")
32
+ hash_pairs = [] # Sub-hashes
33
+ other_pairs = []
34
+
35
+ hash.keys.sort.each do |key|
36
+ val = hash[key]
37
+ # TODO: Refactor for other hash-likes (OrderedHash)
38
+ if val.is_a? Hash
39
+ hash_pairs << [key, val]
40
+ else
41
+ other_pairs << [key, val]
42
+ end
43
+ end
44
+
45
+ # Handle all the key-values
46
+ if !path.empty? && !other_pairs.empty?
47
+ @body += "[#{path}]\n"
48
+ end
49
+ other_pairs.each do |pair|
50
+ key, val = pair
51
+ @body += "#{key} = #{format(val)}\n"
52
+ end
53
+ @body += "\n" unless other_pairs.empty?
54
+
55
+ # Then deal with sub-hashes
56
+ hash_pairs.each do |pair|
57
+ key, hash = pair
58
+ visit(hash, (path.empty? ? key : [path, key].join(".")))
59
+ end
60
+ end#visit
61
+
62
+ # Returns the value formatted for TOML.
63
+ def format(val)
64
+ val.to_toml
65
+ end
66
+
67
+ end#Generator
68
+ end#TOML
@@ -1,6 +1,7 @@
1
1
  module TOML
2
2
  class Key
3
3
  attr_reader :key, :value
4
+
4
5
  def initialize(key, value)
5
6
  @key = key
6
7
  @value = value
@@ -0,0 +1,21 @@
1
+ # Adds to_toml methods to base Ruby classes used by the generator.
2
+ class TrueClass
3
+ def to_toml; "true"; end
4
+ end
5
+ class FalseClass
6
+ def to_toml; "false"; end
7
+ end
8
+ class String
9
+ def to_toml; self.inspect; end
10
+ end
11
+ class Numeric
12
+ def to_toml; self.to_s; end
13
+ end
14
+ class Array
15
+ def to_toml
16
+ unless self.map(&:class).uniq.length < 2
17
+ raise "All array values must be the same type"
18
+ end
19
+ "[" + self.map {|v| v.to_toml }.join(",") + "]"
20
+ end
21
+ end
@@ -6,14 +6,29 @@ module TOML
6
6
  # Make sure we have a newline on the end
7
7
  markup += "\n" unless markup.end_with?("\n")
8
8
 
9
- tree = Parslet.new.parse(markup)
9
+ begin
10
+ tree = Parslet.new.parse(markup)
11
+ rescue Parslet::ParseFailed => failure
12
+ puts failure.cause.ascii_tree
13
+ end
14
+
10
15
  parts = Transformer.new.apply(tree)
11
16
 
12
17
  @parsed = {}
13
18
  @current = @parsed
19
+ @current_path = ''
14
20
 
15
21
  parts.each do |part|
16
22
  if part.is_a? Key
23
+ # Make sure the key isn't already set
24
+ if !@current.is_a?(Hash) || @current.has_key?(part.key)
25
+ err = "Cannot override key '#{part.key}'"
26
+ unless @current_path.empty?
27
+ err += " at path '#{@current_path}'"
28
+ end
29
+ raise err
30
+ end
31
+ # Set the key-value into the current hash
17
32
  @current[part.key] = part.value
18
33
  elsif part.is_a? KeyGroup
19
34
  resolve_key_group(part)
@@ -27,6 +42,8 @@ module TOML
27
42
  @current = @parsed
28
43
 
29
44
  path = kg.keys.dup
45
+ @current_path = path.join('.')
46
+
30
47
  while k = path.shift
31
48
  if @current.has_key? k
32
49
  # pass
@@ -1,27 +1,50 @@
1
1
  module TOML
2
2
  class Parslet < ::Parslet::Parser
3
- rule(:document) { (key_group | key_value | comment_line).repeat(0) }
3
+ rule(:document) {
4
+ all_space >>
5
+ (key_group | key_value | comment_line).repeat(0) >>
6
+ all_space
7
+ }
4
8
  root :document
5
9
 
6
10
  rule(:value) {
7
- array.as(:array) |
11
+ array |
8
12
  string |
9
13
  datetime.as(:datetime) |
10
14
  float.as(:float) |
11
15
  integer.as(:integer) |
12
16
  boolean
13
17
  }
14
-
18
+
19
+ # Finding comments in multiline arrays requires accepting a bunch of
20
+ # possible newlines and stuff before the comment
21
+ rule(:array_comments) { (all_space >> comment_line).repeat(0) }
22
+
15
23
  rule(:array) {
16
- str("[") >> (
17
- all_space >> value >>
18
- (all_space >> str(",") >> all_space >> value).repeat(0) >>
19
- all_space
20
- ).maybe >> str("]")
24
+ str("[") >> ( array_comments >> # Match any comments on first line
25
+ all_space >> value >> array_comments >>
26
+ (
27
+ # Separator followed by any comments
28
+ all_space >> str(",") >> array_comments >>
29
+ # Value followed by any comments
30
+ all_space >> value >> array_comments
31
+ ).repeat(0) >>
32
+ all_space >> array_comments # Grab any remaining comments just in case
33
+ ).maybe.as(:array) >> str("]")
21
34
  }
22
35
 
23
- rule(:key_value) { space >> key.as(:key) >> space >> str("=") >> space >> value >> space >> comment.maybe >> str("\n") >> all_space }
24
- rule(:key_group) { space >> str("[") >> key_group_name.as(:key_group) >> str("]") >> space >> comment.maybe >> str("\n") >> all_space }
36
+ rule(:key_value) {
37
+ space >> key.as(:key) >>
38
+ space >> str("=") >>
39
+ space >> value.as(:value) >>
40
+ space >> comment.maybe >> str("\n") >> all_space
41
+ }
42
+ rule(:key_group) {
43
+ space >> str("[") >>
44
+ key_group_name.as(:key_group) >>
45
+ str("]") >>
46
+ space >> comment.maybe >> str("\n") >> all_space
47
+ }
25
48
 
26
49
  rule(:key) { match("[^. \t\\]]").repeat(1) }
27
50
  rule(:key_group_name) { key.as(:key) >> (str(".") >> key.as(:key)).repeat(0) }
@@ -40,12 +63,13 @@ module TOML
40
63
  }
41
64
 
42
65
  rule(:sign) { str("-") }
66
+ rule(:sign?) { sign.maybe }
67
+
43
68
  rule(:integer) {
44
- str("0") | (sign.maybe >> match("[1-9]") >> match("[0-9]").repeat(0))
69
+ str("0") | (sign? >> match("[1-9]") >> match("[0-9]").repeat(0))
45
70
  }
46
-
47
71
  rule(:float) {
48
- sign.maybe >> match("[0-9]").repeat(1) >> str(".") >> match("[0-9]").repeat(1)
72
+ sign? >> match("[0-9]").repeat(1) >> str(".") >> match("[0-9]").repeat(1)
49
73
  }
50
74
 
51
75
  rule(:boolean) { str("true").as(:true) | str("false").as(:false) }
@@ -1,4 +1,5 @@
1
1
  module TOML
2
+
2
3
  class Transformer < ::Parslet::Transform
3
4
  # Utility to properly handle escape sequences in parsed string.
4
5
  def self.parse_string(val)
@@ -33,9 +34,9 @@ module TOML
33
34
  end
34
35
 
35
36
  # Clean up arrays
36
- rule(:array => subtree(:ar)) { ar.is_a?(Array) ? ar : [ar] }
37
-
38
- # Clean up simples (inside arrays)
37
+ # rule(:array => subtree(:ar)) { ar.is_a?(Array) ? ar : [ar] }
38
+
39
+ # Clean up simple value hashes
39
40
  rule(:integer => simple(:i)) { i.to_i }
40
41
  rule(:float => simple(:f)) { f.to_f }
41
42
  rule(:string => simple(:s)) {
@@ -45,20 +46,41 @@ module TOML
45
46
  rule(:true => simple(:b)) { true }
46
47
  rule(:false => simple(:b)) { false }
47
48
 
48
- # TODO: Refactor to remove redundancy
49
- rule(:key => simple(:k), :array => subtree(:ar)) { Key.new(k.to_s, ar) }
50
- rule(:key => simple(:k), :integer => simple(:i)) { Key.new(k.to_s, i.to_i) }
51
- rule(:key => simple(:k), :float => simple(:f)) { Key.new(k.to_s, f.to_f) }
52
- rule(:key => simple(:k), :string => simple(:s)) {
53
- Key.new(k.to_s, Transformer.parse_string(s.to_s))
54
- }
55
- rule(:key => simple(:k), :datetime => simple(:d)) {
56
- Key.new(k.to_s, DateTime.iso8601(d))
49
+ rule(:key => simple(:k), :value => simple(:v)) { Key.new(k.to_s, v) }
50
+
51
+ # New array cleanup
52
+ # TODO: Make this more readable/understandable.
53
+ def self.visit_array(h)
54
+ if h.is_a? Hash
55
+ # If it's an {:array => ...} hash
56
+ a = h[:array]
57
+ if a.is_a? Array
58
+ # If the value is already an array
59
+ a = a.map {|v| visit_array(v) }
60
+ classes = a.map {|v|
61
+ # Grab the class, with a little exception for true and false since
62
+ # they're different classes (TrueClass and FalseClass).
63
+ (v == true || v == false) ? true : v.class
64
+ }
65
+ if classes.uniq.length != 1
66
+ raise "Conflicting types in array: " + \
67
+ classes.map(&:to_s).join(", ")
68
+ end
69
+ return a
70
+ else
71
+ # Turn the value into an array
72
+ return [visit_array(a)].compact
73
+ end
74
+ else
75
+ # Plain old non-hash value
76
+ return h
77
+ end
78
+ end
79
+ rule(:key => simple(:k), :value => subtree(:v)) {
80
+ Key.new(k.to_s, Transformer.visit_array(v))
57
81
  }
58
- rule(:key => simple(:k), :true => simple(:b)) { Key.new(k.to_s, true) }
59
- rule(:key => simple(:k), :false => simple(:b)) { Key.new(k.to_s, false) }
60
82
 
61
- # Make keys just be strings
83
+ # Make key hashes (inside key_groups) just be strings
62
84
  rule(:key => simple(:k)) { k }
63
85
 
64
86
  # Then objectify the key_groups
@@ -71,4 +93,4 @@ module TOML
71
93
  KeyGroup.new(kg.map &:to_s)
72
94
  }
73
95
  end
74
- end
96
+ end
@@ -1,22 +1,26 @@
1
1
  # Comment
2
2
 
3
+ # Booleans
4
+ true = true
5
+ false = false
6
+
7
+ [strings]
3
8
  # String
4
9
  string = "string\n\t\"string"
5
10
 
6
- # Integer
7
- integer = 42
11
+ [ints]
12
+ simple = 42
13
+ negative = -42
8
14
 
9
- # Float
15
+ [floats]
10
16
  pi = 3.14159
17
+ negative = -10.0
11
18
 
12
- # Booleans
13
- true = true
14
- false = false
15
-
19
+ [datetimes]
16
20
  # DateTime
17
- datetime = 1979-05-27T07:32:00Z
21
+ simple = 1979-05-27T07:32:00Z
18
22
 
19
- # Keygroup
23
+ # Keygroups
20
24
  [a.b.c]
21
25
  d = "test"
22
26
 
@@ -33,7 +37,21 @@ on = "a line" # with markup
33
37
  simple = [1, 2, 3]
34
38
 
35
39
  # Nested array
36
- nested = [[[1], 2], 3]
40
+ nested = [[1, 2], [3]]
41
+
42
+ # Multiline array
43
+ multiline = [
44
+ 1,
45
+ 2,
46
+ 3
47
+ ]
48
+
49
+ # With comments
50
+ multiline_comments = [ # 0
51
+ 1, # 1
52
+ 2, # 2
53
+ 3 # 3
54
+ ]
37
55
 
38
56
  multi = ["lines", "are",
39
57
  "super", "cool", "lol",
@@ -0,0 +1,33 @@
1
+
2
+ require 'rubygems'
3
+ require 'bundler/setup'
4
+
5
+ require 'toml'
6
+ require 'minitest/autorun'
7
+
8
+ class TestGenerator < MiniTest::Unit::TestCase
9
+ def setup
10
+ @doc = {
11
+ "integer" => 1,
12
+ "float" => 3.14159,
13
+ "true" => true,
14
+ "false" => false,
15
+ "string" => "hi",
16
+ "array" => [[1], [2], [3]],
17
+ "key" => {
18
+ "group" => {
19
+ "value" => "lol"
20
+ }
21
+ }
22
+ }
23
+
24
+ end
25
+
26
+ def test_generator
27
+ body = TOML::Generator.new(@doc).body
28
+
29
+ doc_parsed = TOML::Parser.new(body).parsed
30
+
31
+ assert_equal @doc, doc_parsed
32
+ end
33
+ end
@@ -12,39 +12,63 @@ class TestParser < MiniTest::Unit::TestCase
12
12
  end
13
13
 
14
14
  def test_string
15
- assert_equal @doc["string"], "string\n\t\"string"
15
+ assert_equal "string\n\t\"string", @doc["strings"]["string"]
16
16
  end
17
17
 
18
18
  def test_integer
19
- assert_equal @doc["integer"], 42
19
+ assert_equal 42, @doc["ints"]["simple"]
20
+ end
21
+
22
+ def test_negative_integer
23
+ assert_equal -42, @doc["ints"]["negative"]
20
24
  end
21
25
 
22
26
  def test_float
23
- assert_equal @doc["pi"], 3.14159
27
+ assert_equal 3.14159, @doc["floats"]["pi"]
28
+ end
29
+
30
+ def test_negative_float
31
+ assert_equal -10.0, @doc["floats"]["negative"]
24
32
  end
25
33
 
26
34
  def test_datetime
27
- assert_equal @doc["datetime"], DateTime.iso8601("1979-05-27T07:32:00Z")
35
+ assert_equal DateTime.iso8601("1979-05-27T07:32:00Z"), @doc["datetimes"]["simple"]
28
36
  end
29
37
 
30
38
  def test_booleans
31
- assert_equal @doc["true"], true
32
- assert_equal @doc["false"], false
39
+ assert_equal true, @doc["true"]
40
+ assert_equal false, @doc["false"]
33
41
  end
34
42
 
35
43
  def test_simple_array
36
- assert_equal @doc["arrays"]["simple"], [1, 2, 3]
44
+ assert_equal [1, 2, 3], @doc["arrays"]["simple"]
37
45
  end
38
46
 
39
47
  def test_nested_array
40
- assert_equal @doc["arrays"]["nested"], [[[1], 2], 3]
48
+ assert_equal [[1, 2], [3]], @doc["arrays"]["nested"]
49
+ end
50
+
51
+ def test_multiline_arrays
52
+ assert_equal ["lines", "are", "super", "cool", "lol", "amirite"], @doc["arrays"]["multi"]
53
+ end
54
+
55
+ def test_multiline_array
56
+ assert_equal @doc["arrays"]["multiline"], [1, 2, 3]
57
+ end
58
+
59
+ def test_multiline_array_with_comments
60
+ assert_equal @doc["arrays"]["multiline_comments"], [1, 2, 3]
41
61
  end
42
62
 
43
63
  def test_simple_keygroup
44
- assert_equal @doc["e"]["f"], "test"
64
+ assert_equal "test", @doc["e"]["f"]
45
65
  end
46
66
 
47
67
  def test_nested_keygroup
48
- assert_equal @doc["a"]["b"]["c"]["d"], "test"
68
+ assert_equal "test", @doc["a"]["b"]["c"]["d"]
69
+ end
70
+
71
+ def test_inline_comment
72
+ assert_equal "a line", @doc["comments"]["on"]
49
73
  end
50
74
  end
@@ -0,0 +1,25 @@
1
+
2
+ require 'rubygems'
3
+ require 'bundler/setup'
4
+
5
+ require 'toml'
6
+
7
+ doc = "
8
+ a = [true, false]
9
+ "
10
+
11
+ puts TOML.load(doc).inspect
12
+
13
+ # puts TOML.load("a = [[[[1]]]]")["a"].inspect
14
+ # puts "[[[[1]]]] <- expected"
15
+ #
16
+ # puts TOML.load("a = [1]")["a"].inspect
17
+ # puts "[1] <- expected"
18
+ #
19
+ # puts TOML.load("a = [1, 2, 3]")["a"].inspect
20
+ # puts "[1, 2, 3] <- expected"
21
+ #
22
+ # puts TOML.load("a = [[[1], 2], 3]")["a"].inspect
23
+ # puts "[[[1], 2], 3] <- expected"
24
+ #
25
+ # puts TOML.load("a = [[]]")["a"].inspect
@@ -13,8 +13,8 @@ Gem::Specification.new do |s|
13
13
  ## If your rubyforge_project name is different, then edit it and comment out
14
14
  ## the sub! line in the Rakefile
15
15
  s.name = 'toml'
16
- s.version = '0.0.2'
17
- s.date = '2013-02-24'
16
+ s.version = '0.0.3'
17
+ s.date = '2013-03-06'
18
18
 
19
19
  ## Make sure your summary is short. The description may be as long
20
20
  ## as you like.
@@ -45,18 +45,21 @@ Gem::Specification.new do |s|
45
45
  # = MANIFEST =
46
46
  s.files = %w[
47
47
  Gemfile
48
- Gemfile.lock
49
48
  LICENSE
50
49
  README.md
51
50
  Rakefile
52
51
  lib/toml.rb
52
+ lib/toml/generator.rb
53
53
  lib/toml/key.rb
54
54
  lib/toml/key_group.rb
55
+ lib/toml/monkey_patch.rb
55
56
  lib/toml/parser.rb
56
57
  lib/toml/parslet.rb
57
58
  lib/toml/transformer.rb
58
59
  test/spec.toml
60
+ test/test_generator.rb
59
61
  test/test_parser.rb
62
+ test/tmp.rb
60
63
  toml.gemspec
61
64
  ]
62
65
  # = MANIFEST =
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: toml
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2013-02-24 00:00:00.000000000 Z
13
+ date: 2013-03-06 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: parslet
@@ -37,18 +37,21 @@ extra_rdoc_files:
37
37
  - LICENSE
38
38
  files:
39
39
  - Gemfile
40
- - Gemfile.lock
41
40
  - LICENSE
42
41
  - README.md
43
42
  - Rakefile
44
43
  - lib/toml.rb
44
+ - lib/toml/generator.rb
45
45
  - lib/toml/key.rb
46
46
  - lib/toml/key_group.rb
47
+ - lib/toml/monkey_patch.rb
47
48
  - lib/toml/parser.rb
48
49
  - lib/toml/parslet.rb
49
50
  - lib/toml/transformer.rb
50
51
  - test/spec.toml
52
+ - test/test_generator.rb
51
53
  - test/test_parser.rb
54
+ - test/tmp.rb
52
55
  - toml.gemspec
53
56
  homepage: http://github.com/jm/toml
54
57
  licenses: []
@@ -76,4 +79,5 @@ signing_key:
76
79
  specification_version: 2
77
80
  summary: Parse your TOML.
78
81
  test_files:
82
+ - test/test_generator.rb
79
83
  - test/test_parser.rb
@@ -1,20 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- toml (0.0.1)
5
- parslet
6
-
7
- GEM
8
- remote: https://rubygems.org/
9
- specs:
10
- blankslate (2.1.2.4)
11
- minitest (4.6.1)
12
- parslet (1.5.0)
13
- blankslate (~> 2.0)
14
-
15
- PLATFORMS
16
- ruby
17
-
18
- DEPENDENCIES
19
- minitest
20
- toml!