├── spec ├── exemplars │ ├── char.edn │ ├── char.rb │ ├── float.edn │ ├── float.rb │ ├── integer.edn │ ├── integer.rb │ ├── nil.edn │ ├── nil.rb │ ├── true.edn │ ├── true.rb │ ├── empty_string.edn │ ├── empty_string.rb │ ├── false.edn │ ├── false.rb │ ├── gt_symbol.edn │ ├── keyword.edn │ ├── keyword.rb │ ├── list_empty.edn │ ├── list_empty.rb │ ├── map_empty.edn │ ├── map_empty.rb │ ├── set_empty.edn │ ├── simple_skip.rb │ ├── symbol.edn │ ├── utf8.edn │ ├── vector_empty.edn │ ├── vector_empty.rb │ ├── float_neg_exp.edn │ ├── float_pos_exp.edn │ ├── float_pos_exp.rb │ ├── list_one_element.edn │ ├── list_one_element.rb │ ├── set_empty.rb │ ├── set_one_element.edn │ ├── simple_skip.edn │ ├── skip_more_space.rb │ ├── skip_with_spaces.rb │ ├── special_symbol.edn │ ├── utf8.rb │ ├── float_neg_exp.rb │ ├── float_pointneg_exp.edn │ ├── float_sign_exp.edn │ ├── float_sign_exp.rb │ ├── float_with_exp.edn │ ├── float_with_exp.rb │ ├── list_mixed.edn │ ├── list_nested.edn │ ├── list_nested.rb │ ├── map_nested.edn │ ├── map_one_element.edn │ ├── set_nested.edn │ ├── set_one_element.rb │ ├── skip_vector_element.edn │ ├── skip_vector_element.rb │ ├── skip_with_spaces.edn │ ├── skip_yet_more_space.rb │ ├── symbol_with_dash.edn │ ├── vector_nested.edn │ ├── vector_one_element.edn │ ├── vector_one_element.rb │ ├── float_point_plus_exp.edn │ ├── float_point_plus_exp.rb │ ├── float_pointneg_exp.rb │ ├── float_with_exp_no_dec.edn │ ├── float_with_exp_no_dec.rb │ ├── list_mixed.rb │ ├── map_nested.rb │ ├── map_one_element.rb │ ├── map_two_entry.edn │ ├── set_nested.rb │ ├── set_two_elements.edn │ ├── set_two_elements.rb │ ├── skip_more_space.edn │ ├── skip_some_vector_elements.rb │ ├── skip_two_vector_elements.rb │ ├── vector_mixed.edn │ ├── vector_mixed.rb │ ├── vector_nested.rb │ ├── whole_number_with_exp.edn │ ├── whole_number_with_exp.rb │ ├── big_decimal.rb │ ├── big_int.edn │ ├── big_int.rb │ ├── float_point_dec_neg_exp.edn │ ├── float_point_dec_neg_exp.rb │ ├── keyword_with_namespace.edn │ ├── map_two_entry.rb │ ├── skip_more_vector_elements.rb │ ├── skip_yet_more_space.edn │ ├── string_with_newline.edn │ ├── string_with_newline.rb │ ├── symbol_begins_with_false.edn │ ├── symbol_begins_with_nil.edn │ ├── symbol_with_namespace.edn │ ├── big_decimal.edn │ ├── gt_symbol.rb │ ├── keyword_with_namespace.rb │ ├── skip_some_vector_elements.edn │ ├── skip_two_vector_elements.edn │ ├── symbol.rb │ ├── skip_more_vector_elements.edn │ ├── special_symbol.rb │ ├── symbol_with_dash.rb │ ├── tagged_instant.edn │ ├── tagged_uuid.rb │ ├── mmv │ ├── symbol_begins_with_false.rb │ ├── symbol_begins_with_nil.rb │ ├── symbol_with_namespace.rb │ ├── tagged_instant.rb │ └── tagged_uuid.edn ├── edn │ ├── reader_spec.rb │ ├── metadata_spec.rb │ ├── char_stream_spec.rb │ └── parser_spec.rb ├── edn_spec.rb └── spec_helper.rb ├── lib ├── edn │ ├── version.rb │ ├── types.rb │ ├── type │ │ ├── uuid.rb │ │ ├── unknown.rb │ │ ├── list.rb │ │ └── symbol.rb │ ├── reader.rb │ ├── metadata.rb │ ├── char_stream.rb │ ├── parser.rb │ ├── core_ext.rb │ └── ruby_edn_parser.rb └── edn.rb ├── Gemfile ├── .travis.yml ├── Rakefile ├── .gitignore ├── CHANGELOG.md ├── edn.gemspec ├── LICENSE └── README.md /spec/exemplars/char.edn: -------------------------------------------------------------------------------- 1 | \c 2 | -------------------------------------------------------------------------------- /spec/exemplars/char.rb: -------------------------------------------------------------------------------- 1 | "c" 2 | -------------------------------------------------------------------------------- /spec/exemplars/float.edn: -------------------------------------------------------------------------------- 1 | 3.14 2 | -------------------------------------------------------------------------------- /spec/exemplars/float.rb: -------------------------------------------------------------------------------- 1 | 3.14 2 | -------------------------------------------------------------------------------- /spec/exemplars/integer.edn: -------------------------------------------------------------------------------- 1 | 1 2 | -------------------------------------------------------------------------------- /spec/exemplars/integer.rb: -------------------------------------------------------------------------------- 1 | 1 2 | -------------------------------------------------------------------------------- /spec/exemplars/nil.edn: -------------------------------------------------------------------------------- 1 | nil 2 | -------------------------------------------------------------------------------- /spec/exemplars/nil.rb: -------------------------------------------------------------------------------- 1 | nil 2 | -------------------------------------------------------------------------------- /spec/exemplars/true.edn: -------------------------------------------------------------------------------- 1 | true 2 | -------------------------------------------------------------------------------- /spec/exemplars/true.rb: -------------------------------------------------------------------------------- 1 | true 2 | -------------------------------------------------------------------------------- /spec/exemplars/empty_string.edn: -------------------------------------------------------------------------------- 1 | "" 2 | -------------------------------------------------------------------------------- /spec/exemplars/empty_string.rb: -------------------------------------------------------------------------------- 1 | "" 2 | -------------------------------------------------------------------------------- /spec/exemplars/false.edn: -------------------------------------------------------------------------------- 1 | false 2 | -------------------------------------------------------------------------------- /spec/exemplars/false.rb: -------------------------------------------------------------------------------- 1 | false 2 | -------------------------------------------------------------------------------- /spec/exemplars/gt_symbol.edn: -------------------------------------------------------------------------------- 1 | > 2 | -------------------------------------------------------------------------------- /spec/exemplars/keyword.edn: -------------------------------------------------------------------------------- 1 | :hello 2 | -------------------------------------------------------------------------------- /spec/exemplars/keyword.rb: -------------------------------------------------------------------------------- 1 | :hello 2 | -------------------------------------------------------------------------------- /spec/exemplars/list_empty.edn: -------------------------------------------------------------------------------- 1 | () 2 | -------------------------------------------------------------------------------- /spec/exemplars/list_empty.rb: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /spec/exemplars/map_empty.edn: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /spec/exemplars/map_empty.rb: -------------------------------------------------------------------------------- 1 | ({}) 2 | -------------------------------------------------------------------------------- /spec/exemplars/set_empty.edn: -------------------------------------------------------------------------------- 1 | #{} 2 | -------------------------------------------------------------------------------- /spec/exemplars/simple_skip.rb: -------------------------------------------------------------------------------- 1 | 4 2 | -------------------------------------------------------------------------------- /spec/exemplars/symbol.edn: -------------------------------------------------------------------------------- 1 | whatever 2 | -------------------------------------------------------------------------------- /spec/exemplars/utf8.edn: -------------------------------------------------------------------------------- 1 | "øhi utf8" 2 | -------------------------------------------------------------------------------- /spec/exemplars/vector_empty.edn: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /spec/exemplars/vector_empty.rb: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /spec/exemplars/float_neg_exp.edn: -------------------------------------------------------------------------------- 1 | 3e-10 2 | -------------------------------------------------------------------------------- /spec/exemplars/float_pos_exp.edn: -------------------------------------------------------------------------------- 1 | 3e+10 2 | -------------------------------------------------------------------------------- /spec/exemplars/float_pos_exp.rb: -------------------------------------------------------------------------------- 1 | 3.0e10 2 | -------------------------------------------------------------------------------- /spec/exemplars/list_one_element.edn: -------------------------------------------------------------------------------- 1 | (1) 2 | -------------------------------------------------------------------------------- /spec/exemplars/list_one_element.rb: -------------------------------------------------------------------------------- 1 | [1] 2 | -------------------------------------------------------------------------------- /spec/exemplars/set_empty.rb: -------------------------------------------------------------------------------- 1 | Set.new 2 | -------------------------------------------------------------------------------- /spec/exemplars/set_one_element.edn: -------------------------------------------------------------------------------- 1 | #{1} 2 | -------------------------------------------------------------------------------- /spec/exemplars/simple_skip.edn: -------------------------------------------------------------------------------- 1 | #_123 4 2 | -------------------------------------------------------------------------------- /spec/exemplars/skip_more_space.rb: -------------------------------------------------------------------------------- 1 | 4 2 | -------------------------------------------------------------------------------- /spec/exemplars/skip_with_spaces.rb: -------------------------------------------------------------------------------- 1 | 4 2 | -------------------------------------------------------------------------------- /spec/exemplars/special_symbol.edn: -------------------------------------------------------------------------------- 1 | < 2 | -------------------------------------------------------------------------------- /spec/exemplars/utf8.rb: -------------------------------------------------------------------------------- 1 | "øhi utf8" 2 | -------------------------------------------------------------------------------- /spec/exemplars/float_neg_exp.rb: -------------------------------------------------------------------------------- 1 | 3.0e-10 2 | -------------------------------------------------------------------------------- /spec/exemplars/float_pointneg_exp.edn: -------------------------------------------------------------------------------- 1 | 3.e-10 2 | -------------------------------------------------------------------------------- /spec/exemplars/float_sign_exp.edn: -------------------------------------------------------------------------------- 1 | 3.0e+10 2 | -------------------------------------------------------------------------------- /spec/exemplars/float_sign_exp.rb: -------------------------------------------------------------------------------- 1 | 3.0e10 2 | -------------------------------------------------------------------------------- /spec/exemplars/float_with_exp.edn: -------------------------------------------------------------------------------- 1 | 3.0e10 2 | -------------------------------------------------------------------------------- /spec/exemplars/float_with_exp.rb: -------------------------------------------------------------------------------- 1 | 3.0e10 2 | -------------------------------------------------------------------------------- /spec/exemplars/list_mixed.edn: -------------------------------------------------------------------------------- 1 | ("hello" 1 2) 2 | -------------------------------------------------------------------------------- /spec/exemplars/list_nested.edn: -------------------------------------------------------------------------------- 1 | ((1 (:hi))) 2 | -------------------------------------------------------------------------------- /spec/exemplars/list_nested.rb: -------------------------------------------------------------------------------- 1 | [[1, [:hi]]] 2 | -------------------------------------------------------------------------------- /spec/exemplars/map_nested.edn: -------------------------------------------------------------------------------- 1 | {:a {:b :c}} 2 | -------------------------------------------------------------------------------- /spec/exemplars/map_one_element.edn: -------------------------------------------------------------------------------- 1 | {:a :b} 2 | -------------------------------------------------------------------------------- /spec/exemplars/set_nested.edn: -------------------------------------------------------------------------------- 1 | #{1 #{:abc}} 2 | -------------------------------------------------------------------------------- /spec/exemplars/set_one_element.rb: -------------------------------------------------------------------------------- 1 | Set[1] 2 | -------------------------------------------------------------------------------- /spec/exemplars/skip_vector_element.edn: -------------------------------------------------------------------------------- 1 | [#_1] 2 | -------------------------------------------------------------------------------- /spec/exemplars/skip_vector_element.rb: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /spec/exemplars/skip_with_spaces.edn: -------------------------------------------------------------------------------- 1 | #_ 123 4 2 | -------------------------------------------------------------------------------- /spec/exemplars/skip_yet_more_space.rb: -------------------------------------------------------------------------------- 1 | 74 2 | -------------------------------------------------------------------------------- /spec/exemplars/symbol_with_dash.edn: -------------------------------------------------------------------------------- 1 | true-foo 2 | -------------------------------------------------------------------------------- /spec/exemplars/vector_nested.edn: -------------------------------------------------------------------------------- 1 | [[1 [:hi]]] 2 | -------------------------------------------------------------------------------- /spec/exemplars/vector_one_element.edn: -------------------------------------------------------------------------------- 1 | [1] 2 | -------------------------------------------------------------------------------- /spec/exemplars/vector_one_element.rb: -------------------------------------------------------------------------------- 1 | [1] 2 | -------------------------------------------------------------------------------- /spec/exemplars/float_point_plus_exp.edn: -------------------------------------------------------------------------------- 1 | 3.e+10 2 | -------------------------------------------------------------------------------- /spec/exemplars/float_point_plus_exp.rb: -------------------------------------------------------------------------------- 1 | 3.0e10 2 | -------------------------------------------------------------------------------- /spec/exemplars/float_pointneg_exp.rb: -------------------------------------------------------------------------------- 1 | 3.0e-10 2 | -------------------------------------------------------------------------------- /spec/exemplars/float_with_exp_no_dec.edn: -------------------------------------------------------------------------------- 1 | 3.e10 2 | -------------------------------------------------------------------------------- /spec/exemplars/float_with_exp_no_dec.rb: -------------------------------------------------------------------------------- 1 | 3.0e10 2 | -------------------------------------------------------------------------------- /spec/exemplars/list_mixed.rb: -------------------------------------------------------------------------------- 1 | ['hello', 1, 2] 2 | -------------------------------------------------------------------------------- /spec/exemplars/map_nested.rb: -------------------------------------------------------------------------------- 1 | ({:a => {:b => :c}}) 2 | -------------------------------------------------------------------------------- /spec/exemplars/map_one_element.rb: -------------------------------------------------------------------------------- 1 | ({:a => :b}) 2 | -------------------------------------------------------------------------------- /spec/exemplars/map_two_entry.edn: -------------------------------------------------------------------------------- 1 | {:a 1, :b 2} 2 | -------------------------------------------------------------------------------- /spec/exemplars/set_nested.rb: -------------------------------------------------------------------------------- 1 | Set[1, Set[:abc]] 2 | -------------------------------------------------------------------------------- /spec/exemplars/set_two_elements.edn: -------------------------------------------------------------------------------- 1 | #{1 "abc"} 2 | -------------------------------------------------------------------------------- /spec/exemplars/set_two_elements.rb: -------------------------------------------------------------------------------- 1 | Set[1, "abc"] 2 | -------------------------------------------------------------------------------- /spec/exemplars/skip_more_space.edn: -------------------------------------------------------------------------------- 1 | #_ 123 4 2 | -------------------------------------------------------------------------------- /spec/exemplars/skip_some_vector_elements.rb: -------------------------------------------------------------------------------- 1 | [1] 2 | -------------------------------------------------------------------------------- /spec/exemplars/skip_two_vector_elements.rb: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /spec/exemplars/vector_mixed.edn: -------------------------------------------------------------------------------- 1 | ["hello" 1 2] 2 | -------------------------------------------------------------------------------- /spec/exemplars/vector_mixed.rb: -------------------------------------------------------------------------------- 1 | ['hello', 1, 2] 2 | -------------------------------------------------------------------------------- /spec/exemplars/vector_nested.rb: -------------------------------------------------------------------------------- 1 | [[1, [:hi]]] 2 | -------------------------------------------------------------------------------- /spec/exemplars/whole_number_with_exp.edn: -------------------------------------------------------------------------------- 1 | 3e10 2 | -------------------------------------------------------------------------------- /spec/exemplars/whole_number_with_exp.rb: -------------------------------------------------------------------------------- 1 | 3.0e10 2 | -------------------------------------------------------------------------------- /spec/exemplars/big_decimal.rb: -------------------------------------------------------------------------------- 1 | 123412341231212121241234 2 | -------------------------------------------------------------------------------- /spec/exemplars/big_int.edn: -------------------------------------------------------------------------------- 1 | 123412341231212121241234 2 | -------------------------------------------------------------------------------- /spec/exemplars/big_int.rb: -------------------------------------------------------------------------------- 1 | 123412341231212121241234 2 | -------------------------------------------------------------------------------- /spec/exemplars/float_point_dec_neg_exp.edn: -------------------------------------------------------------------------------- 1 | 3.0e-10 2 | -------------------------------------------------------------------------------- /spec/exemplars/float_point_dec_neg_exp.rb: -------------------------------------------------------------------------------- 1 | 3.0e-10 2 | -------------------------------------------------------------------------------- /spec/exemplars/keyword_with_namespace.edn: -------------------------------------------------------------------------------- 1 | :hello/world 2 | -------------------------------------------------------------------------------- /spec/exemplars/map_two_entry.rb: -------------------------------------------------------------------------------- 1 | ({:a => 1, :b => 2}) 2 | -------------------------------------------------------------------------------- /spec/exemplars/skip_more_vector_elements.rb: -------------------------------------------------------------------------------- 1 | [1, 2] 2 | -------------------------------------------------------------------------------- /spec/exemplars/skip_yet_more_space.edn: -------------------------------------------------------------------------------- 1 | #_ 123 74 2 | -------------------------------------------------------------------------------- /spec/exemplars/string_with_newline.edn: -------------------------------------------------------------------------------- 1 | "hello\nworld" 2 | -------------------------------------------------------------------------------- /spec/exemplars/string_with_newline.rb: -------------------------------------------------------------------------------- 1 | "hello\nworld" 2 | -------------------------------------------------------------------------------- /spec/exemplars/symbol_begins_with_false.edn: -------------------------------------------------------------------------------- 1 | falsey 2 | -------------------------------------------------------------------------------- /spec/exemplars/symbol_begins_with_nil.edn: -------------------------------------------------------------------------------- 1 | nillable 2 | -------------------------------------------------------------------------------- /spec/exemplars/symbol_with_namespace.edn: -------------------------------------------------------------------------------- 1 | hello/world 2 | -------------------------------------------------------------------------------- /spec/exemplars/big_decimal.edn: -------------------------------------------------------------------------------- 1 | 123412341231212121241234M 2 | -------------------------------------------------------------------------------- /spec/exemplars/gt_symbol.rb: -------------------------------------------------------------------------------- 1 | EDN::Type::Symbol.new('>') 2 | -------------------------------------------------------------------------------- /spec/exemplars/keyword_with_namespace.rb: -------------------------------------------------------------------------------- 1 | :"hello/world" 2 | -------------------------------------------------------------------------------- /spec/exemplars/skip_some_vector_elements.edn: -------------------------------------------------------------------------------- 1 | [#_64 1 #_ 65] 2 | -------------------------------------------------------------------------------- /spec/exemplars/skip_two_vector_elements.edn: -------------------------------------------------------------------------------- 1 | [#_1 #_"hello"] 2 | -------------------------------------------------------------------------------- /spec/exemplars/symbol.rb: -------------------------------------------------------------------------------- 1 | EDN::Type::Symbol.new('whatever') 2 | -------------------------------------------------------------------------------- /lib/edn/version.rb: -------------------------------------------------------------------------------- 1 | module EDN 2 | VERSION = "1.1.1" 3 | end 4 | -------------------------------------------------------------------------------- /spec/exemplars/skip_more_vector_elements.edn: -------------------------------------------------------------------------------- 1 | [#_ "hello" 1 2] 2 | -------------------------------------------------------------------------------- /spec/exemplars/special_symbol.rb: -------------------------------------------------------------------------------- 1 | EDN::Type::Symbol.new('<') 2 | -------------------------------------------------------------------------------- /spec/exemplars/symbol_with_dash.rb: -------------------------------------------------------------------------------- 1 | EDN::Type::Symbol.new('true-foo') 2 | -------------------------------------------------------------------------------- /spec/exemplars/tagged_instant.edn: -------------------------------------------------------------------------------- 1 | #inst "2012-09-10T16:16:03-04:00" 2 | -------------------------------------------------------------------------------- /spec/exemplars/tagged_uuid.rb: -------------------------------------------------------------------------------- 1 | "f81d4fae-7dec-11d0-a765-00a0c91e6bf6" 2 | -------------------------------------------------------------------------------- /spec/exemplars/mmv: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | mv $1.edn $2.edn 4 | mv $1.rb $2.rb 5 | -------------------------------------------------------------------------------- /spec/exemplars/symbol_begins_with_false.rb: -------------------------------------------------------------------------------- 1 | EDN::Type::Symbol.new('falsey') 2 | -------------------------------------------------------------------------------- /spec/exemplars/symbol_begins_with_nil.rb: -------------------------------------------------------------------------------- 1 | EDN::Type::Symbol.new('nillable') 2 | -------------------------------------------------------------------------------- /spec/exemplars/symbol_with_namespace.rb: -------------------------------------------------------------------------------- 1 | EDN::Type::Symbol.new('hello/world') 2 | -------------------------------------------------------------------------------- /spec/exemplars/tagged_instant.rb: -------------------------------------------------------------------------------- 1 | DateTime.rfc3339("2012-09-10T16:16:03-04:00") 2 | -------------------------------------------------------------------------------- /spec/exemplars/tagged_uuid.edn: -------------------------------------------------------------------------------- 1 | #uuid "f81d4fae-7dec-11d0-a765-00a0c91e6bf6" 2 | -------------------------------------------------------------------------------- /lib/edn/types.rb: -------------------------------------------------------------------------------- 1 | Dir[File.join(File.dirname(__FILE__), 'type', '*.rb')].each do |file| 2 | require file 3 | end 4 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in edn.gemspec 4 | gemspec 5 | 6 | 7 | -------------------------------------------------------------------------------- /lib/edn/type/uuid.rb: -------------------------------------------------------------------------------- 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 10 | -------------------------------------------------------------------------------- /lib/edn/type/unknown.rb: -------------------------------------------------------------------------------- 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 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - "1.9.2" 4 | - "1.9.3" 5 | - "2.0.0" 6 | - jruby-19mode # JRuby in 1.9 mode 7 | # - "1.8.7" 8 | # - jruby-18mode # JRuby in 1.8 mode 9 | script: bundle exec rspec spec 10 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env rake 2 | require "bundler/gem_tasks" 3 | require 'rspec/core/rake_task' 4 | 5 | RSpec::Core::RakeTask.new(:spec) 6 | 7 | task :default => :spec 8 | 9 | task :irb do 10 | sh "irb -I lib -r edn" 11 | sh "reset" 12 | end 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | .bundle 4 | .config 5 | .yardoc 6 | .rspec 7 | README.html 8 | Gemfile.lock 9 | InstalledFiles 10 | _yardoc 11 | coverage 12 | doc/ 13 | lib/bundler/man 14 | pkg 15 | rdoc 16 | spec/reports 17 | test/tmp 18 | test/version_tmp 19 | tmp 20 | -------------------------------------------------------------------------------- /lib/edn/type/list.rb: -------------------------------------------------------------------------------- 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 14 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 1.0.2 2 | * Handle numbers with M precision 3 | 4 | # 1.0.1 5 | * EDN.register defaults to the identity function when no handler is given 6 | 7 | # 0.9.4 (18 Sep 2012) 8 | 9 | * Require `set` 10 | 11 | # 0.9.3 (17 Sep 2012) 12 | 13 | * Updated to latest EDN spec 14 | ** Added new symbol characters 15 | ** Added ability to have a + before a number 16 | 17 | # 0.9.2 (13 Sep 2012) 18 | 19 | * EDN spec fully implemented 20 | -------------------------------------------------------------------------------- /lib/edn/reader.rb: -------------------------------------------------------------------------------- 1 | module EDN 2 | class Reader 3 | 4 | def initialize(source) 5 | @parser = EDN.new_parser(source) 6 | end 7 | 8 | def read(eof_value = NOTHING) 9 | result = @parser.read 10 | if result == EOF 11 | raise "Unexpected end of file" if eof_value == NOTHING 12 | return eof_value 13 | end 14 | result 15 | end 16 | 17 | def each 18 | until (result = @parser.read) == EOF 19 | yield result 20 | end 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /spec/edn/reader_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe EDN::Reader do 4 | let(:reader) { EDN::Reader.new("[1 2] 3 :a {:b c} ") } 5 | 6 | it "should read each value" do 7 | reader.read.should == [1, 2] 8 | reader.read.should == 3 9 | reader.read.should == :a 10 | reader.read.should == {:b => ~"c"} 11 | end 12 | 13 | it "should respond to each" do 14 | reader.each do |element| 15 | element.should_not be_nil 16 | end 17 | end 18 | 19 | it "returns a special end of file value if asked" do 20 | 4.times { reader.read(:the_end).should_not == :the_end } 21 | reader.read(:no_more).should == :no_more 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/edn/metadata.rb: -------------------------------------------------------------------------------- 1 | module EDN 2 | module Metadata 3 | def self.extended(base) 4 | base.instance_eval do 5 | alias :to_edn_without_metadata :to_edn 6 | alias :to_edn :to_edn_with_metadata 7 | end 8 | end 9 | 10 | attr_accessor :metadata 11 | 12 | def has_metadata? 13 | respond_to?(:allows_metadata?) and 14 | allows_metadata? and 15 | !metadata.nil? and 16 | !metadata.empty? 17 | end 18 | 19 | def to_edn_with_metadata 20 | if has_metadata? 21 | '^' + metadata.to_edn + ' ' + to_edn_without_metadata 22 | else 23 | to_edn_without_metadata 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/edn/type/symbol.rb: -------------------------------------------------------------------------------- 1 | module EDN 2 | module Type 3 | class Symbol 4 | include EDN::CoreExt::AllowsMetadata 5 | 6 | attr_reader :symbol 7 | 8 | def initialize(sym) 9 | @symbol = sym.to_sym 10 | end 11 | 12 | def ==(other) 13 | return false unless other.is_a?(Symbol) 14 | to_sym == other.to_sym 15 | end 16 | 17 | def eql?(other) 18 | return false unless other.is_a?(Symbol) 19 | to_sym == other.to_sym 20 | end 21 | 22 | def hash 23 | @symbol.hash 24 | end 25 | 26 | def to_sym 27 | @symbol 28 | end 29 | 30 | def to_s 31 | @symbol.to_s 32 | end 33 | 34 | def to_edn 35 | @symbol.to_s 36 | end 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /edn.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | require File.expand_path('../lib/edn/version', __FILE__) 3 | 4 | Gem::Specification.new do |gem| 5 | gem.authors = ["Clinton N. Dreisbach & Russ Olsen"] 6 | gem.email = ["russ@russolsen.com"] 7 | gem.description = %q{'edn implements a reader for Extensible Data Notation by Rich Hickey.'} 8 | gem.summary = gem.description 9 | gem.homepage = "https://github.com/relevance/edn-ruby" 10 | gem.license = "MIT" 11 | 12 | gem.files = `git ls-files`.split($\) 13 | gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) } 14 | gem.test_files = gem.files.grep(%r{^(test|spec|features)/}) 15 | gem.name = "edn" 16 | gem.require_paths = ["lib"] 17 | gem.version = EDN::VERSION 18 | 19 | gem.add_development_dependency 'pry', '~> 0.9.10' 20 | gem.add_development_dependency 'rspec', '~> 2.11.0' 21 | gem.add_development_dependency 'rantly', '~> 0.3.1' 22 | gem.add_development_dependency 'rake', '~> 10.3' 23 | end 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Relevance Inc & Clinton N. Dreisbach 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 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. 23 | -------------------------------------------------------------------------------- /spec/edn/metadata_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe EDN do 4 | describe "metadata" do 5 | it "reads metadata, which does not change the element's equality" do 6 | EDN.read('[1 2 3]').should == EDN.read('^{:doc "My vec"} [1 2 3]') 7 | end 8 | 9 | it "reads metadata recursively from right to left" do 10 | element = EDN.read('^String ^:foo ^{:foo false :tag Boolean :bar 2} [1 2]') 11 | element.should == [1, 2] 12 | element.metadata.should == {:tag => ~"String", :foo => true, :bar => 2} 13 | end 14 | 15 | it "writes metadata" do 16 | element = EDN.read('^{:doc "My vec"} [1 2 3]') 17 | element.to_edn.should == '^{:doc "My vec"} [1 2 3]' 18 | end 19 | 20 | it "only writes metadata for elements that can have it" do 21 | apply_metadata = lambda { |o| 22 | o.extend(EDN::Metadata) 23 | o.metadata = {:foo => 1} 24 | o 25 | } 26 | 27 | apply_metadata[[1, 2]].to_edn.should == '^{:foo 1} [1 2]' 28 | apply_metadata[~[1, 2]].to_edn.should == '^{:foo 1} (1 2)' 29 | apply_metadata[{1 => 2}].to_edn.should == '^{:foo 1} {1 2}' 30 | apply_metadata[Set.new([1, 2])].to_edn.should == '^{:foo 1} #{1 2}' 31 | apply_metadata[~"bar"].to_edn.should == '^{:foo 1} bar' 32 | 33 | apply_metadata["bar"].to_edn.should == '"bar"' 34 | 35 | # Cannot extend symbols, booleans, and nil, so no test for them. 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/edn.rb: -------------------------------------------------------------------------------- 1 | $:.push(File.dirname(__FILE__)) 2 | require 'set' 3 | require 'edn/version' 4 | require 'edn/core_ext' 5 | require 'edn/types' 6 | require 'edn/metadata' 7 | require 'edn/char_stream' 8 | require 'edn/ruby_edn_parser' 9 | require 'edn/parser' 10 | require 'edn/reader' 11 | 12 | module EDN 13 | @tags = Hash.new 14 | 15 | def self.read(edn, eof_value=NOTHING) 16 | EDN::Reader.new(edn).read(eof_value) 17 | end 18 | 19 | def self.register(tag, func = nil, &block) 20 | if block_given? 21 | func = block 22 | end 23 | 24 | if func.nil? 25 | func = lambda { |x| x } 26 | end 27 | 28 | if func.is_a?(Class) 29 | @tags[tag] = lambda { |*args| func.new(*args) } 30 | else 31 | @tags[tag] = func 32 | end 33 | end 34 | 35 | def self.unregister(tag) 36 | @tags[tag] = nil 37 | end 38 | 39 | def self.tagged_element(tag, element) 40 | func = @tags[tag] 41 | if func 42 | func.call(element) 43 | else 44 | EDN::Type::Unknown.new(tag, element) 45 | end 46 | end 47 | 48 | def self.tagout(tag, element) 49 | ["##{tag}", element.to_edn].join(" ") 50 | end 51 | 52 | def self.symbol(text) 53 | EDN::Type::Symbol.new(text) 54 | end 55 | 56 | def self.list(values) 57 | EDN::Type::List.new(*values) 58 | end 59 | 60 | def self.set(*elems) 61 | Set.new(*elems) 62 | end 63 | 64 | def self.rational(value) 65 | Rational(value) 66 | end 67 | 68 | def self.big_decimal(str) 69 | BigDecimal(str) 70 | end 71 | end 72 | 73 | EDN.register("inst") do |value| 74 | DateTime.parse(value) 75 | end 76 | 77 | EDN.register("uuid") do |value| 78 | EDN::Type::UUID.new(value) 79 | end 80 | 81 | -------------------------------------------------------------------------------- /lib/edn/char_stream.rb: -------------------------------------------------------------------------------- 1 | require 'stringio' 2 | require 'set' 3 | 4 | module EDN 5 | class CharStream 6 | def initialize(io=$stdin) 7 | @io = io 8 | @current = nil 9 | end 10 | 11 | def current 12 | return @current if @current 13 | advance 14 | end 15 | 16 | def advance 17 | return @current if @current == :eof 18 | @current = @io.getc || :eof 19 | end 20 | 21 | def digit?(c=current) 22 | /[0-9]/ =~ c 23 | end 24 | 25 | def alpha?(c=current) 26 | /[a-zA-Z]/ =~ c 27 | end 28 | 29 | def eof?(c=current) 30 | c == :eof 31 | end 32 | 33 | def ws?(c=current) 34 | /[ \t\r\n,]/ =~ c 35 | end 36 | 37 | def newline?(c=current) 38 | /[\n\r]/ =~ c 39 | end 40 | 41 | def repeat(pattern, &block) 42 | result = nil 43 | while current =~ pattern 44 | result ||= '' 45 | result = block.call(result, current) 46 | end 47 | result 48 | end 49 | 50 | def gather(pattern) 51 | repeat(pattern) do |result, ch| 52 | result << ch 53 | end 54 | end 55 | 56 | def skip_past(expected, error_message=nil) 57 | if current == expected 58 | advance 59 | return expected 60 | elsif error_message 61 | raise error_message 62 | end 63 | nil 64 | end 65 | 66 | def skip_to_eol 67 | until current == :eof || newline? 68 | advance 69 | end 70 | end 71 | 72 | def skip_ws 73 | while current != :eof 74 | if ws?(current) 75 | advance 76 | elsif current == ';' 77 | skip_to_eol 78 | else 79 | break 80 | end 81 | end 82 | end 83 | end 84 | end 85 | -------------------------------------------------------------------------------- /lib/edn/parser.rb: -------------------------------------------------------------------------------- 1 | require 'stringio' 2 | require 'set' 3 | 4 | 5 | module EDN 6 | Parser = RubyEdnParser 7 | @parser_class = RubyEdnParser 8 | 9 | def self.parser=(p) 10 | @parser_class = p 11 | end 12 | 13 | def self.new_parser(*args) 14 | @parser_class.new(*args) 15 | end 16 | 17 | # Object returned when there is nothing to return 18 | 19 | NOTHING = Object.new 20 | 21 | # Object to return when we hit end of file. Cant be nil or :eof 22 | # because either of those could be something in the EDN data. 23 | 24 | EOF = Object.new 25 | 26 | # Reader table 27 | 28 | READERS = {} 29 | SYMBOL_INTERIOR_CHARS = 30 | Set.new(%w{. # * ! - _ + ? $ % & = < > :} + ('a'..'z').to_a + ('A'..'Z').to_a + ('0'..'9').to_a) 31 | 32 | SYMBOL_INTERIOR_CHARS.each {|n| READERS[n.to_s] = :read_symbol} 33 | 34 | DIGITS = Set.new(('0'..'9').to_a) 35 | 36 | DIGITS.each {|n| READERS[n.to_s] = :read_number} 37 | 38 | READERS.default = :unknown 39 | 40 | READERS['{'] = :read_map 41 | READERS['['] = :read_vector 42 | READERS['('] = :read_list 43 | READERS['\\'] = :read_char 44 | READERS['"'] = :read_string 45 | READERS['.'] = :read_number_or_symbol 46 | READERS['+'] = :read_number_or_symbol 47 | READERS['-'] = :read_number_or_symbol 48 | READERS[''] = :read_number_or_symbol 49 | READERS['/'] = :read_slash 50 | READERS[':'] = :read_keyword 51 | READERS['#'] = :read_extension 52 | READERS[:eof] = :read_eof 53 | 54 | def self.register_reader(ch, handler=nil, &block) 55 | if handler 56 | READERS[ch] = handler 57 | else 58 | READERS[ch] = block 59 | end 60 | end 61 | 62 | TAGS = {} 63 | 64 | def self.register(tag, func = nil, &block) 65 | if block_given? 66 | func = block 67 | end 68 | 69 | if func.nil? 70 | func = lambda { |x| x } 71 | end 72 | 73 | if func.is_a?(Class) 74 | TAGS[tag] = lambda { |*args| func.new(*args) } 75 | else 76 | TAGS[tag] = func 77 | end 78 | end 79 | 80 | def self.unregister(tag) 81 | TAGS[tag] = nil 82 | end 83 | 84 | def self.tagged_element(tag, element) 85 | func = TAGS[tag] 86 | if func 87 | func.call(element) 88 | else 89 | EDN::Type::Unknown.new(tag, element) 90 | end 91 | end 92 | end 93 | -------------------------------------------------------------------------------- /lib/edn/core_ext.rb: -------------------------------------------------------------------------------- 1 | require 'time' 2 | require 'bigdecimal' 3 | require 'set' 4 | 5 | module EDN 6 | module CoreExt 7 | module Unquoted 8 | def to_edn 9 | self.to_s 10 | end 11 | end 12 | 13 | module AllowsMetadata 14 | def allows_metadata? 15 | true 16 | end 17 | end 18 | 19 | module Integer 20 | def to_edn 21 | self.to_s + 'N' 22 | end 23 | end 24 | 25 | module Bignum 26 | def to_edn 27 | self.to_s + 'N' 28 | end 29 | end 30 | 31 | module BigDecimal 32 | def to_edn 33 | self.to_s('F') + 'M' 34 | end 35 | end 36 | 37 | module String 38 | def ~@ 39 | EDN::Type::Symbol.new(self) 40 | end 41 | 42 | def to_edn 43 | array = chars.map do |ch| 44 | if %w{" \\}.include?(ch) 45 | '\\' + ch 46 | else 47 | ch 48 | end 49 | end 50 | '"' + array.join + '"' 51 | end 52 | end 53 | 54 | module Symbol 55 | def to_edn 56 | ":#{self.to_s}" 57 | end 58 | end 59 | 60 | module Array 61 | def ~@ 62 | EDN::Type::List.new(*self) 63 | end 64 | 65 | def to_edn 66 | '[' + self.map(&:to_edn).join(" ") + ']' 67 | end 68 | end 69 | 70 | module Hash 71 | def to_edn 72 | '{' + self.map { |k, v| [k, v].map(&:to_edn).join(" ") }.join(", ") + '}' 73 | end 74 | end 75 | 76 | module Set 77 | def to_edn 78 | '#{' + self.to_a.map(&:to_edn).join(" ") + '}' 79 | end 80 | end 81 | 82 | module NilClass 83 | def to_edn 84 | "nil" 85 | end 86 | end 87 | 88 | module DateTime 89 | def to_edn 90 | EDN.tagout("inst", self.rfc3339) 91 | end 92 | end 93 | 94 | module Time 95 | def to_edn 96 | EDN.tagout("inst", self.xmlschema) 97 | end 98 | end 99 | end 100 | end 101 | 102 | Numeric.send(:include, EDN::CoreExt::Unquoted) 103 | Integer.send(:include, EDN::CoreExt::Integer) 104 | Bignum.send(:include, EDN::CoreExt::Bignum) if defined?(Bignum) 105 | BigDecimal.send(:include, EDN::CoreExt::BigDecimal) 106 | TrueClass.send(:include, EDN::CoreExt::Unquoted) 107 | FalseClass.send(:include, EDN::CoreExt::Unquoted) 108 | NilClass.send(:include, EDN::CoreExt::NilClass) 109 | String.send(:include, EDN::CoreExt::String) 110 | Symbol.send(:include, EDN::CoreExt::Symbol) 111 | Array.send(:include, EDN::CoreExt::Array) 112 | Hash.send(:include, EDN::CoreExt::Hash) 113 | Set.send(:include, EDN::CoreExt::Set) 114 | DateTime.send(:include, EDN::CoreExt::DateTime) 115 | Time.send(:include, EDN::CoreExt::Time) 116 | 117 | [Array, Hash, Set].each do |klass| 118 | klass.send(:include, EDN::CoreExt::AllowsMetadata) 119 | end 120 | -------------------------------------------------------------------------------- /spec/edn/char_stream_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe EDN::CharStream do 4 | it "reads a stream in order" do 5 | s = EDN::CharStream.new(io_for("abc")) 6 | s.current.should == "a" 7 | s.advance.should == "b" 8 | s.advance.should == "c" 9 | s.advance.should == :eof 10 | s.current.should == :eof 11 | end 12 | 13 | it "keeps returning the current until your advance" do 14 | s = EDN::CharStream.new(io_for("abc")) 15 | s.current.should == "a" 16 | s.current.should == "a" 17 | s.advance.should == "b" 18 | end 19 | 20 | it "knows if the current char is a digit" do 21 | s = EDN::CharStream.new(io_for("4f")) 22 | s.digit?.should be_truthy 23 | s.advance 24 | s.digit?.should be_falsey 25 | end 26 | 27 | it "knows if the current char is an alpha" do 28 | s = EDN::CharStream.new(io_for("a9")) 29 | s.alpha?.should be_truthy 30 | s.advance 31 | s.alpha?.should be_falsey 32 | end 33 | 34 | it "knows if the current char is whitespace" do 35 | s = EDN::CharStream.new(io_for("a b\nc\td,")) 36 | s.ws?.should be_falsey # a 37 | 38 | s.advance 39 | s.ws?.should be_truthy # " " 40 | 41 | s.advance 42 | s.ws?.should be_falsey # b 43 | 44 | s.advance 45 | s.ws?.should be_truthy # \n 46 | 47 | s.advance 48 | s.ws?.should be_falsey # c 49 | 50 | s.advance 51 | s.ws?.should be_truthy # \t 52 | 53 | s.advance 54 | s.ws?.should be_falsey # d 55 | 56 | s.advance 57 | s.ws?.should be_truthy # , 58 | end 59 | 60 | it "knows if the current char is a newline" do 61 | s = EDN::CharStream.new(io_for("a\nb\rc")) 62 | s.newline?.should be_falsey # a 63 | 64 | s.advance 65 | s.newline?.should be_truthy # \n 66 | 67 | s.advance 68 | s.newline?.should be_falsey # b 69 | 70 | s.advance 71 | s.newline?.should be_truthy # \r 72 | 73 | s.advance 74 | s.newline?.should be_falsey # c 75 | end 76 | 77 | it "knows if it is at the eof" do 78 | s = EDN::CharStream.new(io_for("abc")) 79 | s.eof?.should be_falsey # a 80 | s.advance 81 | s.eof?.should be_falsey # b 82 | s.advance 83 | s.eof?.should be_falsey # c 84 | s.advance 85 | s.eof?.should be_truthy 86 | end 87 | 88 | it "knows how to skip past a char" do 89 | s = EDN::CharStream.new(io_for("abc")) 90 | s.skip_past("a").should == "a" 91 | s.current.should == "b" 92 | end 93 | 94 | it "knows how not to skip a char" do 95 | s = EDN::CharStream.new(io_for("abc")) 96 | s.skip_past("X").should be_nil 97 | end 98 | 99 | it "knows how skip to the end of a line" do 100 | s = EDN::CharStream.new(io_for("abc\ndef")) 101 | s.skip_to_eol 102 | s.current.should == "\n" 103 | s.advance.should == "d" 104 | end 105 | 106 | it "knows how skip whitespace" do 107 | s = EDN::CharStream.new(io_for(" \n \t,,,,abc")) 108 | s.skip_ws 109 | s.current.should == "a" 110 | end 111 | end 112 | -------------------------------------------------------------------------------- /spec/edn_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'stringio' 3 | 4 | describe EDN do 5 | include RantlyHelpers 6 | 7 | ExemplarPattern = "#{File.dirname(__FILE__)}/exemplars/*.edn" 8 | 9 | context 'Exemplar' do 10 | edn_files = Dir[ExemplarPattern] 11 | edn_files.each do |edn_file| 12 | rb_file = edn_file.sub(/\.edn$/, '.rb') 13 | it "reads file #{File.basename(edn_file)} correctly" do 14 | expected = eval(File.read(rb_file)) 15 | actual = EDN.read(File.read(edn_file)) 16 | actual.should == expected 17 | end 18 | 19 | it "round trips the value in #{File.basename(edn_file)} correctly" do 20 | expected = eval(File.read(rb_file)) 21 | actual = EDN.read(File.read(edn_file)) 22 | round_trip = EDN.read(actual.to_edn) 23 | round_trip.should == expected 24 | end 25 | end 26 | end 27 | 28 | 29 | context "#read" do 30 | #it "reads from a stream" do 31 | # io = StringIO.new("123") 32 | # EDN.read(io).should == 123 33 | #end 34 | 35 | #it "reads mutiple values from a stream" do 36 | # io = StringIO.new("123 456 789") 37 | # EDN.read(io).should == 123 38 | # EDN.read(io).should == 456 39 | # EDN.read(io).should == 789 40 | #end 41 | 42 | it "raises an exception on eof by default" do 43 | expect { EDN.read('') }.to raise_error 44 | end 45 | 46 | #it "allows you to specify an eof value" do 47 | # io = StringIO.new("123 456") 48 | # EDN.read(io, :my_eof).should == 123 49 | # EDN.read(io, :my_eof).should == 456 50 | # EDN.read(io, :my_eof).should == :my_eof 51 | #end 52 | 53 | it "allows you to specify nil as an eof value" do 54 | EDN.read("", nil).should == nil 55 | end 56 | end 57 | 58 | context "reading data" do 59 | it "treats carriage returns like whitespace" do 60 | EDN.read("\r\n[\r\n]\r\n").should == [] 61 | EDN.read("\r[\r]\r\r").should == [] 62 | end 63 | 64 | it "reads any valid element" do 65 | elements = rant(RantlyHelpers::ELEMENT) 66 | elements.each do |element| 67 | begin 68 | if element == "nil" 69 | EDN.read(element).should be_nil 70 | else 71 | EDN.read(element).should_not be_nil 72 | end 73 | rescue Exception => ex 74 | puts "Bad element: #{element}" 75 | raise ex 76 | end 77 | end 78 | end 79 | end 80 | 81 | context "#register" do 82 | it "uses the identity function when no handler is given" do 83 | EDN.register "some/tag" 84 | EDN.read("#some/tag {}").class.should == Hash 85 | end 86 | end 87 | 88 | context "writing" do 89 | it "writes any valid element" do 90 | elements = rant(RantlyHelpers::ELEMENT) 91 | elements.each do |element| 92 | expect { 93 | begin 94 | EDN.read(element).to_edn 95 | rescue Exception => ex 96 | puts "Bad element: #{element}" 97 | raise ex 98 | end 99 | }.to_not raise_error 100 | end 101 | end 102 | 103 | it "writes equivalent edn to what it reads" do 104 | elements = rant(RantlyHelpers::ELEMENT) 105 | elements.each do |element| 106 | ruby_element = EDN.read(element) 107 | ruby_element.should == EDN.read(ruby_element.to_edn) 108 | if ruby_element.respond_to?(:metadata) 109 | ruby_element.metadata.should == EDN.read(ruby_element.to_edn).metadata 110 | end 111 | end 112 | end 113 | end 114 | end 115 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'rspec' 2 | require 'edn' 3 | require 'rantly' 4 | require 'date' 5 | require 'time' 6 | 7 | REPEAT = (ENV["REPEAT"] || 150).to_i 8 | 9 | RSpec.configure do |c| 10 | c.fail_fast = true 11 | c.filter_run_including :focused => true 12 | c.alias_example_to :fit, :focused => true 13 | c.treat_symbols_as_metadata_keys_with_true_values = true 14 | c.run_all_when_everything_filtered = true 15 | 16 | c.filter_run :focus 17 | c.run_all_when_everything_filtered = true 18 | end 19 | 20 | def io_for(s) 21 | StringIO.new(s) 22 | end 23 | 24 | module RantlyHelpers 25 | 26 | KEYWORD = lambda { |_| 27 | call(SYMBOL).to_sym.to_edn 28 | } 29 | 30 | SYMBOL = lambda { |_| 31 | branch(PLAIN_SYMBOL, NAMESPACED_SYMBOL) 32 | } 33 | 34 | PLAIN_SYMBOL = lambda { |_| 35 | sized(range(1, 100)) { 36 | s = string(/[[:alnum:]]|[\.\*\+\!\-\?\$_%<>&=:#]/) 37 | guard s !~ /^[0-9]/ 38 | guard s !~ /^[\+\-\.][0-9]/ 39 | guard s !~ /^[\:\#]/ 40 | s 41 | } 42 | } 43 | 44 | NAMESPACED_SYMBOL = lambda { |_| 45 | [call(PLAIN_SYMBOL), call(PLAIN_SYMBOL)].join("/") 46 | } 47 | 48 | INTEGER = lambda { |_| integer.to_edn } 49 | 50 | STRING = lambda { |_| sized(range(1, 100)) { string.to_edn } } 51 | 52 | RUBY_STRING = lambda { |_| sized(range(1, 100)) { string } } 53 | 54 | FLOAT = lambda { |_| (float * range(-4000, 5000)).to_edn } 55 | 56 | FLOAT_WITH_EXP = lambda { |_| 57 | # limited range because of Infinity 58 | f = float.to_s 59 | guard f !~ /[Ee]/ 60 | 61 | [f, choose("e", "E", "e+", "E+", "e-", "e+"), range(1, 100)]. 62 | map(&:to_s). 63 | join("") 64 | } 65 | 66 | RUBY_CHAR = lambda { |_| 67 | "\\" + 68 | sized(1) { 69 | freq([1, [:choose, "\n", "\r", " ", "\t"]], 70 | [5, [:string, :graph]]) 71 | } 72 | } 73 | 74 | CHARACTER = lambda { |_| 75 | "\\" + 76 | sized(1) { 77 | freq([1, [:choose, "newline", "return", "space", "tab"]], 78 | [5, [:string, :graph]]) 79 | } 80 | } 81 | 82 | BOOL_OR_NIL = lambda { |_| 83 | choose("true", "false", "nil") 84 | } 85 | 86 | ARRAY = lambda { |_| 87 | array(range(1, 10)) { call(ELEMENT) } 88 | } 89 | 90 | VECTOR = lambda { |_| 91 | '[' + call(ARRAY).join(', ') + ']' 92 | } 93 | 94 | LIST = lambda { |_| 95 | '(' + call(ARRAY).join(', ') + ')' 96 | } 97 | 98 | SET = lambda { |_| 99 | '#{' + call(ARRAY).join(', ') + '}' 100 | } 101 | 102 | MAP = lambda { |_| 103 | size = range(0, 10) 104 | keys = array(size) { call(ELEMENT) } 105 | elements = array(size) { call(ELEMENT) } 106 | arrays = keys.zip(elements) 107 | '{' + arrays.map { |array| array.join(" ") }.join(", ") + '}' 108 | } 109 | 110 | ELEMENT = lambda { |_| 111 | freq([8, BASIC_ELEMENT], 112 | [2, ELEMENT_WITH_METADATA], 113 | [1, INST], 114 | [1, TAGGED_ELEMENT]) 115 | } 116 | 117 | BASIC_ELEMENT = lambda { |_| 118 | branch(INTEGER, 119 | FLOAT, 120 | FLOAT_WITH_EXP, 121 | STRING, 122 | KEYWORD, 123 | SYMBOL, 124 | CHARACTER, 125 | BOOL_OR_NIL, 126 | VECTOR, 127 | LIST, 128 | SET, 129 | MAP) 130 | } 131 | 132 | METADATA = lambda { |_| 133 | size = range(1, 4) 134 | keys = array(size) { branch(KEYWORD, SYMBOL, STRING) } 135 | elements = array(size) { call(ELEMENT) } 136 | arrays = keys.zip(elements) 137 | '^{' + arrays.map { |array| array.join(" ") }.join(", ") + '}' 138 | } 139 | 140 | ELEMENT_WITH_METADATA = lambda { |_| 141 | [call(METADATA), branch(SYMBOL, VECTOR, LIST, SET, MAP)].join(" ") 142 | } 143 | 144 | TAG = lambda { |_| 145 | tag = call(SYMBOL) 146 | guard tag =~ /^[A-Za-z]/ 147 | "##{tag}" 148 | } 149 | 150 | TAGGED_ELEMENT = lambda { |_| 151 | [call(TAG), call(BASIC_ELEMENT)].join(" ") 152 | } 153 | 154 | INST = lambda { |_| 155 | begin 156 | DateTime.new(range(0, 2500), range(1, 12), range(1, 28), range(0, 23), range(0, 59), range(0, 59), "#{range(-12,12)}").to_edn 157 | rescue ArgumentError 158 | guard false 159 | end 160 | } 161 | 162 | def rant(fun, count = REPEAT) 163 | Rantly(count) { call(fun) } 164 | end 165 | end 166 | -------------------------------------------------------------------------------- /spec/edn/parser_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'edn_parser' do 4 | include RantlyHelpers 5 | 6 | let(:parser) { EDN.new_parser } 7 | 8 | it "can contain comments" do 9 | edn = ";; This is some sample data\n[1 2 ;; the first two values\n3]" 10 | EDN.read(edn).should == [1, 2, 3] 11 | end 12 | 13 | it "can discard using the discard reader macro" do 14 | edn = "[1 2 #_3 {:foo #_bar :baz}]" 15 | EDN.read(edn).should == [1, 2, {:foo => :baz}] 16 | end 17 | 18 | context "element" do 19 | it "should consume metadata with the element" do 20 | x = EDN.read('^{:doc "test"} [1 2]') 21 | x.should == [1, 2] 22 | x.metadata.should == {doc: "test"} 23 | end 24 | end 25 | 26 | context "integer" do 27 | it "should consume integers" do 28 | rant(RantlyHelpers::INTEGER).each do |int| 29 | (EDN.read int.to_s).should == int.to_i 30 | end 31 | end 32 | 33 | it "should consume integers prefixed with a +" do 34 | rant(RantlyHelpers::INTEGER).each do |int| 35 | (EDN.read "+#{int.to_i.abs.to_s}").should == int.to_i.abs 36 | end 37 | end 38 | end 39 | 40 | context "float" do 41 | it "should consume simple floats" do 42 | rant(RantlyHelpers::FLOAT).each do |float| 43 | EDN.read(float.to_s).should == float.to_f 44 | end 45 | end 46 | 47 | it "should consume floats with exponents" do 48 | rant(RantlyHelpers::FLOAT_WITH_EXP).each do |float| 49 | EDN.read(float.to_s).should == float.to_f 50 | end 51 | end 52 | 53 | it "should consume floats prefixed with a +" do 54 | rant(RantlyHelpers::FLOAT).each do |float| 55 | EDN.read("+#{float.to_f.abs.to_s}").should == float.to_f.abs 56 | end 57 | end 58 | end 59 | 60 | context "symbol" do 61 | context "special cases" do 62 | it "should consume '/'" do 63 | EDN.read('/').should == EDN::Type::Symbol.new(:"/") 64 | end 65 | 66 | it "should consume '.'" do 67 | EDN.read('.').should == EDN::Type::Symbol.new(:".") 68 | end 69 | 70 | it "should consume '-'" do 71 | EDN.read('-').should == EDN::Type::Symbol.new(:"-") 72 | end 73 | end 74 | end 75 | 76 | context "keyword" do 77 | it "should consume any keywords" do 78 | rant(RantlyHelpers::SYMBOL).each do |symbol| 79 | EDN.read(":#{symbol}").should == symbol.to_sym 80 | end 81 | end 82 | end 83 | 84 | context "string" do 85 | it "should consume any string" do 86 | rant(RantlyHelpers::RUBY_STRING).each do |string| 87 | EDN.read(string.to_edn).should == string 88 | end 89 | end 90 | end 91 | 92 | context "character" do 93 | it "should consume any character" do 94 | rant(RantlyHelpers::RUBY_CHAR).each do |char| 95 | EDN.read(char.to_edn).should == char 96 | end 97 | end 98 | end 99 | 100 | context "vector" do 101 | it "should consume an empty vector" do 102 | EDN.read('[]').should == [] 103 | EDN.read('[ ]').should == [] 104 | end 105 | 106 | it "should consume vectors of mixed elements" do 107 | rant(RantlyHelpers::VECTOR).each do |vector| 108 | expect { EDN.read(vector) }.to_not raise_error 109 | end 110 | end 111 | end 112 | 113 | context "list" do 114 | it "should consume an empty list" do 115 | EDN.read('()').should == [] 116 | EDN.read('( )').should == [] 117 | end 118 | 119 | it "should consume lists of mixed elements" do 120 | rant(RantlyHelpers::LIST).each do |list| 121 | expect { EDN.read(list) }.to_not raise_error 122 | end 123 | end 124 | end 125 | 126 | context "set" do 127 | it "should consume an empty set" do 128 | EDN.read('#{}').should == Set.new 129 | EDN.read('#{ }').should == Set.new 130 | end 131 | 132 | it "should consume sets of mixed elements" do 133 | rant(RantlyHelpers::SET).each do |set| 134 | EDN.read(set) 135 | end 136 | end 137 | end 138 | 139 | context "map" do 140 | it "should consume maps of mixed elements" do 141 | rant(RantlyHelpers::MAP).each do |map| 142 | expect { EDN.read(map) }.not_to raise_error 143 | end 144 | end 145 | end 146 | 147 | context "tagged element" do 148 | context "#inst" do 149 | it "should consume #inst" do 150 | rant(RantlyHelpers::INST).each do |element| 151 | expect { EDN.read(element) }.not_to raise_error 152 | end 153 | end 154 | end 155 | 156 | it "should consume tagged elements" do 157 | rant(RantlyHelpers::TAGGED_ELEMENT).each do |element| 158 | expect { EDN.read(element) }.not_to raise_error 159 | end 160 | end 161 | end 162 | end 163 | -------------------------------------------------------------------------------- /lib/edn/ruby_edn_parser.rb: -------------------------------------------------------------------------------- 1 | require 'stringio' 2 | require 'set' 3 | 4 | 5 | module EDN 6 | class RubyEdnParser 7 | def initialize(source, *extra) 8 | io = source.instance_of?(String) ? StringIO.new(source) : source 9 | @s = CharStream.new(io) 10 | end 11 | 12 | def read(return_nothing=false) 13 | meta = read_meta 14 | value = read_basic(return_nothing) 15 | if meta && value != NOTHING 16 | value.extend EDN::Metadata 17 | value.metadata = meta 18 | end 19 | value 20 | end 21 | 22 | def eof? 23 | @s.eof? 24 | end 25 | 26 | def unknown 27 | raise "Don't know what to do with #{@s.current} #{@s.current.class}" 28 | end 29 | 30 | def read_eof 31 | EOF 32 | end 33 | 34 | def read_char 35 | result = @s.advance 36 | @s.advance 37 | until @s.eof? 38 | break unless @s.digit? || @s.alpha? 39 | result += @s.current 40 | @s.advance 41 | end 42 | 43 | return result if result.size == 1 44 | 45 | case result 46 | when 'newline' 47 | "\n" 48 | when 'return' 49 | "\r" 50 | when 'tab' 51 | "\t" 52 | when 'space' 53 | " " 54 | else 55 | raise "Unknown char #{result}" 56 | end 57 | end 58 | 59 | def read_slash 60 | @s.advance 61 | Type::Symbol.new('/') 62 | end 63 | 64 | def read_number_or_symbol 65 | leading = @s.current 66 | @s.advance 67 | return read_number(leading) if @s.digit? 68 | read_symbol(leading) 69 | end 70 | 71 | def read_symbol_chars 72 | result = '' 73 | 74 | ch = @s.current 75 | while SYMBOL_INTERIOR_CHARS.include?(ch) 76 | result << ch 77 | ch = @s.advance 78 | end 79 | return result unless @s.skip_past('/') 80 | 81 | result << '/' 82 | ch = @s.current 83 | while SYMBOL_INTERIOR_CHARS.include?(ch) 84 | result << ch 85 | ch = @s.advance 86 | end 87 | 88 | result 89 | end 90 | 91 | def read_extension 92 | @s.advance 93 | if @s.current == '{' 94 | @s.advance 95 | read_collection(Set, '}') 96 | elsif @s.current == "_" 97 | @s.advance 98 | x = read 99 | NOTHING 100 | else 101 | tag = read_symbol_chars 102 | value = read 103 | EDN.tagged_element(tag, value) 104 | end 105 | end 106 | 107 | def read_symbol(leading='') 108 | token = leading + read_symbol_chars 109 | return true if token == "true" 110 | return false if token == "false" 111 | return nil if token == "nil" 112 | Type::Symbol.new(token) 113 | end 114 | 115 | def read_keyword 116 | @s.advance 117 | read_symbol_chars.to_sym 118 | end 119 | 120 | def escape_char(ch) 121 | return '\\' if ch == '\\' 122 | return "\n" if ch == 'n' 123 | return "\t" if ch == 't' 124 | return "\r" if ch == 'r' 125 | ch 126 | end 127 | 128 | def read_string 129 | @s.advance 130 | 131 | result = '' 132 | until @s.current == '"' 133 | raise "Unexpected eof" if @s.eof? 134 | if @s.current == '\\' 135 | @s.advance 136 | result << escape_char(@s.current) 137 | else 138 | result << @s.current 139 | end 140 | @s.advance 141 | end 142 | @s.advance 143 | result 144 | end 145 | 146 | def call_reader(reader) 147 | if reader.instance_of? Symbol 148 | self.send(reader) 149 | else 150 | self.instance_exec(&reader) 151 | end 152 | end 153 | 154 | def read_basic(return_nothing=false) 155 | @s.skip_ws 156 | ch = @s.current 157 | result = call_reader(READERS[ch]) 158 | while NOTHING.equal?(result) && !return_nothing 159 | @s.skip_ws 160 | result = call_reader(READERS[@s.current]) 161 | end 162 | 163 | result 164 | end 165 | 166 | def read_digits(min_digits=0) 167 | result = '' 168 | 169 | if @s.current == '+' || @s.current == '-' 170 | result << @s.current 171 | @s.advance 172 | end 173 | 174 | n_digits = 0 175 | while @s.current =~ /[0-9]/ 176 | n_digits += 1 177 | result << @s.current 178 | @s.advance 179 | end 180 | 181 | raise "Expected at least #{min_digits} digits, found #{result}" unless n_digits >= min_digits 182 | result 183 | end 184 | 185 | def finish_float(whole_part) 186 | result = whole_part 187 | 188 | if @s.current == '.' 189 | result += '.' 190 | @s.advance 191 | result = @s.digit? ? result + read_digits : result + '0' 192 | #puts "aaa: #{result}" 193 | end 194 | 195 | if @s.current == 'e' || @s.current == 'E' 196 | @s.advance 197 | result = result + 'e' + read_digits 198 | #puts "bbb: #{result}" 199 | end 200 | #puts result 201 | result.to_f 202 | end 203 | 204 | def read_number(leading='') 205 | result = leading + read_digits 206 | 207 | if %w{. e E}.include? @s.current 208 | return finish_float(result) 209 | elsif @s.skip_past('M') || @s.skip_past('N') 210 | result.to_i 211 | else 212 | result.to_i 213 | end 214 | end 215 | 216 | def read_meta 217 | raw_metadata = [] 218 | @s.skip_ws 219 | while @s.current == '^' 220 | @s.advance 221 | raw_metadata << read_basic 222 | @s.skip_ws 223 | end 224 | 225 | metadata = raw_metadata.reverse.reduce({}) do |acc, m| 226 | case m 227 | when Symbol then acc.merge(m => true) 228 | when EDN::Type::Symbol then acc.merge(:tag => m) 229 | else acc.merge(m) 230 | end 231 | end 232 | metadata.empty? ? nil : metadata 233 | end 234 | 235 | def read_list 236 | @s.advance 237 | read_collection(EDN::Type::List, ')') 238 | end 239 | 240 | def read_vector 241 | @s.advance 242 | read_collection(Array, ']') 243 | end 244 | 245 | def read_map 246 | @s.advance 247 | array = read_collection(Array, '}') 248 | raise "Need an even number of items for a map" unless array.count.even? 249 | Hash[*array] 250 | end 251 | 252 | def read_collection(clazz, closing) 253 | result = clazz.new 254 | 255 | while true 256 | @s.skip_ws 257 | raise "Unexpected eof" if @s.eof? 258 | break if @s.current == closing 259 | next_value = read(true) 260 | result << next_value unless next_value == NOTHING 261 | end 262 | @s.advance 263 | result 264 | end 265 | end 266 | end 267 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # edn-ruby 2 | 3 | [![Build Status](https://secure.travis-ci.org/relevance/edn-ruby.png)](http://travis-ci.org/relevance/edn-ruby) 4 | 5 | 6 | © 2012 Relevance Inc 7 | 8 | **edn-ruby** is a Ruby library to read and write EDN (extensible data notation), a subset of Clojure used for transferring data between applications, much like JSON, YAML, or XML. 9 | 10 | ## Installation 11 | 12 | Add this line to your application's Gemfile: 13 | 14 | gem 'edn' 15 | 16 | And then execute: 17 | 18 | $ bundle 19 | 20 | Or install it yourself as: 21 | 22 | $ gem install edn 23 | 24 | 25 | Note that you might also want to look at [edn_turbo](https://github.com/edporras/edn_turbo) 26 | which provides a much faster EDN parser (It's written in C) with an interface that is largely compatible 27 | with ths gem. 28 | 29 | ## Usage 30 | 31 | To read a string of EDN: 32 | 33 | ```ruby 34 | EDN.read('[1 2 {:foo "bar"}]') 35 | ``` 36 | 37 | Alternatively you can pass in an IO instance, for 38 | example an open file: 39 | 40 | ```ruby 41 | File.open("data.edn") do |f| 42 | data = EDN.read(f) 43 | # Do something with data 44 | end 45 | ``` 46 | 47 | By default EDN.read will throw an execption 48 | if you try to read past the end of the data: 49 | 50 | ```ruby 51 | EDN.read("") # Boom! 52 | ``` 53 | 54 | Alternatively, the `EDN.read` method takes an optional 55 | parameter, which is the value to return 56 | when it hits the end of data: 57 | 58 | ```ruby 59 | EDN.read("", :nomore) 60 | 61 | #=> :nomore 62 | ``` 63 | 64 | There is no problem using `nil` as an eof value. 65 | 66 | ### EDN::Reader 67 | 68 | You can also do things in a more object oriented way by 69 | creating instances of `EDN::Reader`: 70 | 71 | ```ruby 72 | r = EDN::Reader.new('[1 2 3] {:a 1 :b 2}') 73 | 74 | r.read #=> [1, 2, 3] 75 | r.read #=> {:a => 1, :b => 2} 76 | r.read #=> RuntimeError: Unexpected end of file 77 | ``` 78 | 79 | `EDN:Reader` will also take an IO instance: 80 | 81 | ```ruby 82 | r = EDN::Reader.new(open("data.edn")) 83 | 84 | r.read # Read the first form from the file. 85 | r.read # Read the second form from the file. 86 | r.read # Read the third from from the file. 87 | ``` 88 | 89 | You can also iterate through the forms with `each`: 90 | 91 | ```ruby 92 | r = EDN::Reader.new('[1 2 3] {:a 1 :b 2}') 93 | 94 | r.each do |form| 95 | p form 96 | end 97 | 98 | #=> [1, 2, 3] 99 | #=> {:a => 1, :b => 2} 100 | ``` 101 | 102 | Note that in contrast to earlier versions of this gem, 103 | EDN::Reader is no longer `Enumerable`. 104 | 105 | Like `EDN.read`, `Reader.read` also takes an optional 106 | parameter, which is returned when there is no more data: 107 | 108 | ```ruby 109 | r = EDN::Reader.new('1 2 3') 110 | r.read(:eof) # returns 1 111 | r.read(:eof) # returns 2 112 | r.read(:eof) # returns 3 113 | r.read(:eof) # returns :eof 114 | ``` 115 | 116 | ### Converting Ruby data to EDN 117 | 118 | To convert a data structure to an EDN string: 119 | 120 | ```ruby 121 | data.to_edn 122 | ``` 123 | 124 | By default, this will work for strings, symbols, numbers, arrays, hashes, sets, nil, Time, and boolean values. 125 | 126 | ### Value Translations 127 | 128 | Note that EDN uses its own terminology for the types of objects it represents 129 | and in some cases those types not map cleanly to Ruby. 130 | 131 | In EDN, you have _keywords_, which look like Ruby symbols and have the same meaning and 132 | purpose. These are converted to Ruby symbols. 133 | 134 | You also have EDN _symbols_, which generally reflect variable names, but have 135 | several purposes. We parse these and return `EDN::Type::Symbol` values for them, 136 | as they don't map to anything built into Ruby. To create an EDN symbol in Ruby, 137 | call `EDN::Type::Symbol.new` or `EDN.symbol` with a string argument, or use the 138 | convenience unary operator `~` like so: `~"elf/rings"`. 139 | 140 | EDN also has _vectors_, which map to Ruby arrays, and _lists_, which are linked lists 141 | in Clojure. We map EDN lists to `EDN::Type::List` values, which are type-compatible with 142 | arrays. To create an EDN list in Ruby, call `EDN::Type::List.new` or `EDN.list` 143 | with all arguments to go in the list. If you have an array, you will use the splat 144 | operator, like so: `EDN.list(*[1, 2, 3])`. You can also use the `~` unary 145 | operator like so: `~[1, 2, 3]`. 146 | 147 | EDN also has character types, but Ruby does not. These are converted into one-character strings. 148 | 149 | ### Tagged Values 150 | 151 | The interesting part of EDN is the _extensible_ part. 152 | Data can be be _tagged_ to coerce interpretation of 153 | it to a particular data type. An example of a tagged data element: 154 | 155 | ``` 156 | #wolf/pack {:alpha "Greybeard" :betas ["Frostpaw" "Blackwind" "Bloodjaw"]} 157 | ``` 158 | 159 | The tag (`#wolf/pack`) will tell any consumers of this data 160 | to use a data type registered to handle `wolf/pack` to represent this data. 161 | 162 | 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. 163 | 164 | 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`. 165 | 166 | Tags that are not registered generate a struct of the type `EDN::Type::Unknown` with the methods `tag` and `value`. 167 | 168 | ### Registering a New Tag For Reading 169 | 170 | To register a tag for reading, call the method `EDN.register` with a tag and one of the following: 171 | 172 | - A block that accepts data and returns a value. 173 | - A lambda that accepts data and returns a value. 174 | - A class that has an `initialize` method that accepts data. 175 | 176 | Examples: 177 | 178 | ```ruby 179 | EDN.register("clinton/uri") do |uri| 180 | URI(uri) 181 | end 182 | 183 | EDN.register("clinton/date", lambda { |date_array| Date.new(*date_array) }) 184 | 185 | class Dog 186 | def initialize(name) 187 | @name = name 188 | end 189 | end 190 | 191 | EDN.register("clinton/dog", Dog) 192 | ``` 193 | 194 | ### Writing Tags 195 | 196 | Writing tags should be done as part of the class's `.to_edn` method, like so: 197 | 198 | ```ruby 199 | class Dog 200 | def to_edn 201 | ["#clinton/dog", @name.to_edn].join(" ") 202 | end 203 | end 204 | ``` 205 | 206 | `EDN` provides a helper method, `EDN.tagout`: 207 | 208 | ```ruby 209 | class Dog 210 | def to_edn 211 | EDN.tagout("clinton/dog", @name) 212 | end 213 | end 214 | ``` 215 | 216 | This method calls `.to_edn` on the second argument and joins the arguments appropriately. 217 | 218 | Other examples are: 219 | ``` 220 | EDN.tagout("wolf/pack", {:alpha=>"Greybeard", :betas=>["Frostpaw", "Blackwind", "Bloodjaw"]}) 221 | => "#wolf/pack {:alpha \"Greybeard\", :betas [\"Frostpaw\" \"Blackwind\" \"Bloodjaw\"]}" 222 | 223 | class Range 224 | def to_edn 225 | EDN.tagout("ruby/range", [self.begin, self.end, self.exclude_end?]) 226 | end 227 | end 228 | 229 | (0..9).to_edn 230 | => "#ruby/range [0 9 false]" 231 | ``` 232 | 233 | 234 | ## Metadata 235 | 236 | Certain elements of EDN can have *metadata*. Metadata is a map of values about the element, which must follow specific rules. 237 | 238 | * Only symbols, lists, vectors, maps, and sets can have metadata. Tagged elements *cannot* have metadata. 239 | * Metadata keys must be symbols, keywords, or strings. 240 | 241 | Metadata can be expressed in one of the following three ways: 242 | 243 | * Via a map. The element is prefixed with a map which has a caret (`^`) prefixed to it, like so: `^{:doc "This is my vector" :rel :temps} [98.6 99.7]`. 244 | * Via a keyword. The element is prefixed with a keyword, also prefixed by a caret: `^:awesome #{1 2 \c}`. This results in the key `:awesome` being set to `true`, as if the metadata was: `^{:awesome true} #{1 2 \c}`. 245 | * Via a symbol. The element is prefixed with a symbol, also prefixed by a caret: `^Boolean "true"`. This results in the key `:tag` being set to the symbol, as if the metadata was: `^{:tag Boolean} "true"`. This is used in Clojure to indicate the Java type of the element. In other EDN implementations, it may be ignored or used differently. 246 | 247 | More than one piece of metadata can be applied to an element. Metadata is applied to the next element appearing after it, so in the case of `^:foo ^{:bar false} [1 2]`, the metadata would be, in total, `^{:foo true, :bar false}`. Note that `^:foo` is applied to the element `[1 2]` with the metadata `^{:bar false}` applied to it. Because of this, key collisions are resolved *right-to-left*. 248 | 249 | ## Contributors 250 | 251 | * Clinton N. Dreisbach (@crnixon) 252 | * Michael Ficarra (@michaelficarra) 253 | * Andrew Forward (@aforward) 254 | * Gabriel Horner (@cldwalker) 255 | * Russ Olsen (@russolsen) 256 | 257 | ## Contributing 258 | 259 | 1. Fork it 260 | 2. Create your feature branch (`git checkout -b my-new-feature`) 261 | 3. Commit your changes (`git commit -am 'Added some feature'`) 262 | 4. Push to the branch (`git push origin my-new-feature`) 263 | 5. Create new Pull Request 264 | 265 | [edn]: https://github.com/edn-format/edn 266 | [README]: https://github.com/edn-format/edn/blob/master/README.md 267 | --------------------------------------------------------------------------------