edn 0.0.0 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -3,6 +3,7 @@
3
3
  .bundle
4
4
  .config
5
5
  .yardoc
6
+ .rspec
6
7
  README.html
7
8
  Gemfile.lock
8
9
  InstalledFiles
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2012 Clinton R. Nixon
1
+ Copyright (c) 2012 Relevance Inc & Clinton N. Dreisbach
2
2
 
3
3
  MIT License
4
4
 
@@ -19,4 +19,4 @@ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
19
  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
20
  LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
21
  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # edn-ruby
2
2
 
3
+ © 2012 Relevance Inc and Clinton N. Dreisbach
4
+
3
5
  **edn-ruby** is a Ruby library to read and write [edn][edn] (extensible data notation), a subset of Clojure used for transferring data between applications, much like JSON, YAML, or XML.
4
6
 
5
7
  ## Installation
@@ -21,7 +23,7 @@ Or install it yourself as:
21
23
  To read a string of **edn**:
22
24
 
23
25
  ```ruby
24
- EDN.read("[1 2 {:foo \"bar\"}]")
26
+ EDN.read('[1 2 {:foo "bar"}]')
25
27
  ```
26
28
 
27
29
  To convert a data structure to an **edn** string:
@@ -32,6 +34,20 @@ data.to_edn
32
34
 
33
35
  By default, this will work for strings, symbols, numbers, arrays, hashes, sets, nil, Time, and boolean values.
34
36
 
37
+ ### Value Translations
38
+
39
+ **edn** has some different terminology, and some types that do not map cleanly to Ruby.
40
+
41
+ **NOTE**: Comments requested on the following.
42
+
43
+ In **edn**, you have _keywords_, which look like Ruby symbols and have the same meaning and purpose. These are converted to Ruby symbols.
44
+
45
+ You have **edn** _symbols_, which generally reflect variable names, but have several purposes. We parse these and return `EDN::Type::Symbol` values for them, as they are not directly portable into Ruby.
46
+
47
+ You have _vectors_, which map to Ruby arrays, and _lists_, which are linked lists in Clojure. We map these to `EDN::Type::List` values, which are type-compatible with arrays.
48
+
49
+ **edn** has character types, but Ruby does not. These are converted into one-character strings.
50
+
35
51
  ### Tagged Values
36
52
 
37
53
  The interesting part of **edn** is the _extensible_ part. Data can be be _tagged_ to coerce interpretation of it to a particular data type. An example of a tagged data element:
@@ -44,9 +60,9 @@ The tag (`#wolf/pack`) will tell any consumers of this data to use a data type r
44
60
 
45
61
  The rules for tags from the [**edn** README][README] should be followed. In short, custom tags should have a prefix (the part before the `/`) designating the user that created them or context they are used in. Non-prefixed tags are reserved for built-in tags.
46
62
 
47
- There are two tags built in by default: `#uuid`, used for UUIDs, and `#inst`, used for an instant in time. In `edn-ruby`, `#inst` is converted to a Time, and Time values are tagged as `#inst`. There is not a UUID data type built into Ruby, so `#uuid` is converted to a string, but if you require `edn/uuid`, `#uuid` values are converted to an instance of `EDN::UUID`.
63
+ There are two tags built in by default: `#uuid`, used for UUIDs, and `#inst`, used for an instant in time. In `edn-ruby`, `#inst` is converted to a Time, and Time values are tagged as `#inst`. There is not a UUID data type built into Ruby, so `#uuid` is converted to an instance of `EDN::Type::UUID`.
48
64
 
49
- Tags that are not registered are converted as their base data type and a warning will be shown.
65
+ Tags that are not registered generate a struct of the type `EDN::Type::Unknown` with the methods `tag` and `value`.
50
66
 
51
67
  ### Registering a New Tag For Reading
52
68
 
@@ -106,6 +122,5 @@ This method calls `.to_edn` on the second argument and joins the arguments appro
106
122
  4. Push to the branch (`git push origin my-new-feature`)
107
123
  5. Create new Pull Request
108
124
 
109
-
110
125
  [edn]: https://github.com/richhickey/edn
111
126
  [README]: https://github.com/richhickey/edn/blob/master/README.md
data/Rakefile CHANGED
@@ -1,2 +1,7 @@
1
1
  #!/usr/bin/env rake
2
2
  require "bundler/gem_tasks"
3
+ require 'rspec/core/rake_task'
4
+
5
+ RSpec::Core::RakeTask.new(:spec)
6
+
7
+ task :default => :spec
data/edn.gemspec CHANGED
@@ -14,4 +14,9 @@ Gem::Specification.new do |gem|
14
14
  gem.name = "edn"
15
15
  gem.require_paths = ["lib"]
16
16
  gem.version = EDN::VERSION
17
+
18
+ gem.add_dependency 'parslet'
19
+ gem.add_development_dependency 'pry'
20
+ gem.add_development_dependency 'rspec'
21
+ gem.add_development_dependency 'rantly'
17
22
  end
data/lib/edn.rb CHANGED
@@ -1,5 +1,54 @@
1
+ $:.push(File.dirname(__FILE__))
1
2
  require "edn/version"
3
+ require "edn/core_ext"
4
+ require "edn/parser"
5
+ require "edn/transform"
2
6
 
3
7
  module EDN
4
- # Your code goes here...
8
+ @parser = EDN::Parser.new
9
+ @transform = EDN::Transform.new
10
+ @tags = Hash.new
11
+
12
+ def self.read(edn)
13
+ @transform.apply(@parser.parse(edn))
14
+ end
15
+
16
+ def self.register(tag, func = nil, &block)
17
+ if block_given?
18
+ func = block
19
+ end
20
+
21
+ raise "EDN.register requires a block or callable." if func.nil?
22
+
23
+ if func.is_a?(Class)
24
+ @tags[tag] = lambda { |*args| func.new(*args) }
25
+ else
26
+ @tags[tag] = func
27
+ end
28
+ end
29
+
30
+ def self.unregister(tag)
31
+ @tags[tag] = nil
32
+ end
33
+
34
+ def self.tag_value(tag, value)
35
+ func = @tags[tag]
36
+ if func
37
+ func.call(value)
38
+ else
39
+ EDN::Type::Unknown.new(tag, value)
40
+ end
41
+ end
42
+
43
+ def self.tagout(tag, value)
44
+ ["##{tag}", value.to_edn].join(" ")
45
+ end
46
+ end
47
+
48
+ EDN.register("inst") do |value|
49
+ Time.parse(value)
50
+ end
51
+
52
+ EDN.register("uuid") do |value|
53
+ EDN::Type::UUID.new(value)
5
54
  end
@@ -0,0 +1,64 @@
1
+ require 'time'
2
+
3
+ module EDN
4
+ module CoreExt
5
+ module Unquoted
6
+ def to_edn
7
+ self.to_s
8
+ end
9
+ end
10
+
11
+ module String
12
+ def to_edn
13
+ self.inspect
14
+ end
15
+ end
16
+
17
+ module Symbol
18
+ def to_edn
19
+ ":#{self.to_s}"
20
+ end
21
+ end
22
+
23
+ module Array
24
+ def to_edn
25
+ '[' + self.map(&:to_edn).join(" ") + ']'
26
+ end
27
+ end
28
+
29
+ module Hash
30
+ def to_edn
31
+ '{' + self.map { |k, v| [k, v].map(&:to_edn).join(" ") }.join(", ") + '}'
32
+ end
33
+ end
34
+
35
+ module Set
36
+ def to_edn
37
+ '#{' + self.to_a.map(&:to_edn).join(" ") + '}'
38
+ end
39
+ end
40
+
41
+ module NilClass
42
+ def to_edn
43
+ "nil"
44
+ end
45
+ end
46
+
47
+ module Time
48
+ def to_edn
49
+ EDN.tagout("inst", self.xmlschema)
50
+ end
51
+ end
52
+ end
53
+ end
54
+
55
+ Numeric.send(:include, EDN::CoreExt::Unquoted)
56
+ TrueClass.send(:include, EDN::CoreExt::Unquoted)
57
+ FalseClass.send(:include, EDN::CoreExt::Unquoted)
58
+ NilClass.send(:include, EDN::CoreExt::NilClass)
59
+ String.send(:include, EDN::CoreExt::String)
60
+ Symbol.send(:include, EDN::CoreExt::Symbol)
61
+ Array.send(:include, EDN::CoreExt::Array)
62
+ Hash.send(:include, EDN::CoreExt::Hash)
63
+ Set.send(:include, EDN::CoreExt::Set)
64
+ Time.send(:include, EDN::CoreExt::Time)
data/lib/edn/parser.rb ADDED
@@ -0,0 +1,140 @@
1
+ require 'parslet'
2
+
3
+ module EDN
4
+ class Parser < Parslet::Parser
5
+ root(:top)
6
+
7
+ rule(:top) {
8
+ space? >> value >> space?
9
+ }
10
+
11
+ rule(:value) {
12
+ tagged_value | base_value
13
+ }
14
+
15
+ rule(:base_value) {
16
+ vector |
17
+ list |
18
+ set |
19
+ map |
20
+ boolean |
21
+ str('nil').as(:nil) |
22
+ keyword |
23
+ string |
24
+ character |
25
+ float |
26
+ integer |
27
+ symbol
28
+ }
29
+
30
+ rule(:tagged_value) {
31
+ (tag >> space >> base_value.as(:value)).as(:tagged_value)
32
+ }
33
+
34
+ # Collections
35
+
36
+ rule(:vector) {
37
+ str('[') >>
38
+ top.repeat.as(:vector) >>
39
+ space? >>
40
+ str(']')
41
+ }
42
+
43
+ rule(:list) {
44
+ str('(') >>
45
+ top.repeat.as(:list) >>
46
+ space? >>
47
+ str(')')
48
+ }
49
+
50
+ rule(:set) {
51
+ str('#{') >>
52
+ top.repeat.as(:set) >>
53
+ space? >>
54
+ str('}')
55
+ }
56
+
57
+ rule(:map) {
58
+ str('{') >>
59
+ (top.as(:key) >> top.as(:value)).repeat.as(:map) >>
60
+ space? >>
61
+ str('}')
62
+ }
63
+
64
+ # Primitives
65
+
66
+ rule(:integer) {
67
+ (str('-').maybe >>
68
+ (str('0') | match('[1-9]') >> digit.repeat)).as(:integer) >>
69
+ str('N').maybe
70
+ }
71
+
72
+ rule(:float) {
73
+ (str('-').maybe >>
74
+ (str('0') | (match('[1-9]') >> digit.repeat)) >>
75
+ str('.') >> digit.repeat(1) >>
76
+ (match('[eE]') >> match('[\-+]').maybe >> digit.repeat).maybe).as(:float) >>
77
+ str('M').maybe
78
+ }
79
+
80
+ rule(:string) {
81
+ str('"') >>
82
+ (str('\\') >> any | str('"').absent? >> any).repeat.as(:string) >>
83
+ str('"')
84
+ }
85
+
86
+ rule(:character) {
87
+ str("\\") >>
88
+ (str('newline') | str('space') | str('tab') |
89
+ match['[:graph:]']).as(:character)
90
+ }
91
+
92
+ rule(:keyword) {
93
+ str(':') >> symbol.as(:keyword)
94
+ }
95
+
96
+ rule(:symbol) {
97
+ (symbol_chars >> (str('/') >> symbol_chars).maybe |
98
+ str('/')).as(:symbol)
99
+ }
100
+
101
+ rule(:boolean) {
102
+ str('true').as(:true) | str('false').as(:false)
103
+ }
104
+
105
+ # Parts
106
+
107
+ rule(:tag) {
108
+ (str('#') >> symbol).as(:tag)
109
+ }
110
+
111
+ rule(:symbol_chars) {
112
+ (symbol_first_char >>
113
+ valid_chars.repeat) |
114
+ match['\-\.']
115
+ }
116
+
117
+ rule(:symbol_first_char) {
118
+ (match['\-\.'] >> match['0-9'].absent? |
119
+ match['\#\:0-9'].absent?) >> valid_chars
120
+ }
121
+
122
+ rule(:valid_chars) {
123
+ match['[:alnum:]'] | sym_punct
124
+ }
125
+
126
+ rule(:sym_punct) {
127
+ match['\.\*\+\!\-\?\:\#\_']
128
+ }
129
+
130
+ rule(:digit) {
131
+ match['0-9']
132
+ }
133
+
134
+ rule(:space) {
135
+ match('[\s,]').repeat(1)
136
+ }
137
+
138
+ rule(:space?) { space.maybe }
139
+ end
140
+ end
@@ -0,0 +1,46 @@
1
+ module EDN
2
+ module StringTransformer
3
+ # Unescape characters in strings.
4
+ # Borrowed from json-pure gem.
5
+ UNESCAPE_MAP = Hash.new { |h, k| h[k] = k.chr }
6
+ UNESCAPE_MAP.update({ ?" => '"',
7
+ ?\\ => '\\',
8
+ ?/ => '/',
9
+ #' Clear messed up syntax highlighting with Emacs.
10
+ ?b => "\b",
11
+ ?f => "\f",
12
+ ?n => "\n",
13
+ ?r => "\r",
14
+ ?t => "\t",
15
+ ?u => nil,
16
+ })
17
+
18
+ EMPTY_8BIT_STRING = ''
19
+ if ::String.method_defined?(:encode)
20
+ EMPTY_8BIT_STRING.force_encoding Encoding::ASCII_8BIT
21
+ end
22
+
23
+ def self.parse_string(string)
24
+ string = string.to_s
25
+ return '' if string.empty?
26
+ string = string.gsub(%r((?:\\[\\bfnrt"/]|(?:\\u(?:[A-Fa-f\d]{4}))+|\\[\x20-\xff]))n) do |c|
27
+ #" Clear messed up syntax highlighting with Emacs.
28
+ if u = UNESCAPE_MAP[$&[1]]
29
+ u
30
+ else # \uXXXX
31
+ bytes = EMPTY_8BIT_STRING.dup
32
+ i = 0
33
+ while c[6 * i] == ?\\ && c[6 * i + 1] == ?u
34
+ bytes << c[6 * i + 2, 2].to_i(16) << c[6 * i + 4, 2].to_i(16)
35
+ i += 1
36
+ end
37
+ JSON.iconv('utf-8', 'utf-16be', bytes)
38
+ end
39
+ end
40
+ if string.respond_to?(:force_encoding)
41
+ string.force_encoding(::Encoding::UTF_8)
42
+ end
43
+ string
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,34 @@
1
+ require 'edn/string_transformer'
2
+ require 'edn/types'
3
+
4
+ module EDN
5
+ class Transform < Parslet::Transform
6
+ rule(:true => simple(:x)) { true }
7
+ rule(:false => simple(:x)) { false }
8
+ rule(:nil => simple(:x)) { nil }
9
+
10
+ rule(:integer => simple(:x)) { Integer(x) }
11
+ rule(:float => simple(:x)) { Float(x) }
12
+
13
+ rule(:string => simple(:x)) { EDN::StringTransformer.parse_string(x) }
14
+ rule(:keyword => simple(:x)) { x.to_sym }
15
+ rule(:symbol => simple(:x)) { EDN::Type::Symbol.new(x) }
16
+ rule(:character => simple(:x)) {
17
+ case x
18
+ when "newline" then "\n"
19
+ when "tab" then "\t"
20
+ when "space" then " "
21
+ else x.to_s
22
+ end
23
+ }
24
+
25
+ rule(:vector => subtree(:array)) { array }
26
+ rule(:list => subtree(:array)) { EDN::Type::List.new(*array) }
27
+ rule(:set => subtree(:array)) { Set.new(array) }
28
+ rule(:map => subtree(:array)) { Hash[array.map { |hash| [hash[:key], hash[:value]] }] }
29
+
30
+ rule(:tagged_value => subtree(:x)) {
31
+ EDN.tag_value(x[:tag].to_s, x[:value])
32
+ }
33
+ end
34
+ end
@@ -0,0 +1,13 @@
1
+ module EDN
2
+ module Type
3
+ class List < ::Array
4
+ def self.new(*values)
5
+ self.[](*values)
6
+ end
7
+
8
+ def to_edn
9
+ '(' + self.map(&:to_edn).join(" ") + ')'
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,37 @@
1
+ module EDN
2
+ module Type
3
+ class Symbol
4
+ attr_reader :symbol
5
+
6
+ def initialize(sym)
7
+ @symbol = sym.to_sym
8
+ end
9
+
10
+ def ==(other)
11
+ return false unless other.is_a?(Symbol)
12
+ to_sym == other.to_sym
13
+ end
14
+
15
+ def eql?(other)
16
+ return false unless other.is_a?(Symbol)
17
+ to_sym == other.to_sym
18
+ end
19
+
20
+ def hash
21
+ @symbol.hash
22
+ end
23
+
24
+ def to_sym
25
+ @symbol
26
+ end
27
+
28
+ def to_s
29
+ @symbol.to_s
30
+ end
31
+
32
+ def to_edn
33
+ @symbol.to_s
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,9 @@
1
+ module EDN
2
+ module Type
3
+ class Unknown < Struct.new(:tag, :value)
4
+ def to_edn
5
+ EDN.tagout(tag, value)
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module EDN
2
+ module Type
3
+ class UUID < String
4
+ def to_edn
5
+ "#uuid #{self.inspect}"
6
+ end
7
+ end
8
+ end
9
+ end
data/lib/edn/types.rb ADDED
@@ -0,0 +1,3 @@
1
+ Dir[File.join(File.dirname(__FILE__), 'type', '*.rb')].each do |file|
2
+ require file
3
+ end
data/lib/edn/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module EDN
2
- VERSION = "0.0.0"
2
+ VERSION = "0.9.0"
3
3
  end
@@ -0,0 +1,160 @@
1
+ require 'spec_helper'
2
+
3
+ describe EDN::Parser do
4
+ include RantlyHelpers
5
+
6
+ let(:parser) { EDN::Parser.new }
7
+
8
+ context "value" do
9
+ it "should consume nil" do
10
+ parser.value.should parse("nil")
11
+ end
12
+ end
13
+
14
+ context "boolean" do
15
+ it "should consume true" do
16
+ parser.boolean.should parse("true")
17
+ end
18
+
19
+ it "should consume false" do
20
+ parser.boolean.should parse("false")
21
+ end
22
+ end
23
+
24
+ context "integer" do
25
+ it "should consume integers" do
26
+ rant(RantlyHelpers::INTEGER).each do |int|
27
+ parser.integer.should parse(int)
28
+ end
29
+ end
30
+ end
31
+
32
+ context "float" do
33
+ it "should consume simple floats" do
34
+ rant(RantlyHelpers::FLOAT).each do |float|
35
+ parser.float.should parse(float)
36
+ end
37
+ end
38
+
39
+ it "should consume floats with exponents" do
40
+ rant(RantlyHelpers::FLOAT_WITH_EXP).each do |float|
41
+ parser.float.should parse(float)
42
+ end
43
+ end
44
+ end
45
+
46
+ context "symbol" do
47
+ it "should consume any symbols" do
48
+ rant(RantlyHelpers::SYMBOL).each do |symbol|
49
+ parser.symbol.should parse("#{symbol}")
50
+ end
51
+ end
52
+
53
+ context "special cases" do
54
+ it "should consume '/'" do
55
+ parser.symbol.should parse('/')
56
+ end
57
+
58
+ it "should consume '.'" do
59
+ parser.symbol.should parse('.')
60
+ end
61
+
62
+ it "should consume '-'" do
63
+ parser.symbol.should parse('-')
64
+ end
65
+ end
66
+ end
67
+
68
+ context "keyword" do
69
+ it "should consume any keywords" do
70
+ rant(RantlyHelpers::SYMBOL).each do |symbol|
71
+ parser.keyword.should parse(":#{symbol}")
72
+ end
73
+ end
74
+ end
75
+
76
+ context "tag" do
77
+ it "should consume any tags" do
78
+ rant(RantlyHelpers::SYMBOL).each do |symbol|
79
+ parser.tag.should parse("##{symbol}")
80
+ end
81
+ end
82
+ end
83
+
84
+ context "string" do
85
+ it "should consume any string" do
86
+ rant(RantlyHelpers::STRING).each do |string|
87
+ parser.string.should parse(string)
88
+ end
89
+ end
90
+ end
91
+
92
+ context "character" do
93
+ it "should consume any character" do
94
+ rant(RantlyHelpers::CHARACTER).each do |char|
95
+ parser.character.should parse(char)
96
+ end
97
+ end
98
+ end
99
+
100
+ context "vector" do
101
+ it "should consume an empty vector" do
102
+ parser.vector.should parse('[]')
103
+ parser.vector.should parse('[ ]')
104
+ end
105
+
106
+ it "should consume vectors of mixed elements" do
107
+ rant(RantlyHelpers::VECTOR).each do |vector|
108
+ parser.vector.should parse(vector)
109
+ end
110
+ end
111
+ end
112
+
113
+ context "list" do
114
+ it "should consume an empty list" do
115
+ parser.list.should parse('()')
116
+ parser.list.should parse('( )')
117
+ end
118
+
119
+ it "should consume lists of mixed elements" do
120
+ rant(RantlyHelpers::LIST).each do |list|
121
+ parser.list.should parse(list)
122
+ end
123
+ end
124
+ end
125
+
126
+ context "set" do
127
+ it "should consume an empty set" do
128
+ parser.set.parse_with_debug('#{}')
129
+ parser.set.should parse('#{}')
130
+ parser.set.should parse('#{ }')
131
+ end
132
+
133
+ it "should consume sets of mixed elements" do
134
+ rant(RantlyHelpers::SET).each do |set|
135
+ parser.set.should parse(set)
136
+ end
137
+ end
138
+ end
139
+
140
+ context "map" do
141
+ it "should consume an empty map" do
142
+ parser.map.should parse('{}')
143
+ parser.map.should parse('{ }')
144
+ end
145
+
146
+ it "should consume maps of mixed elements" do
147
+ rant(RantlyHelpers::MAP).each do |map|
148
+ parser.map.should parse(map)
149
+ end
150
+ end
151
+ end
152
+
153
+ context "tagged value" do
154
+ it "should consume tagged values" do
155
+ rant(RantlyHelpers::TAGGED_VALUE).each do |value|
156
+ parser.tagged_value.should parse(value)
157
+ end
158
+ end
159
+ end
160
+ end
@@ -0,0 +1,129 @@
1
+ require 'spec_helper'
2
+
3
+ describe EDN::Transform do
4
+ context "integer" do
5
+ it "should emit an integer" do
6
+ subject.apply(:integer => "1").should == 1
7
+ end
8
+ end
9
+
10
+ context "float" do
11
+ it "should emit an float" do
12
+ subject.apply(:float => "1.0").should == 1.0
13
+ end
14
+ end
15
+
16
+ context "string" do
17
+ it "should emit a string with control characters substituted" do
18
+ subject.apply(:string => 'hello\nworld').should == "hello\nworld"
19
+ end
20
+
21
+ it "should not evaluate interpolated Ruby code" do
22
+ subject.apply(:string => 'hello\n#{world}').should == "hello\n\#{world}"
23
+ end
24
+ end
25
+
26
+ context "keyword" do
27
+ it "should emit a Ruby symbol" do
28
+ subject.apply(:keyword => "test").should == :test
29
+ end
30
+ end
31
+
32
+ context "symbol" do
33
+ it "should emit an EDN symbol" do
34
+ subject.apply(:symbol => "test").should == EDN::Type::Symbol.new('test')
35
+ end
36
+ end
37
+
38
+ context "boolean" do
39
+ it "should emit true or false" do
40
+ subject.apply(:true => "true").should == true
41
+ subject.apply(:false => "false").should == false
42
+ end
43
+ end
44
+
45
+ context "nil" do
46
+ it "should emit nil" do
47
+ subject.apply(:nil => "nil").should == nil
48
+ end
49
+ end
50
+
51
+ context "character" do
52
+ it "should emit a string" do
53
+ subject.apply(:character => "&").should == "&"
54
+ end
55
+
56
+ it "should handle newline, space, and tab special cases" do
57
+ subject.apply(:character => "newline").should == "\n"
58
+ subject.apply(:character => "space").should == " "
59
+ subject.apply(:character => "tab").should == "\t"
60
+ end
61
+ end
62
+
63
+ context "vector" do
64
+ it "should emit an array" do
65
+ subject.apply(:vector => []).should == []
66
+ subject.apply(:vector => [{:integer => "1"}, {:string => "abc"}]).should == [1, "abc"]
67
+ subject.apply(:vector => [{:vector => [{:integer => "1"}, {:string => "abc"}]}, {:float => "3.14"}]).should == [[1, "abc"], 3.14]
68
+ end
69
+ end
70
+
71
+ context "list" do
72
+ it "should emit a list" do
73
+ subject.apply(:list => []).should == EDN::Type::List.new
74
+ subject.apply(:list => [{:integer => "1"}, {:string => "abc"}]).should == EDN::Type::List.new(1, "abc")
75
+ subject.apply(:list => [{:list => [{:integer => "1"}, {:string => "abc"}]}, {:float => "3.14"}]).should == \
76
+ EDN::Type::List.new(EDN::Type::List.new(1, "abc"), 3.14)
77
+ end
78
+
79
+ it "should be type-compatible with arrays" do
80
+ subject.apply(:list => [{:integer => "1"}, {:string => "abc"}]).should == [1, "abc"]
81
+ end
82
+ end
83
+
84
+ context "set" do
85
+ it "should emit a set" do
86
+ subject.apply(:set => []).should == Set.new
87
+ subject.apply(:set => [1, "abc", 2]).should == Set.new([1, "abc", 2])
88
+ end
89
+ end
90
+
91
+ context "map" do
92
+ it "should emit a hash" do
93
+ map_tree = {:map=>
94
+ [ {:key=>{:keyword=>{:symbol=>"a"}}, :value=>{:integer=>"1"}},
95
+ {:key=>{:keyword=>{:symbol=>"b"}}, :value=>{:integer=>"2"}}
96
+ ]
97
+ }
98
+
99
+ subject.apply(map_tree).should == {:a => 1, :b => 2}
100
+ end
101
+
102
+ it "should work with nested maps" do
103
+ map_tree = {:map=>
104
+ [{:key=>{:keyword=>{:symbol=>"a"}}, :value=>{:integer=>"1"}},
105
+ {:key=>{:keyword=>{:symbol=>"b"}}, :value=>{:integer=>"2"}},
106
+ {:key=>
107
+ {:map=>
108
+ [{:key=>{:keyword=>{:symbol=>"c"}}, :value=>{:integer=>"3"}}]},
109
+ :value=>{:integer=>"4"}}]}
110
+ subject.apply(map_tree).should == {:a => 1, :b => 2, {:c => 3} => 4}
111
+ end
112
+ end
113
+
114
+ context "tagged value" do
115
+ it "should emit a EDN::Type::Unknown if the tag is not registered" do
116
+ subject.apply(:tagged_value => {
117
+ :tag => 'uri', :value => {:string => 'http://google.com'}
118
+ }).should == EDN::Type::Unknown.new("uri", "http://google.com")
119
+ end
120
+
121
+ it "should emit the transformed value if the tag is registered" do
122
+ EDN.register("uri", lambda { |uri| URI(uri) })
123
+ subject.apply(:tagged_value => {
124
+ :tag => 'uri', :value => {:string => 'http://google.com'}
125
+ }).should == URI("http://google.com")
126
+ EDN.unregister("uri") # cleanup
127
+ end
128
+ end
129
+ end
data/spec/edn_spec.rb ADDED
@@ -0,0 +1,83 @@
1
+ require 'spec_helper'
2
+
3
+ describe EDN do
4
+ include RantlyHelpers
5
+
6
+ context "#read" do
7
+ it "reads single values" do
8
+ EDN.read("1").should == 1
9
+ EDN.read("3.14").should == 3.14
10
+ EDN.read('"hello\nworld"').should == "hello\nworld"
11
+ EDN.read(':hello').should == :hello
12
+ EDN.read(':hello/world').should == :"hello/world"
13
+ EDN.read('hello').should == EDN::Type::Symbol.new('hello')
14
+ EDN.read('hello/world').should == EDN::Type::Symbol.new('hello/world')
15
+ EDN.read('true').should == true
16
+ EDN.read('false').should == false
17
+ EDN.read('nil').should == nil
18
+ EDN.read('\c').should == "c"
19
+ end
20
+
21
+ it "reads #inst tagged values" do
22
+ EDN.read('#inst "2012-09-10T16:16:03-04:00"').should == Time.new(2012, 9, 10, 16, 16, 3, '-04:00')
23
+ end
24
+
25
+ it "reads vectors" do
26
+ EDN.read('[]').should == []
27
+ EDN.read('[1]').should == [1]
28
+ EDN.read('["hello" 1 2]').should == ['hello', 1, 2]
29
+ EDN.read('[[1 [:hi]]]').should == [[1, [:hi]]]
30
+ end
31
+
32
+ it "reads lists" do
33
+ EDN.read('()').should == []
34
+ EDN.read('(1)').should == [1]
35
+ EDN.read('("hello" 1 2)').should == ['hello', 1, 2]
36
+ EDN.read('((1 (:hi)))').should == [[1, [:hi]]]
37
+ end
38
+
39
+ it "reads maps" do
40
+ EDN.read('{}').should == {}
41
+ EDN.read('{:a :b}').should == {:a => :b}
42
+ EDN.read('{:a 1, :b 2}').should == {:a => 1, :b => 2}
43
+ EDN.read('{:a {:b :c}}').should == {:a => {:b => :c}}
44
+ end
45
+
46
+ it "reads sets" do
47
+ EDN.read('#{}').should == Set.new
48
+ EDN.read('#{1}').should == Set[1]
49
+ EDN.read('#{1 "abc"}').should == Set[1, "abc"]
50
+ EDN.read('#{1 #{:abc}}').should == Set[1, Set[:abc]]
51
+ end
52
+
53
+ it "reads any valid value" do
54
+ values = rant(RantlyHelpers::VALUE)
55
+ values.each do |value|
56
+ if value == "nil"
57
+ EDN.read(value).should be_nil
58
+ else
59
+ EDN.read(value).should_not be_nil
60
+ end
61
+ end
62
+ end
63
+ end
64
+
65
+ context "writing" do
66
+ it "writes any valid value" do
67
+ values = rant(RantlyHelpers::VALUE)
68
+ values.each do |value|
69
+ expect {
70
+ EDN.read(value).to_edn
71
+ }.to_not raise_error
72
+ end
73
+ end
74
+
75
+ it "writes equivalent edn to what it reads" do
76
+ values = rant(RantlyHelpers::VALUE)
77
+ values.each do |value|
78
+ ruby_value = EDN.read(value)
79
+ ruby_value.should == EDN.read(ruby_value.to_edn)
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,108 @@
1
+ require 'rspec'
2
+ require 'edn'
3
+ require 'parslet/rig/rspec'
4
+ require 'parslet/convenience'
5
+ require 'rantly'
6
+
7
+ REPEAT = (ENV["REPEAT"] || 100).to_i
8
+
9
+ RSpec.configure do |c|
10
+ c.fail_fast = true
11
+ end
12
+
13
+ module RantlyHelpers
14
+
15
+ SYMBOL = lambda { |_|
16
+ branch(PLAIN_SYMBOL, NAMESPACED_SYMBOL)
17
+ }
18
+
19
+ PLAIN_SYMBOL = lambda { |_|
20
+ sized(range(1, 100)) {
21
+ s = string(/[[:alnum:]]|[\-\.\.\*\+\!\-\?\:\#\_]/)
22
+ guard s =~ /^[A-Za-z\-\.\.\*\+\!\-\?\:\#\_]/
23
+ guard s !~ /^[\-\.][0-9]/
24
+ guard s !~ /^[\:\#]/
25
+ s
26
+ }
27
+ }
28
+
29
+ NAMESPACED_SYMBOL = lambda { |_|
30
+ [call(PLAIN_SYMBOL), call(PLAIN_SYMBOL)].join("/")
31
+ }
32
+
33
+ INTEGER = lambda { |_| integer.to_s }
34
+
35
+ STRING = lambda { |_| sized(range(1, 100)) { string.inspect } }
36
+
37
+ FLOAT = lambda { |_| (float * range(-1000, 1000)).to_s }
38
+
39
+ FLOAT_WITH_EXP = lambda { |_|
40
+ # limited range because of Infinity
41
+ [float, choose("e", "E", "e+", "E+", "e-", "e+"), range(1, 100)].
42
+ map(&:to_s).
43
+ join("")
44
+ }
45
+
46
+ CHARACTER = lambda { |_|
47
+ "\\" +
48
+ sized(1) {
49
+ freq([1, [:choose, "newline", "space", "tab"]],
50
+ [5, [:string, :graph]])
51
+ }
52
+ }
53
+
54
+ BOOL_OR_NIL = lambda { |_|
55
+ choose("true", "false", "nil")
56
+ }
57
+
58
+ ARRAY = lambda { |_|
59
+ array(range(0, 10)) { call(VALUE) }
60
+ }
61
+
62
+ VECTOR = lambda { |_|
63
+ "[" + call(ARRAY).join(" ") + "]"
64
+ }
65
+
66
+ LIST = lambda { |_|
67
+ "(" + call(ARRAY).join(" ") + ")"
68
+ }
69
+
70
+ SET = lambda { |_|
71
+ '#{' + call(ARRAY).join(" ") + '}'
72
+ }
73
+
74
+ MAP = lambda { |_|
75
+ size = range(0, 10)
76
+ keys = array(size) { call(VALUE) }
77
+ values = array(size) { call(VALUE) }
78
+ arrays = keys.zip(values)
79
+ '{' + arrays.map { |array| array.join(" ") }.join(", ") + '}'
80
+ }
81
+
82
+ VALUE = lambda { |_|
83
+ freq([10, BASIC_VALUE],
84
+ [1, TAGGED_VALUE])
85
+ }
86
+
87
+ BASIC_VALUE = lambda { |_|
88
+ branch(INTEGER,
89
+ FLOAT,
90
+ FLOAT_WITH_EXP,
91
+ STRING,
92
+ SYMBOL,
93
+ CHARACTER,
94
+ BOOL_OR_NIL,
95
+ VECTOR,
96
+ LIST,
97
+ SET,
98
+ MAP)
99
+ }
100
+
101
+ TAGGED_VALUE = lambda { |_|
102
+ "#" + [call(SYMBOL), call(BASIC_VALUE)].join(" ")
103
+ }
104
+
105
+ def rant(fun, count = REPEAT)
106
+ Rantly(count) { call(fun) }
107
+ end
108
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: edn
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.0
4
+ version: 0.9.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,8 +9,72 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-09-07 00:00:00.000000000 Z
13
- dependencies: []
12
+ date: 2012-09-10 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: parslet
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: pry
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rspec
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: rantly
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
14
78
  description: ! '''edn implements a reader for Extensible Data Notation by Rich Hickey.'''
15
79
  email:
16
80
  - clinton@thinkrelevance.com
@@ -25,7 +89,20 @@ files:
25
89
  - Rakefile
26
90
  - edn.gemspec
27
91
  - lib/edn.rb
92
+ - lib/edn/core_ext.rb
93
+ - lib/edn/parser.rb
94
+ - lib/edn/string_transformer.rb
95
+ - lib/edn/transform.rb
96
+ - lib/edn/type/list.rb
97
+ - lib/edn/type/symbol.rb
98
+ - lib/edn/type/unknown.rb
99
+ - lib/edn/type/uuid.rb
100
+ - lib/edn/types.rb
28
101
  - lib/edn/version.rb
102
+ - spec/edn/parser_spec.rb
103
+ - spec/edn/transform_spec.rb
104
+ - spec/edn_spec.rb
105
+ - spec/spec_helper.rb
29
106
  homepage: ''
30
107
  licenses: []
31
108
  post_install_message:
@@ -50,4 +127,8 @@ rubygems_version: 1.8.23
50
127
  signing_key:
51
128
  specification_version: 3
52
129
  summary: ! '''edn implements a reader for Extensible Data Notation by Rich Hickey.'''
53
- test_files: []
130
+ test_files:
131
+ - spec/edn/parser_spec.rb
132
+ - spec/edn/transform_spec.rb
133
+ - spec/edn_spec.rb
134
+ - spec/spec_helper.rb