toml 0.0.2 → 0.0.3

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