ro 1.4.6 → 4.4.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.
Files changed (157) hide show
  1. checksums.yaml +6 -14
  2. data/Gemfile +2 -0
  3. data/Gemfile.lock +64 -0
  4. data/LICENSE +1 -0
  5. data/README.md +276 -105
  6. data/README.md.erb +159 -0
  7. data/Rakefile +129 -78
  8. data/bin/ro +241 -68
  9. data/lib/ro/_lib.rb +107 -0
  10. data/lib/ro/asset.rb +55 -0
  11. data/lib/ro/collection/list.rb +23 -0
  12. data/lib/ro/collection.rb +211 -0
  13. data/lib/ro/config.rb +72 -0
  14. data/lib/ro/error.rb +11 -0
  15. data/lib/ro/html.rb +23 -0
  16. data/lib/ro/html_safe.rb +143 -0
  17. data/lib/ro/klass.rb +25 -0
  18. data/lib/ro/methods.rb +295 -0
  19. data/lib/ro/model.rb +83 -114
  20. data/lib/ro/node.rb +209 -349
  21. data/lib/ro/pagination.rb +7 -4
  22. data/lib/ro/path.rb +229 -0
  23. data/lib/ro/root.rb +52 -31
  24. data/lib/ro/script/builder.rb +221 -0
  25. data/lib/ro/script/console.rb +47 -0
  26. data/lib/ro/script/server.rb +76 -0
  27. data/lib/ro/script.rb +189 -0
  28. data/lib/ro/slug.rb +19 -18
  29. data/lib/ro/template/rouge_formatter.rb +42 -0
  30. data/lib/ro/template.rb +145 -51
  31. data/lib/ro/text.rb +120 -0
  32. data/lib/ro.rb +89 -317
  33. data/public/api/ro/index-1.json +1065 -0
  34. data/public/api/ro/index.json +1055 -0
  35. data/public/api/ro/nerd/fastest-possible-embeddings/index.json +90 -0
  36. data/public/api/ro/nerd/ima/index.json +49 -0
  37. data/public/api/ro/nerd/index/index.json +74 -0
  38. data/public/api/ro/nerd/index-1.json +204 -0
  39. data/public/api/ro/nerd/index.json +194 -0
  40. data/public/api/ro/pages/about/index.json +60 -0
  41. data/public/api/ro/pages/contact/index.json +50 -0
  42. data/public/api/ro/pages/cv/index.json +49 -0
  43. data/public/api/ro/pages/disco/index.json +117 -0
  44. data/public/api/ro/pages/index/index.json +30 -0
  45. data/public/api/ro/pages/index-1.json +366 -0
  46. data/public/api/ro/pages/index.json +356 -0
  47. data/public/api/ro/pages/jess/index.json +62 -0
  48. data/public/api/ro/pages/now/index.json +43 -0
  49. data/public/api/ro/posts/almost-died-in-an-ice-cave/index.json +265 -0
  50. data/public/api/ro/posts/facebook-and-global-extremism/index.json +90 -0
  51. data/public/api/ro/posts/index-1.json +527 -0
  52. data/public/api/ro/posts/index.json +517 -0
  53. data/public/api/ro/posts/lemmings-considered-harmful/index.json +49 -0
  54. data/public/api/ro/posts/lost-in-the-desert/index.json +49 -0
  55. data/public/api/ro/posts/mission/index.json +49 -0
  56. data/public/api/ro/posts/return-your-laptop/index.json +61 -0
  57. data/public/ro/nerd/fastest-possible-embeddings/assets/giraffe.jpeg +0 -0
  58. data/public/ro/nerd/fastest-possible-embeddings/assets/let-me-in.jpg +0 -0
  59. data/public/ro/nerd/fastest-possible-embeddings/assets/src/fastembed.js +70 -0
  60. data/public/ro/nerd/fastest-possible-embeddings/assets/src/fastembed.rs +68 -0
  61. data/public/ro/nerd/fastest-possible-embeddings/assets/terminal.jpg +0 -0
  62. data/public/ro/nerd/fastest-possible-embeddings/attributes.yml +7 -0
  63. data/public/ro/nerd/fastest-possible-embeddings/body.md +266 -0
  64. data/public/ro/nerd/ima/assets/og.jpeg +0 -0
  65. data/public/ro/nerd/ima/attributes.yml +8 -0
  66. data/public/ro/nerd/ima/body.md +22 -0
  67. data/public/ro/nerd/index/assets/giraffe.jpeg +0 -0
  68. data/public/ro/nerd/index/assets/let-me-in.jpg +0 -0
  69. data/public/ro/nerd/index/assets/terminal.jpg +0 -0
  70. data/public/ro/nerd/index/attributes.yml +7 -0
  71. data/public/ro/nerd/index/body.md +130 -0
  72. data/public/ro/pages/about/assets/og.jpeg +0 -0
  73. data/public/ro/pages/about/assets/speak-english-pulp-fiction.gif +0 -0
  74. data/public/ro/pages/about/body.md +40 -0
  75. data/public/ro/pages/contact/assets/giraffe.jpeg +0 -0
  76. data/public/ro/pages/contact/attributes.yml +7 -0
  77. data/public/ro/pages/contact/body.md +9 -0
  78. data/public/ro/pages/cv/assets/ara.jpg +0 -0
  79. data/public/ro/pages/cv/attributes.yml +6 -0
  80. data/public/ro/pages/cv/body.md +122 -0
  81. data/public/ro/pages/disco/assets/disco.jpg +0 -0
  82. data/public/ro/pages/disco/assets/disco.png +0 -0
  83. data/public/ro/pages/disco/assets/speak-english-pulp-fiction.gif +0 -0
  84. data/public/ro/pages/disco/assets/src/environment.md +2354 -0
  85. data/public/ro/pages/disco/assets/src/fortune-500.md +2518 -0
  86. data/public/ro/pages/disco/assets/src/greed.md +2703 -0
  87. data/public/ro/pages/disco/assets/src/up-at-night.md +2337 -0
  88. data/public/ro/pages/disco/attributes.yml +9 -0
  89. data/public/ro/pages/disco/body.md +99 -0
  90. data/public/ro/pages/disco/samples/environment.md +2354 -0
  91. data/public/ro/pages/disco/samples/fortune-500.md +2518 -0
  92. data/public/ro/pages/disco/samples/greed.md +2703 -0
  93. data/public/ro/pages/disco/samples/up-at-night.md +2337 -0
  94. data/public/ro/pages/index/attributes.yml +1 -0
  95. data/public/ro/pages/index/body.md +15 -0
  96. data/public/ro/pages/jess/assets/og.jpg +0 -0
  97. data/public/ro/pages/jess/assets/speak-english-pulp-fiction.gif +0 -0
  98. data/public/ro/pages/jess/attributes.yml +7 -0
  99. data/public/ro/pages/jess/body.md +3 -0
  100. data/public/ro/pages/now/assets/speak-english-pulp-fiction.gif +0 -0
  101. data/public/ro/pages/now/attributes.yml +1 -0
  102. data/public/ro/pages/now/body.md +24 -0
  103. data/public/ro/posts/almost-died-in-an-ice-cave/assets/image1.png +0 -0
  104. data/public/ro/posts/almost-died-in-an-ice-cave/assets/image10.png +0 -0
  105. data/public/ro/posts/almost-died-in-an-ice-cave/assets/image11.png +0 -0
  106. data/public/ro/posts/almost-died-in-an-ice-cave/assets/image12.png +0 -0
  107. data/public/ro/posts/almost-died-in-an-ice-cave/assets/image13.png +0 -0
  108. data/public/ro/posts/almost-died-in-an-ice-cave/assets/image14.png +0 -0
  109. data/public/ro/posts/almost-died-in-an-ice-cave/assets/image15.png +0 -0
  110. data/public/ro/posts/almost-died-in-an-ice-cave/assets/image2.png +0 -0
  111. data/public/ro/posts/almost-died-in-an-ice-cave/assets/image3.png +0 -0
  112. data/public/ro/posts/almost-died-in-an-ice-cave/assets/image4.png +0 -0
  113. data/public/ro/posts/almost-died-in-an-ice-cave/assets/image5.png +0 -0
  114. data/public/ro/posts/almost-died-in-an-ice-cave/assets/image6.png +0 -0
  115. data/public/ro/posts/almost-died-in-an-ice-cave/assets/image7.png +0 -0
  116. data/public/ro/posts/almost-died-in-an-ice-cave/assets/image8.png +0 -0
  117. data/public/ro/posts/almost-died-in-an-ice-cave/assets/image9.png +0 -0
  118. data/public/ro/posts/almost-died-in-an-ice-cave/assets/josh-pointing.jpg +0 -0
  119. data/public/ro/posts/almost-died-in-an-ice-cave/assets/levi-rawr.png +0 -0
  120. data/public/ro/posts/almost-died-in-an-ice-cave/assets/og.jpg +0 -0
  121. data/public/ro/posts/almost-died-in-an-ice-cave/assets/purple-heart.jpg +0 -0
  122. data/public/ro/posts/almost-died-in-an-ice-cave/attributes.yml +6 -0
  123. data/public/ro/posts/almost-died-in-an-ice-cave/body.md +419 -0
  124. data/public/ro/posts/facebook-and-global-extremism/assets/background.html +125 -0
  125. data/public/ro/posts/facebook-and-global-extremism/assets/background.md +95 -0
  126. data/public/ro/posts/facebook-and-global-extremism/assets/og.jpg +0 -0
  127. data/public/ro/posts/facebook-and-global-extremism/assets/prompt.txt +122 -0
  128. data/public/ro/posts/facebook-and-global-extremism/assets/results.md +183 -0
  129. data/public/ro/posts/facebook-and-global-extremism/assets/survey.txt +190 -0
  130. data/public/ro/posts/facebook-and-global-extremism/attributes.yml +7 -0
  131. data/public/ro/posts/facebook-and-global-extremism/body.md +393 -0
  132. data/public/ro/posts/lemmings-considered-harmful/assets/lemming.jpeg +0 -0
  133. data/public/ro/posts/lemmings-considered-harmful/attributes.yml +6 -0
  134. data/public/ro/posts/lemmings-considered-harmful/body.md +43 -0
  135. data/public/ro/posts/lost-in-the-desert/assets/og.jpg +0 -0
  136. data/public/ro/posts/lost-in-the-desert/attributes.yml +6 -0
  137. data/public/ro/posts/lost-in-the-desert/body.md +7 -0
  138. data/public/ro/posts/mission/assets/og.jpg +0 -0
  139. data/public/ro/posts/mission/attributes.yml +6 -0
  140. data/public/ro/posts/mission/body.md +4 -0
  141. data/public/ro/posts/return-your-laptop/assets/og.jpg +0 -0
  142. data/public/ro/posts/return-your-laptop/assets/return-your-laptop.png +0 -0
  143. data/public/ro/posts/return-your-laptop/attributes.yml +6 -0
  144. data/public/ro/posts/return-your-laptop/body.md +58 -0
  145. data/ro.gemspec +217 -28
  146. data/scripts/speedtest.rb +324 -0
  147. data/tmp/gem-details.oe +0 -0
  148. metadata +214 -74
  149. data/TODO.md +0 -50
  150. data/lib/ro/blankslate.rb +0 -7
  151. data/lib/ro/cache.rb +0 -26
  152. data/lib/ro/git.rb +0 -374
  153. data/lib/ro/initializers/env.rb +0 -5
  154. data/lib/ro/initializers/tilt.rb +0 -104
  155. data/lib/ro/lock.rb +0 -53
  156. data/lib/ro/node/list.rb +0 -142
  157. data/notes/ara.txt +0 -215
@@ -0,0 +1,211 @@
1
+ module Ro
2
+ class Collection
3
+ include Klass
4
+ include Enumerable
5
+
6
+ attr_reader :path, :root
7
+
8
+ def initialize(path)
9
+ @path = Path.for(path)
10
+ @root = Root.for(@path.parent)
11
+ end
12
+
13
+ def name
14
+ @path.name
15
+ end
16
+
17
+ def id
18
+ name
19
+ end
20
+
21
+ def type
22
+ name
23
+ end
24
+
25
+ def identifier
26
+ type
27
+ end
28
+
29
+ def inspect
30
+ identifier
31
+ end
32
+
33
+ def node_for(path)
34
+ Node.new(path)
35
+ end
36
+
37
+ def subdirectories(...)
38
+ @path.subdirectories(...)
39
+ end
40
+
41
+ def subdirectory_for(name)
42
+ @path.subdirectory_for(name)
43
+ end
44
+
45
+ def each(offset:nil, limit:nil, &block)
46
+ accum = []
47
+
48
+ if offset
49
+ i = -1
50
+ n = 0
51
+ subdirectories do |subdirectory|
52
+ i += 1
53
+ next if i < offset
54
+ node = node_for(subdirectory)
55
+ block ? block.call(node) : accum.push(node)
56
+ n += 1
57
+ break if limit && n >= limit
58
+ end
59
+ else
60
+ subdirectories do |subdirectory|
61
+ node = node_for(subdirectory)
62
+ block ? block.call(node) : accum.push(node)
63
+ end
64
+ end
65
+
66
+ block ? self : accum
67
+ end
68
+
69
+ class Page < ::Array
70
+ attr_accessor :number
71
+
72
+ def initialize(nodes = [], number: 1)
73
+ replace(nodes)
74
+ @number = number
75
+ end
76
+ end
77
+
78
+ def page(number, size: 10)
79
+ offset = [(number - 1), 0].max * size
80
+ limit = [size, 1].max
81
+
82
+ nodes = each(offset:, limit:)
83
+ Page.new(nodes, number:)
84
+ end
85
+
86
+ def paginate(size: 10, &block)
87
+ number = 0
88
+ accum = []
89
+
90
+ loop do
91
+ number += 1
92
+ page = self.page(number, size:)
93
+ break if page.empty?
94
+ block ? block.call(page) : accum.push(page)
95
+ end
96
+
97
+ block ? self : accum
98
+ end
99
+
100
+ def load(&block)
101
+ n = 8
102
+ q = Queue.new # FIXME
103
+ o = Queue.new # FIXME
104
+
105
+ producer =
106
+ Thread.new do
107
+ Thread.current.abort_on_exception = true
108
+
109
+ subdirectories do |subdirectory|
110
+ q.push(subdirectory)
111
+ end
112
+ end
113
+
114
+ loaders =
115
+ n.times.map do
116
+ Thread.new do
117
+ Thread.current.abort_on_exception = true
118
+
119
+ loop do
120
+ subdirectory = q.pop
121
+
122
+ begin
123
+ node = node_for(subdirectory)
124
+ o.push(node)
125
+ rescue => e
126
+ o.push(e) # FIXME
127
+ nil # FIXME
128
+ end
129
+ end
130
+ end
131
+ end
132
+
133
+ accum = []
134
+
135
+ consumer =
136
+ Thread.new do
137
+ Thread.current.abort_on_exception = true
138
+ loop do
139
+ node = o.pop
140
+ block ? block.call(node) : accum.push(node)
141
+ end
142
+ end
143
+
144
+ producer.join
145
+ loaders.map{|loader| loader.join}
146
+ consumer.join
147
+
148
+ block ? self : accum
149
+ end
150
+
151
+ def to_array(...)
152
+ each(...)
153
+ end
154
+
155
+ alias to_a to_array
156
+
157
+ alias all to_array
158
+
159
+ alias nodes to_array
160
+
161
+ def first(*args)
162
+ if args.size.zero?
163
+ node_for(subdirectories.first)
164
+ else
165
+ subdirectories.first(*args).map{|subdirectory| node_for(subdirectory)}
166
+ end
167
+ end
168
+
169
+ def last(*args)
170
+ if args.size.zero?
171
+ node_for(subdirectories.last)
172
+ else
173
+ subdirectories.last(*args).map{|subdirectory| node_for(subdirectory)}
174
+ end
175
+ end
176
+
177
+ def size
178
+ subdirectories.size
179
+ end
180
+
181
+ def paths_for(name)
182
+ [
183
+ subdirectory_for(name),
184
+ subdirectory_for(Slug.for(name, :join => '-')),
185
+ subdirectory_for(Slug.for(name, :join => '_')),
186
+ ]
187
+ end
188
+
189
+ def get(name)
190
+ paths_for(name).each do |path|
191
+ next unless path.exist?
192
+
193
+ return node_for(path)
194
+ end
195
+
196
+ nil
197
+ end
198
+
199
+ def [](name)
200
+ get(name)
201
+ end
202
+
203
+ def slice(...)
204
+ subdirectories.slice(...).map{|subdirectory| node_for(subdirectory)}
205
+ end
206
+
207
+ def method_missing(name, *args, **kws, &block)
208
+ get(name) || super
209
+ end
210
+ end
211
+ end
data/lib/ro/config.rb ADDED
@@ -0,0 +1,72 @@
1
+ module Ro
2
+ class Config < ::Map
3
+ def Config.defaults
4
+ {
5
+ :root =>
6
+ (Ro.env.root || Ro.defaults.root),
7
+
8
+ :build =>
9
+ (Ro.env.build || Ro.defaults.build),
10
+
11
+ :url =>
12
+ (Ro.env.url || Ro.defaults.url),
13
+
14
+ :img_url =>
15
+ (Ro.env.img_url || Ro.defaults.img_url),
16
+
17
+ :page_size =>
18
+ (Ro.env.page_size || Ro.defaults.page_size),
19
+
20
+ :log =>
21
+ (Ro.env.log || Ro.defaults.log),
22
+
23
+ :debug =>
24
+ (Ro.env.debug || Ro.defaults.debug),
25
+
26
+ :port =>
27
+ (Ro.env.port || Ro.defaults.port),
28
+
29
+ :md_theme =>
30
+ (Ro.env.md_theme || Ro.defaults.md_theme),
31
+ }
32
+ end
33
+
34
+ def initialize(*args, **kws)
35
+ configure!(Config.defaults)
36
+
37
+ args.each do |arg|
38
+ configure!(arg) if arg.is_a?(Hash)
39
+ end
40
+
41
+ configure!(kws)
42
+ end
43
+
44
+ def configure!(hash)
45
+ hash.each do |key, value|
46
+ send("#{ key }=", value)
47
+ end
48
+ end
49
+
50
+ {
51
+ :root => :root,
52
+ :build => :path,
53
+ :url => :url,
54
+ :img_url => :url,
55
+ :page_size => :int,
56
+ :log => :bool,
57
+ :debug => :bool,
58
+ :port => :int,
59
+ :md_theme => :string,
60
+ }.each do |attribute, cast|
61
+ class_eval <<-____, __FILE__, __LINE__ + 1
62
+ def #{ attribute }
63
+ get :#{ attribute }
64
+ end
65
+
66
+ def #{ attribute }=(value)
67
+ set :#{ attribute }, Ro.cast(:#{ cast }, value)
68
+ end
69
+ ____
70
+ end
71
+ end
72
+ end
data/lib/ro/error.rb ADDED
@@ -0,0 +1,11 @@
1
+ module Ro
2
+ class Error < ::StandardError
3
+ attr_reader :context
4
+
5
+ def initialize(message, **context)
6
+ @context = context
7
+ msg = context.empty? ? "#{ message }" : "#{ message }, #{ context.inspect }"
8
+ super(msg)
9
+ end
10
+ end
11
+ end
data/lib/ro/html.rb ADDED
@@ -0,0 +1,23 @@
1
+ module Ro
2
+ require_relative 'html_safe'
3
+
4
+ class HTML < ::ActiveSupport::SafeBuffer
5
+ def initialize(*args, **kws, &block)
6
+ self.front_matter = kws.fetch(:front_matter){ {} }
7
+
8
+ super(args.join)
9
+ end
10
+
11
+ def front_matter
12
+ @front_matter ||= Map.new
13
+ end
14
+
15
+ def front_matter=(hash = {})
16
+ @front_matter = Map.for(hash)
17
+ end
18
+
19
+ def attributes
20
+ front_matter
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,143 @@
1
+ # frozen_string_literal: true
2
+
3
+ unless defined?(ActiveSupport::SafeBuffer)
4
+
5
+ class Object
6
+ def html_safe?
7
+ false
8
+ end
9
+ end
10
+
11
+ class Numeric
12
+ def html_safe?
13
+ true
14
+ end
15
+ end
16
+
17
+ module ActiveSupport #:nodoc:
18
+ class SafeBuffer < String
19
+ UNSAFE_STRING_METHODS = %w(
20
+ capitalize chomp chop delete downcase gsub lstrip next reverse rstrip
21
+ slice squeeze strip sub succ swapcase tr tr_s upcase
22
+ )
23
+
24
+ alias_method :original_concat, :concat
25
+ private :original_concat
26
+
27
+ # Raised when <tt>ActiveSupport::SafeBuffer#safe_concat</tt> is called on unsafe buffers.
28
+ class SafeConcatError < StandardError
29
+ def initialize
30
+ super "Could not concatenate to the buffer because it is not html safe."
31
+ end
32
+ end
33
+
34
+ def [](*args)
35
+ if args.size < 2
36
+ super
37
+ elsif html_safe?
38
+ new_safe_buffer = super
39
+
40
+ if new_safe_buffer
41
+ new_safe_buffer.instance_variable_set :@html_safe, true
42
+ end
43
+
44
+ new_safe_buffer
45
+ else
46
+ to_str[*args]
47
+ end
48
+ end
49
+
50
+ def safe_concat(value)
51
+ raise SafeConcatError unless html_safe?
52
+ original_concat(value)
53
+ end
54
+
55
+ def initialize(str = "")
56
+ @html_safe = true
57
+ super
58
+ end
59
+
60
+ def initialize_copy(other)
61
+ super
62
+ @html_safe = other.html_safe?
63
+ end
64
+
65
+ def clone_empty
66
+ self[0, 0]
67
+ end
68
+
69
+ def concat(value)
70
+ super(html_escape_interpolated_argument(value))
71
+ end
72
+ alias << concat
73
+
74
+ def prepend(value)
75
+ super(html_escape_interpolated_argument(value))
76
+ end
77
+
78
+ def +(other)
79
+ dup.concat(other)
80
+ end
81
+
82
+ def %(args)
83
+ case args
84
+ when Hash
85
+ escaped_args = Hash[args.map { |k, arg| [k, html_escape_interpolated_argument(arg)] }]
86
+ else
87
+ escaped_args = Array(args).map { |arg| html_escape_interpolated_argument(arg) }
88
+ end
89
+
90
+ self.class.new(super(escaped_args))
91
+ end
92
+
93
+ def html_safe?
94
+ defined?(@html_safe) && @html_safe
95
+ end
96
+
97
+ def to_s
98
+ self
99
+ end
100
+
101
+ def to_param
102
+ to_str
103
+ end
104
+
105
+ def encode_with(coder)
106
+ coder.represent_object nil, to_str
107
+ end
108
+
109
+ UNSAFE_STRING_METHODS.each do |unsafe_method|
110
+ if unsafe_method.respond_to?(unsafe_method)
111
+ class_eval <<-EOT, __FILE__, __LINE__ + 1
112
+ def #{unsafe_method}(*args, &block) # def capitalize(*args, &block)
113
+ to_str.#{unsafe_method}(*args, &block) # to_str.capitalize(*args, &block)
114
+ end # end
115
+
116
+ def #{unsafe_method}!(*args) # def capitalize!(*args)
117
+ @html_safe = false # @html_safe = false
118
+ super # super
119
+ end # end
120
+ EOT
121
+ end
122
+ end
123
+
124
+ private
125
+
126
+ def html_escape_interpolated_argument(arg)
127
+ (!html_safe? || arg.html_safe?) ? arg : CGI.escapeHTML(arg.to_s)
128
+ end
129
+ end
130
+ end
131
+
132
+ class String
133
+ # Marks a string as trusted safe. It will be inserted into HTML with no
134
+ # additional escaping performed. It is your responsibility to ensure that the
135
+ # string contains no malicious content. This method is equivalent to the
136
+ # +raw+ helper in views. It is recommended that you use +sanitize+ instead of
137
+ # this method. It should never be called on user input.
138
+ def html_safe
139
+ ActiveSupport::SafeBuffer.new(self)
140
+ end
141
+ end
142
+
143
+ end
data/lib/ro/klass.rb ADDED
@@ -0,0 +1,25 @@
1
+ module Ro
2
+ module Klass
3
+ module ClassMethods
4
+ def klass(arg, *args, **kws, &block)
5
+ return arg if arg.is_a?(self.class) && args.empty? && kws.empty? && block.nil?
6
+
7
+ new(arg, *args, **kws, &block)
8
+ end
9
+
10
+ alias for klass
11
+ end
12
+
13
+ module InstanceMethods
14
+ def klass
15
+ self.class
16
+ end
17
+ end
18
+
19
+ def Klass.included(other)
20
+ other.send(:extend, ClassMethods)
21
+ other.send(:include, InstanceMethods)
22
+ super
23
+ end
24
+ end
25
+ end