richtext 0.1.0 → 0.2.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 +12 -15
- data/lib/richtext/document/entry.rb +62 -78
- data/lib/richtext/document.rb +85 -102
- data/lib/richtext/node.rb +106 -128
- data/lib/richtext/version.rb +1 -1
- data/lib/richtext.rb +3 -6
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1056b27569ae91520079931c9a5da0efc921467a
|
4
|
+
data.tar.gz: 7a58d34765134f7b85a3254de3e09a1c59cada8e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 64eac480d4354c710fc26e97888314b9e5143ea56e3ad4443530712d63168470fff018f71f768d5da6402892e58f7efd334fe07903042d78e1e605008ee25daa
|
7
|
+
data.tar.gz: 56c7742bfdeaac68dba853766e0214f98bbd6ab0436d1ba188a9a98ff151783d218885bcf28cb1852c21d7ce0fb429b6ab97c83eacb83ece8ab85ebee2c04df1
|
data/README.md
CHANGED
@@ -35,7 +35,7 @@ entry = rt.append('world', bold: true, my_attribute: '.')
|
|
35
35
|
# Some common styling attributes are supported directly
|
36
36
|
# This line is equivalent to entry[:italic] = true
|
37
37
|
entry.italic = true
|
38
|
-
# Under the covers the attributes are stored as
|
38
|
+
# Under the covers the attributes are stored as
|
39
39
|
# key-value pairs, so any attribute is valid
|
40
40
|
entry[:my_attribute] = '!'
|
41
41
|
|
@@ -43,12 +43,12 @@ entry[:my_attribute] = '!'
|
|
43
43
|
puts rt.to_s # => 'hello world'
|
44
44
|
|
45
45
|
# Or style the text yourself
|
46
|
-
html = rt.to_s do |
|
46
|
+
html = rt.to_s do |e, string|
|
47
47
|
# Access the attributes from the entry and format the
|
48
48
|
# string accordingly
|
49
|
-
string +=
|
50
|
-
string = "<b>#{string}</b>" if
|
51
|
-
|
49
|
+
string += e[:my_attribute] if e[:my_attribute]
|
50
|
+
string = "<b>#{string}</b>" if e.bold?
|
51
|
+
|
52
52
|
# Return the formatted string at the end of the block
|
53
53
|
string
|
54
54
|
end
|
@@ -60,30 +60,27 @@ Implementing new formats is easy. Just extend the `RichText::Document` class and
|
|
60
60
|
|
61
61
|
```ruby
|
62
62
|
class MyFormat < RichText::Document
|
63
|
-
# Use this method to signal if the document needs to be
|
64
|
-
# parsed, or if its raw form will work.
|
65
63
|
def should_parse?
|
66
64
|
true
|
67
65
|
end
|
68
66
|
|
69
|
-
def self.parse string
|
70
|
-
base = RichText::Document::Entry.new
|
67
|
+
def self.parse(base, string)
|
71
68
|
# Format specific implementation to parse a string. Here
|
72
69
|
# each word is represented by its own entry. Entries are
|
73
70
|
# given a random visibility attribute.
|
74
71
|
string.split(' ').each do |word|
|
75
|
-
|
76
|
-
base.add entry
|
72
|
+
base.create_child word, visible: (word.length > 6)
|
77
73
|
end
|
78
|
-
base
|
79
74
|
end
|
80
75
|
|
81
|
-
def self.render
|
76
|
+
def self.render(base)
|
82
77
|
# Format specific implementation to render the document
|
83
|
-
base.to_s do |entry, string|
|
78
|
+
str = base.to_s do |entry, string|
|
84
79
|
next string unless entry.leaf?
|
85
80
|
entry[:visible] ? string + ' ' : ''
|
86
|
-
end
|
81
|
+
end
|
82
|
+
|
83
|
+
str.rstrip
|
87
84
|
end
|
88
85
|
end
|
89
86
|
|
@@ -1,142 +1,126 @@
|
|
1
|
-
# Entry
|
2
|
-
#
|
3
|
-
# The Entry class extends the basic Node class and adds methods that make
|
4
|
-
# handling text a little nicer. Essentially the :text attribute is given special
|
5
|
-
# status by allowing it to a) be set during initialization, b) only visible in
|
6
|
-
# leaf nodes and c) copied over when adding children to leaf nodes.
|
7
|
-
#
|
8
|
-
# Some attributes are also supported explicitly by the inclusion of special
|
9
|
-
# accesser methods. The attributes are are bold, italic, underline, color and
|
10
|
-
# font.
|
11
|
-
|
12
1
|
module RichText
|
13
2
|
class Document
|
3
|
+
# Entry
|
4
|
+
#
|
5
|
+
# The Entry class extends the basic Node class and adds methods that make
|
6
|
+
# handling text a little nicer. Essentially the :text attribute is given
|
7
|
+
# special status by allowing it to a) be set during initialization, b) only
|
8
|
+
# visible in leaf nodes and c) copied over when adding children to leaf
|
9
|
+
# nodes.
|
10
|
+
#
|
11
|
+
# Some attributes are also supported explicitly by the inclusion of special
|
12
|
+
# accesser methods. The attributes are are bold, italic, underline, color
|
13
|
+
# and font.
|
14
|
+
#
|
14
15
|
class Entry < Node
|
15
|
-
|
16
16
|
# Initialize
|
17
17
|
#
|
18
|
-
# Extend the default Node initializer by also accepting a string. It will,
|
18
|
+
# Extend the default Node initializer by also accepting a string. It will,
|
19
19
|
# if given, be stored as a text attribute.
|
20
|
-
|
21
|
-
def initialize text = nil, **attributes
|
20
|
+
def initialize(text = nil, **attributes)
|
22
21
|
super attributes
|
23
22
|
self[:text] = text if text
|
24
23
|
end
|
25
|
-
|
26
|
-
|
24
|
+
|
27
25
|
# Text
|
28
26
|
#
|
29
|
-
# Read the text of the node. This will return nil unless the node is a
|
30
|
-
# leaf node. Note that nodes that are not leafs can have the text entry,
|
27
|
+
# Read the text of the node. This will return nil unless the node is a
|
28
|
+
# leaf node. Note that nodes that are not leafs can have the text entry,
|
31
29
|
# but it is discouraged by dissalowing access using this method.
|
32
|
-
|
33
30
|
def text
|
34
|
-
if leaf?
|
35
|
-
self[:text] || ''
|
36
|
-
else
|
37
|
-
nil
|
38
|
-
end
|
31
|
+
self[:text] || '' if leaf?
|
39
32
|
end
|
40
|
-
|
41
|
-
|
42
|
-
# Add child
|
33
|
+
|
34
|
+
# Append
|
43
35
|
#
|
44
|
-
#
|
45
|
-
|
46
|
-
|
36
|
+
# Since the text attribute is treated differently, and only leaf nodes can
|
37
|
+
# expose it, it must be pushed to a new child if a) this node was a leaf
|
38
|
+
# prior to this method call and b) its text attribute is not empty.
|
39
|
+
def <<(child)
|
47
40
|
if leaf?
|
48
|
-
# Remove the text entry from the node and put it in a new leaf node
|
41
|
+
# Remove the text entry from the node and put it in a new leaf node
|
49
42
|
# among the children, unless it is empty
|
50
|
-
if t = @attributes.delete
|
51
|
-
|
43
|
+
if (t = @attributes.delete :text)
|
44
|
+
create_child(t) unless t.empty?
|
52
45
|
end
|
53
46
|
end
|
54
|
-
|
47
|
+
|
55
48
|
super
|
56
49
|
end
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
50
|
+
|
51
|
+
def create_child(text = '', **attributes)
|
52
|
+
super attributes.merge(text: text)
|
53
|
+
end
|
54
|
+
|
61
55
|
# To String
|
62
56
|
#
|
63
|
-
# Combine the text from all the leaf nodes in the tree, from left to
|
64
|
-
# right. If a block is given the node, along with its text will be passed
|
65
|
-
# as arguments. The block will be called recursivly, starting at the leaf
|
66
|
-
# nodes and propagating up until the entire tree has been "rendered" in
|
57
|
+
# Combine the text from all the leaf nodes in the tree, from left to
|
58
|
+
# right. If a block is given the node, along with its text will be passed
|
59
|
+
# as arguments. The block will be called recursivly, starting at the leaf
|
60
|
+
# nodes and propagating up until the entire tree has been "rendered" in
|
67
61
|
# this way.
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
62
|
+
def to_s(&block)
|
63
|
+
string =
|
64
|
+
if leaf?
|
65
|
+
text
|
66
|
+
else
|
67
|
+
@children.reduce('') { |a, e| a + e.to_s(&block) }
|
68
|
+
end
|
69
|
+
|
74
70
|
block_given? ? yield(self, string) : string
|
75
71
|
end
|
76
|
-
|
77
|
-
|
72
|
+
|
78
73
|
# Supported Text Attributes
|
79
|
-
|
80
|
-
|
81
|
-
|
74
|
+
|
82
75
|
# Bold
|
83
76
|
#
|
84
|
-
|
85
77
|
def bold?
|
86
78
|
self[:bold]
|
87
79
|
end
|
88
|
-
|
89
|
-
def bold=
|
80
|
+
|
81
|
+
def bold=(b)
|
90
82
|
self[:bold] = b ? true : false
|
91
83
|
end
|
92
|
-
|
93
|
-
|
84
|
+
|
94
85
|
# Italic
|
95
86
|
#
|
96
|
-
|
97
87
|
def italic?
|
98
88
|
self[:italic]
|
99
89
|
end
|
100
|
-
|
101
|
-
def italic=
|
90
|
+
|
91
|
+
def italic=(i)
|
102
92
|
self[:italic] = i ? true : false
|
103
93
|
end
|
104
|
-
|
105
|
-
|
94
|
+
|
106
95
|
# Underline
|
107
96
|
#
|
108
|
-
|
109
97
|
def underline?
|
110
98
|
self[:underline]
|
111
99
|
end
|
112
|
-
|
113
|
-
def underline=
|
100
|
+
|
101
|
+
def underline=(u)
|
114
102
|
self[:underline] = u ? true : false
|
115
103
|
end
|
116
|
-
|
117
|
-
|
104
|
+
|
118
105
|
# Color
|
119
106
|
#
|
120
|
-
|
121
107
|
def color
|
122
108
|
self[:color]
|
123
109
|
end
|
124
|
-
|
125
|
-
def color=
|
110
|
+
|
111
|
+
def color=(c)
|
126
112
|
self[:color] = c
|
127
113
|
end
|
128
|
-
|
129
|
-
|
114
|
+
|
130
115
|
# Font
|
131
116
|
#
|
132
|
-
|
133
117
|
def font
|
134
118
|
self[:font]
|
135
119
|
end
|
136
|
-
|
137
|
-
def font=
|
120
|
+
|
121
|
+
def font=(f)
|
138
122
|
self[:font] = f
|
139
123
|
end
|
140
124
|
end
|
141
125
|
end
|
142
|
-
end
|
126
|
+
end
|
data/lib/richtext/document.rb
CHANGED
@@ -1,45 +1,46 @@
|
|
1
1
|
module RichText
|
2
|
+
# Document
|
3
|
+
#
|
2
4
|
class Document
|
5
|
+
attr_reader :raw
|
6
|
+
protected :raw
|
7
|
+
|
3
8
|
# Initialize
|
4
9
|
#
|
5
|
-
# Create a new RichText Document, either from a string or from an existing
|
6
|
-
# ducument. That feature is particularly useful when converting between
|
10
|
+
# Create a new RichText Document, either from a string or from an existing
|
11
|
+
# ducument. That feature is particularly useful when converting between
|
7
12
|
# formats.
|
8
13
|
#
|
9
|
-
# When given a string or a RichText Document of the same class no parsing is
|
10
|
-
# performed. Only when given a document of a different subclass will the
|
11
|
-
# parser need to be run parsed. Note that the document(s) may already be in
|
12
|
-
# parsed form, in which case no further parsing is performed. See #base for
|
14
|
+
# When given a string or a RichText Document of the same class no parsing is
|
15
|
+
# performed. Only when given a document of a different subclass will the
|
16
|
+
# parser need to be run parsed. Note that the document(s) may already be in
|
17
|
+
# parsed form, in which case no further parsing is performed. See #base for
|
13
18
|
# more details.
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
[nil, arg.to_s]
|
29
|
-
end
|
19
|
+
def initialize(arg = '')
|
20
|
+
@base, @raw =
|
21
|
+
if arg.class == self.class
|
22
|
+
arg.parsed? ? [arg.base, nil] : [nil, arg.raw]
|
23
|
+
elsif arg.is_a? Document
|
24
|
+
# For any other RichText object we take the base node
|
25
|
+
[arg.base, nil]
|
26
|
+
elsif arg.is_a? Entry
|
27
|
+
# Also accept an Entry which will be used as the
|
28
|
+
# document base
|
29
|
+
[arg, nil]
|
30
|
+
else
|
31
|
+
[nil, arg.to_s]
|
32
|
+
end
|
30
33
|
end
|
31
|
-
|
32
|
-
|
34
|
+
|
33
35
|
# To String
|
34
36
|
#
|
35
37
|
# Use the static implementation of .render to convert the document back into
|
36
|
-
# a string. If the document was never parsed (and is unchanged) the
|
38
|
+
# a string. If the document was never parsed (and is unchanged) the
|
37
39
|
# origninal string is just returned.
|
38
40
|
#
|
39
|
-
# If a block is given it will be used in place of .render to format the node
|
41
|
+
# If a block is given it will be used in place of .render to format the node
|
40
42
|
# tree.
|
41
|
-
|
42
|
-
def to_s &block
|
43
|
+
def to_s(&block)
|
43
44
|
if block_given?
|
44
45
|
base.to_s(&block)
|
45
46
|
elsif parsed? || should_parse?
|
@@ -48,133 +49,115 @@ module RichText
|
|
48
49
|
@raw
|
49
50
|
end
|
50
51
|
end
|
51
|
-
|
52
|
-
|
52
|
+
|
53
53
|
# Add (+)
|
54
54
|
#
|
55
|
-
# Add
|
56
|
-
|
57
|
-
|
55
|
+
# Add another Document to this one. If the two are of (exactly) the same
|
56
|
+
# class and neither one has been parsed, the two raw strings will be
|
57
|
+
# concatenated. If the other is a Document the two base nodes will be merged
|
58
|
+
# and the new root added to a new Document.
|
59
|
+
#
|
60
|
+
# Lastly, if other is a string it will first be wraped in a new Document and
|
61
|
+
# then added to this one.
|
62
|
+
def +(other)
|
58
63
|
# If the other object is of the same class, and neither
|
59
64
|
# one of the texts have been parsed, we can concatenate
|
60
65
|
# the raw inputs together
|
61
66
|
if other.class == self.class && !parsed? && !other.parsed?
|
62
|
-
return self.class.new
|
67
|
+
return self.class.new(@raw + other.raw)
|
63
68
|
end
|
64
|
-
|
69
|
+
|
65
70
|
# Same root class
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
unless other.respond_to?(:to_s)
|
71
|
+
return self.class.new(base + other.base) if other.is_a? Document
|
72
|
+
|
73
|
+
unless other.respond_to? :to_s
|
71
74
|
raise TypeError,
|
72
|
-
|
75
|
+
"Cannot add #{other.class.name} to #{self.class.name}"
|
73
76
|
end
|
74
|
-
|
77
|
+
|
75
78
|
# Assume that the input is a raw string of the same
|
76
79
|
# class as the current RichText object and wrap it
|
77
80
|
# before adding it
|
78
81
|
self + self.class.new(other)
|
79
82
|
end
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
83
|
+
|
84
|
+
# Append
|
85
|
+
#
|
86
|
+
#
|
87
|
+
def append(string, **attributes)
|
88
|
+
base.create_child string, **attributes
|
86
89
|
end
|
87
|
-
|
88
|
-
|
90
|
+
|
89
91
|
# Base
|
90
92
|
#
|
91
|
-
# Getter for the base node. If the raw input has not yet been
|
93
|
+
# Getter for the base node. If the raw input has not yet been
|
92
94
|
# parsed that will happen first, before the base node is returned.
|
93
|
-
|
94
95
|
def base
|
95
96
|
unless @base
|
96
97
|
@base = Entry.new
|
97
98
|
self.class.parse @base, @raw
|
98
|
-
@raw
|
99
|
+
@raw = nil
|
99
100
|
end
|
100
|
-
|
101
|
+
|
101
102
|
@base
|
102
103
|
end
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
# Raw
|
108
|
-
#
|
109
|
-
# Protected getter for the raw input.
|
110
|
-
|
111
|
-
protected def raw
|
112
|
-
@raw
|
113
|
-
end
|
114
|
-
|
115
|
-
|
104
|
+
|
105
|
+
alias root base
|
106
|
+
|
116
107
|
# Parsed?
|
117
108
|
#
|
118
|
-
# Returns true if the raw input has been parsed and the internal
|
109
|
+
# Returns true if the raw input has been parsed and the internal
|
119
110
|
# representation is now a tree of nodes.
|
120
|
-
|
121
111
|
def parsed?
|
122
112
|
@raw.nil?
|
123
113
|
end
|
124
|
-
|
125
|
-
|
114
|
+
|
126
115
|
protected def should_parse?
|
127
116
|
false
|
128
117
|
end
|
129
|
-
|
130
|
-
|
118
|
+
|
131
119
|
# Each Node
|
132
120
|
#
|
133
121
|
# Iterate over all Entry nodes in the document tree.
|
134
|
-
|
135
|
-
def each_node &block
|
122
|
+
def each_node(&block)
|
136
123
|
base.each(&block)
|
137
124
|
end
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
125
|
+
|
126
|
+
alias each_entry each_node
|
127
|
+
|
142
128
|
# Parse
|
143
129
|
#
|
144
|
-
# Document type specific method for parsing a string and turning it into a
|
145
|
-
# tree of entry nodes. This method is intended to be overridden when the
|
146
|
-
# Document is subclassed. The default implementation just creates a top
|
130
|
+
# Document type specific method for parsing a string and turning it into a
|
131
|
+
# tree of entry nodes. This method is intended to be overridden when the
|
132
|
+
# Document is subclassed. The default implementation just creates a top
|
147
133
|
# level Entry containing the given string.
|
148
|
-
|
149
|
-
def self.parse
|
134
|
+
|
135
|
+
def self.parse(base, string)
|
150
136
|
base[:text] = string
|
151
137
|
end
|
152
|
-
|
153
|
-
|
138
|
+
|
154
139
|
# Render
|
155
140
|
#
|
156
|
-
# Document type specific method for rendering a tree of entry nodes. This
|
157
|
-
# method is intended to be overridden when the Document is subclassed. The
|
141
|
+
# Document type specific method for rendering a tree of entry nodes. This
|
142
|
+
# method is intended to be overridden when the Document is subclassed. The
|
158
143
|
# default implementation just concatenates the text entries into.
|
159
|
-
|
160
|
-
def self.render
|
144
|
+
|
145
|
+
def self.render(base)
|
161
146
|
base.to_s
|
162
147
|
end
|
163
|
-
|
164
|
-
|
148
|
+
|
165
149
|
# From
|
166
150
|
#
|
167
|
-
# Convenience method for instansiating one RichText object from another. The
|
168
|
-
# methods only purpose is to make that intent more clear, and to make the
|
151
|
+
# Convenience method for instansiating one RichText object from another. The
|
152
|
+
# methods only purpose is to make that intent more clear, and to make the
|
169
153
|
# creation from another RichText object explicit.
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
"Can only create a #{self.name} from other RichText objects"
|
154
|
+
def self.from(doc)
|
155
|
+
unless doc.is_a? Document
|
156
|
+
raise TypeError,
|
157
|
+
"Can only create a #{name} from other RichText Documents"
|
175
158
|
end
|
176
|
-
|
177
|
-
|
159
|
+
|
160
|
+
new doc
|
178
161
|
end
|
179
162
|
end
|
180
|
-
end
|
163
|
+
end
|
data/lib/richtext/node.rb
CHANGED
@@ -1,114 +1,105 @@
|
|
1
|
-
# Node
|
2
|
-
#
|
3
|
-
# A Node can have children, which themselvs can have children. A tree like
|
4
|
-
# structure can thus be formed by composing multiple Nodes. An example of such a
|
5
|
-
# tree structure can be seen below.
|
6
|
-
#
|
7
|
-
# The Node class implements some convenience methods for iterating, left to
|
8
|
-
# right, over either all
|
9
|
-
# - nodes in the tree
|
10
|
-
# - leafs in the tree
|
11
|
-
# - direct decendant of a node
|
12
|
-
#
|
13
|
-
# In addition to having children a Node can also have attributes, represented by # simple key => value pairs.
|
14
|
-
#
|
15
|
-
# Example Tree
|
16
|
-
# +--------------------------+
|
17
|
-
# A <- Root Node | Left to right order: ABC |
|
18
|
-
# / \ +--------------------------+
|
19
|
-
# Leaf Node -> B C <- Child to A
|
20
|
-
# (no children) /|\
|
21
|
-
# ...
|
22
|
-
#
|
23
|
-
|
24
1
|
module RichText
|
2
|
+
# Node
|
3
|
+
#
|
4
|
+
# A Node can have children, which themselvs can have children. A tree like
|
5
|
+
# structure can thus be formed by composing multiple Nodes. An example of such
|
6
|
+
# a tree structure can be seen below.
|
7
|
+
#
|
8
|
+
# The Node class implements some convenience methods for iterating, left to
|
9
|
+
# right, over either all
|
10
|
+
# - nodes in the tree
|
11
|
+
# - leafs in the tree
|
12
|
+
# - direct decendant of a node
|
13
|
+
#
|
14
|
+
# In addition to having children a Node can also have attributes, represented
|
15
|
+
# by simple key => value pairs.
|
16
|
+
#
|
17
|
+
# Example Tree
|
18
|
+
# +--------------------------+
|
19
|
+
# A <- Root Node | Left to right order: ABC |
|
20
|
+
# / \ +--------------------------+
|
21
|
+
# Leaf Node -> B C <- Child to A
|
22
|
+
# (no children) /|\
|
23
|
+
# ...
|
24
|
+
#
|
25
25
|
class Node
|
26
26
|
include Enumerable
|
27
|
-
|
28
|
-
attr_reader :attributes
|
29
|
-
|
30
|
-
|
27
|
+
|
28
|
+
attr_reader :attributes, :children
|
29
|
+
protected :children
|
30
|
+
|
31
|
+
def initialize(**attributes)
|
31
32
|
@children = []
|
32
33
|
@attributes = attributes
|
33
|
-
#@attributes[:text] = text if text
|
34
34
|
end
|
35
|
-
|
36
|
-
|
37
|
-
def initialize_copy original
|
35
|
+
|
36
|
+
def initialize_copy(original)
|
38
37
|
@children = original.children.map(&:dup)
|
39
38
|
@attributes = original.attributes.dup
|
40
39
|
end
|
41
|
-
|
42
|
-
|
40
|
+
|
43
41
|
# Leaf?
|
44
42
|
#
|
45
43
|
# Returns true if this node a leaf (childless) node.
|
46
|
-
|
47
44
|
def leaf?
|
48
45
|
@children.empty?
|
49
46
|
end
|
50
|
-
|
51
|
-
|
52
|
-
# Children
|
53
|
-
#
|
54
|
-
# Protected accessor for the children array. This array should never be
|
55
|
-
# mutated from the outside and is only protected rather than private to be
|
56
|
-
# accessable to ther Nodes.
|
57
|
-
|
58
|
-
protected def children
|
59
|
-
@children
|
60
|
-
end
|
61
|
-
|
62
|
-
|
63
|
-
# Add child
|
47
|
+
|
48
|
+
# Append
|
64
49
|
#
|
65
|
-
#
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
50
|
+
# Add a child to the end of the node child list. The child must be of this
|
51
|
+
# class to be accepted. Note that subclasses of Node will not accept regular
|
52
|
+
# Nodes. The method returns self so that multiple children can be added via
|
53
|
+
# chaining:
|
54
|
+
# root << child_a << child_b
|
55
|
+
def <<(child)
|
56
|
+
unless child.is_a? self.class
|
57
|
+
raise TypeError,
|
58
|
+
"Only objects of class #{self.class.name} can be appended"
|
70
59
|
end
|
71
60
|
|
72
|
-
@children
|
61
|
+
@children << child
|
62
|
+
self
|
73
63
|
end
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
64
|
+
|
65
|
+
# Create Child
|
66
|
+
#
|
67
|
+
# Create and append a new child, initialized with the given attributes.
|
68
|
+
def create_child(**attributes)
|
69
|
+
child = self.class.new(**attributes)
|
70
|
+
self << child
|
71
|
+
child
|
72
|
+
end
|
73
|
+
|
78
74
|
# Add (+)
|
79
75
|
#
|
80
76
|
# Combines two nodes by creating a new root and adding the two as children.
|
81
|
-
|
82
|
-
|
83
|
-
self.class.new.tap {|root| root.add self, other }
|
77
|
+
def +(other)
|
78
|
+
self.class.new.tap { |root| root << self << other }
|
84
79
|
end
|
85
|
-
|
86
|
-
|
80
|
+
|
87
81
|
# Each
|
88
82
|
#
|
89
83
|
# Iterate over each node in the tree, including self.
|
90
|
-
|
91
|
-
def each &block
|
84
|
+
def each(&block)
|
92
85
|
return to_enum(__callee__) unless block_given?
|
93
|
-
|
86
|
+
|
94
87
|
yield self
|
95
|
-
|
88
|
+
|
96
89
|
@children.each do |child|
|
97
90
|
yield child
|
98
91
|
child.each(&block) unless child.leaf?
|
99
92
|
end
|
100
93
|
end
|
101
|
-
|
102
|
-
|
94
|
+
|
103
95
|
# Each Leaf
|
104
96
|
#
|
105
|
-
# Iterate over each leaf in the tree. This method will yield the leaf nodes
|
97
|
+
# Iterate over each leaf in the tree. This method will yield the leaf nodes
|
106
98
|
# of the tree from left to right.
|
107
|
-
|
108
|
-
def each_leaf &block
|
99
|
+
def each_leaf(&block)
|
109
100
|
return to_enum(__callee__) unless block_given?
|
110
101
|
return yield self if leaf?
|
111
|
-
|
102
|
+
|
112
103
|
@children.each do |child|
|
113
104
|
if child.leaf?
|
114
105
|
yield child
|
@@ -117,72 +108,60 @@ module RichText
|
|
117
108
|
end
|
118
109
|
end
|
119
110
|
end
|
120
|
-
|
121
|
-
|
111
|
+
|
122
112
|
# Each child
|
123
113
|
#
|
124
114
|
# Iterate over the children of this node.
|
125
|
-
|
126
|
-
def each_child &block
|
115
|
+
def each_child(&block)
|
127
116
|
@children.each(&block)
|
128
117
|
end
|
129
|
-
|
130
|
-
|
118
|
+
|
131
119
|
# Attribute accessor
|
132
120
|
#
|
133
|
-
# Read and write an attribute of the node. Attributes are simply key-value
|
121
|
+
# Read and write an attribute of the node. Attributes are simply key-value
|
134
122
|
# pairs stored internally in a hash.
|
135
|
-
|
136
|
-
def [] attribute
|
123
|
+
def [](attribute)
|
137
124
|
@attributes[attribute]
|
138
125
|
end
|
139
|
-
|
140
|
-
def []=
|
126
|
+
|
127
|
+
def []=(attribute, value)
|
141
128
|
@attributes[attribute] = value
|
142
129
|
end
|
143
|
-
|
144
|
-
|
130
|
+
|
145
131
|
# Count
|
146
132
|
#
|
147
133
|
# Returns the child count of this node.
|
148
|
-
|
149
134
|
def count
|
150
135
|
@children.size
|
151
136
|
end
|
152
|
-
|
153
|
-
|
137
|
+
|
154
138
|
# Size
|
155
139
|
#
|
156
140
|
# Returns the size of the tree where this node is the root.
|
157
|
-
|
158
141
|
def size
|
159
|
-
@children.reduce(1) {|
|
142
|
+
@children.reduce(1) { |a, e| a + e.size }
|
160
143
|
end
|
161
|
-
|
162
|
-
|
144
|
+
|
163
145
|
# Minimal?
|
164
146
|
#
|
165
|
-
# Test if the tree under this node is minimal or not. A non minimal tree
|
147
|
+
# Test if the tree under this node is minimal or not. A non minimal tree
|
166
148
|
# contains children which themselvs only have one child.
|
167
|
-
|
168
149
|
def minimal?
|
169
|
-
all? {|node| node.count != 1 }
|
150
|
+
all? { |node| node.count != 1 }
|
170
151
|
end
|
171
|
-
|
172
|
-
|
152
|
+
|
173
153
|
# Optimize!
|
174
154
|
#
|
175
|
-
# Go through each child and merge any node that a) is not a lead node and b)
|
176
|
-
# only has one child, with its child. The attributes of the child will
|
155
|
+
# Go through each child and merge any node that a) is not a lead node and b)
|
156
|
+
# only has one child, with its child. The attributes of the child will
|
177
157
|
# override those of the parent.
|
178
|
-
|
179
158
|
def optimize!
|
180
159
|
# If the node is a leaf it cannot be optimized further
|
181
160
|
return if leaf?
|
182
|
-
|
161
|
+
|
183
162
|
# First optimize each of the children
|
184
163
|
@children.map(&:optimize!)
|
185
|
-
|
164
|
+
|
186
165
|
# If we only have one child it is superfluous and
|
187
166
|
# should be merged. That means this node will inherrit
|
188
167
|
# the children of the single child as well as its
|
@@ -194,41 +173,40 @@ module RichText
|
|
194
173
|
@attributes.merge! child.attributes
|
195
174
|
end
|
196
175
|
end
|
197
|
-
|
198
|
-
|
176
|
+
|
177
|
+
def optimize
|
178
|
+
dup.optimize!
|
179
|
+
end
|
180
|
+
|
199
181
|
# Shallow equality (exclude children)
|
200
182
|
#
|
201
183
|
# Returns true if the other node has the exact same attributes.
|
202
|
-
|
203
|
-
|
204
|
-
@attributes == other.attributes
|
184
|
+
def equal?(other)
|
185
|
+
count == other.count && @attributes == other.attributes
|
205
186
|
end
|
206
|
-
|
207
|
-
|
187
|
+
|
208
188
|
# Deep equality (include children)
|
209
189
|
#
|
210
|
-
# Returns true if the other node has the same attributes and its children
|
190
|
+
# Returns true if the other node has the same attributes and its children
|
211
191
|
# are also identical.
|
212
|
-
|
213
|
-
def == other
|
192
|
+
def ==(other)
|
214
193
|
# First make sure the nodes child count matches
|
215
|
-
return false unless
|
216
|
-
|
217
|
-
# The nodes are not equal if their attributes do not
|
218
|
-
# match
|
219
|
-
return false unless self === other
|
220
|
-
|
194
|
+
return false unless equal? other
|
195
|
+
|
221
196
|
# Lastly make sure all of the children are equal
|
222
|
-
each_child.zip(other.each_child).all? {|c| c[0] == c[1] }
|
197
|
+
each_child.zip(other.each_child).all? { |c| c[0] == c[1] }
|
223
198
|
end
|
224
|
-
|
225
|
-
|
199
|
+
|
226
200
|
def inspect
|
227
|
-
children = @children.reduce('')
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
201
|
+
children = @children.reduce('') do |s, c|
|
202
|
+
s + "\n" + c.inspect.gsub(/(^)/) { |m| m[1] + ' ' }
|
203
|
+
end
|
204
|
+
|
205
|
+
format '#<%{name} %<attrs>p:%<id>#x>%{children}',
|
206
|
+
name: self.class.name,
|
207
|
+
id: object_id,
|
208
|
+
attrs: @attributes,
|
209
|
+
children: children
|
232
210
|
end
|
233
211
|
end
|
234
|
-
end
|
212
|
+
end
|
data/lib/richtext/version.rb
CHANGED
data/lib/richtext.rb
CHANGED
@@ -3,16 +3,13 @@ require 'richtext/node'
|
|
3
3
|
require 'richtext/document/entry'
|
4
4
|
require 'richtext/document'
|
5
5
|
|
6
|
-
module RichText
|
7
|
-
|
6
|
+
module RichText
|
8
7
|
end
|
9
8
|
|
10
|
-
|
11
9
|
# RichText
|
12
10
|
#
|
13
|
-
# Convenience method for creating RichText objects. Calling RichText(obj) is
|
11
|
+
# Convenience method for creating RichText objects. Calling RichText(obj) is
|
14
12
|
# equivalent to RichText::Document.new(obj).
|
15
|
-
|
16
|
-
def RichText string
|
13
|
+
def RichText(string)
|
17
14
|
RichText::Document.new string
|
18
15
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: richtext
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.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-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|