d-mark 0.1

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.
@@ -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: