utopia-project 0.9.0 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b9fece64054731c29fd23e542e548d9da6e0105e62fef66fc6588b3a2637e5fe
4
- data.tar.gz: acda1b55bb672b64969496b9cc64f9e91c397ae3799c39af15417a3153d1628d
3
+ metadata.gz: b54d7f25350dab0e6b2ef5078f4dcfedf0420f7169785edea0b83e10363e1c7f
4
+ data.tar.gz: f497a26763c3dbea3d9c39fb6f632a89891410a13f3d8ac152be4a2b1b1df4d6
5
5
  SHA512:
6
- metadata.gz: b0a9ceb4a8ac9cf660ca05f8941874ef16dd724f438b3be44033b96177227327087855058accfa8a6f34bbd6370c02a41cfdacf215d3a5c2956267c38b25a7ba
7
- data.tar.gz: c8b1f67c938eb4af0ae6be43a027a7fdea622e4e0253e481f82107965d547acdf3e54477de0f2019382b672fd7f9b237ab46aaa2161d45830ed82afccd44ea1e
6
+ metadata.gz: 80431d065a3c9d5d4fa111626dd42cf243911d73198edf644acf8aed6399f9332567a08c28ff3f297f73477da652bd8ab8a11c168b748de1756a61b06021e6a8
7
+ data.tar.gz: 3592b185830475bc40aa8a04b3a4ce1eb2645c90d673a597ca92fa33368a55fe74c67964c013f9d24593a0011c1e87361dceac7e4a4ee2c568aedc9659277ef0
@@ -28,9 +28,9 @@ require 'decode'
28
28
 
29
29
  require 'thread/local'
30
30
 
31
- require 'kramdown'
32
-
31
+ require_relative 'document'
33
32
  require_relative 'guide'
33
+ require_relative 'linkify'
34
34
 
35
35
  module Utopia
36
36
  module Project
@@ -106,6 +106,16 @@ module Utopia
106
106
  end
107
107
  end
108
108
 
109
+ def linkify(text, definition, language: definition&.language)
110
+ rewriter = Linkify.new(self, language, text)
111
+
112
+ code = language.code_for(text, @index, relative_to: definition)
113
+
114
+ code.extract(rewriter)
115
+
116
+ return rewriter.apply
117
+ end
118
+
109
119
  # Format the given text in the context of the given definition and language.
110
120
  # See {document} for details.
111
121
  # @returns [Trenni::MarkupString]
@@ -126,41 +136,11 @@ module Utopia
126
136
 
127
137
  # Convert the given markdown text into HTML.
128
138
  #
129
- # - Updates source code references (`{language identifier}`) into links.
130
- # - Uses {Kramdown} to convert the text into HTML.
139
+ # Updates source code references (`{language identifier}`) into links.
131
140
  #
132
- # @returns [Kramdown::Document]
141
+ # @returns [Document]
133
142
  def document(text, definition = nil, language: definition&.language)
134
- text = text&.gsub(/(?<!`){(.*?)}/) do |match|
135
- linkify($1, definition, language: language)
136
- end
137
-
138
- return Kramdown::Document.new(text, syntax_highlighter: nil)
139
- end
140
-
141
- # Replace source code references in the given text with HTML anchors.
142
- #
143
- # @returns [Trenni::Builder]
144
- def linkify(text, definition = nil, language: definition&.language)
145
- reference = @index.languages.parse_reference(text, default_language: language)
146
-
147
- Trenni::Builder.fragment do |builder|
148
- if reference and definition = @index.lookup(reference, relative_to: definition)&.first
149
- builder.inline('a', href: link_for(definition)) do
150
- builder.inline('code', class: "language-#{definition.language.name}") do
151
- builder.text definition.qualified_form
152
- end
153
- end
154
- elsif reference
155
- builder.inline('code', class: "language-#{reference.language.name}") do
156
- builder.text text
157
- end
158
- else
159
- builder.inline('code') do
160
- builder.text text
161
- end
162
- end
163
- end
143
+ Document.new(text, self, definition: definition, default_language: language)
164
144
  end
165
145
 
166
146
  # Compute a unique string which can be used as `id` attribute in the HTML output.
@@ -0,0 +1,179 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright, 2020, by Samuel G. D. Williams. <http://www.codeotaku.com>
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ # THE SOFTWARE.
22
+
23
+ require 'commonmarker'
24
+
25
+ module Utopia
26
+ module Project
27
+ class Document
28
+ def initialize(text, base = nil, definition: nil, default_language: nil)
29
+ @text = text
30
+ @base = base
31
+ @index = base&.index
32
+
33
+ @definition = definition
34
+ @default_language = default_language
35
+
36
+ @root = nil
37
+ end
38
+
39
+ def root
40
+ @root ||= resolve(CommonMarker.render_doc(@text, [:DEFAULT]))
41
+ end
42
+
43
+ def first_child
44
+ self.root.first_child
45
+ end
46
+
47
+ def replace_section(name)
48
+ child = self.first_child
49
+
50
+ while child
51
+ if child.type == :header
52
+ header = child
53
+
54
+ # We found the matched header:
55
+ if header.first_child.string_content == name
56
+ # Now subsequent children:
57
+ current = header.next
58
+ while current.type != :header and following = current.next
59
+ current.delete
60
+ current = following
61
+ end
62
+
63
+ return yield(header)
64
+ end
65
+ end
66
+
67
+ child = child.next
68
+ end
69
+ end
70
+
71
+ def to_html(node = self.root)
72
+ Trenni::MarkupString.raw(node.to_html(:UNSAFE))
73
+ end
74
+
75
+ def paragraph_node(child)
76
+ node = CommonMarker::Node.new(:paragraph)
77
+ node.append_child(child)
78
+ return node
79
+ end
80
+
81
+ def html_node(content, type = :html)
82
+ node = CommonMarker::Node.new(:html)
83
+ node.string_content = content
84
+ return node
85
+ end
86
+
87
+ def inline_html_node(content)
88
+ node = CommonMarker::Node.new(:inline_html)
89
+ node.string_content = content
90
+ return node
91
+ end
92
+
93
+ def text_node(content)
94
+ node = CommonMarker::Node.new(:text)
95
+ node.string_content = content
96
+ return node
97
+ end
98
+
99
+ def link_node(title, url, child)
100
+ node = CommonMarker::Node.new(:link)
101
+ node.title = title
102
+ node.url = url.to_s
103
+
104
+ node.append_child(child)
105
+
106
+ return node
107
+ end
108
+
109
+ def code_node(content, language)
110
+ if language
111
+ node = inline_html_node(
112
+ "<code class=\"language-#{language}\">#{Trenni::Strings.to_html(content)}</code>"
113
+ )
114
+ else
115
+ node = CommonMarker::Node.new(:code)
116
+ node.string_content = content
117
+ return node
118
+ end
119
+
120
+ return node
121
+ end
122
+
123
+ private
124
+
125
+ # Replace source code references in the given text with HTML anchors.
126
+ #
127
+ def reference_node(content)
128
+ if reference = @index.languages.parse_reference(content, default_language: @default_language)
129
+ definition = @index.lookup(reference, relative_to: @definition)
130
+ end
131
+
132
+ if definition
133
+ link_node(reference.identifier, @base.link_for(definition),
134
+ code_node(definition.qualified_form, reference.language.name)
135
+ )
136
+ elsif reference
137
+ code_node(reference.identifier, reference.language.name)
138
+ else
139
+ code_node(content)
140
+ end
141
+ end
142
+
143
+ def resolve(root)
144
+ return root if @index.nil?
145
+
146
+ root.walk do |node|
147
+ if node.type == :text
148
+ content = node.string_content
149
+ offset = 0
150
+
151
+ while match = content.match(/{(?<reference>.*?)}/, offset)
152
+ a, b = match.offset(0)
153
+
154
+ if a > offset
155
+ node.insert_before(
156
+ text_node(content[offset...a])
157
+ )
158
+ end
159
+
160
+ node.insert_before(
161
+ reference_node(match[:reference])
162
+ )
163
+
164
+ offset = b
165
+ end
166
+
167
+ if offset == content.bytesize
168
+ node.delete
169
+ else
170
+ node.string_content = content[offset..-1]
171
+ end
172
+ end
173
+ end
174
+
175
+ return root
176
+ end
177
+ end
178
+ end
179
+ end
@@ -23,7 +23,6 @@
23
23
  require 'utopia/path'
24
24
  require 'trenni/reference'
25
25
  require 'decode'
26
- require 'kramdown'
27
26
 
28
27
  module Utopia
29
28
  module Project
@@ -64,25 +63,16 @@ module Utopia
64
63
  end
65
64
 
66
65
  # The document for the README, if one exists.
67
- # @returns [Kramdown::Document]
68
66
  def document
69
67
  if self.readme?
70
68
  @document ||= self.readme_document.tap do |document|
71
- root = document.root
72
- if element = root.children.first
73
- if element.type == :header
74
- @title = element.children.first.value
75
-
76
- # Remove the title:
77
- root.children.shift
78
-
79
- # Remove any blank lines:
80
- root.children.shift while root.children.first&.type == :blank
81
-
82
- # Read the description:
83
- root.children.first.options[:encoding] = root.options[:encoding]
84
- @description = Kramdown::Converter::Kramdown.convert(root.children.first).first
85
- end
69
+ child = document.first_child
70
+
71
+ if child.type == :header
72
+ @title = child.first_child.string_content
73
+
74
+ @description = child.next
75
+ child.delete
86
76
  end
87
77
  end
88
78
  end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright, 2020, by Samuel G. D. Williams. <http://www.codeotaku.com>
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ # THE SOFTWARE.
22
+
23
+ require 'decode/syntax/rewriter'
24
+
25
+ module Utopia
26
+ module Project
27
+ class Linkify < Decode::Syntax::Rewriter
28
+ # @parameter base [Base] The base data.
29
+ def initialize(base, language, text)
30
+ @base = base
31
+ @language = language
32
+
33
+ super(text)
34
+ end
35
+
36
+ def text_for(range)
37
+ text = super(range)
38
+
39
+ return Trenni::Strings.to_html(text)
40
+ end
41
+
42
+ def link_to(definition, text)
43
+ Trenni::Builder.fragment do |builder|
44
+ builder.inline('a', href: @base.link_for(definition)) do
45
+ builder.text(definition.qualified_name)
46
+ end
47
+ end
48
+ end
49
+
50
+ def apply(output = Trenni::Builder.new)
51
+ output.inline('code', class: "language-#{@language.name}") do
52
+ super
53
+ end
54
+
55
+ return output.to_str
56
+ end
57
+ end
58
+ end
59
+ end
@@ -22,6 +22,6 @@
22
22
 
23
23
  module Utopia
24
24
  module Project
25
- VERSION = "0.9.0"
25
+ VERSION = "0.10.0"
26
26
  end
27
27
  end
data/pages/_usage.xnode CHANGED
@@ -12,7 +12,7 @@
12
12
  <?r if documentation = guide.documentation ?>
13
13
  #{base.format(documentation.text, language: guide.documentation.language)}
14
14
  <?r elsif description = guide.description ?>
15
- #{MarkupString.raw Kramdown::Document.new(description, syntax_highlighter: nil).to_html}
15
+ #{MarkupString.raw description.to_html}
16
16
  <?r else ?>
17
17
  <p>No description.</p>
18
18
  <?r end ?>
data/pages/controller.rb CHANGED
@@ -5,21 +5,10 @@ on 'index' do
5
5
  @base = Utopia::Project::Base.instance
6
6
 
7
7
  if readme_path = @base.path_for('README.md')
8
- @document = Kramdown::Document.new(File.read(readme_path), syntax_highlighter: nil)
8
+ @document = Utopia::Project::Document.new(File.read(readme_path), @base)
9
9
 
10
- start = @document.root.children.index{|node| node.children.first&.value == "Usage"}
11
- finish = start + 1
12
-
13
- while node = @document.root.children[finish]
14
- if node.type == :header
15
- break
16
- else
17
- finish += 1
18
- end
10
+ @document.replace_section("Usage") do |header|
11
+ header.insert_after(@document.html_node("<content:usage/>"))
19
12
  end
20
-
21
- @document.root.children[start...finish] = [
22
- Kramdown::Element.new(:raw, "<content:usage/>")
23
- ]
24
13
  end
25
14
  end
data/pages/index.xnode CHANGED
@@ -1,17 +1,18 @@
1
1
  <content:page>
2
2
  <?r
3
3
  if document = self[:document]
4
- children = document.root.children
4
+ child = document.first_child
5
5
 
6
- if children.first.type == :header
7
- header = children.shift
8
- title = header.children.first
6
+ if child.type == :header
7
+ header = child
8
+ child.delete
9
+ title = header.first_child
10
+
9
11
  case title.type
10
12
  when :text
11
- ?><content:heading>#{title.value}</content:heading><?r
13
+ ?><content:heading>#{title.string_content}</content:heading><?r
12
14
  when :img
13
- self.document.attributes[:title] ||= title.attr["alt"]
14
- ?><header><img src="#{title.attr["src"]}" /></header><?r
15
+ ?><header><img src="#{title.src}" /></header><?r
15
16
  else
16
17
  ?><content:heading>Project</content:heading><?r
17
18
  end
@@ -9,8 +9,8 @@ Syntax.lib.camelCaseType={pattern:/\b_*[A-Z][\w]*\b/g,klass:"type"};Syntax.lib.c
9
9
  Syntax.lib.doubleQuotedString={pattern:/"([^\\"\n]|\\.)*"/g,klass:"string"};Syntax.lib.singleQuotedString={pattern:/'([^\\'\n]|\\.)*'/g,klass:"string"};Syntax.lib.multiLineDoubleQuotedString={pattern:/"([^\\"]|\\.)*"/g,klass:"string"};Syntax.lib.multiLineSingleQuotedString={pattern:/'([^\\']|\\.)*'/g,klass:"string"};Syntax.lib.stringEscape={pattern:/\\./g,klass:"escape",only:["string"]};
10
10
  Syntax.Match=function(b,a,c,d){this.offset=b;this.endOffset=b+a;this.length=a;this.expression=c;this.value=d;this.children=[];this.next=this.parent=null};Syntax.Match.prototype.shift=function(b,a){this.adjust(b,null,a);for(var c=0;c<this.children.length;c++)this.children[c].shift(b,a)};Syntax.Match.prototype.adjust=function(b,a,c){this.offset+=b;this.endOffset+=b;a&&(this.length=a,this.endOffset=this.offset+a);c&&(this.value=c.substr(this.offset,this.length))};
11
11
  Syntax.Match.sort=function(b,a){return b.offset-a.offset||a.length-b.length};Syntax.Match.prototype.contains=function(b){return b.offset>=this.offset&&b.endOffset<=this.endOffset};Syntax.Match.defaultReduceCallback=function(b,a){"string"===typeof b&&(b=document.createTextNode(b));a.appendChild(b)};
12
- Syntax.Match.prototype.reduce=function(b,a){var c=this.offset,d=document.createElement("span");b=b||Syntax.Match.defaultReduceCallback;this.expression&&this.expression.klass&&(0<d.className.length&&(d.className+=" "),d.className+=this.expression.klass);this.className&&(d.className+=" ",d.className+=this.className);for(var e=0;e<this.children.length;e+=1){var f=this.children[e],g=f.offset;f.offset<this.offset&&console.log("Syntax Warning: Offset of child",f,"is before offset of parent",this);c=this.value.substr(c-
13
- this.offset,g-c);b(c,d);b(f.reduce(b,a),d);c=f.endOffset}c===this.offset?b(this.value,d):c<this.endOffset?b(this.value.substr(c-this.offset,this.endOffset-c),d):c>this.endOffset&&console.log("Syntax Warning: Start position "+c+" exceeds end of value "+this.endOffset);a&&(d=a(d,this));return d};
12
+ Syntax.Match.prototype.reduce=function(b,a){var c=this.offset;var d=this.expression&&this.expression.element?this.expression.element.cloneNode(!1):document.createElement("span");b=b||Syntax.Match.defaultReduceCallback;this.expression&&this.expression.klass&&(0<d.className.length&&(d.className+=" "),d.className+=this.expression.klass);this.className&&(d.className+=" ",d.className+=this.className);for(var e=0;e<this.children.length;e+=1){var f=this.children[e],g=f.offset;f.offset<this.offset&&console.log("Syntax Warning: Offset of child",
13
+ f,"is before offset of parent",this);c=this.value.substr(c-this.offset,g-c);b(c,d);b(f.reduce(b,a),d);c=f.endOffset}c===this.offset?b(this.value,d):c<this.endOffset?b(this.value.substr(c-this.offset,this.endOffset-c),d):c>this.endOffset&&console.log("Syntax Warning: Start position "+c+" exceeds end of value "+this.endOffset);a&&(d=a(d,this));return d};
14
14
  Syntax.Match.prototype.canContain=function(b){return b.expression.force?!0:this.complete?!1:b.expression.only?!0:"undefined"===typeof this.expression.allow||jQuery.isArray(this.expression.disallow)&&-1!==jQuery.inArray(b.expression.klass,this.expression.disallow)?!1:"*"===this.expression.allow||jQuery.isArray(this.expression.allow)&&-1!==jQuery.inArray(b.expression.klass,this.expression.allow)?!0:!1};
15
15
  Syntax.Match.prototype.canHaveChild=function(b){if(b=b.expression.only){for(var a=this;null!==a;){if(-1!==jQuery.inArray(a.expression.klass,b))return!0;if((a=a.parent)&&a.complete)break}return!1}return!0};Syntax.Match.prototype._splice=function(b,a){return this.canHaveChild(a)?(this.children.splice(b,0,a),a.parent=this,a.expression.owner||(a.expression.owner=this.expression.owner),this):null};
16
16
  Syntax.Match.prototype.insert=function(b,a){if(!this.contains(b))return null;if(a){a=this;for(var c=0;c<a.children.length;)a.children[c].contains(b)?(a=a.children[c],c=0):c+=1;return a._insertWhole(b)}return this._insert(b)};