d-mark 0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 2e6f0ea7fb496bb3aadc7ea266b4a0a7650eaa05
4
+ data.tar.gz: 80049f389cec0e03ecf36c7d91320f99d2e45c89
5
+ SHA512:
6
+ metadata.gz: 65ccc586b328445e4b76e4b6c0d020055b00c209f73a6c392449b47cc4ec14aef7c3d3a4ac2e72769ca34ba270fa0855148363eb1a5d046ddb248ee865e5e079
7
+ data.tar.gz: 1854baf6627c1c856255d855cc0f1e4b3dcac4b273843b64c205fc0c3902e5f651b31b152598a26618225a6b21ed89e80811769c77d372f8874203a2ab80314a
data/Gemfile ADDED
@@ -0,0 +1,11 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ group :devel do
6
+ gem 'guard-rake'
7
+ gem 'rake'
8
+ gem 'rspec'
9
+ gem 'rubocop'
10
+ gem 'yard'
11
+ end
@@ -0,0 +1,84 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ dmark (0.1)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ ast (2.2.0)
10
+ coderay (1.1.0)
11
+ diff-lcs (1.2.5)
12
+ ffi (1.9.10)
13
+ formatador (0.2.5)
14
+ guard (2.13.0)
15
+ formatador (>= 0.2.4)
16
+ listen (>= 2.7, <= 4.0)
17
+ lumberjack (~> 1.0)
18
+ nenv (~> 0.1)
19
+ notiffany (~> 0.0)
20
+ pry (>= 0.9.12)
21
+ shellany (~> 0.0)
22
+ thor (>= 0.18.1)
23
+ guard-rake (1.0.0)
24
+ guard
25
+ rake
26
+ listen (3.0.5)
27
+ rb-fsevent (>= 0.9.3)
28
+ rb-inotify (>= 0.9)
29
+ lumberjack (1.0.10)
30
+ method_source (0.8.2)
31
+ nenv (0.2.0)
32
+ notiffany (0.0.8)
33
+ nenv (~> 0.1)
34
+ shellany (~> 0.0)
35
+ parser (2.3.0.2)
36
+ ast (~> 2.2)
37
+ powerpack (0.1.1)
38
+ pry (0.10.3)
39
+ coderay (~> 1.1.0)
40
+ method_source (~> 0.8.1)
41
+ slop (~> 3.4)
42
+ rainbow (2.1.0)
43
+ rake (10.5.0)
44
+ rb-fsevent (0.9.7)
45
+ rb-inotify (0.9.5)
46
+ ffi (>= 0.5.0)
47
+ rspec (3.4.0)
48
+ rspec-core (~> 3.4.0)
49
+ rspec-expectations (~> 3.4.0)
50
+ rspec-mocks (~> 3.4.0)
51
+ rspec-core (3.4.2)
52
+ rspec-support (~> 3.4.0)
53
+ rspec-expectations (3.4.0)
54
+ diff-lcs (>= 1.2.0, < 2.0)
55
+ rspec-support (~> 3.4.0)
56
+ rspec-mocks (3.4.1)
57
+ diff-lcs (>= 1.2.0, < 2.0)
58
+ rspec-support (~> 3.4.0)
59
+ rspec-support (3.4.1)
60
+ rubocop (0.36.0)
61
+ parser (>= 2.3.0.0, < 3.0)
62
+ powerpack (~> 0.1)
63
+ rainbow (>= 1.99.1, < 3.0)
64
+ ruby-progressbar (~> 1.7)
65
+ ruby-progressbar (1.7.5)
66
+ shellany (0.0.1)
67
+ slop (3.6.0)
68
+ thor (0.19.1)
69
+ yard (0.8.7.6)
70
+
71
+ PLATFORMS
72
+ ruby
73
+
74
+ DEPENDENCIES
75
+ bundler (>= 1.11.2, < 2.0)
76
+ dmark!
77
+ guard-rake
78
+ rake
79
+ rspec
80
+ rubocop
81
+ yard
82
+
83
+ BUNDLED WITH
84
+ 1.11.2
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2016 Denis Defreyne and contributors
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
data/NEWS.md ADDED
@@ -0,0 +1,7 @@
1
+ # D★Mark news
2
+
3
+ ## 0.1 (???)
4
+
5
+ Features:
6
+
7
+ * Initial release
@@ -0,0 +1,70 @@
1
+ D★Mark
2
+ ======
3
+
4
+ **Status:** experimental — use at your own risk!
5
+
6
+ _D★Mark_ is a markup language for writing text.
7
+
8
+ It is aimed at being able to write semantically meaningful text without limiting itself to the semantics provided by HTML or Markdown.
9
+
10
+ ## Usage
11
+
12
+ Handling a D★Mark file consists of three stages: lexing, parsing, and translating.
13
+
14
+ The lexing stage converts the data into a stream of tokens. Construct a lexer with the data as input, and call `#run` to get the tokens, catching any `DMark::Lexer::LexerError`:
15
+
16
+ begin
17
+ tokens = DMark::Lexer.new(File.read(ARGV[0])).run
18
+ rescue DMark::Lexer::LexerError => e
19
+ $stderr.puts e.message_for_tty
20
+ exit 1
21
+ end
22
+
23
+ The parsing stage converts the stream of tokens into a node tree. Construct a parser with the tokens as input, and call `#run` to get the tree.
24
+
25
+ tree = DMark::Parser.new(tokens).run
26
+
27
+ The translating stage is not the responsibility of D★Mark. A translator is part of the domain of the source text, and D★Mark only deals with syntax rather than semantics. A translator will run over the tree and convert it into something else (usually another string). To do so, handle each node type (`RootNode`, `TextNode`, `ElementNode`). For example, the following translator will convert the tree into something that resembles XML:
28
+
29
+ class MyXMLLikeTranslator < DMark::Translator
30
+ def handle(node)
31
+ case node
32
+ when DMark::Nodes::RootNode
33
+ handle_children(node)
34
+ when DMark::Nodes::TextNode
35
+ out << node.text
36
+ when DMark::Nodes::ElementNode
37
+ out << "<#{node.name}>"
38
+ handle_children(node)
39
+ out << "</#{node.name}>"
40
+ end
41
+ end
42
+ end
43
+
44
+ result = MyXMLLikeTranslator.new(tree).run
45
+ puts result
46
+
47
+ ## Samples
48
+
49
+ The `samples/` directory contains some sample D★Mark files. They can be converted to HTML by running the `scripts/translate-to-html.rb` Ruby script, passing in the name of the file. The resulting HTML will be printed to standard output. For example:
50
+
51
+ ruby scripts/translate-to-html.rb samples/identifiers-and-patterns.dmark
52
+
53
+ ## Format
54
+
55
+ _D★Mark_ knows two constructs:
56
+
57
+ * Block-level elements. For example:
58
+
59
+ p. Patterns are used to find items and layouts based on their identifier. They come in three varieties.
60
+
61
+ * Inline elements. For example:
62
+
63
+ p. Identifiers come in two types: the %emph{full} type, new in Nanoc 4, and the %emph{legacy} type, used in Nanoc 3.
64
+
65
+ Block-level elements can be nested. For example:
66
+
67
+ ul.
68
+ li. glob patterns
69
+ li. regular expression patterns
70
+ li. legacy patterns
@@ -0,0 +1,3 @@
1
+ Rake.add_rakelib 'tasks'
2
+
3
+ task default: [:test, :rubocop]
@@ -0,0 +1,26 @@
1
+ require_relative 'lib/dmark/version'
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = 'd-mark'
5
+ s.version = DMark::VERSION
6
+ s.homepage = 'http://rubygems.org/gems/d-mark'
7
+ s.summary = 'markup language for writing text'
8
+ s.description = 'D★Mark is a markup language aimed at being able to write semantically meaningful text without limiting itself to the semantics provided by HTML or Markdown.'
9
+
10
+ s.author = 'Denis Defreyne'
11
+ s.email = 'denis.defreyne@stoneship.org'
12
+ s.license = 'MIT'
13
+
14
+ s.files =
15
+ Dir['[A-Z]*'] +
16
+ Dir['{bin,lib,tasks,spec,samples,scripts}/**/*'] +
17
+ ['d-mark.gemspec']
18
+ s.require_paths = ['lib']
19
+
20
+ s.rdoc_options = ['--main', 'README.md']
21
+ s.extra_rdoc_files = ['LICENSE', 'README.md', 'NEWS.md']
22
+
23
+ s.required_ruby_version = '>= 2.1.0'
24
+
25
+ s.add_development_dependency('bundler', '>= 1.11.2', '< 2.0')
26
+ end
@@ -0,0 +1,9 @@
1
+ module DMark
2
+ end
3
+
4
+ require_relative 'dmark/lexer'
5
+ require_relative 'dmark/nodes'
6
+ require_relative 'dmark/parser'
7
+ require_relative 'dmark/tokens'
8
+ require_relative 'dmark/translator'
9
+ require_relative 'dmark/version'
@@ -0,0 +1,235 @@
1
+ module DMark
2
+ class Lexer
3
+ INDENTATION = 2
4
+
5
+ def initialize(string)
6
+ @string = string
7
+
8
+ @element_stack = []
9
+ @tokens = []
10
+ @pending_blanks = 0
11
+ end
12
+
13
+ def run
14
+ @string.lines.each_with_index do |line, line_nr|
15
+ case line
16
+ when /^\s+$/
17
+ # blank line
18
+ @pending_blanks += 1
19
+ when /^(\s*)([a-z0-9-]+)(\[(.*?)\])?\.\s*$/
20
+ # empty element
21
+ indentation = Regexp.last_match[1]
22
+ element = Regexp.last_match[2]
23
+ attributes = parse_attributes(Regexp.last_match[4])
24
+
25
+ unwind_stack_until(indentation.size)
26
+
27
+ @element_stack << element
28
+ @tokens << DMark::Tokens::TagBeginToken.new(name: element, attributes: attributes)
29
+ when /^(\s*)([a-z0-9-]+)(\[(.*?)\])?\. (.*)$/
30
+ # element with inline content
31
+ indentation = Regexp.last_match[1]
32
+ element = Regexp.last_match[2]
33
+ attributes = parse_attributes(Regexp.last_match[4])
34
+ data = Regexp.last_match[5]
35
+
36
+ unwind_stack_until(indentation.size)
37
+
38
+ @tokens << DMark::Tokens::TagBeginToken.new(name: element, attributes: attributes)
39
+ @tokens.concat(lex_inline(data, line_nr + 1))
40
+ @tokens << DMark::Tokens::TagEndToken.new(name: element)
41
+ when /^(\s*)(.*)$/
42
+ # other line (e.g. data)
43
+ indentation = Regexp.last_match[1]
44
+ data = Regexp.last_match[2]
45
+
46
+ unwind_stack_until(indentation.size)
47
+
48
+ if @element_stack.empty?
49
+ # FIXME: unify format of messages (uppercase, lowercase, …)
50
+ raise LexerError.new("Can’t insert raw data at root level", line, line_nr, 1)
51
+ end
52
+
53
+ extra_indentation = [indentation.size - INDENTATION * @element_stack.size, 0].max
54
+
55
+ @tokens.concat(lex_inline(' ' * extra_indentation + data + "\n", line_nr + 1))
56
+ end
57
+ end
58
+
59
+ unwind_stack_until(0)
60
+
61
+ @tokens
62
+ end
63
+
64
+ private
65
+
66
+ def parse_attributes(data)
67
+ # FIXME: write a proper parser
68
+
69
+ (data || '').split(',').map { |part| part.split('=') }.each_with_object({}) do |pair, res|
70
+ res[pair.first] = pair.last || pair.first
71
+ end
72
+ end
73
+
74
+ def unwind_stack_until(num)
75
+ while @element_stack.size * INDENTATION > num
76
+ elem = @element_stack.pop
77
+
78
+ @tokens << DMark::Tokens::TagEndToken.new(name: elem)
79
+ end
80
+
81
+ append_text(@tokens, "\n" * @pending_blanks)
82
+ @pending_blanks = 0
83
+ end
84
+
85
+ def append_text(out, text)
86
+ if out.empty? || !out.last.is_a?(DMark::Tokens::TextToken)
87
+ out << DMark::Tokens::TextToken.new(text: text)
88
+ else
89
+ out.last.text << text
90
+ end
91
+ end
92
+
93
+ class LexerError < StandardError
94
+ def initialize(message, line, line_nr, col_nr)
95
+ @message = message
96
+ @line = line
97
+ @line_nr = line_nr
98
+ @col_nr = col_nr
99
+ end
100
+
101
+ class Coloriser
102
+ def red
103
+ "\e[31m".freeze
104
+ end
105
+
106
+ def bold
107
+ "\e[1m".freeze
108
+ end
109
+
110
+ def reset
111
+ "\e[0m".freeze
112
+ end
113
+ end
114
+
115
+ class NullColoriser
116
+ def red
117
+ ''.freeze
118
+ end
119
+
120
+ def bold
121
+ ''.freeze
122
+ end
123
+
124
+ def reset
125
+ ''.freeze
126
+ end
127
+ end
128
+
129
+ def message
130
+ formatted_message(NullColoriser.new)
131
+ end
132
+
133
+ def message_for_tty
134
+ formatted_message(Coloriser.new)
135
+ end
136
+
137
+ def formatted_message(coloriser)
138
+ line_excerpt_start = [@col_nr - 38, 0].max
139
+ line_excerpt_end = @col_nr + 38
140
+ line_excerpt = @line[line_excerpt_start..line_excerpt_end]
141
+
142
+ if line_excerpt_start > 0
143
+ line_excerpt[0] = '…'
144
+ end
145
+
146
+ if line_excerpt_end < @line.size
147
+ line_excerpt[-1] = '…'
148
+ end
149
+
150
+ [
151
+ "#{coloriser.red}#{coloriser.bold}ERROR#{coloriser.reset} (line #{@line_nr}, col #{@col_nr}): #{coloriser.red}#{@message}#{coloriser.reset}",
152
+ '',
153
+ line_excerpt,
154
+ coloriser.red + ' ' * (@col_nr - 1 - line_excerpt_start) + '^' + coloriser.reset,
155
+ '',
156
+ ].join("\n")
157
+ end
158
+ end
159
+
160
+ def lex_inline(string, line_nr)
161
+ stack = []
162
+ state = :root
163
+ tokens = []
164
+ name = ''
165
+ attributes = ''
166
+ col_nr = 0
167
+
168
+ string.chars.each_with_index do |char|
169
+ col_nr += 1
170
+
171
+ case state
172
+ when :root
173
+ case char
174
+ when '%'
175
+ state = :after_pct
176
+ when '}'
177
+ if stack.empty?
178
+ message = 'Unexpected `}`. Try escaping it as `%}`.'
179
+ raise LexerError.new(message, string, line_nr, col_nr)
180
+ else
181
+ data = stack.pop
182
+ case data.first
183
+ when :raw
184
+ append_text(tokens, data.last)
185
+ when :elem
186
+ tokens << DMark::Tokens::TagEndToken.new(name: data.last)
187
+ else
188
+ raise "Unexpected entry on stack: #{data.inspect}"
189
+ end
190
+ end
191
+ else
192
+ append_text(tokens, char)
193
+ end
194
+ when :after_pct
195
+ # FIXME: require at least one character after %
196
+
197
+ case char
198
+ when 'a'..'z', '0'..'9', '-'
199
+ name << char
200
+ when '%' # escaped
201
+ state = :root
202
+ col_nr -= 1
203
+ append_text(tokens, '%')
204
+ when '}' # escaped
205
+ state = :root
206
+ col_nr -= 1
207
+ append_text(tokens, '}')
208
+ when '['
209
+ state = :after_lbracket
210
+ when '{'
211
+ state = :root
212
+ stack << [:elem, name]
213
+ tokens << DMark::Tokens::TagBeginToken.new(name: name, attributes: parse_attributes(attributes))
214
+ name = ''
215
+ attributes = ''
216
+ else
217
+ raise LexerError.new("unexpected `#{char}` after `%`", string, line_nr, col_nr)
218
+ end
219
+ when :after_lbracket
220
+ case char
221
+ when ']'
222
+ # FIXME: might make sense to have after_rbracket instead (to prevent %foo[a][b]{…})
223
+ state = :after_pct
224
+ else
225
+ attributes << char
226
+ end
227
+ else
228
+ raise "Unexpected state: #{state.inspect}"
229
+ end
230
+ end
231
+
232
+ tokens
233
+ end
234
+ end
235
+ end
@@ -0,0 +1,76 @@
1
+ module DMark
2
+ module Nodes
3
+ class Node
4
+ attr_reader :children
5
+
6
+ def initialize
7
+ @children = []
8
+ end
9
+
10
+ def inspect(_indent = 0)
11
+ 'Node()'
12
+ end
13
+ end
14
+
15
+ class RootNode < Node
16
+ def inspect(indent = 0)
17
+ io = ''
18
+ io << ' ' * indent
19
+ io << 'Root('
20
+ io << "\n" if children.any?
21
+ children.each { |c| io << c.inspect(indent + 1) }
22
+ io << ' ' * indent if children.any?
23
+ io << ')'
24
+ io << "\n"
25
+ io
26
+ end
27
+ end
28
+
29
+ class TextNode < Node
30
+ attr_reader :text
31
+
32
+ def initialize(text:)
33
+ super()
34
+ @text = text
35
+ end
36
+
37
+ def inspect(indent = 0)
38
+ io = ''
39
+ io << ' ' * indent
40
+ io << 'Text('
41
+ io << @text.inspect
42
+ io << "\n" if children.any?
43
+ children.each { |c| io << c.inspect(indent + 1) }
44
+ io << ' ' * indent if children.any?
45
+ io << ')'
46
+ io << "\n"
47
+ io
48
+ end
49
+ end
50
+
51
+ class ElementNode < Node
52
+ attr_reader :name
53
+ attr_reader :attributes
54
+
55
+ def initialize(name:, attributes:)
56
+ super()
57
+ @name = name
58
+ @attributes = attributes
59
+ end
60
+
61
+ def inspect(indent = 0)
62
+ io = ''
63
+ io << ' ' * indent
64
+ io << 'Element('
65
+ io << @name
66
+ io << ',' << @attributes.inspect unless @attributes.empty?
67
+ io << "\n" if children.any?
68
+ children.each { |c| io << c.inspect(indent + 1) }
69
+ io << ' ' * indent if children.any?
70
+ io << ')'
71
+ io << "\n"
72
+ io
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,28 @@
1
+ module DMark
2
+ class Parser
3
+ def initialize(tokens)
4
+ @tokens = tokens
5
+
6
+ @root_node = DMark::Nodes::RootNode.new
7
+ end
8
+
9
+ def run
10
+ node_stack = [@root_node]
11
+
12
+ @tokens.each do |token|
13
+ case token
14
+ when DMark::Tokens::TextToken
15
+ node_stack.last.children << DMark::Nodes::TextNode.new(text: token.text)
16
+ when DMark::Tokens::TagBeginToken
17
+ new_node = DMark::Nodes::ElementNode.new(name: token.name, attributes: token.attributes)
18
+ node_stack.last.children << new_node
19
+ node_stack.push(new_node)
20
+ when DMark::Tokens::TagEndToken
21
+ node_stack.pop
22
+ end
23
+ end
24
+
25
+ @root_node
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,49 @@
1
+ module DMark
2
+ module Tokens
3
+ class Token
4
+ def to_s
5
+ raise NotImplementedError
6
+ end
7
+ end
8
+
9
+ class TextToken < Token
10
+ attr_reader :text
11
+
12
+ def initialize(text:)
13
+ @text = text
14
+ end
15
+
16
+ def to_s
17
+ "Text(#{@text.inspect})"
18
+ end
19
+ end
20
+
21
+ class AbstractTagToken < Token
22
+ attr_reader :name
23
+
24
+ def initialize(name:)
25
+ @name = name
26
+ end
27
+ end
28
+
29
+ class TagBeginToken < AbstractTagToken
30
+ attr_reader :attributes
31
+
32
+ def initialize(name:, attributes:)
33
+ super(name: name)
34
+
35
+ @attributes = attributes
36
+ end
37
+
38
+ def to_s
39
+ "TagBegin(#{name.inspect}, #{attributes.inspect})"
40
+ end
41
+ end
42
+
43
+ class TagEndToken < AbstractTagToken
44
+ def to_s
45
+ "TagEnd(#{name.inspect})"
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,26 @@
1
+ module DMark
2
+ class Translator
3
+ attr_reader :out
4
+
5
+ def initialize(tree)
6
+ @tree = tree
7
+
8
+ @out = ''
9
+ end
10
+
11
+ def run
12
+ handle(@tree)
13
+ @out
14
+ end
15
+
16
+ private
17
+
18
+ def handle(_node)
19
+ raise NotImplementedError
20
+ end
21
+
22
+ def handle_children(node)
23
+ node.children.each { |child| handle(child) }
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,3 @@
1
+ module DMark
2
+ VERSION = '0.1'.freeze
3
+ end
@@ -0,0 +1,122 @@
1
+ p. In Nanoc, every item (page or asset) and every layout has a unique %firstterm{identifier}: a string derived from the file’s path. A %firstterm{pattern} is an expression that is used to select items or layouts based on their identifier.
2
+
3
+ h2. Identifiers
4
+
5
+ p. Identifiers come in two types: the %emph{full} type, new in Nanoc 4, and the %emph{legacy} type, used in Nanoc 3.
6
+
7
+ dl.
8
+ dt. full
9
+ dd. An identifier with the full type is the filename, with the path to the content directory removed. For example, the file %filename{/Users/denis/stoneship/content/about.md} will have the full identifier %identifier{/about.md}.
10
+
11
+ dt. legacy
12
+ dd. An identifier with the legacy type is the filename, with the path to the content directory removed, the extension removed, and a slash appended. For example, the file %filename{/Users/denis/stoneship/content/about.md} will have the legacy identifier %identifier{/about/}. This corresponds closely with paths in clean URLs.
13
+
14
+ p. The following methods are useful for full identifiers:
15
+
16
+ dl.
17
+ dt. %code{identifier.without_ext} → %class{String}
18
+ dd. identifier with the last extension removed
19
+
20
+ dt. %code{identifier.without_exts} → %class{String}
21
+ dd. identifier with all extensions removed
22
+
23
+ dt. %code{identifier.ext} → %class{String}
24
+ dd. the last extension of this identifier
25
+
26
+ dt. %code{identifier.exts} → %class{String}
27
+ dd. all extensions of this identifier
28
+
29
+ dt. %code{identifier + string} → %class{String}
30
+ dd. identifier with the given string appended
31
+
32
+ p. Here are some examples:
33
+
34
+ listing[lang=ruby].
35
+ identifier = Nanoc::Identifier.new('/about.md')
36
+
37
+ identifier.without_ext
38
+ # => "/about"
39
+
40
+ identifier.ext
41
+ # => "md"
42
+
43
+ p. The following method is useful for legacy identifiers:
44
+
45
+ dl[legacy].
46
+ dt. %code{identifier.chop} → %class{String}
47
+ dd. identifier with the last character removed
48
+
49
+ p. Here are some examples:
50
+
51
+ listing[lang=ruby].
52
+ identifier = Nanoc::Identifier.new('/about/', type: :legacy)
53
+
54
+ identifier.chop
55
+ # => "/about"
56
+
57
+ identifier.chop + '.html'
58
+ # => "/about.html"
59
+
60
+ identifier + 'index.html'
61
+ # => "/about/index.html"
62
+
63
+ h2. Patterns
64
+
65
+ p. Patterns are used to find items and layouts based on their identifier. They come in three varieties:
66
+
67
+ ul.
68
+ li. glob patterns
69
+ li. regular expression patterns
70
+ li. legacy patterns
71
+
72
+ h3. Glob patterns
73
+
74
+ p. Glob patterns are strings that contain wildcard characters. Wildcard characters are characters that can be substituted for other characters in a identifier. An example of a glob pattern is %glob{/projects/*.md}, which matches all files with a %filename{md} extension in the %filename{/projects} directory.
75
+
76
+ p. Globs are commonplace in Unix-like environments. For example, the Unix command for listing all files with the %filename{md} extension in the current directory is %command{ls *.md}. In this example, the argument to the %command{ls} command is a wildcard.
77
+
78
+ p. Nanoc supports the following wildcards in glob patterns:
79
+
80
+ dl.
81
+ dt. %code{*}
82
+ dd. Matches any file or directory name. Does not cross directory boundaries. For example, %glob{/projects/*.md} matches %identifier{/projects/nanoc.md}, but not %identifier{/projects/cri.adoc} nor %identifier{/projects/nanoc/about.md}.
83
+
84
+ dt. %code{**/}
85
+ dd. Matches zero or more levels of nested directories. For example, %glob{/projects/**/*.md} matches both %identifier{/projects/nanoc.md} and %identifier{/projects/nanoc/history.md}.
86
+
87
+ dt. %code{?}
88
+ dd. Matches a single character.
89
+
90
+ dt. %code{[abc]}
91
+ dd. Matches any single character in the set. For example, %glob{/people/[kt]im.md} matches only %identifier{/people/kim.md} and %identifier{/people/tim.md}.
92
+
93
+ dt. %code{{foo,bar%}}
94
+ dd. Matches either string in the comma-separated list. More than two strings are possible. For example, %glob{/c{at,ub,ount%}s.txt} matches %identifier{/cats.txt}, %identifier{/cubs.txt} and %identifier{/counts.txt}, but not %identifier{/cabs.txt}.
95
+
96
+ p. A glob pattern that matches every item is %glob{/**/*}. A glob pattern that matches every item/layout with the extension %filename{md} is %glob{/**/*.md}.
97
+
98
+ h3. Regular expression patterns
99
+
100
+ p. You can use a regular expression to select items and layouts.
101
+
102
+ p. For matching identifiers, the %code{%%r{…%}} syntax is (arguably) nicer than the %code{/…/} syntax. The latter is not a good fit for identifiers (or filenames), because all slashes need to be escaped. The %code{\A} and %code{\z} anchors are also useful to make sure the entire identifier is matched.
103
+
104
+ p. An example of a regular expression pattern is %code{%%r{\A/projects/(cri|nanoc)\.md\z%}}, which matches both %identifier{/projects/nanoc.md} and %identifier{/projects/cri.md}.
105
+
106
+ h3. Legacy patterns
107
+
108
+ p. Legacy patterns are strings that contain wildcard characters. The wildcard characters behave differently than the glob wildcard characters.
109
+
110
+ p. To enable legacy patterns, set %code{string_pattern_type} to %code{"legacy"} in the configuration. For example:
111
+
112
+ listing[lang=yaml].
113
+ string_pattern_type: "legacy"
114
+
115
+ p. For legacy patterns, Nanoc supports the following wildcards:
116
+
117
+ dl.
118
+ dt. %code{*}
119
+ dd. Matches zero or more characters, including a slash. For example, %glob{/projects/*/} matches %glob{/projects/nanoc/} and %identifier{/projects/nanoc/about/}, but not %identifier{/projects/}.
120
+
121
+ dt. %code{+}
122
+ dd. Matches one or more characters, including a slash. For example, %glob{/projects/+} matches %identifier{/projects/nanoc/} and %identifier{/projects/nanoc/about/}, but not %identifier{/projects/}.
@@ -0,0 +1,59 @@
1
+ <p>In Nanoc, every item (page or asset) and every layout has a unique <i>identifier</i>: a string derived from the file’s path. A <i>pattern</i> is an expression that is used to select items or layouts based on their identifier.</p>
2
+ <h2>Identifiers</h2>
3
+ <p>Identifiers come in two types: the <i>full</i> type, new in Nanoc 4, and the <i>legacy</i> type, used in Nanoc 3.</p>
4
+ <dl><dt>full</dt><dd>An identifier with the full type is the filename, with the path to the content directory removed. For example, the file <i>/Users/denis/stoneship/content/about.md</i> will have the full identifier <i>/about.md</i>.</dd>
5
+ <dt>legacy</dt><dd>An identifier with the legacy type is the filename, with the path to the content directory removed, the extension removed, and a slash appended. For example, the file <i>/Users/denis/stoneship/content/about.md</i> will have the legacy identifier <i>/about/</i>. This corresponds closely with paths in clean URLs.</dd></dl>
6
+ <p>The following methods are useful for full identifiers:</p>
7
+ <dl><dt><code>identifier.without_ext</code> → <i>String</i></dt><dd>identifier with the last extension removed</dd>
8
+ <dt><code>identifier.without_exts</code> → <i>String</i></dt><dd>identifier with all extensions removed</dd>
9
+ <dt><code donkey="true">identifier.ext</code> → <i>String</i></dt><dd>the last extension of this identifier</dd>
10
+ <dt><code>identifier.exts</code> → <i>String</i></dt><dd>all extensions of this identifier</dd>
11
+ <dt><code>identifier + string</code> → <i>String</i></dt><dd>identifier with the given string appended</dd></dl>
12
+ <p>Here are some &lt; examples:</p>
13
+ <pre>identifier = Nanoc::Identifier.new('/about.md')
14
+
15
+ identifier.without_ext
16
+ # => "/about"
17
+
18
+ identifier.ext
19
+ # => "md"
20
+ </pre>
21
+ <p>The following method is useful for legacy identifiers:</p>
22
+ <dl><dt><code>identifier.chop</code> → <i>String</i></dt><dd>identifier with the last character removed</dd></dl>
23
+ <p>Here are some examples:</p>
24
+ <pre>identifier = Nanoc::Identifier.new('/about/', type: :legacy)
25
+
26
+ identifier.chop
27
+ # => "/about"
28
+
29
+ identifier.chop + '.html'
30
+ # => "/about.html"
31
+
32
+ identifier + 'index.html'
33
+ # => "/about/index.html"
34
+ </pre>
35
+ <h2>Patterns</h2>
36
+ <p>Patterns are used to find items and layouts based on their identifier. They come in three varieties:</p>
37
+ <ul><li>glob patterns</li><li>regular expression patterns</li><li>legacy patterns</li></ul>
38
+ <h3>Glob patterns</h3>
39
+ <p>Glob patterns are strings that contain wildcard characters. Wildcard characters are characters that can be substituted for other characters in a identifier. An example of a glob pattern is <i>/projects/*.md</i>, which matches all files with a <i>md</i> extension in the <i>/projects</i> directory.</p>
40
+ <p>Globs are commonplace in Unix-like environments. For example, the Unix command for listing all files with the <i>md</i> extension in the current directory is <code>ls *.md</code>. In this example, the argument to the <code>ls</code> command is a wildcard.</p>
41
+ <p>Nanoc supports the following wildcards in glob patterns:</p>
42
+ <dl><dt><code>*</code></dt><dd>Matches any file or directory name. Does not cross directory boundaries. For example, <i>/projects/*.md</i> matches <i>/projects/nanoc.md</i>, but not <i>/projects/cri.adoc</i> nor <i>/projects/nanoc/about.md</i>.</dd>
43
+ <dt><code>**/</code></dt><dd>Matches zero or more levels of nested directories. For example, <i>/projects/**/*.md</i> matches both <i>/projects/nanoc.md</i> and <i>/projects/nanoc/history.md</i>.</dd>
44
+ <dt><code>?</code></dt><dd>Matches a single character.</dd>
45
+ <dt><code>[abc]</code></dt><dd>Matches any single character in the set. For example, <i>/people/[kt]im.md</i> matches only <i>/people/kim.md</i> and <i>/people/tim.md</i>.</dd>
46
+ <dt><code>{foo,bar}</code></dt><dd>Matches either string in the comma-separated list. More than two strings are possible. For example, <i>/c{at,ub,ount}s.txt</i> matches <i>/cats.txt</i>, <i>/cubs.txt</i> and <i>/counts.txt</i>, but not <i>/cabs.txt</i>.</dd></dl>
47
+ <p>A glob pattern that matches every item is <i>/**/*</i>. A glob pattern that matches every item/layout with the extension <i>md</i> is <i>/**/*.md</i>.</p>
48
+ <h3>Regular expression patterns</h3>
49
+ <p>You can use a regular expression to select items and layouts.</p>
50
+ <p>For matching identifiers, the <code>%r{…}</code> syntax is (arguably) nicer than the <code>/…/</code> syntax. The latter is not a good fit for identifiers (or filenames), because all slashes need to be escaped. The <code>\A</code> and <code>\z</code> anchors are also useful to make sure the entire identifier is matched.</p>
51
+ <p>An example of a regular expression pattern is <code>%r{\A/projects/(cri|nanoc)\.md\z}</code>, which matches both <i>/projects/nanoc.md</i> and <i>/projects/cri.md</i>.</p>
52
+ <h3>Legacy patterns</h3>
53
+ <p>Legacy patterns are strings that contain wildcard characters. The wildcard characters behave differently than the glob wildcard characters.</p>
54
+ <p>To enable legacy patterns, set <code>string_pattern_type</code> to <code>"legacy"</code> in the configuration. For example:</p>
55
+ <pre>string_pattern_type: "legacy"
56
+ </pre>
57
+ <p>For legacy patterns, Nanoc supports the following wildcards:</p>
58
+ <dl><dt><code>*</code></dt><dd>Matches zero or more characters, including a slash. For example, <i>/projects/*/</i> matches <i>/projects/nanoc/</i> and <i>/projects/nanoc/about/</i>, but not <i>/projects/</i>.</dd>
59
+ <dt><code>+</code></dt><dd>Matches one or more characters, including a slash. For example, <i>/projects/+</i> matches <i>/projects/nanoc/</i> and <i>/projects/nanoc/about/</i>, but not <i>/projects/</i>.</dd></dl>
@@ -0,0 +1,46 @@
1
+ require_relative '../lib/dmark'
2
+
3
+ class MyHTMLTranslator < DMark::Translator
4
+ def handle(node)
5
+ case node
6
+ when DMark::Nodes::RootNode
7
+ handle_children(node)
8
+ when DMark::Nodes::TextNode
9
+ out << node.text
10
+ when DMark::Nodes::ElementNode
11
+ out << "<#{translate_elem_name(node.name)}>"
12
+ handle_children(node)
13
+ out << "</#{translate_elem_name(node.name)}>"
14
+ end
15
+ end
16
+
17
+ def translate_elem_name(name)
18
+ case name
19
+ when 'listing'
20
+ 'pre'
21
+ when 'firstterm', 'identifier', 'glob', 'emph', 'filename', 'class'
22
+ 'i'
23
+ when 'command'
24
+ 'code'
25
+ when 'p', 'dl', 'dt', 'dd', 'code', 'h1', 'h2', 'h3', 'ul', 'li'
26
+ name
27
+ else
28
+ raise "Cannot translate #{name}"
29
+ end
30
+ end
31
+ end
32
+
33
+ # Lex
34
+ begin
35
+ tokens = DMark::Lexer.new(File.read(ARGV[0])).run
36
+ rescue DMark::Lexer::LexerError => e
37
+ $stderr.puts e.message_for_tty
38
+ exit 1
39
+ end
40
+
41
+ # Parse
42
+ tree = DMark::Parser.new(tokens).run
43
+
44
+ # Translate
45
+ result = MyHTMLTranslator.new(tree).run
46
+ puts result
@@ -0,0 +1,13 @@
1
+ require 'yard'
2
+
3
+ YARD::Rake::YardocTask.new(:doc) do |yard|
4
+ yard.files = Dir['lib/**/*.rb']
5
+ yard.options = [
6
+ '--markup', 'markdown',
7
+ '--markup-provider', 'kramdown',
8
+ '--charset', 'utf-8',
9
+ '--readme', 'README.md',
10
+ '--files', 'NEWS.md,LICENSE',
11
+ '--output-dir', 'doc/yardoc',
12
+ ]
13
+ end
@@ -0,0 +1,6 @@
1
+ require 'rubocop/rake_task'
2
+
3
+ RuboCop::RakeTask.new(:rubocop) do |task|
4
+ task.options = %w( --display-cop-names --format simple )
5
+ task.patterns = ['lib/**/*.rb', 'spec/**/*.rb']
6
+ end
@@ -0,0 +1,6 @@
1
+ require 'rspec/core/rake_task'
2
+
3
+ RSpec::Core::RakeTask.new(:spec) do |t|
4
+ t.rspec_opts = '-r ./spec/spec_helper.rb --color'
5
+ t.verbose = false
6
+ end
metadata ADDED
@@ -0,0 +1,90 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: d-mark
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.1'
5
+ platform: ruby
6
+ authors:
7
+ - Denis Defreyne
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-01-31 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 1.11.2
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '2.0'
23
+ type: :development
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: 1.11.2
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '2.0'
33
+ description: D★Mark is a markup language aimed at being able to write semantically
34
+ meaningful text without limiting itself to the semantics provided by HTML or Markdown.
35
+ email: denis.defreyne@stoneship.org
36
+ executables: []
37
+ extensions: []
38
+ extra_rdoc_files:
39
+ - LICENSE
40
+ - README.md
41
+ - NEWS.md
42
+ files:
43
+ - Gemfile
44
+ - Gemfile.lock
45
+ - LICENSE
46
+ - NEWS.md
47
+ - README.md
48
+ - Rakefile
49
+ - d-mark.gemspec
50
+ - lib/dmark.rb
51
+ - lib/dmark/lexer.rb
52
+ - lib/dmark/nodes.rb
53
+ - lib/dmark/parser.rb
54
+ - lib/dmark/tokens.rb
55
+ - lib/dmark/translator.rb
56
+ - lib/dmark/version.rb
57
+ - samples/identifiers-and-patterns.dmark
58
+ - samples/identifiers-and-patterns.html
59
+ - scripts/translate-to-html.rb
60
+ - tasks/doc.rake
61
+ - tasks/rubocop.rake
62
+ - tasks/test.rake
63
+ homepage: http://rubygems.org/gems/d-mark
64
+ licenses:
65
+ - MIT
66
+ metadata: {}
67
+ post_install_message:
68
+ rdoc_options:
69
+ - "--main"
70
+ - README.md
71
+ require_paths:
72
+ - lib
73
+ required_ruby_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: 2.1.0
78
+ required_rubygems_version: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ requirements: []
84
+ rubyforge_project:
85
+ rubygems_version: 2.5.1
86
+ signing_key:
87
+ specification_version: 4
88
+ summary: markup language for writing text
89
+ test_files: []
90
+ has_rdoc: