structure_digest 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 8184011e1799c2b33e1341e3dad068f0aabd094c
4
+ data.tar.gz: ba65e85584a22cbc2efac85705e0de0b6eb1b262
5
+ SHA512:
6
+ metadata.gz: 0360f22b4bf9d8febe85219c0a0650c61538a20c8785e3746b63f2136a9cc278589c7729c65c291305f9b4330eea62058dda7d5e2e6f6fafe9384161e51d0852
7
+ data.tar.gz: b4ad1f41ef59909ece4e1de10af1e951f16337270d5be9818509fcf0e12e2051a449b90cd115266c46d45127e79beeffed115270d02fbe5ba2b21f6188334629
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.ruby-gemset ADDED
@@ -0,0 +1 @@
1
+ structure_digest
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.0.0
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in structure_digest.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Mike Grafton and Sara Tansey
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.
data/README.md ADDED
@@ -0,0 +1,85 @@
1
+ # StructureDigest
2
+
3
+ Run the binary with one or more YAML files to find out what it does
4
+
5
+
6
+ ## Installation
7
+
8
+ Add this line to your application's Gemfile:
9
+
10
+ gem 'structure_digest'
11
+
12
+ And then execute:
13
+
14
+ $ bundle
15
+
16
+ Or install it yourself as:
17
+
18
+ $ gem install structure_digest
19
+
20
+ ## Usage
21
+
22
+ Given a sample YAML document from Wikipedia:
23
+
24
+ ---
25
+ receipt: Oz-Ware Purchase Invoice
26
+ date: 2012-08-06
27
+ customer:
28
+ given: Dorothy
29
+ family: Gale
30
+
31
+ items:
32
+ - part_no: A4786
33
+ descrip: Water Bucket (Filled)
34
+ price: 1.47
35
+ quantity: 4
36
+
37
+ - part_no: E1628
38
+ descrip: High Heeled "Ruby" Slippers
39
+ size: 8
40
+ price: 100.27
41
+ quantity: 1
42
+
43
+ bill-to: &id001
44
+ street: |
45
+ 123 Tornado Alley
46
+ Suite 16
47
+ city: East Centerville
48
+ state: KS
49
+
50
+ ship-to: *id001
51
+
52
+ specialDelivery: >
53
+ Follow the Yellow Brick
54
+ Road to the Emerald City.
55
+ Pay no attention to the
56
+ man behind the curtain.
57
+
58
+ `structure_digest example.yml` will produce:
59
+
60
+ .receipt
61
+ .date
62
+ .customer.given
63
+ .customer.family
64
+ .items[].part_no
65
+ .items[].descrip
66
+ .items[].price
67
+ .items[].quantity
68
+ .items[].size
69
+ .bill-to.street
70
+ .bill-to.city
71
+ .bill-to.state
72
+ .ship-to.street
73
+ .ship-to.city
74
+ .ship-to.state
75
+ .specialDelivery
76
+
77
+ ## Contributing
78
+
79
+ Could use a shorter name maybe
80
+
81
+ 1. Fork it
82
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
83
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
84
+ 4. Push to the branch (`git push origin my-new-feature`)
85
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ $LOAD_PATH << File.join(File.dirname(__FILE__), '../lib/')
3
+ require 'structure_digest'
4
+ require 'tempfile'
5
+
6
+ require 'optparse'
7
+ ARGV << "--help" if ARGV.empty? && STDIN.tty?
8
+ tree = false
9
+
10
+ opt = OptionParser.new do |opt|
11
+ opt.banner = "Usage: structure_digest [options] File1[, File2, ...]"
12
+ opt.on('-t', '--tree', 'replace repeated suffixes with indents') { tree = true }
13
+ end
14
+
15
+ opt.parse!
16
+
17
+ files = ARGV
18
+ file = Tempfile.new('structure_digest_in')
19
+ if !STDIN.tty?
20
+ file.write STDIN.read
21
+ files += [file.path]
22
+ file.rewind
23
+ end
24
+
25
+ $h = StructureDigest::Digest.new(tree: tree).injest_yml_files(files)
26
+ print $h.shorthand
27
+
28
+ file.close!
29
+
@@ -0,0 +1,16 @@
1
+ module StructureDigest
2
+ module SchemaParts
3
+ class AbstractArrayDereference
4
+ def ==(other)
5
+ self.class == other.class
6
+ end
7
+
8
+ def serialize
9
+ "[]"
10
+ end
11
+
12
+ alias :eql? :==
13
+ def hash; [].hash; end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,45 @@
1
+ module StructureDigest
2
+ module SchemaParts
3
+ class AbstractPath
4
+ def initialize(abstract_parts)
5
+ @parts = abstract_parts
6
+ end
7
+
8
+ def serialize
9
+ @parts.map(&:serialize).join
10
+ end
11
+
12
+ def accepts(path)
13
+ self == path.abstract
14
+ end
15
+
16
+ def add_part(part)
17
+ AbstractPath.new(@parts + [part])
18
+ end
19
+
20
+ def self.from_shorthand(shorthand)
21
+ shorthand = orig_shorthand.clone
22
+ abstract_path = AbstractPath.new
23
+ while !shorthand.empty?
24
+ if key = shorthand[/^\.(\w+)/, 1]
25
+ abstract_path = abstract_path.add_part(HashDereference.new(key))
26
+ shorthand.sub!(/^\.\w+/, '')
27
+ elsif shorthand[/^\[\]/]
28
+ abstract_path = abstract_path.add_part(AbstractArrayDereference.new)
29
+ shorthand.sub!("[]", '')
30
+ else
31
+ raise "shorthand #{shorthand} isn't a valid path shorthand"
32
+ end
33
+ end
34
+ abstract_path
35
+ end
36
+
37
+ attr_reader :parts
38
+ def ==(other)
39
+ self.class == other.class && @parts == other.parts
40
+ end
41
+ alias :eql? :==
42
+ def hash; [@parts].hash; end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,20 @@
1
+ module StructureDigest
2
+ module SchemaParts
3
+ class ArrayDereference
4
+ attr_reader :index
5
+ def initialize(index)
6
+ @index = index
7
+ end
8
+
9
+ def ==(other)
10
+ self.class == other.class && @index == other.index
11
+ end
12
+
13
+ alias :eql? :==
14
+ def hash; [@index].hash; end
15
+ def abstract
16
+ AbstractArrayDereference.new
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,104 @@
1
+ require 'yaml'
2
+
3
+ module StructureDigest
4
+ class Digest
5
+ def initialize(opts={})
6
+ @tree = opts[:tree] || false
7
+ end
8
+
9
+ def injest_yml_files(file_paths)
10
+ file_paths.each do |p|
11
+ y = YAML.load_file(p)
12
+ gather_paths(y, paths)
13
+ end
14
+ @abstract_paths = paths.map(&:abstract).uniq
15
+ self
16
+ end
17
+
18
+ def add_validation(shorthand, &validateFn)
19
+ raise "isn't applicable for core paths of schema" unless @abstract_paths.include? SchemaParts::AbstractPath.from_shorthand(shorthand)
20
+ validators[shorthand] ||= []
21
+ validators[shorthand] << validateFn
22
+ end
23
+
24
+ def validate(hash)
25
+ paths = []
26
+ gather_paths(hash, paths)
27
+ paths.all? do |p|
28
+ print '.'
29
+ @abstract_paths.any?{|my_p| my_p.accepts(p) } && validators_for(p).all?{|v| v.call(p.last[:value]) }
30
+ end.tap do
31
+ puts
32
+ end
33
+ end
34
+
35
+ def validators_for(p)
36
+ validators[p.abstract.serialize] || []
37
+ end
38
+
39
+ def shorthand
40
+ if @tree
41
+ root = {}
42
+ @abstract_paths.each do |apath|
43
+ append_to_tree(root, apath.parts)
44
+ end
45
+ sio = StringIO.new
46
+ pretty_print(sio, root)
47
+ sio.rewind
48
+ sio.read.chomp
49
+ else
50
+ @abstract_paths.map(&:serialize).uniq.sort.join("\n")
51
+ end
52
+ end
53
+
54
+ def pretty_print(io, tree, level=0)
55
+ tree.keys.sort.each do |k|
56
+ v = tree[k]
57
+ io << ' '*level + k
58
+ if v.keys.empty?
59
+ io << "\n"
60
+ elsif v.keys.size == 1
61
+ pretty_print(io, v, level)
62
+ else
63
+ io << "\n"
64
+ pretty_print(io, v, level+1)
65
+ end
66
+ end
67
+ end
68
+
69
+ def append_to_tree(tree, parts)
70
+ return if parts.empty?
71
+ append_to_tree(tree[parts.first.serialize] || (tree[parts.first.serialize] = {}), parts.drop(1))
72
+ end
73
+
74
+ private
75
+
76
+ def paths
77
+ @paths ||= []
78
+ end
79
+
80
+ def validators
81
+ @validators ||= {}
82
+ @validators
83
+ end
84
+
85
+ def gather_paths(node, solutions=[], partial_solution=SchemaParts::Path.new)
86
+ if Array === node
87
+ node.each.with_index do |e, i|
88
+ gather_paths(e, solutions, partial_solution.add_part(SchemaParts::ArrayDereference.new(i)))
89
+ end
90
+ elsif Hash === node
91
+ node.each do |k,v|
92
+ gather_paths(v, solutions, partial_solution.add_part(SchemaParts::HashDereference.new(k)))
93
+ end
94
+ else
95
+ solutions << (partial_solution.add_part(SchemaParts::Value.new(node)))
96
+ end
97
+ self
98
+ end
99
+
100
+ def deserialize(orig_shorthand)
101
+ SchemaParts::AbstractPath.from_shorthand(orig_shorthand)
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,23 @@
1
+ module StructureDigest
2
+ module SchemaParts
3
+ class HashDereference
4
+ def initialize(key)
5
+ @key = key
6
+ end
7
+
8
+ def serialize
9
+ ".#{key}"
10
+ end
11
+
12
+ def abstract
13
+ self
14
+ end
15
+ attr_reader :key
16
+ def ==(other)
17
+ self.class == other.class && @key == other.key
18
+ end
19
+ alias :eql? :==
20
+ def hash; [@key].hash; end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,24 @@
1
+ module StructureDigest
2
+ module SchemaParts
3
+ class Path
4
+ def initialize(parts=[])
5
+ @parts = parts
6
+ end
7
+
8
+ def abstract
9
+ AbstractPath.new(@parts.map(&:abstract).compact)
10
+ end
11
+
12
+ def add_part(part)
13
+ Path.new(@parts + [part])
14
+ end
15
+
16
+ attr_reader :parts
17
+ def ==(other)
18
+ self.class == other.class && @parts == other.parts
19
+ end
20
+ alias :eql? :==
21
+ def hash; [@parts].hash; end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,20 @@
1
+ module StructureDigest
2
+ module SchemaParts
3
+ class Value
4
+ def initialize(val)
5
+ @value = val
6
+ end
7
+
8
+ def abstract
9
+ nil
10
+ end
11
+
12
+ attr_reader :value
13
+ def ==(other)
14
+ self.class == other.class && @value == other.value
15
+ end
16
+ alias :eql? :==
17
+ def hash; [@value].hash; end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,3 @@
1
+ module StructureDigest
2
+ VERSION = "0.2.0"
3
+ end
@@ -0,0 +1 @@
1
+ Dir[File.dirname(__FILE__) + "/**/*.rb"].each(&method(:require))
data/sandbox/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ gem 'pry'
2
+ gem 'structure_digest', path: '../'
@@ -0,0 +1,21 @@
1
+ {
2
+ "firstName": "John",
3
+ "lastName": "Smith",
4
+ "age": 25,
5
+ "address": {
6
+ "streetAddress": "21 2nd Street",
7
+ "city": "New York",
8
+ "state": "NY",
9
+ "postalCode": 10021
10
+ },
11
+ "phoneNumbers": [
12
+ {
13
+ "type": "home",
14
+ "number": "212 555-1234"
15
+ },
16
+ {
17
+ "type": "fax",
18
+ "number": "646 555-4567"
19
+ }
20
+ ]
21
+ }
@@ -0,0 +1,44 @@
1
+ ---
2
+ receipt: Oz-Ware Purchase Invoice
3
+ date: 2012-08-06
4
+ customer:
5
+ given: Dorothy
6
+ family: Gale
7
+
8
+ problem:
9
+ return: true
10
+
11
+ recursive:
12
+ problem:
13
+ que: 'what'
14
+ ans: 'who'
15
+ problem2:
16
+ que: 'a'
17
+ ans: 'b'
18
+
19
+ items:
20
+ - part_no: A4786
21
+ descrip: Water Bucket (Filled)
22
+ price: 1.47
23
+ quantity: 4
24
+
25
+ - part_no: E1628
26
+ descrip: High Heeled "Ruby" Slippers
27
+ size: 8
28
+ price: 100.27
29
+ quantity: 1
30
+
31
+ bill-to: &id001
32
+ street: |
33
+ 123 Tornado Alley
34
+ Suite 16
35
+ city: East Centerville
36
+ state: KS
37
+
38
+ ship-to: *id001
39
+
40
+ specialDelivery: >
41
+ Follow the Yellow Brick
42
+ Road to the Emerald City.
43
+ Pay no attention to the
44
+ man behind the curtain.
@@ -0,0 +1,68 @@
1
+ lib_path = File.join(File.dirname(__FILE__), '../lib/')
2
+ $LOAD_PATH << lib_path
3
+ require 'structure_digest'
4
+
5
+ describe "structure_digest" do
6
+
7
+ def digest_fixture(file, opts={})
8
+ StructureDigest::Digest.new(opts).injest_yml_files(
9
+ [File.expand_path("./fixtures/#{file}", File.dirname(__FILE__))]
10
+ ).shorthand
11
+ end
12
+
13
+ it "works with yml" do
14
+ digest_fixture("sample.yml").should == <<HEREDOC.chomp
15
+ .bill-to.city
16
+ .bill-to.state
17
+ .bill-to.street
18
+ .customer.family
19
+ .customer.given
20
+ .date
21
+ .items[].descrip
22
+ .items[].part_no
23
+ .items[].price
24
+ .items[].quantity
25
+ .items[].size
26
+ .problem.return
27
+ .receipt
28
+ .recursive.problem.ans
29
+ .recursive.problem.que
30
+ .recursive.problem2.ans
31
+ .recursive.problem2.que
32
+ .ship-to.city
33
+ .ship-to.state
34
+ .ship-to.street
35
+ .specialDelivery
36
+ HEREDOC
37
+ end
38
+
39
+ it "works with JSON" do
40
+ digest_fixture("sample.json").should == <<HEREDOC.chomp
41
+ .address.city
42
+ .address.postalCode
43
+ .address.state
44
+ .address.streetAddress
45
+ .age
46
+ .firstName
47
+ .lastName
48
+ .phoneNumbers[].number
49
+ .phoneNumbers[].type
50
+ HEREDOC
51
+ end
52
+
53
+ it "works with JSON in tree format" do
54
+ digest_fixture("sample.json", tree: true).should == <<HEREDOC.chomp
55
+ .address
56
+ .city
57
+ .postalCode
58
+ .state
59
+ .streetAddress
60
+ .age
61
+ .firstName
62
+ .lastName
63
+ .phoneNumbers[]
64
+ .number
65
+ .type
66
+ HEREDOC
67
+ end
68
+ end
@@ -0,0 +1,23 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'structure_digest/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "structure_digest"
8
+ spec.version = StructureDigest::VERSION
9
+ spec.authors = ["Serguei Filimonov"]
10
+ spec.email = ["serguei.filimonov@gmail.com"]
11
+ spec.description = %q{Digests and lists all the paths through a nested dictionary}
12
+ spec.summary = %q{run the binary on YAML files to check it out}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.3"
22
+ spec.add_development_dependency "rake"
23
+ end
metadata ADDED
@@ -0,0 +1,98 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: structure_digest
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Serguei Filimonov
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-02-12 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '1.3'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description: Digests and lists all the paths through a nested dictionary
42
+ email:
43
+ - serguei.filimonov@gmail.com
44
+ executables:
45
+ - structure_digest
46
+ extensions: []
47
+ extra_rdoc_files: []
48
+ files:
49
+ - .gitignore
50
+ - .ruby-gemset
51
+ - .ruby-version
52
+ - Gemfile
53
+ - LICENSE.txt
54
+ - README.md
55
+ - Rakefile
56
+ - bin/structure_digest
57
+ - lib/structure_digest.rb
58
+ - lib/structure_digest/abstract_array_dereference.rb
59
+ - lib/structure_digest/abstract_path.rb
60
+ - lib/structure_digest/array_dereference.rb
61
+ - lib/structure_digest/digest.rb
62
+ - lib/structure_digest/hash_dereference.rb
63
+ - lib/structure_digest/path.rb
64
+ - lib/structure_digest/value.rb
65
+ - lib/structure_digest/version.rb
66
+ - sandbox/Gemfile
67
+ - spec/fixtures/sample.json
68
+ - spec/fixtures/sample.yml
69
+ - spec/integration_spec.rb
70
+ - structure_digest.gemspec
71
+ homepage: ''
72
+ licenses:
73
+ - MIT
74
+ metadata: {}
75
+ post_install_message:
76
+ rdoc_options: []
77
+ require_paths:
78
+ - lib
79
+ required_ruby_version: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - '>='
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
84
+ required_rubygems_version: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - '>='
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ requirements: []
90
+ rubyforge_project:
91
+ rubygems_version: 2.2.1
92
+ signing_key:
93
+ specification_version: 4
94
+ summary: run the binary on YAML files to check it out
95
+ test_files:
96
+ - spec/fixtures/sample.json
97
+ - spec/fixtures/sample.yml
98
+ - spec/integration_spec.rb