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.
- checksums.yaml +7 -0
- data/Gemfile +11 -0
- data/Gemfile.lock +84 -0
- data/LICENSE +19 -0
- data/NEWS.md +7 -0
- data/README.md +70 -0
- data/Rakefile +3 -0
- data/d-mark.gemspec +26 -0
- data/lib/dmark.rb +9 -0
- data/lib/dmark/lexer.rb +235 -0
- data/lib/dmark/nodes.rb +76 -0
- data/lib/dmark/parser.rb +28 -0
- data/lib/dmark/tokens.rb +49 -0
- data/lib/dmark/translator.rb +26 -0
- data/lib/dmark/version.rb +3 -0
- data/samples/identifiers-and-patterns.dmark +122 -0
- data/samples/identifiers-and-patterns.html +59 -0
- data/scripts/translate-to-html.rb +46 -0
- data/tasks/doc.rake +13 -0
- data/tasks/rubocop.rake +6 -0
- data/tasks/test.rake +6 -0
- metadata +90 -0
checksums.yaml
ADDED
@@ -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
data/Gemfile.lock
ADDED
@@ -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
data/README.md
ADDED
@@ -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
|
data/Rakefile
ADDED
data/d-mark.gemspec
ADDED
@@ -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
|
data/lib/dmark.rb
ADDED
data/lib/dmark/lexer.rb
ADDED
@@ -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
|
data/lib/dmark/nodes.rb
ADDED
@@ -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
|
data/lib/dmark/parser.rb
ADDED
@@ -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
|
data/lib/dmark/tokens.rb
ADDED
@@ -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,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 < 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
|
data/tasks/doc.rake
ADDED
@@ -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
|
data/tasks/rubocop.rake
ADDED
data/tasks/test.rake
ADDED
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:
|