basic-rope 0.0.2

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.
@@ -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