richtext 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|