fzip 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/.gitignore +1 -0
- data/LICENSE +7 -0
- data/README.md +33 -0
- data/Rakefile +12 -0
- data/fzip.gemspec +20 -0
- data/lib/fzip/adapter.rb +12 -0
- data/lib/fzip/array_adapter.rb +16 -0
- data/lib/fzip/version.rb +3 -0
- data/lib/fzip/zipper.rb +167 -0
- data/lib/fzip.rb +9 -0
- data/spec/fzip_spec.rb +34 -0
- metadata +85 -0
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
|
data/lib/fzip/adapter.rb
ADDED
data/lib/fzip/version.rb
ADDED
data/lib/fzip/zipper.rb
ADDED
@@ -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
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
|