basic-rope 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 6643ff0ca0cec28bee1440332fc9d71c3ce7f67f
4
+ data.tar.gz: c7563f1d28ee7587b850ed0cc7d4d454b2e29288
5
+ SHA512:
6
+ metadata.gz: 27581ca183715f80cc38c1b358ff9356f478d589c40bbf0ff7e0fb4c91002058423e32b9089bd304d6df6aa215f52e22fa5d2b88992e7d496d56afac0ba23579
7
+ data.tar.gz: 6e7031ddabc83fc06940f147e155ab8a14dacac15f6f0b67d2b53e58bba206ef2f5aaf21b4d16f0e438ecf185d70b74eb0fab5acdfe7598a50a6c800ff6ebe7d
@@ -0,0 +1,5 @@
1
+ pkg/*
2
+ *.gem
3
+ .bundle
4
+ *.swp
5
+ Gemfile.lock
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in rope.gemspec
4
+ gemspec
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2010 Andy Lindeman
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
@@ -0,0 +1,69 @@
1
+ p{color:red}. *rope* is still under early development. Check it out if you wish to follow my progress or help, but do not use it in your applications (yet!).
2
+
3
+ *rope* is a pure Ruby implementation of the "Rope data structure":http://en.wikipedia.org/wiki/Rope_%28computer_science%29.
4
+
5
+ For many applications, the *Rope* class can be a drop-in replacement for *String* that is optimized for certain use cases.
6
+
7
+ Using a *Rope* instance over a *String* may be desirable in applications that manipulate large amounts of text.
8
+
9
+ *rope* currently offers:
10
+ * Fast string concatenation and substring operations involving large strings
11
+ * Immutability, which is desirable for functional programming techniques or multithreaded applications
12
+
13
+ Planned features for *rope*:
14
+ * Ability to view a function producing characters as a Rope (including I/O operations). For instance, a piece of a Rope may be a 100MB file, but is only read when that section of the string is examined. Concatenating to the end of that Rope does not involve reading the entire file.
15
+ * Implement a *Rope* counterpart to every immutable method available on the *String* class.
16
+
17
+ Disadvantages of *rope*:
18
+ * Single character replacements are expensive
19
+ * Iterating character-by-character is slightly more expensive than in a String (TODO: how much? .. haven't implemented iterators yet)
20
+
21
+ h1. Installation
22
+
23
+ *rope* is hosted on "rubygems":http://www.rubygems.org/
24
+
25
+ <pre>
26
+ gem install rope
27
+ </pre>
28
+
29
+ ... or in your Gemfile
30
+
31
+ <pre>
32
+ gem 'rope'
33
+ </pre>
34
+
35
+ *rope* is tested against MRI 1.8.7 and 1.9.2.
36
+
37
+ h1. Usage
38
+
39
+ h2. Creating a Rope
40
+
41
+ <pre>
42
+ rope = "123456789".to_rope # Rope::Rope.new("123456789") also works
43
+
44
+ puts rope # "123456789"
45
+ </pre>
46
+
47
+ h2. Concatenation
48
+
49
+ A *Rope* instance can be concatenated with another *Rope* or *String* instance.
50
+
51
+ <pre>
52
+ rope = "12345"
53
+ string = "6789"
54
+
55
+ rope += string
56
+ puts rope # "123456789"
57
+ </pre>
58
+
59
+ h2. Slices/Substrings
60
+
61
+ A *Rope* instance offers efficient substring operations. The *slice* and *[]* methods are synonymous with their "String counterparts (Ruby API documentation)":http://ruby-doc.org/core-1.9/classes/String.html#M000293.
62
+
63
+ <pre>
64
+ rope = "123456789".to_rope
65
+
66
+ puts rope.slice(3, 4) # 4567
67
+ puts rope.slice(-6, 4) # 4567
68
+ # TODO: More examples when they are implemented
69
+ </pre>
@@ -0,0 +1,2 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
@@ -0,0 +1,22 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "rope/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "basic-rope"
7
+ s.version = Rope::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Andy Lindeman", "Logan Bowers"]
10
+ s.email = ["alindeman@gmail.com", "logan@datacurrent.com"]
11
+ s.homepage = "http://rubygems.org/gems/basic-rope"
12
+ s.summary = %q{Pure Ruby implementation of a Rope data structure}
13
+ s.description = %q{A Rope is a convenient data structure for manipulating large amounts of text. This implementation is inspired by http://www.rubyquiz.com/quiz137.html and https://rubygems.org/gems/cord. Basic-rope is a fork of Andy Lindeman's original rope gem to generalize the rope data structure to work with any data type that has a length and supports slice(). }
14
+
15
+ s.files = `git ls-files`.split("\n")
16
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
17
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
18
+ s.require_paths = ["lib"]
19
+
20
+ s.add_development_dependency "rspec"
21
+ s.add_development_dependency "rdoc"
22
+ end
@@ -0,0 +1,53 @@
1
+ # Benchmarking code from
2
+ # http://www.rubyquiz.com/quiz137.html
3
+
4
+ # Compare:
5
+ # ruby benchmark.rb
6
+ # ruby -r lib/rope.rb -I lib benchmark.rb Rope::Rope
7
+
8
+ require 'benchmark'
9
+
10
+ #This code make a String/Rope of CHUNCKS chunks of text
11
+ #each chunck is SIZE bytes long. Each chunck starts with
12
+ #an 8 byte number. Initially the chuncks are shuffled the
13
+ #qsort method sorts them into ascending order.
14
+
15
+ puts 'preparing data...'
16
+ TextClass = eval(ARGV.shift || "String")
17
+
18
+ def qsort(text)
19
+ return TextClass.new if text.length == 0
20
+ pivot = text.slice(0,8).to_s.to_i
21
+ less = TextClass.new
22
+ more = TextClass.new
23
+ offset = 8+SIZE
24
+ while (offset < text.length)
25
+ i = text.slice(offset,8).to_s.to_i
26
+ (i < pivot ? less : more) << text.slice(offset,8+SIZE)
27
+ offset = offset + 8+SIZE
28
+ end
29
+ print "*"
30
+ return qsort(less) << text.slice(0,8+SIZE) << qsort(more)
31
+ end
32
+
33
+ SIZE = 512 * 1024
34
+ CHUNCKS = 128
35
+ CHARS = %w[R O P E]
36
+ data = TextClass.new
37
+ bulk_string =
38
+ TextClass.new(Array.new(SIZE) { CHARS[rand(4)] }.join)
39
+ puts 'Building Text...'
40
+ build = Benchmark.measure do
41
+ (0..CHUNCKS).sort_by { rand }.each do |n|
42
+ data<< sprintf("%08i",n) << bulk_string
43
+ end
44
+ data.normalize if data.respond_to? :normalize
45
+ end
46
+ GC.start
47
+ sort = Benchmark.measure do
48
+ puts "Sorting Text..."
49
+ qsort(data)
50
+ puts"\nEND"
51
+ end
52
+
53
+ puts "Build: #{build}Sort: #{sort}"
@@ -0,0 +1,16 @@
1
+ require 'forwardable'
2
+
3
+ require 'rope/basic_rope'
4
+
5
+ require 'rope/string_methods'
6
+
7
+ module Rope
8
+ Rope = BasicRope.rope_for_type(String) do
9
+ #
10
+ # Special case for string Ropes since to_s is universal
11
+ #
12
+ def to_s
13
+ to_primitive
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,36 @@
1
+ module Rope
2
+ class BasicNode
3
+ # Length of the underlying data in the tree and its descendants
4
+ attr_reader :length
5
+
6
+ # Depth of the tree
7
+ attr_reader :depth
8
+
9
+ # Concatenates this tree with another tree (non-destructive to either
10
+ # tree)
11
+ def +(other)
12
+ InteriorNode.new(self, other)
13
+ end
14
+
15
+ # Gets the string representation of the underlying data in the tree
16
+ def to_primitive
17
+ data
18
+ end
19
+
20
+ # Returns the Node that contains this index or nil if the index is out of bounds
21
+ def segment(index)
22
+ raise NotImplementedError
23
+ end
24
+
25
+ # Rebalances this tree
26
+ def rebalance!
27
+ raise NotImplementedError
28
+ end
29
+
30
+ #Swaps out the data in this node to be rhs. Must be same length
31
+ def replace!(index, length, rhs)
32
+ raise NotImplementedError
33
+ end
34
+
35
+ end
36
+ end
@@ -0,0 +1,109 @@
1
+ require 'rope/leaf_node'
2
+ require 'rope/interior_node'
3
+
4
+ module Rope
5
+ # BasicRope is a data-type-agnostic Rope data structure. The data type is
6
+ # typically a string but can be anything that implements the following
7
+ # methods:
8
+ #
9
+ # * length - integer length of a unit of the data type
10
+ # * slice - break a unit of the data type into two pieces on an index boundary
11
+ # * + - join two pieces of the data type together
12
+ # * [] - alias for slice
13
+ #
14
+ class BasicRope
15
+ extend Forwardable
16
+
17
+ def self.rope_for_type(type, &block)
18
+ Class.new(self) do
19
+ define_method :primitive_type do
20
+ type
21
+ end
22
+
23
+ class_eval &block if block_given?
24
+ end
25
+ end
26
+
27
+ # Initializes a new rope
28
+ def initialize(arg=nil)
29
+ case arg
30
+ when BasicNode
31
+ @root = arg
32
+ when NilClass
33
+ @root = LeafNode.new(primitive_type.new)
34
+ when primitive_type
35
+ @root = LeafNode.new(arg)
36
+ when self.class, InteriorNode
37
+ @root = LeafNode.new(arg.to_primitive)
38
+ else
39
+ raise ArgumentError, "#{arg} is not a #{primitive_type}"
40
+ end
41
+ end
42
+
43
+ def_delegators :@root, :to_primitive, :length, :rebalance!, :segment
44
+
45
+ # Concatenates this rope with another rope or string
46
+ def +(other)
47
+ self.class.new(concatenate(other))
48
+ end
49
+
50
+ # Tests whether this rope is equal to another rope
51
+ def ==(other)
52
+ to_primitive == (BasicRope === other ? other.to_primitive : other)
53
+ end
54
+
55
+ # Creates a copy of this rope
56
+ def dup
57
+ root.freeze #Prevents errors when
58
+ self.class.new(root)
59
+ end
60
+
61
+ # Gets a slice of this rope
62
+ def slice(*args)
63
+ slice = root.slice(*args)
64
+
65
+ case slice
66
+ when Fixnum # slice(Fixnum) returns a plain Fixnum
67
+ slice
68
+ when BasicNode, primitive_type # create a new Rope with the returned tree as the root
69
+ self.class.new(slice)
70
+ else
71
+ nil
72
+ end
73
+ end
74
+ alias :[] :slice
75
+
76
+ def []=(index, length=1, rhs)
77
+ @root = @root.replace!(index, length, rhs)
78
+ self
79
+ end
80
+
81
+
82
+ def <<(rhs)
83
+ self[length] = rhs
84
+ end
85
+ protected
86
+
87
+ # Root node (could either be a LeafNode or some child of LeafNode)
88
+ attr_reader :root
89
+
90
+ private
91
+
92
+ # Generate a concatenation node to combine this rope and another rope
93
+ # or string
94
+ def concatenate(other)
95
+ # TODO: Automatically balance the tree if needed
96
+ case other
97
+ when primitive_type
98
+ InteriorNode.new(root, LeafNode.new(other))
99
+ when BasicRope
100
+ InteriorNode.new(root, other.root)
101
+ end
102
+ end
103
+
104
+ def primitive_type
105
+ raise NotImplementedError, "This method must return the data type stored in the rope"
106
+ end
107
+
108
+ end
109
+ end
@@ -0,0 +1,170 @@
1
+ require 'rope/basic_node'
2
+
3
+ module Rope
4
+ # Specifies an interior node. Its underlying data is retrieved by combining
5
+ # its children recursively.
6
+ class InteriorNode < BasicNode
7
+ # Left and right nodes
8
+ attr_reader :left, :right
9
+
10
+ # Initializes a new concatenation node.
11
+ def initialize(left, right)
12
+ @left = left
13
+ @right = right
14
+
15
+ @length = left.length + right.length
16
+ @depth = [left.depth, right.depth].max + 1
17
+ end
18
+
19
+ # Gets the underlying data in the tree
20
+ def data
21
+ left.data + right.data
22
+ end
23
+
24
+ # Gets a slice of the underlying data in the tree
25
+ def slice(arg0, *args)
26
+ if args.length == 0
27
+ if arg0.is_a?(Fixnum)
28
+ slice(arg0, 1)
29
+ elsif arg0.is_a?(Range)
30
+ from, to = arg0.minmax
31
+
32
+ # Special case when the range doesn't actually describe a valid range
33
+ return nil if from.nil? || to.nil?
34
+
35
+ # Normalize so both from and to are positive indices
36
+ if from < 0
37
+ from += @length
38
+ end
39
+ if to < 0
40
+ to += @length
41
+ end
42
+
43
+ if from <= to
44
+ subtree(from, (to - from) + 1)
45
+ else
46
+ # Range first is greater than range last
47
+ # Return empty string to match what String does
48
+ raise "TODO"
49
+ end
50
+ end
51
+
52
+ # TODO: arg0.is_a?(Range)
53
+ # TODO: arg0.is_a?(Regexp)
54
+ # TODO: arg0.is_a?(String)
55
+ else
56
+ arg1 = args[0] # may be slightly confusing; refer to method definition
57
+ if arg0.is_a?(Fixnum) && arg1.is_a?(Fixnum) # Fixnum, Fixnum
58
+ if arg1 >= 0
59
+ subtree(arg0, arg1)
60
+ else
61
+ # Negative length, return nil to match what String does
62
+ nil
63
+ end
64
+ end
65
+
66
+ # TODO: arg0.is_a?(Regexp) && arg1.is_a?(Fixnum)
67
+ end
68
+ end
69
+
70
+ #
71
+ # Freeze is redefined to freeze the subtree; used when dup'ing a Rope to protect against aliasing
72
+ #
73
+ def freeze
74
+ unless frozen?
75
+ super
76
+ @left.freeze unless @left.frozen?
77
+ @right.freeze unless @right.frozen?
78
+ end
79
+ self
80
+ end
81
+
82
+ # Rebalances this tree
83
+ def rebalance!
84
+ # TODO
85
+ end
86
+
87
+ # Returns a node that represents a slice of this tree
88
+ def subtree(from, length)
89
+ # Translate to positive index if given a negative one
90
+ if from < 0
91
+ from += @length
92
+ end
93
+
94
+ # If more than @length characters are requested, truncate
95
+ length = [(@length - from), length].min
96
+
97
+ # Entire length requested
98
+ return self if length == @length
99
+
100
+ # Check if the requested subtree is entirely in the right subtree
101
+ rfrom = from - @left.length
102
+ return @right.subtree(rfrom, length) if rfrom >= 0
103
+
104
+ llen = @left.length - from
105
+ rlen = length - llen
106
+ if rlen > 0
107
+ # Requested subtree overlaps both the left and the right subtree
108
+ @left.subtree(from, llen) + @right.subtree(0, rlen)
109
+ else
110
+ # Requested subtree is entirely in the left subtree
111
+ @left.subtree(from, length)
112
+ end
113
+ end
114
+
115
+ #
116
+ # Overwrites data at index with substr
117
+ #
118
+ # Returns self, however leaf nodes may return a new interior node if the replace! causes a leaf to be split
119
+ #
120
+ def replace!(index, length, substr)
121
+ # We may be frozen, so this all gets reified into a new node or overwrites on ourselves
122
+ new_left = @left
123
+ new_right = @right
124
+
125
+ # Translate to positive index if given a negative one
126
+ if index < 0
127
+ index += @length
128
+ end
129
+
130
+ rindex = index - @left.length
131
+ if(index == 0 && length == @left.length)
132
+ #substr exactly replaces left sub-tree
133
+ new_left = LeafNode.new(substr)
134
+ elsif(index == @left.length && length == @right.length)
135
+ #substr exactly replaces right sub-tree
136
+ new_right = LeafNode.new(substr)
137
+ elsif rindex < 0
138
+ if(index + length <= @left.length)
139
+ #Replacement segment is a subsection of the left tree
140
+
141
+ #Requested index is in the left subtree, and a split may occur
142
+ new_left = @left.replace!(index, length, substr)
143
+ else
144
+ #Replacement segement is a subsection of left tree along with a subsection of the right tree
145
+ left_count = @left.length - index
146
+
147
+ new_left = InteriorNode.new(
148
+ @left.subtree(0, index),
149
+ LeafNode.new(substr)
150
+ )
151
+ new_right = @right.subtree(rindex + length, @right.length - (rindex + length))
152
+ end
153
+ else
154
+ # Requested index is in the right subtree, and a split may occur
155
+ new_right = @right.replace!(rindex, length, substr)
156
+ end
157
+
158
+ if(frozen?)
159
+ InteriorNode.new(new_left, new_right)
160
+ else
161
+ #Length could have changed if the substr replaced a section of a different size or there was an append
162
+ @left = new_left
163
+ @right = new_right
164
+ @length = @left.length + @right.length
165
+ @depth = [left.depth, right.depth].max + 1
166
+ self
167
+ end
168
+ end
169
+ end
170
+ end
@@ -0,0 +1,48 @@
1
+ require 'rope/basic_node'
2
+
3
+ module Rope
4
+ # Specifies a leaf node that contains a basic string
5
+ class LeafNode < BasicNode
6
+ extend Forwardable
7
+
8
+ # The underlying data in the tree
9
+ attr_reader :data
10
+
11
+ def_delegator :@data, :slice
12
+
13
+ # Initializes a node that contains a basic string
14
+ def initialize(data)
15
+ @data = data.freeze #Freezes the data to protect against aliasing errors
16
+ @length = data.length
17
+ @depth = 0
18
+ end
19
+
20
+ def subtree(from, length)
21
+ if length == @data.length
22
+ self
23
+ else
24
+ self.class.new(@data.slice(from, length))
25
+ end
26
+ end
27
+
28
+ def replace!(index, length, substr)
29
+ left = if(index == 0)
30
+ LeafNode.new(substr)
31
+ else
32
+ InteriorNode.new(
33
+ LeafNode.new(@data.slice(0,index)),
34
+ LeafNode.new(substr)
35
+ )
36
+ end
37
+
38
+ if((index + length) < @data.length)
39
+ InteriorNode.new(
40
+ left,
41
+ LeafNode.new(@data.slice(index + length, @data.length - (index + length)))
42
+ )
43
+ else
44
+ left
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,27 @@
1
+ module Rope
2
+ class Node
3
+ # Length of the underlying data in the tree and its descendants
4
+ attr_reader :length
5
+
6
+ # Depth of the tree
7
+ attr_reader :depth
8
+
9
+ # The underlying data in the tree
10
+ attr_reader :data
11
+
12
+ # Concatenates this tree with another tree (non-destructive to either
13
+ # tree)
14
+ def +(other)
15
+ ConcatenationNode.new(self, other)
16
+ end
17
+
18
+ # Gets the string representation of the underlying data in the tree
19
+ def to_s
20
+ data.to_s
21
+ end
22
+
23
+ # Rebalances this tree
24
+ def rebalance!
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,6 @@
1
+ class String
2
+ # Converts the String to a Rope
3
+ def to_rope
4
+ Rope::Rope.new(self)
5
+ end
6
+ end
@@ -0,0 +1,3 @@
1
+ module Rope
2
+ VERSION = "0.0.2"
3
+ end
@@ -0,0 +1,35 @@
1
+
2
+ require 'rope'
3
+
4
+ describe "rope" do
5
+ describe "#+" do
6
+ it "should allow concatenation of two Rope instances" do
7
+ rope1 = "123".to_rope
8
+ rope2 = "456".to_rope
9
+
10
+ rope3 = rope1 + rope2
11
+ rope3.to_s.should == "123456"
12
+
13
+ # rope1 and rope2 should not have been affected
14
+ rope1.to_s.should == "123"
15
+ rope2.to_s.should == "456"
16
+ end
17
+
18
+ it "should allow concatenation of a Rope and a String" do
19
+ rope = "123".to_rope
20
+ string = "456"
21
+
22
+ rope_concat = rope + string
23
+ rope_concat.to_s.should == "123456"
24
+
25
+ # rope and string should not have been affected
26
+ rope.to_s.should == "123"
27
+ string.should == "456"
28
+ end
29
+
30
+ it "should allow concatenation of many Rope instances" do
31
+ rope_concat = ["123", "456", "789", "012"].inject(Rope::Rope.new) { |combined, str| combined += str.to_rope }
32
+ rope_concat.to_s.should == "123456789012"
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,14 @@
1
+
2
+ require 'rope'
3
+
4
+ describe "rope" do
5
+ describe "#dup" do
6
+ it "should create a shallow copy" do
7
+ rope = "123".to_rope
8
+ rope_dupped = rope.dup
9
+
10
+ rope.should == rope_dupped
11
+ rope.should_not equal(rope_dupped)
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,15 @@
1
+ require 'rope'
2
+
3
+ describe "rope" do
4
+ describe "#initialize" do
5
+ it "can be constructed using the Rope constructor" do
6
+ rope = Rope::Rope.new("testing123")
7
+ rope.to_s.should == "testing123"
8
+ end
9
+
10
+ it "can be constructed using the to_rope method on a string" do
11
+ rope = "testing123".to_rope
12
+ rope.to_s.should == "testing123"
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,62 @@
1
+ require 'rope'
2
+
3
+ describe Rope::Rope do
4
+ describe "[<Fixnum>]=" do
5
+ (1..4).each do |segments|
6
+ rope_len = segments.times.collect { |i| i + 1 }.reduce(0, :+)
7
+
8
+ context "a #{segments} segment rope (length #{rope_len})" do
9
+ #Makes a rope of successive pieces, each one longer than the previous
10
+ subject {
11
+ segments.times.collect { |i| i + 1 }.reduce("".freeze.to_rope) do |rope, i|
12
+ rope += (rope.length..(rope.length + i)).collect { |x| (x.to_i % 10).to_s.freeze }.join
13
+ end
14
+ }
15
+
16
+ (0..2).each do |replacement_len|
17
+ context "a #{replacement_len} length replacement str" do
18
+ let(:replacement) { ('a'..'z').to_a[0,replacement_len].join }
19
+
20
+ (0..rope_len).each do |offset|
21
+ (0..2).each do |replace_len|
22
+ it "replaces a substring of length #{replace_len} at offset #{offset} with a string" do
23
+ as_string = subject.to_s.dup
24
+
25
+ as_string[offset, replace_len] = replacement
26
+ subject[offset, replace_len] = replacement
27
+
28
+ expect(subject.to_s).to eq(as_string)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+
37
+ it "doesn't alias" do
38
+ r1 = "foo".to_rope + "bar" + "baz"
39
+ r2 = r1.dup
40
+
41
+ r2[0,3] = "baz"
42
+
43
+ expect(r2).not_to eq(r1)
44
+ end
45
+ end
46
+
47
+ subject { "foo".to_rope }
48
+
49
+ describe "[self.length]=" do
50
+ it "appends to the end of the rope" do
51
+ subject[subject.length] = "bar"
52
+ should eq "foobar"
53
+ end
54
+ end
55
+
56
+ describe "<<" do
57
+ it "appends to the end of the rope" do
58
+ subject << "bar"
59
+ should eq "foobar"
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,83 @@
1
+
2
+ require 'rope'
3
+
4
+ describe Rope::Rope do
5
+ describe "#slice" do
6
+ context "Fixnum, Fixnum" do # slice(Fixnum, Fixnum)
7
+ it "should return a slice for a Rope instance created with a String" do
8
+ rope = "12345".to_rope
9
+ rope_slice = rope.slice(0, 2)
10
+
11
+ rope_slice.to_s.should == "12"
12
+ rope_slice.class.should == Rope::Rope
13
+ end
14
+
15
+ it "should return a slice for a Rope instance created by concatenating other Rope instances" do
16
+ rope = ["123", "456", "789", "012"].inject(Rope::Rope.new) { |combined, str| combined += str.to_rope }
17
+ rope_slice = rope.slice(2, 6)
18
+
19
+ rope_slice.to_s.should == "345678"
20
+ rope_slice.class.should == Rope::Rope
21
+ end
22
+
23
+ it "should return a slice when given a negative index" do
24
+ rope = ["123", "456", "789", "012"].inject(Rope::Rope.new) { |combined, str| combined += str.to_rope }
25
+ rope_slice = rope.slice(-8, 6)
26
+
27
+ rope_slice.to_s.should == "567890"
28
+ rope_slice.class.should == Rope::Rope
29
+ end
30
+
31
+ it "should return nil when given a negative length" do
32
+ "1234567".to_rope.slice(2, -1).should be_nil
33
+ end
34
+
35
+ it "should return an empty string when given a 0 length" do
36
+ "1234567".to_rope.slice(2, 0).to_s.should be_empty
37
+ end
38
+ end
39
+
40
+ context "Fixnum" do # slice(Fixnum)
41
+ it "should return the character value of the character at the given position" do
42
+ rope = ["123", "456", "789", "012"].inject(Rope::Rope.new) { |combined, str| combined += str.to_rope }
43
+
44
+ rope.slice(0).should == ?1
45
+ rope.slice(3).should == ?4
46
+ rope.slice(7).should == ?8
47
+ end
48
+
49
+ it "should return a single character string" do
50
+ "12345".slice(0).should == "1"
51
+ end
52
+ end
53
+
54
+ context "Range" do # slice(Range)
55
+ it "should return a slice for a Rope when given a Range" do
56
+ rope = ["123", "456", "789", "012"].inject(Rope::Rope.new) { |combined, str| combined += str.to_rope }
57
+
58
+ rope.slice(0..2).to_s.should == "123"
59
+ rope.slice(0...2).to_s.should == "12"
60
+ end
61
+
62
+ it "should return a slice for a Rope when given a Range that exercises edge cases" do
63
+ rope = ["123", "456", "789", "012"].inject(Rope::Rope.new) { |combined, str| combined += str.to_rope }
64
+
65
+ rope.slice(5..5).to_s.should == "6"
66
+ rope.slice(5...5).to_s.should == ""
67
+ rope.slice(0...0).to_s.should == ""
68
+ end
69
+
70
+ it "should return a slice for a Rope when given a Range that contains negative indices" do
71
+ rope = ["123", "456", "789", "012"].inject(Rope::Rope.new) { |combined, str| combined += str.to_rope }
72
+
73
+ rope.slice(-4..-2).to_s.should == "901"
74
+ rope.slice(-4...-2).to_s.should == "90"
75
+ end
76
+
77
+ it "should return an empty Rope when given a Range where the first index is greater than the last index" do
78
+ "1234567".slice(4..2).to_s.should be_empty
79
+ "1234567".slice(-2..-4).to_s.should be_empty
80
+ end
81
+ end
82
+ end
83
+ end
metadata ADDED
@@ -0,0 +1,102 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: basic-rope
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Andy Lindeman
8
+ - Logan Bowers
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2014-11-14 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ">="
19
+ - !ruby/object:Gem::Version
20
+ version: '0'
21
+ type: :development
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ version: '0'
28
+ - !ruby/object:Gem::Dependency
29
+ name: rdoc
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: '0'
35
+ type: :development
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: '0'
42
+ description: 'A Rope is a convenient data structure for manipulating large amounts
43
+ of text. This implementation is inspired by http://www.rubyquiz.com/quiz137.html
44
+ and https://rubygems.org/gems/cord. Basic-rope is a fork of Andy Lindeman''s original
45
+ rope gem to generalize the rope data structure to work with any data type that has
46
+ a length and supports slice(). '
47
+ email:
48
+ - alindeman@gmail.com
49
+ - logan@datacurrent.com
50
+ executables: []
51
+ extensions: []
52
+ extra_rdoc_files: []
53
+ files:
54
+ - ".gitignore"
55
+ - Gemfile
56
+ - LICENSE.txt
57
+ - README.textile
58
+ - Rakefile
59
+ - basic-rope.gemspec
60
+ - benchmark.rb
61
+ - lib/rope.rb
62
+ - lib/rope/basic_node.rb
63
+ - lib/rope/basic_rope.rb
64
+ - lib/rope/interior_node.rb
65
+ - lib/rope/leaf_node.rb
66
+ - lib/rope/node.rb
67
+ - lib/rope/string_methods.rb
68
+ - lib/rope/version.rb
69
+ - spec/concatenation_spec.rb
70
+ - spec/dup_spec.rb
71
+ - spec/initialization_spec.rb
72
+ - spec/replace_spec.rb
73
+ - spec/slice_spec.rb
74
+ homepage: http://rubygems.org/gems/basic-rope
75
+ licenses: []
76
+ metadata: {}
77
+ post_install_message:
78
+ rdoc_options: []
79
+ require_paths:
80
+ - lib
81
+ required_ruby_version: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ required_rubygems_version: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ requirements: []
92
+ rubyforge_project:
93
+ rubygems_version: 2.4.2
94
+ signing_key:
95
+ specification_version: 4
96
+ summary: Pure Ruby implementation of a Rope data structure
97
+ test_files:
98
+ - spec/concatenation_spec.rb
99
+ - spec/dup_spec.rb
100
+ - spec/initialization_spec.rb
101
+ - spec/replace_spec.rb
102
+ - spec/slice_spec.rb