richtext 0.2.4 → 0.3.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 +4 -4
- data/README.md +7 -2
- data/lib/richtext.rb +4 -3
- data/lib/richtext/document.rb +52 -49
- data/lib/richtext/document/entry.rb +126 -86
- data/lib/richtext/styleable.rb +68 -0
- data/lib/richtext/version.rb +1 -1
- data/richtext.gemspec +3 -0
- metadata +31 -3
- data/lib/richtext/node.rb +0 -236
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: af2b68e549093d993d27e2a6d32f7f1f5388636e
|
4
|
+
data.tar.gz: 6c92d9931c6252d9e56bf7594f09e969e903fde2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9181421572a02691f179593a544414df3e25b6aae59c134915962e83afe9aa006ffa67dc05925650938101e8c7825feb4cf57fbc443096cdc5eab295af9d1dd3
|
7
|
+
data.tar.gz: 513bc38940ae2c975aa6656e6c0ae9edbd9a92dcfb58efee10383e5288d01fdacc2ecd4d41a92491a745e49d0535f281a5cdea6db53c77e268bffb512202a892
|
data/README.md
CHANGED
@@ -1,4 +1,9 @@
|
|
1
|
-
#
|
1
|
+
# RichText
|
2
|
+
|
3
|
+
[](http://badge.fury.io/rb/richtext)
|
4
|
+
[](https://travis-ci.org/seblindberg/ruby-richtext)
|
5
|
+
[](https://coveralls.io/github/seblindberg/ruby-richtext?branch=master)
|
6
|
+
[](http://inch-ci.org/github/seblindberg/ruby-richtext)
|
2
7
|
|
3
8
|
This gem is intended to simplify the handling of formatted text. Out of the box there is no support for any actual format, but that is intentional. The RichText::Document class is primarily ment to be subclassed and extended, and only includes functionality that is (potentially) useful to any format.
|
4
9
|
|
@@ -69,7 +74,7 @@ class MyFormat < RichText::Document
|
|
69
74
|
# each word is represented by its own entry. Entries are
|
70
75
|
# given a random visibility attribute.
|
71
76
|
string.split(' ').each do |word|
|
72
|
-
base.
|
77
|
+
base.append_child word, visible: (word.length > 6)
|
73
78
|
end
|
74
79
|
end
|
75
80
|
|
data/lib/richtext.rb
CHANGED
@@ -1,15 +1,16 @@
|
|
1
|
+
require 'rooted_tree'
|
2
|
+
|
1
3
|
require 'richtext/version'
|
2
|
-
require 'richtext/
|
4
|
+
require 'richtext/styleable'
|
3
5
|
require 'richtext/document/entry'
|
4
6
|
require 'richtext/document'
|
5
7
|
|
6
8
|
module RichText
|
7
9
|
end
|
8
10
|
|
9
|
-
# RichText
|
10
|
-
#
|
11
11
|
# Convenience method for creating RichText objects. Calling RichText(obj) is
|
12
12
|
# equivalent to RichText::Document.new(obj).
|
13
|
+
|
13
14
|
def RichText(string)
|
14
15
|
RichText::Document.new string
|
15
16
|
end
|
data/lib/richtext/document.rb
CHANGED
@@ -7,68 +7,69 @@ module RichText
|
|
7
7
|
attr_reader :raw
|
8
8
|
protected :raw
|
9
9
|
|
10
|
-
# Initialize
|
11
|
-
#
|
12
10
|
# Create a new RichText Document, either from a string or from an existing
|
13
|
-
#
|
11
|
+
# document. That feature is particularly useful when converting between
|
14
12
|
# formats.
|
15
13
|
#
|
16
14
|
# When given a string or a RichText Document of the same class no parsing is
|
17
15
|
# performed. Only when given a document of a different subclass will the
|
18
16
|
# parser need to be run parsed. Note that the document(s) may already be in
|
19
|
-
# parsed form, in which case no further parsing is performed. See #
|
17
|
+
# parsed form, in which case no further parsing is performed. See #root for
|
20
18
|
# more details.
|
19
|
+
|
21
20
|
def initialize(arg = '')
|
22
|
-
@
|
23
|
-
if arg.
|
24
|
-
arg.parsed? ? [arg.
|
21
|
+
@root, @raw =
|
22
|
+
if arg.instance_of? self.class
|
23
|
+
arg.parsed? ? [arg.root, nil] : [nil, arg.raw]
|
25
24
|
elsif arg.is_a? Document
|
26
|
-
# For any other RichText object we take the
|
27
|
-
[arg.
|
25
|
+
# For any other RichText object we take the root node
|
26
|
+
[arg.root, nil]
|
28
27
|
elsif arg.is_a? Entry
|
29
28
|
# Also accept an Entry which will be used as the
|
30
|
-
# document
|
31
|
-
[arg, nil]
|
29
|
+
# document root
|
30
|
+
[arg.root, nil]
|
32
31
|
else
|
33
32
|
[nil, arg.to_s]
|
34
33
|
end
|
35
34
|
end
|
36
35
|
|
37
|
-
#
|
38
|
-
#
|
39
|
-
# Use the static implementation of .render to convert the document back into
|
40
|
-
# a string. If the document was never parsed (and is unchanged) the
|
36
|
+
# Uses the static implementation of .render to convert the document back
|
37
|
+
# into a string. If the document was never parsed (and is unchanged) the
|
41
38
|
# origninal string is just returned.
|
42
39
|
#
|
43
40
|
# If a block is given it will be used in place of .render to format the node
|
44
41
|
# tree.
|
42
|
+
#
|
43
|
+
# Returns a string formatted according to the rules outlined by the Document
|
44
|
+
# format.
|
45
|
+
|
45
46
|
def to_s(&block)
|
46
47
|
if block_given?
|
47
|
-
|
48
|
+
root.to_s(&block)
|
48
49
|
elsif parsed? || should_parse?
|
49
|
-
self.class.render
|
50
|
+
self.class.render root
|
50
51
|
else
|
51
52
|
@raw
|
52
53
|
end
|
53
54
|
end
|
54
55
|
|
55
|
-
#
|
56
|
+
# Uses Entry#to_s to reduce the node structure down to a string.
|
56
57
|
#
|
57
58
|
# Returns the strings from all of the leaf nodes without any formatting
|
58
59
|
# applied.
|
60
|
+
|
59
61
|
def to_plain
|
60
|
-
|
62
|
+
root.to_s
|
61
63
|
end
|
62
64
|
|
63
|
-
# Add (+)
|
64
|
-
#
|
65
65
|
# Add another Document to this one. If the two are of (exactly) the same
|
66
66
|
# class and neither one has been parsed, the two raw strings will be
|
67
|
-
# concatenated. If the other is a Document the two
|
67
|
+
# concatenated. If the other is a Document the two root nodes will be merged
|
68
68
|
# and the new root added to a new Document.
|
69
69
|
#
|
70
70
|
# Lastly, if other is a string it will first be wraped in a new Document and
|
71
71
|
# then added to this one.
|
72
|
+
|
72
73
|
def +(other)
|
73
74
|
# If the other object is of the same class, and neither
|
74
75
|
# one of the texts have been parsed, we can concatenate
|
@@ -78,7 +79,7 @@ module RichText
|
|
78
79
|
end
|
79
80
|
|
80
81
|
# Same root class
|
81
|
-
return self.class.new(
|
82
|
+
return self.class.new(root + other.root) if other.is_a? Document
|
82
83
|
|
83
84
|
unless other.respond_to? :to_s
|
84
85
|
raise TypeError,
|
@@ -91,33 +92,39 @@ module RichText
|
|
91
92
|
self + self.class.new(other)
|
92
93
|
end
|
93
94
|
|
94
|
-
# Append
|
95
|
+
# Append a string to the document. The string will not be parsed but
|
96
|
+
# inserted into a new entry, directly under the document root.
|
95
97
|
#
|
98
|
+
# string - the string that will be wrapped in an Entry object.
|
99
|
+
# attributes - a hash of attributes that will be applied to the Entry.
|
96
100
|
#
|
101
|
+
# Returns the newly created child.
|
102
|
+
|
97
103
|
def append(string, **attributes)
|
98
|
-
|
104
|
+
root.append_child string, **attributes
|
105
|
+
root.child(-1)
|
99
106
|
end
|
100
107
|
|
101
|
-
#
|
108
|
+
# Getter for the root node. If the raw input has not yet been
|
109
|
+
# parsed that will happen first, before the root node is returned.
|
102
110
|
#
|
103
|
-
#
|
104
|
-
|
105
|
-
def
|
106
|
-
unless @
|
107
|
-
@
|
108
|
-
self.class.parse @
|
111
|
+
# Returns the root Entry.
|
112
|
+
|
113
|
+
def root
|
114
|
+
unless @root
|
115
|
+
@root = Entry.new
|
116
|
+
self.class.parse @root, @raw
|
109
117
|
@raw = nil
|
110
118
|
end
|
111
119
|
|
112
|
-
@
|
120
|
+
@root
|
113
121
|
end
|
114
122
|
|
115
|
-
alias root
|
123
|
+
alias base root
|
116
124
|
|
117
|
-
# Parsed?
|
118
|
-
#
|
119
125
|
# Returns true if the raw input has been parsed and the internal
|
120
126
|
# representation is now a tree of nodes.
|
127
|
+
|
121
128
|
def parsed?
|
122
129
|
@raw.nil?
|
123
130
|
end
|
@@ -126,39 +133,35 @@ module RichText
|
|
126
133
|
false
|
127
134
|
end
|
128
135
|
|
129
|
-
# Each Node
|
130
|
-
#
|
131
136
|
# Iterate over all Entry nodes in the document tree.
|
137
|
+
|
132
138
|
def each_node(&block)
|
133
|
-
|
139
|
+
root.each(&block)
|
134
140
|
end
|
135
141
|
|
136
142
|
alias each_entry each_node
|
137
143
|
|
138
|
-
# Parse
|
139
|
-
#
|
140
144
|
# Document type specific method for parsing a string and turning it into a
|
141
145
|
# tree of entry nodes. This method is intended to be overridden when the
|
142
146
|
# Document is subclassed. The default implementation just creates a top
|
143
147
|
# level Entry containing the given string.
|
144
|
-
|
145
|
-
|
148
|
+
|
149
|
+
def self.parse(root, string)
|
150
|
+
root.text = string
|
146
151
|
end
|
147
152
|
|
148
|
-
# Render
|
149
|
-
#
|
150
153
|
# Document type specific method for rendering a tree of entry nodes. This
|
151
154
|
# method is intended to be overridden when the Document is subclassed. The
|
152
155
|
# default implementation just concatenates the text entries into.
|
153
|
-
|
154
|
-
|
156
|
+
|
157
|
+
def self.render(root)
|
158
|
+
root.to_s
|
155
159
|
end
|
156
160
|
|
157
|
-
# From
|
158
|
-
#
|
159
161
|
# Convenience method for instansiating one RichText object from another. The
|
160
162
|
# methods only purpose is to make that intent more clear, and to make the
|
161
163
|
# creation from another RichText object explicit.
|
164
|
+
|
162
165
|
def self.from(doc)
|
163
166
|
unless doc.is_a? Document
|
164
167
|
raise TypeError,
|
@@ -2,8 +2,7 @@
|
|
2
2
|
|
3
3
|
module RichText
|
4
4
|
class Document
|
5
|
-
|
6
|
-
#
|
5
|
+
|
7
6
|
# The Entry class extends the basic Node class and adds methods that make
|
8
7
|
# handling text a little nicer. Essentially the :text attribute is given
|
9
8
|
# special status by allowing it to a) be set during initialization, b) only
|
@@ -13,129 +12,170 @@ module RichText
|
|
13
12
|
# Some attributes are also supported explicitly by the inclusion of special
|
14
13
|
# accesser methods. The attributes are are bold, italic, underline, color
|
15
14
|
# and font.
|
16
|
-
|
17
|
-
class Entry < Node
|
18
|
-
|
19
|
-
|
15
|
+
|
16
|
+
class Entry < RootedTree::Node
|
17
|
+
include Styleable
|
18
|
+
|
19
|
+
attr_reader :attributes
|
20
|
+
protected :prepend_child, :prepend_sibling, :value, :value=
|
21
|
+
|
20
22
|
# Extend the default Node initializer by also accepting a string. It will,
|
21
23
|
# if given, be stored as a text attribute.
|
22
|
-
def initialize(text = nil, **attributes)
|
23
|
-
attributes[:text] = text if text
|
24
|
-
super attributes
|
25
|
-
end
|
26
24
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
# leaf node. Note that nodes that are not leafs can have the text entry,
|
31
|
-
# but it is discouraged by dissalowing access using this method.
|
32
|
-
def text
|
33
|
-
self[:text] || '' if leaf?
|
25
|
+
def initialize(text = nil, **attributes)
|
26
|
+
@attributes = attributes
|
27
|
+
super text
|
34
28
|
end
|
35
29
|
|
36
|
-
#
|
30
|
+
# Freeze the attributes hash, as well as the node structure.
|
37
31
|
#
|
38
|
-
#
|
39
|
-
# expose it, it must be pushed to a new child if a) this node was a leaf
|
40
|
-
# prior to this method call and b) its text attribute is not empty.
|
41
|
-
def <<(child)
|
42
|
-
if leaf?
|
43
|
-
# Remove the text entry from the node and put it in a new leaf node
|
44
|
-
# among the children, unless it is empty
|
45
|
-
if (t = @attributes.delete :text)
|
46
|
-
create_child(t) unless t.empty?
|
47
|
-
end
|
48
|
-
end
|
32
|
+
# Returns self.
|
49
33
|
|
34
|
+
def freeze
|
35
|
+
@attributes.freeze
|
50
36
|
super
|
51
37
|
end
|
52
38
|
|
53
|
-
#
|
39
|
+
# Accessor for single attributes.
|
54
40
|
#
|
55
|
-
#
|
56
|
-
#
|
57
|
-
|
58
|
-
|
59
|
-
|
41
|
+
# key - the attribute key
|
42
|
+
#
|
43
|
+
# Returns the attribute value if it is set and nil otherwise.
|
44
|
+
|
45
|
+
def [](key)
|
46
|
+
attributes[key]
|
60
47
|
end
|
61
48
|
|
62
|
-
#
|
49
|
+
# Write a single attribute.
|
63
50
|
#
|
64
|
-
#
|
65
|
-
#
|
66
|
-
|
67
|
-
def
|
68
|
-
|
51
|
+
# key - the attribute key
|
52
|
+
# v - the new value
|
53
|
+
|
54
|
+
def []=(key, v)
|
55
|
+
attributes[key] = v
|
69
56
|
end
|
70
57
|
|
71
|
-
#
|
58
|
+
# Read the text of the node.
|
72
59
|
#
|
73
|
-
#
|
74
|
-
# right. If a block is given the node, along with its text will be passed
|
75
|
-
# as arguments. The block will be called recursivly, starting at the leaf
|
76
|
-
# nodes and propagating up until the entire tree has been "rendered" in
|
77
|
-
# this way.
|
78
|
-
def to_s(&block)
|
79
|
-
string =
|
80
|
-
if leaf?
|
81
|
-
text
|
82
|
-
else
|
83
|
-
@children.reduce('') { |a, e| a + e.to_s(&block) }
|
84
|
-
end
|
60
|
+
# Returns the string stored in the node, if it is a leaf. Otherwise nil.
|
85
61
|
|
86
|
-
|
62
|
+
def text
|
63
|
+
value || '' if leaf?
|
87
64
|
end
|
88
65
|
|
89
|
-
#
|
66
|
+
# Write the text of the node. The method will raise a RuntimeException if
|
67
|
+
# the node is not a leaf.
|
90
68
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
self[:bold]
|
69
|
+
def text=(new_text)
|
70
|
+
raise 'Only leafs can have a text entry' unless leaf?
|
71
|
+
self.value = new_text
|
95
72
|
end
|
96
73
|
|
97
|
-
|
98
|
-
|
74
|
+
# Create and append a new child, initialized with the given text and
|
75
|
+
# attributes.
|
76
|
+
#
|
77
|
+
# child_text - the text of the child or an Entry object.
|
78
|
+
# attributes - a hash of attributes to apply to the child if child_text is
|
79
|
+
# not an Entry object.
|
80
|
+
#
|
81
|
+
# Returns self to allow chaining.
|
82
|
+
|
83
|
+
def append_child(child_text = nil, **attributes)
|
84
|
+
if leaf? && !text.empty?
|
85
|
+
super self.class.new(value)
|
86
|
+
end
|
87
|
+
|
88
|
+
if child_text.is_a? self.class
|
89
|
+
super child_text
|
90
|
+
else
|
91
|
+
super self.class.new(child_text, attributes)
|
92
|
+
end
|
99
93
|
end
|
100
94
|
|
101
|
-
|
95
|
+
alias << append_child
|
96
|
+
|
97
|
+
# Go through each child and merge any node that a) is not a lead node and
|
98
|
+
# b) only has one child, with its child. The attributes of the child will
|
99
|
+
# override those of the parent.
|
102
100
|
#
|
103
|
-
|
104
|
-
|
105
|
-
|
101
|
+
# Returns self.
|
102
|
+
|
103
|
+
def optimize!(&block)
|
104
|
+
# If the node is a leaf it cannot be optimized further
|
105
|
+
return self if leaf?
|
106
106
|
|
107
|
-
|
108
|
-
|
107
|
+
block = proc { |e| e.leaf? && e.text.empty? } unless block_given?
|
108
|
+
|
109
|
+
children.each do |child|
|
110
|
+
child.delete if block.call child.optimize!(&block)
|
111
|
+
end
|
112
|
+
|
113
|
+
# If we only have one child it is superfluous and
|
114
|
+
# should be merged. That means this node will inherrit
|
115
|
+
# the children of the single child as well as its
|
116
|
+
# attributes
|
117
|
+
if degree == 1
|
118
|
+
# Move the attributes over
|
119
|
+
attributes.merge! child.attributes
|
120
|
+
self.value = child.text
|
121
|
+
# Get the children of the child and add them to self
|
122
|
+
first_child.delete.each { |child| append_child child }
|
123
|
+
end
|
124
|
+
|
125
|
+
self
|
109
126
|
end
|
110
127
|
|
111
|
-
#
|
128
|
+
# Optimize a copy of the node tree based on the rules outlined for
|
129
|
+
# #optimize!.
|
112
130
|
#
|
113
|
-
|
114
|
-
self[:underline]
|
115
|
-
end
|
131
|
+
# Returns the root of the new optimized node structure.
|
116
132
|
|
117
|
-
def
|
118
|
-
|
133
|
+
def optimize(&block)
|
134
|
+
dup.optimize!(&block)
|
119
135
|
end
|
120
136
|
|
121
|
-
#
|
137
|
+
# Combine the text from all the leaf nodes in the tree, from left to
|
138
|
+
# right. If a block is given the node, along with its text will be passed
|
139
|
+
# as arguments. The block will be called recursivly, starting at the leaf
|
140
|
+
# nodes and propagating up until the entire tree has been "rendered" in
|
141
|
+
# this way.
|
122
142
|
#
|
123
|
-
|
124
|
-
|
125
|
-
|
143
|
+
# block - a block that will be used to generate strings for each node.
|
144
|
+
#
|
145
|
+
# Returns a string representation of the node structure.
|
126
146
|
|
127
|
-
def
|
128
|
-
|
147
|
+
def to_s(&block)
|
148
|
+
string =
|
149
|
+
if leaf?
|
150
|
+
text
|
151
|
+
else
|
152
|
+
children.reduce('') { |a, e| a + e.to_s(&block) }
|
153
|
+
end
|
154
|
+
|
155
|
+
block_given? ? yield(self, string) : string
|
129
156
|
end
|
130
157
|
|
131
|
-
#
|
158
|
+
# Represents the Entry structure as a hierarchy, showing the attributes of
|
159
|
+
# each node as well as the text entries in the leafs.
|
132
160
|
#
|
133
|
-
|
134
|
-
|
135
|
-
|
161
|
+
# If a block is given, it will be called once for each entry, and the
|
162
|
+
# returned string will be used to represent the object in the output
|
163
|
+
# graph.
|
164
|
+
#
|
165
|
+
# Returns a string. Note that it will contain newline characters if the
|
166
|
+
# node has children.
|
167
|
+
|
168
|
+
def inspect *args, &block
|
169
|
+
unless block_given?
|
170
|
+
block = proc do |entry|
|
171
|
+
base_name = entry.leaf? ? %Q{"#{entry.text}"} : '◯'
|
172
|
+
base_name + entry.attributes.reduce('') do |a, (k, v)|
|
173
|
+
a + " #{k}=#{v.inspect}"
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
136
177
|
|
137
|
-
|
138
|
-
self[:font] = f
|
178
|
+
super(*args, &block)
|
139
179
|
end
|
140
180
|
end
|
141
181
|
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module RichText
|
2
|
+
module Styleable
|
3
|
+
|
4
|
+
# Returns true if bold formatting is applied.
|
5
|
+
|
6
|
+
def bold?
|
7
|
+
self[:bold]
|
8
|
+
end
|
9
|
+
|
10
|
+
# Sets bold to either true or false, depending on the given argument.
|
11
|
+
|
12
|
+
def bold=(b)
|
13
|
+
self[:bold] = b ? true : false
|
14
|
+
end
|
15
|
+
|
16
|
+
# Returns true if italic formatting is applied.
|
17
|
+
|
18
|
+
def italic?
|
19
|
+
self[:italic]
|
20
|
+
end
|
21
|
+
|
22
|
+
# Sets italic to either true or false, depending on the given argument.
|
23
|
+
|
24
|
+
def italic=(i)
|
25
|
+
self[:italic] = i ? true : false
|
26
|
+
end
|
27
|
+
|
28
|
+
# Returns true if underlined formatting is applied.
|
29
|
+
|
30
|
+
def underlined?
|
31
|
+
self[:underlined]
|
32
|
+
end
|
33
|
+
|
34
|
+
alias underline? underlined?
|
35
|
+
|
36
|
+
# Sets underlined to either true or false, depending on the given argument.
|
37
|
+
|
38
|
+
def underlined=(u)
|
39
|
+
self[:underlined] = u ? true : false
|
40
|
+
end
|
41
|
+
|
42
|
+
alias underline= underlined=
|
43
|
+
|
44
|
+
# Returns the color value if it is set, otherwise nil.
|
45
|
+
|
46
|
+
def color
|
47
|
+
self[:color]
|
48
|
+
end
|
49
|
+
|
50
|
+
# Sets the color value.
|
51
|
+
|
52
|
+
def color=(c)
|
53
|
+
self[:color] = c
|
54
|
+
end
|
55
|
+
|
56
|
+
# Returns the font value if it is set, otherwise nil.
|
57
|
+
|
58
|
+
def font
|
59
|
+
self[:font]
|
60
|
+
end
|
61
|
+
|
62
|
+
# Sets the font value.
|
63
|
+
|
64
|
+
def font=(f)
|
65
|
+
self[:font] = f
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
data/lib/richtext/version.rb
CHANGED
data/richtext.gemspec
CHANGED
@@ -19,7 +19,10 @@ Gem::Specification.new do |spec|
|
|
19
19
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
20
20
|
spec.require_paths = ["lib"]
|
21
21
|
|
22
|
+
spec.add_dependency "rooted_tree", "~> 0.2.3"
|
23
|
+
|
22
24
|
spec.add_development_dependency "bundler", "~> 1.12"
|
23
25
|
spec.add_development_dependency "rake", "~> 10.0"
|
24
26
|
spec.add_development_dependency "minitest", "~> 5.0"
|
27
|
+
spec.add_development_dependency "coveralls", "~> 0.8"
|
25
28
|
end
|
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: richtext
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sebastian Lindberg
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-07-
|
11
|
+
date: 2016-07-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rooted_tree
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.2.3
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.2.3
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: bundler
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -52,6 +66,20 @@ dependencies:
|
|
52
66
|
- - "~>"
|
53
67
|
- !ruby/object:Gem::Version
|
54
68
|
version: '5.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: coveralls
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0.8'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0.8'
|
55
83
|
description:
|
56
84
|
email:
|
57
85
|
- seb.lindberg@gmail.com
|
@@ -70,7 +98,7 @@ files:
|
|
70
98
|
- lib/richtext.rb
|
71
99
|
- lib/richtext/document.rb
|
72
100
|
- lib/richtext/document/entry.rb
|
73
|
-
- lib/richtext/
|
101
|
+
- lib/richtext/styleable.rb
|
74
102
|
- lib/richtext/version.rb
|
75
103
|
- richtext.gemspec
|
76
104
|
homepage: https://github.com/seblindberg/ruby-richtext
|
data/lib/richtext/node.rb
DELETED
@@ -1,236 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module RichText
|
4
|
-
# Node
|
5
|
-
#
|
6
|
-
# A Node can have children, which themselvs can have children. A tree like
|
7
|
-
# structure can thus be formed by composing multiple Nodes. An example of such
|
8
|
-
# a tree structure can be seen below.
|
9
|
-
#
|
10
|
-
# The Node class implements some convenience methods for iterating, left to
|
11
|
-
# right, over either all
|
12
|
-
# - nodes in the tree
|
13
|
-
# - leafs in the tree
|
14
|
-
# - direct decendant of a node
|
15
|
-
#
|
16
|
-
# In addition to having children a Node can also have attributes, represented
|
17
|
-
# by simple key => value pairs.
|
18
|
-
#
|
19
|
-
# Example Tree
|
20
|
-
# +--------------------------+
|
21
|
-
# A <- Root Node | Left to right order: ABC |
|
22
|
-
# / \ +--------------------------+
|
23
|
-
# Leaf Node -> B C <- Child to A
|
24
|
-
# (no children) /|\
|
25
|
-
# ...
|
26
|
-
#
|
27
|
-
class Node
|
28
|
-
include Enumerable
|
29
|
-
|
30
|
-
attr_reader :attributes, :children
|
31
|
-
protected :children
|
32
|
-
|
33
|
-
def initialize(**attributes)
|
34
|
-
@children = []
|
35
|
-
@attributes = attributes
|
36
|
-
end
|
37
|
-
|
38
|
-
def initialize_copy(original)
|
39
|
-
@children = original.children.map(&:dup)
|
40
|
-
@attributes = original.attributes.dup
|
41
|
-
end
|
42
|
-
|
43
|
-
# Leaf?
|
44
|
-
#
|
45
|
-
# Returns true if this node a leaf (childless) node.
|
46
|
-
def leaf?
|
47
|
-
@children.empty?
|
48
|
-
end
|
49
|
-
|
50
|
-
# Child
|
51
|
-
#
|
52
|
-
# Access the individual children of the node. If the method is called
|
53
|
-
# without argument and the node has only one child it will be returned.
|
54
|
-
# Otherwise an exception will be raised.
|
55
|
-
def child n = nil
|
56
|
-
if n
|
57
|
-
@children[n]
|
58
|
-
else
|
59
|
-
raise 'Node does not have one child' unless count == 1
|
60
|
-
@children[0]
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
# Append
|
65
|
-
#
|
66
|
-
# Add a child to the end of the node child list. The child must be of this
|
67
|
-
# class to be accepted. Note that subclasses of Node will not accept regular
|
68
|
-
# Nodes. The method returns self so that multiple children can be added via
|
69
|
-
# chaining:
|
70
|
-
# root << child_a << child_b
|
71
|
-
def <<(child)
|
72
|
-
unless child.is_a? self.class
|
73
|
-
raise TypeError,
|
74
|
-
"Only objects of class #{self.class.name} can be appended"
|
75
|
-
end
|
76
|
-
|
77
|
-
@children << child
|
78
|
-
self
|
79
|
-
end
|
80
|
-
|
81
|
-
# Create Child
|
82
|
-
#
|
83
|
-
# Create and append a new child, initialized with the given attributes.
|
84
|
-
def create_child(**attributes)
|
85
|
-
child = self.class.new(**attributes)
|
86
|
-
self << child
|
87
|
-
child
|
88
|
-
end
|
89
|
-
|
90
|
-
# Add (+)
|
91
|
-
#
|
92
|
-
# Combines two nodes by creating a new root and adding the two as children.
|
93
|
-
def +(other)
|
94
|
-
self.class.new.tap { |root| root << self << other }
|
95
|
-
end
|
96
|
-
|
97
|
-
# Each
|
98
|
-
#
|
99
|
-
# Iterate over each node in the tree, including self.
|
100
|
-
def each(&block)
|
101
|
-
return to_enum(__callee__) unless block_given?
|
102
|
-
|
103
|
-
yield self
|
104
|
-
|
105
|
-
@children.each do |child|
|
106
|
-
yield child
|
107
|
-
child.each(&block) unless child.leaf?
|
108
|
-
end
|
109
|
-
end
|
110
|
-
|
111
|
-
# Each Leaf
|
112
|
-
#
|
113
|
-
# Iterate over each leaf in the tree. This method will yield the leaf nodes
|
114
|
-
# of the tree from left to right.
|
115
|
-
def each_leaf(&block)
|
116
|
-
return to_enum(__callee__) unless block_given?
|
117
|
-
return yield self if leaf?
|
118
|
-
|
119
|
-
@children.each do |child|
|
120
|
-
if child.leaf?
|
121
|
-
yield child
|
122
|
-
else
|
123
|
-
child.each_leaf(&block)
|
124
|
-
end
|
125
|
-
end
|
126
|
-
end
|
127
|
-
|
128
|
-
# Each child
|
129
|
-
#
|
130
|
-
# Iterate over the children of this node.
|
131
|
-
def each_child(&block)
|
132
|
-
@children.each(&block)
|
133
|
-
end
|
134
|
-
|
135
|
-
# Attribute accessor
|
136
|
-
#
|
137
|
-
# Read and write an attribute of the node. Attributes are simply key-value
|
138
|
-
# pairs stored internally in a hash.
|
139
|
-
def [](attribute)
|
140
|
-
@attributes[attribute]
|
141
|
-
end
|
142
|
-
|
143
|
-
def []=(attribute, value)
|
144
|
-
@attributes[attribute] = value
|
145
|
-
end
|
146
|
-
|
147
|
-
# Count
|
148
|
-
#
|
149
|
-
# Returns the child count of this node.
|
150
|
-
def count
|
151
|
-
@children.size
|
152
|
-
end
|
153
|
-
|
154
|
-
# Size
|
155
|
-
#
|
156
|
-
# Returns the size of the tree where this node is the root.
|
157
|
-
def size
|
158
|
-
@children.reduce(1) { |a, e| a + e.size }
|
159
|
-
end
|
160
|
-
|
161
|
-
# Minimal?
|
162
|
-
#
|
163
|
-
# Test if the tree under this node is minimal or not. A non minimal tree
|
164
|
-
# contains children which themselvs only have one child.
|
165
|
-
def minimal?
|
166
|
-
all? { |node| node.count != 1 }
|
167
|
-
end
|
168
|
-
|
169
|
-
# Optimize!
|
170
|
-
#
|
171
|
-
# Go through each child and merge any node that a) is not a lead node and b)
|
172
|
-
# only has one child, with its child. The attributes of the child will
|
173
|
-
# override those of the parent.
|
174
|
-
def optimize!
|
175
|
-
# If the node is a leaf it cannot be optimized further
|
176
|
-
return self if leaf?
|
177
|
-
|
178
|
-
# First optimize each of the children. If a block was
|
179
|
-
# given each child will be yielded to it, and children
|
180
|
-
# for which the block returns false will be removed
|
181
|
-
if block_given?
|
182
|
-
@children.select! { |child| yield child.optimize! }
|
183
|
-
else
|
184
|
-
@children.map(&:optimize!)
|
185
|
-
end
|
186
|
-
|
187
|
-
# If we only have one child it is superfluous and
|
188
|
-
# should be merged. That means this node will inherrit
|
189
|
-
# the children of the single child as well as its
|
190
|
-
# attributes
|
191
|
-
if count == 1
|
192
|
-
child = @children[0]
|
193
|
-
# Move the children over
|
194
|
-
@children = child.children
|
195
|
-
@attributes.merge! child.attributes
|
196
|
-
end
|
197
|
-
|
198
|
-
self
|
199
|
-
end
|
200
|
-
|
201
|
-
def optimize
|
202
|
-
dup.optimize!
|
203
|
-
end
|
204
|
-
|
205
|
-
# Shallow equality (exclude children)
|
206
|
-
#
|
207
|
-
# Returns true if the other node has the exact same attributes.
|
208
|
-
def equal?(other)
|
209
|
-
count == other.count && @attributes == other.attributes
|
210
|
-
end
|
211
|
-
|
212
|
-
# Deep equality (include children)
|
213
|
-
#
|
214
|
-
# Returns true if the other node has the same attributes and its children
|
215
|
-
# are also identical.
|
216
|
-
def ==(other)
|
217
|
-
# First make sure the nodes child count matches
|
218
|
-
return false unless equal? other
|
219
|
-
|
220
|
-
# Lastly make sure all of the children are equal
|
221
|
-
each_child.zip(other.each_child).all? { |c| c[0] == c[1] }
|
222
|
-
end
|
223
|
-
|
224
|
-
def inspect
|
225
|
-
children = @children.reduce('') do |s, c|
|
226
|
-
s + "\n" + c.inspect.gsub(/(^)/) { |m| m + ' ' }
|
227
|
-
end
|
228
|
-
|
229
|
-
format '#<%{name} %<attrs>p:%<id>#x>%{children}',
|
230
|
-
name: self.class.name,
|
231
|
-
id: object_id,
|
232
|
-
attrs: @attributes,
|
233
|
-
children: children
|
234
|
-
end
|
235
|
-
end
|
236
|
-
end
|