fzip 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ OTg0MWE1NTE4M2IyYmVjN2UxZjQ1NGM4YTdjNTQwZTAxNzkzOWQ5NQ==
5
+ data.tar.gz: !binary |-
6
+ ZTg4YWE1NWIxMDViZDA3YThiZDliYTI3MjY0MTQ2YTU5Nzc2MmI0YQ==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ MmRiYzljMTVhNzhmNDk0MDdlZTZmOTViNTIyZmZjM2FhZTBkZjZmYmUzNTlm
10
+ ZTA0NWU5ZDYyNDhjNmY0YWNhYjA5MzBhNjc1NzJhZTA0MzgxZTc0NzVhMzRm
11
+ MDE0NjcyYjI0ZDAyZjVkMWJiZmU1ZThjNWZiNjFmZDU5NjViNDg=
12
+ data.tar.gz: !binary |-
13
+ NTQwZDkzMzVmYWYzMzRjYWI1OGIwMzUzODEyN2U4MzFlZmJhNGJiODBmMDg2
14
+ YmNlNWIyYzM4ZjA5M2VkMzhjODA5MzYxZmM1MWMxZDAwNTZkNjg3ODRkM2Iw
15
+ MzAyYmVlYjhiNWY3ZTMzYmVjMjI2YjAyMmNhNDQ0ZmM2OWY1ZDg=
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ coverage
data/LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ Copyright (c) 2013-2014 Arne Brasseur
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,33 @@
1
+ # Fzip : Functional zippers for Ruby
2
+
3
+ To traverse and alter a hierarchical data structure in a functional way is best done using the "zipper" data structure as described originally by [Huet](http://www.st.cs.uni-saarland.de/edu/seminare/2005/advanced-fp/docs/huet-zipper.pdf).
4
+
5
+ `Fzip::Zipper` is a straight up port of the Clojure zip/zip library.
6
+
7
+ The Zipper can work with arbitrary hierarchical objects (trees). To do that it requires an adapter that implements three methods: `branch?(node)`, `children(node)`, and `create_node(node, children)`. An implementation for nested arrays is provided.
8
+
9
+ ```ruby
10
+ zipper = Fzip.array(
11
+ [
12
+ ['x', '+', 'y'],
13
+ '=',
14
+ ['a', '/', 'b']
15
+ ]
16
+ )
17
+
18
+ p zipper.down.right.right.down.right.replace(:foo).root
19
+ # >> [["x", "+", "y"], "=", ["a", :foo, "b"]]
20
+
21
+ p zipper.down.append_child(:bar).root
22
+ # >> [["x", "+", "y", :bar], "=", ["a", "/", "b"]]
23
+
24
+ p zipper.down.insert_child(:bar).down.right.insert_right(:baz).up.insert_left(:quux).root
25
+ # >> [:quux, [:bar, "x", :baz, "+", "y"], "=", ["a", "/", "b"]]
26
+ ```
27
+
28
+ ## Further reading
29
+
30
+ * [Wikipedia: Zipper (data structure)](https://en.wikipedia.org/wiki/Zipper_%28data_structure%29)
31
+ * [Three part article](http://pavpanchekha.com/blog/zippers/huet.html)
32
+
33
+ The Wikipedia page has more interesting links.
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ require 'rubygems/package_task'
2
+
3
+ spec = Gem::Specification.load(File.expand_path('../fzip.gemspec', __FILE__))
4
+ gem = Gem::PackageTask.new(spec)
5
+ gem.define
6
+
7
+ desc "Push gem to rubygems.org"
8
+ task :push => :gem do
9
+ sh "git tag v#{Fzip::VERSION}"
10
+ sh "git push --tags"
11
+ sh "gem push pkg/fzip-#{Fzip::VERSION}.gem"
12
+ end
data/fzip.gemspec ADDED
@@ -0,0 +1,20 @@
1
+ require File.expand_path('../lib/fzip/version', __FILE__)
2
+
3
+ Gem::Specification.new do |gem|
4
+ gem.name = 'fzip'
5
+ gem.version = '0.1.0'
6
+ gem.authors = [ 'Arne Brasseur' ]
7
+ gem.email = [ 'arne@arnebrasseur.net' ]
8
+ gem.description = 'Functional zipper class'
9
+ gem.summary = gem.description
10
+ gem.homepage = 'https://github.com/plexus/fzip'
11
+ gem.license = 'MIT'
12
+
13
+ gem.require_paths = %w[lib]
14
+ gem.files = `git ls-files`.split($/)
15
+ gem.test_files = `git ls-files -- spec`.split($/)
16
+ gem.extra_rdoc_files = %w[README.md]
17
+
18
+ gem.add_development_dependency 'rake', '~> 10.1'
19
+ gem.add_development_dependency 'rspec', '~> 2.14'
20
+ end
@@ -0,0 +1,12 @@
1
+ module FZip
2
+ class Adapter
3
+ def branch?(node)
4
+ end
5
+
6
+ def children(node)
7
+ end
8
+
9
+ def make_node(node, children)
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,16 @@
1
+ module Fzip
2
+ class ArrayAdapter
3
+ def branch?(node)
4
+ node.respond_to? :to_ary
5
+ end
6
+
7
+ def children(node)
8
+ node.empty? ? nil : node
9
+ end
10
+
11
+ def make_node(node, children)
12
+ children
13
+ end
14
+
15
+ end
16
+ end
@@ -0,0 +1,3 @@
1
+ module Fzip
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,167 @@
1
+ module Fzip
2
+ class Zipper
3
+ attr_reader :adapter, :parent, :path, :node, :lefts, :rights, :at_end
4
+
5
+ def initialize(adapter, node, lefts = nil, path = nil, parent = nil, rights = nil, changed = false, at_end = false)
6
+ @adapter = adapter
7
+ @node = node
8
+ @lefts = lefts
9
+ @path = path
10
+ @parent = parent
11
+ @rights = rights
12
+ @changed = changed
13
+ @at_end = at_end
14
+ end
15
+
16
+ def new(changes = {})
17
+ self.class.new(
18
+ @adapter,
19
+ changes.fetch(:node) { self.node },
20
+ changes.fetch(:lefts) { self.lefts },
21
+ changes.fetch(:path) { self.path },
22
+ changes.fetch(:parent) { self.parent },
23
+ changes.fetch(:rights) { self.rights },
24
+ changes.fetch(:changed) { self.changed? },
25
+ changes.fetch(:end) { self.end? }
26
+ )
27
+ end
28
+
29
+ def branch?
30
+ adapter.branch?(node)
31
+ end
32
+
33
+ def children
34
+ raise "called children on a leaf node" unless branch?
35
+ @cs ||= adapter.children(node)
36
+ end
37
+
38
+ def make_node(node, children)
39
+ adapter.make_node(node, children)
40
+ end
41
+
42
+ def changed?
43
+ @changed
44
+ end
45
+
46
+ def end?
47
+ @at_end
48
+ end
49
+
50
+ def down
51
+ if branch? && children
52
+ new(
53
+ node: children.first,
54
+ lefts: [],
55
+ path: path ? [node] + path : [node],
56
+ parent: self,
57
+ rights: children.drop(1)
58
+ )
59
+ end
60
+ end
61
+
62
+ def up
63
+ if path
64
+ return parent unless changed?
65
+ parent_path = path.drop(1)
66
+ new(
67
+ node: make_node(node, lefts + [node] + rights),
68
+ lefts: parent.lefts,
69
+ path: parent_path.empty? ? nil : parent_path,
70
+ parent: parent.parent,
71
+ rights: parent.rights
72
+ )
73
+ end
74
+ end
75
+
76
+ def root
77
+ return node unless path
78
+ up.root
79
+ end
80
+
81
+ def right
82
+ if path && rights && !rights.empty?
83
+ new(
84
+ node: rights.first,
85
+ lefts: lefts + [node],
86
+ rights: rights.drop(1)
87
+ )
88
+ end
89
+ end
90
+
91
+ #def rightmost
92
+
93
+ def left
94
+ if path && lefts && !lefts.empty?
95
+ new(
96
+ node: lefts.last,
97
+ lefts: lefts[0...-1],
98
+ rights: [node] + rights
99
+ )
100
+ end
101
+ end
102
+
103
+ # def leftmost
104
+
105
+ def insert_left(item)
106
+ raise "insert at top" unless path
107
+ new(
108
+ lefts: lefts + [item],
109
+ changed: true
110
+ )
111
+ end
112
+
113
+ def insert_right(item)
114
+ raise "insert at top" unless path
115
+ new(
116
+ rights: [item] + rights,
117
+ changed: true
118
+ )
119
+ end
120
+
121
+ def replace(item)
122
+ new(
123
+ node: item,
124
+ changed: true
125
+ )
126
+ end
127
+
128
+ def edit
129
+ replace(yield node)
130
+ end
131
+
132
+ def insert_child(item)
133
+ replace(make_node(node, [item] + children))
134
+ end
135
+
136
+ def append_child(item)
137
+ replace(make_node(node, children + [item]))
138
+ end
139
+
140
+ def next
141
+ backtrack = ->(node) {
142
+ if node.up
143
+ node.up.right || backtrack.(node.up)
144
+ else
145
+ node.new(end: true)
146
+ end
147
+ }
148
+
149
+ (self if end?) ||
150
+ (branch? && down) ||
151
+ right ||
152
+ backtrack.(self)
153
+ end
154
+
155
+ # def prev
156
+
157
+ # def remove
158
+
159
+ def each
160
+ return to_enum unless block_given?
161
+ loc = self
162
+ until (loc = loc.next).end?
163
+ yield loc
164
+ end
165
+ end
166
+ end
167
+ end
data/lib/fzip.rb ADDED
@@ -0,0 +1,9 @@
1
+
2
+ module Fzip
3
+ def self.array(node)
4
+ Zipper.new(ArrayAdapter.new, node)
5
+ end
6
+ end
7
+
8
+ require_relative 'fzip/array_adapter'
9
+ require_relative 'fzip/zipper'
data/spec/fzip_spec.rb ADDED
@@ -0,0 +1,34 @@
1
+ $LOAD_PATH.unshift(Pathname(__FILE__).dirname.parent.join('lib'))
2
+
3
+ require 'fzip'
4
+
5
+ describe Fzip, 'array' do
6
+ let(:tree) {
7
+ [
8
+ ['x', '+', 'y'],
9
+ ['a', '*', 'b']
10
+ ]
11
+ }
12
+
13
+ let(:zipper) { Fzip.array(tree) }
14
+
15
+ it 'should return a zipper suitable for arrays' do
16
+ expect(zipper.adapter).to be_an_instance_of(Fzip::ArrayAdapter)
17
+ end
18
+
19
+ it 'should allow navigating' do
20
+ expect(zipper.down.down.right.right.left.node).to eq('+')
21
+ end
22
+
23
+ it 'should allow inserting nodes' do
24
+ expect(zipper.down.down.right.insert_left(:foo).insert_right(:bar).root).to eq [["x", :foo, "+", :bar, "y"], ["a", "*", "b"]]
25
+ end
26
+
27
+ it 'should allow replacing nodes' do
28
+ expect(zipper.down.right.down.right.replace(:foo).root).to eq [["x", "+", "y"], ["a", :foo, "b"]]
29
+ end
30
+
31
+ it 'should allow editing with a block' do
32
+ expect(zipper.down.right.down.right.edit{:foo}.root).to eq [["x", "+", "y"], ["a", :foo, "b"]]
33
+ end
34
+ end
metadata ADDED
@@ -0,0 +1,85 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fzip
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Arne Brasseur
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-01-31 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ prerelease: false
15
+ name: rake
16
+ version_requirements: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ~>
19
+ - !ruby/object:Gem::Version
20
+ version: '10.1'
21
+ requirement: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ~>
24
+ - !ruby/object:Gem::Version
25
+ version: '10.1'
26
+ type: :development
27
+ - !ruby/object:Gem::Dependency
28
+ prerelease: false
29
+ name: rspec
30
+ version_requirements: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ~>
33
+ - !ruby/object:Gem::Version
34
+ version: '2.14'
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ~>
38
+ - !ruby/object:Gem::Version
39
+ version: '2.14'
40
+ type: :development
41
+ description: Functional zipper class
42
+ email:
43
+ - arne@arnebrasseur.net
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files:
47
+ - README.md
48
+ files:
49
+ - .gitignore
50
+ - LICENSE
51
+ - README.md
52
+ - Rakefile
53
+ - fzip.gemspec
54
+ - lib/fzip.rb
55
+ - lib/fzip/adapter.rb
56
+ - lib/fzip/array_adapter.rb
57
+ - lib/fzip/version.rb
58
+ - lib/fzip/zipper.rb
59
+ - spec/fzip_spec.rb
60
+ homepage: https://github.com/plexus/fzip
61
+ licenses:
62
+ - MIT
63
+ metadata: {}
64
+ post_install_message:
65
+ rdoc_options: []
66
+ require_paths:
67
+ - lib
68
+ required_ruby_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ! '>='
71
+ - !ruby/object:Gem::Version
72
+ version: '0'
73
+ required_rubygems_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ requirements: []
79
+ rubyforge_project:
80
+ rubygems_version: 2.2.1
81
+ signing_key:
82
+ specification_version: 4
83
+ summary: Functional zipper class
84
+ test_files:
85
+ - spec/fzip_spec.rb