marko 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +5 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +22 -0
- data/README.md +159 -0
- data/Rakefile +12 -0
- data/exe/marko +20 -0
- data/lib/assets/demo/README.md +13 -0
- data/lib/assets/demo/src/fr/assemble.md +27 -0
- data/lib/assets/demo/src/fr/compile.md +25 -0
- data/lib/assets/demo/src/fr/markup.md +111 -0
- data/lib/assets/demo/src/fr/storage.md +16 -0
- data/lib/assets/demo/src/fr/treenode.md +34 -0
- data/lib/assets/demo/src/index.md +34 -0
- data/lib/assets/demo/src/intro.md +98 -0
- data/lib/assets/demo/src/ui/cli.md +26 -0
- data/lib/assets/demo/src/ui/gem.md +14 -0
- data/lib/assets/demo/src/ur/uc.create.project.md +8 -0
- data/lib/assets/demo/src/ur/uc.general.flow.md +14 -0
- data/lib/assets/init/README.md +61 -0
- data/lib/assets/init/Rakefile +100 -0
- data/lib/assets/init/tt/artifact.md.tt +3 -0
- data/lib/marko/artifact.rb +3 -0
- data/lib/marko/assembler.rb +82 -0
- data/lib/marko/cli.rb +121 -0
- data/lib/marko/compiler.rb +16 -0
- data/lib/marko/config.rb +20 -0
- data/lib/marko/gadgets/pluggable.rb +55 -0
- data/lib/marko/gadgets/sentry.rb +66 -0
- data/lib/marko/gadgets/service.rb +52 -0
- data/lib/marko/gadgets.rb +3 -0
- data/lib/marko/loader.rb +38 -0
- data/lib/marko/markup/compiler.rb +36 -0
- data/lib/marko/markup/decorator.rb +65 -0
- data/lib/marko/markup/macro.rb +176 -0
- data/lib/marko/markup/parser.rb +122 -0
- data/lib/marko/markup/storage.rb +100 -0
- data/lib/marko/markup/validator.rb +101 -0
- data/lib/marko/markup.rb +24 -0
- data/lib/marko/parser.rb +19 -0
- data/lib/marko/services/assemble.rb +16 -0
- data/lib/marko/services/compile.rb +30 -0
- data/lib/marko/services.rb +2 -0
- data/lib/marko/storage.rb +36 -0
- data/lib/marko/tree_node.rb +128 -0
- data/lib/marko/validator.rb +19 -0
- data/lib/marko/version.rb +5 -0
- data/lib/marko.rb +37 -0
- data/marko.gemspec +44 -0
- metadata +99 -0
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "delegate"
|
4
|
+
|
5
|
+
module Marko
|
6
|
+
module Markup
|
7
|
+
|
8
|
+
class Decorator < SimpleDelegator
|
9
|
+
|
10
|
+
def initialize(obj)
|
11
|
+
super(obj)
|
12
|
+
@macroproc = MacroProcPlug.plugged
|
13
|
+
end
|
14
|
+
|
15
|
+
def find_node(ref)
|
16
|
+
obj = super(ref)
|
17
|
+
return nil unless obj
|
18
|
+
self.class.new(obj)
|
19
|
+
end
|
20
|
+
|
21
|
+
def url
|
22
|
+
id.downcase
|
23
|
+
.gsub(/\W{1,}/, '-')
|
24
|
+
.gsub(/^-/, '')
|
25
|
+
.gsub(/-$/, '')
|
26
|
+
.then{"##{_1}"}
|
27
|
+
end
|
28
|
+
|
29
|
+
def ref
|
30
|
+
"[#{title}](#{url})"
|
31
|
+
end
|
32
|
+
|
33
|
+
def title
|
34
|
+
str = super
|
35
|
+
str = ".#{id.split(/\./).last}" if str.empty?
|
36
|
+
str
|
37
|
+
end
|
38
|
+
|
39
|
+
def header
|
40
|
+
return "% #{title}\n" if root?
|
41
|
+
"#{'#' * nesting_level} #{title.strip} {#{url}}\n"
|
42
|
+
end
|
43
|
+
|
44
|
+
def meta
|
45
|
+
hsh = super.dup
|
46
|
+
hsh[:id] = id # full id will be there
|
47
|
+
hsh.delete(:order_index)
|
48
|
+
hsh.delete(:parent)
|
49
|
+
hsh.delete(:origin)
|
50
|
+
len = hsh.keys.map(&:length).max
|
51
|
+
[].tap{|ary|
|
52
|
+
ary << "key | value"
|
53
|
+
ary << "--- | -----"
|
54
|
+
hsh.each{|k,v| ary << "#{k} | #{v}"}
|
55
|
+
}.join(?\n) + ?\n
|
56
|
+
end
|
57
|
+
|
58
|
+
def body
|
59
|
+
text = @macroproc.process(super, self)
|
60
|
+
text.strip + ?\n
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,176 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "decorator"
|
4
|
+
require_relative "../gadgets"
|
5
|
+
|
6
|
+
module Marko
|
7
|
+
module Markup
|
8
|
+
|
9
|
+
# @todo confusing circular depenencies
|
10
|
+
# List/Tree/Link Macros -> Decorator
|
11
|
+
# Decorator#body -> MacroProcessor
|
12
|
+
|
13
|
+
# Base class for macro substitutions
|
14
|
+
# @example
|
15
|
+
# class Todo < Macro
|
16
|
+
# @pattern = /@@todo[^\n]*\n/
|
17
|
+
# def subs(sample, obj = nil)
|
18
|
+
# # code that returns <substitution>
|
19
|
+
# # for the sample parameter
|
20
|
+
# end
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# text = "bla-bla-bla @@todo foo\nbla-bla-bla"
|
24
|
+
# Todo.new.gsub!(text)
|
25
|
+
# # => "bla-bla-bla <substitution for @@todo foo>"
|
26
|
+
#
|
27
|
+
class Macro
|
28
|
+
def self.pattern
|
29
|
+
@pattern
|
30
|
+
end
|
31
|
+
|
32
|
+
# @return [Regexp] pattern to process
|
33
|
+
def pattern
|
34
|
+
self.class.pattern
|
35
|
+
end
|
36
|
+
|
37
|
+
# substitutes all occured #pattern it text
|
38
|
+
def gsub!(text, obj = nil)
|
39
|
+
fn = subfn(text, obj)
|
40
|
+
text.scan(pattern).each(&fn)
|
41
|
+
end
|
42
|
+
|
43
|
+
protected
|
44
|
+
|
45
|
+
# build substitution for the text sample
|
46
|
+
# @param sample [String] sample for substitution
|
47
|
+
# @param obj [Object] might be used for substitution
|
48
|
+
# @return [String] substitution for text
|
49
|
+
def subs(sample, obj)
|
50
|
+
fail '#subs must be overridden'
|
51
|
+
end
|
52
|
+
|
53
|
+
def subfn(source, obj)
|
54
|
+
fn = proc{|source, obj, sample|
|
55
|
+
source.sub!(sample, subs(sample, obj))
|
56
|
+
}.curry
|
57
|
+
fn.(source, obj)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
class MLink < Macro
|
62
|
+
@pattern = /\[\[[\w\.]*\]\]/
|
63
|
+
|
64
|
+
# the macro requires obj [Decorator]
|
65
|
+
def subs(sample, node)
|
66
|
+
capture = /\[\[([\w\.]*)\]\]/
|
67
|
+
ref = sample
|
68
|
+
.match(capture)
|
69
|
+
.captures.first
|
70
|
+
obj = node.find_node(ref)
|
71
|
+
obj ? obj.ref : "[#{ref}](#lost-link)"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
class MList < Macro
|
76
|
+
@pattern = /@@list/
|
77
|
+
|
78
|
+
def subs(sample, node)
|
79
|
+
# @todo require sentry Decorator
|
80
|
+
# MustbeTreeNode.(node)
|
81
|
+
node.items
|
82
|
+
.map{|n| d = Decorator.new(n); "- #{d.ref}" }
|
83
|
+
.join(?\n) + ?\n
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
class MTree < Macro
|
88
|
+
@pattern = /@@tree/
|
89
|
+
|
90
|
+
def subs(sample, node)
|
91
|
+
level = node.nesting_level + 1
|
92
|
+
node.to_a.drop(1)
|
93
|
+
.inject([]){|memo, n| memo << Decorator.new(n)}
|
94
|
+
.map{|n| "#{' ' * (n.nesting_level - level)}- #{n.ref}" }
|
95
|
+
.join(?\n) + ?\n
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# @todo there is no sense to have such macro for releases
|
100
|
+
# instead it might be helpful to have some custom
|
101
|
+
# todo command that will create a file with nodes
|
102
|
+
# and list of todo for each node
|
103
|
+
#
|
104
|
+
# class MTodo < Macro
|
105
|
+
# @pattern = /@@todo[^\n]*\n/
|
106
|
+
#
|
107
|
+
# def subs(sample, obj = nil)
|
108
|
+
# capture = /@@todo([^\n]*)\n/
|
109
|
+
# payload = sample.match(capture)
|
110
|
+
# .captures
|
111
|
+
# .first
|
112
|
+
# .strip
|
113
|
+
# "__TODO__ #{payload}\n"
|
114
|
+
# end
|
115
|
+
# end
|
116
|
+
|
117
|
+
# inline @@todo macro
|
118
|
+
# @todo remove line with \n when it starts from @@todo
|
119
|
+
class MTodo < Macro
|
120
|
+
@pattern = /.*@@todo.*$/
|
121
|
+
|
122
|
+
def subs(sample, obj = nil)
|
123
|
+
cap = /(.*)@@todo.*$/
|
124
|
+
m = sample.match(cap)
|
125
|
+
m[1].strip + sample.gsub(pattern, '')
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
class MSkip < Macro
|
130
|
+
@pattern = /@@skip.*$/m
|
131
|
+
|
132
|
+
def subs(sample, obj = nil)
|
133
|
+
''
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
MustbeMacro = Sentry.new(:macro, "must be Macro"
|
138
|
+
) {|v| v.is_a? Macro }
|
139
|
+
|
140
|
+
# Macro processor
|
141
|
+
# @example
|
142
|
+
# processor = MacroProcessor.new
|
143
|
+
# processor << Toc.new
|
144
|
+
# processor << Todo.new
|
145
|
+
# processor.('bla bla @@todo, bla bla @@list')
|
146
|
+
# # => 'bla bla <substitute @@todo>, bla bla..'
|
147
|
+
class MacroProcessor
|
148
|
+
extend Marko::Pluggable
|
149
|
+
|
150
|
+
def initialize
|
151
|
+
@macros = {}
|
152
|
+
self.<<(MList.new)
|
153
|
+
self.<<(MTree.new)
|
154
|
+
self.<<(MLink.new)
|
155
|
+
self.<<(MTodo.new)
|
156
|
+
self.<<(MSkip.new)
|
157
|
+
end
|
158
|
+
|
159
|
+
def <<(macro)
|
160
|
+
MustbeMacro.(macro)
|
161
|
+
@macros[macro.pattern] = macro
|
162
|
+
macro
|
163
|
+
end
|
164
|
+
|
165
|
+
def process(text, obj)
|
166
|
+
fail 'No macro registered' unless @macros.any?
|
167
|
+
String.new(text).tap {|str|
|
168
|
+
@macros.values.each{|m| m.gsub!(str, obj) }
|
169
|
+
}
|
170
|
+
end
|
171
|
+
|
172
|
+
alias :call :process
|
173
|
+
end
|
174
|
+
|
175
|
+
end
|
176
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../parser"
|
4
|
+
require_relative "../tree_node"
|
5
|
+
|
6
|
+
module Marko
|
7
|
+
module Markup
|
8
|
+
|
9
|
+
class Parser < Marko::Parser
|
10
|
+
|
11
|
+
# @see Parser#call
|
12
|
+
def call(content, source, &block)
|
13
|
+
@source = source
|
14
|
+
parse(content, &block)
|
15
|
+
end
|
16
|
+
|
17
|
+
protected
|
18
|
+
|
19
|
+
def parse(content, &block)
|
20
|
+
result, errors = [], [], []
|
21
|
+
scan_treenode(content)
|
22
|
+
.map{|origin|
|
23
|
+
begin
|
24
|
+
parse_treenode(origin.markup)
|
25
|
+
.tap{|n| n[:origin] = origin }
|
26
|
+
rescue => e
|
27
|
+
errmsg = "wrong markup #{origin} #{e.message}\n"
|
28
|
+
errors << errmsg
|
29
|
+
nil
|
30
|
+
end
|
31
|
+
}
|
32
|
+
.compact
|
33
|
+
.each{|node|
|
34
|
+
parent = find_parent(result, node[:origin].level)
|
35
|
+
unless parent
|
36
|
+
origin = node[:origin].to_s
|
37
|
+
errors << "wrong header #{origin}\n"
|
38
|
+
parent = result # it goes to the root!
|
39
|
+
end
|
40
|
+
parent << node
|
41
|
+
}
|
42
|
+
|
43
|
+
[result, errors]
|
44
|
+
end
|
45
|
+
|
46
|
+
def find_parent(ary, level)
|
47
|
+
return ary if level == 1
|
48
|
+
parent = ary.last
|
49
|
+
l = 1
|
50
|
+
while parent && (l+1) < level
|
51
|
+
parent = parent.last
|
52
|
+
l += 1
|
53
|
+
end
|
54
|
+
parent
|
55
|
+
end
|
56
|
+
|
57
|
+
Origin = Struct.new(:origin, :lineno, :markup) do
|
58
|
+
def header
|
59
|
+
@header ||= markup.lines.first
|
60
|
+
end
|
61
|
+
|
62
|
+
def level
|
63
|
+
@level ||= header.scan(/^#*/).first.size
|
64
|
+
end
|
65
|
+
|
66
|
+
def to_s
|
67
|
+
"#{origin}:#{lineno.to_s.rjust(2,'0')} >> #{header}".strip
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# @return [Array<Origin>]
|
72
|
+
def scan_treenode(text)
|
73
|
+
quote, buffer, lineno = false, [], 0
|
74
|
+
origin = proc{
|
75
|
+
Origin.new(@source, lineno - buffer.size + 1, buffer.join(''))
|
76
|
+
}
|
77
|
+
|
78
|
+
[].tap{|ary|
|
79
|
+
text.each_line do |line|
|
80
|
+
if line =~ /^#/ && !quote && buffer.any?
|
81
|
+
ary << origin.()
|
82
|
+
buffer.clear
|
83
|
+
end
|
84
|
+
lineno += 1
|
85
|
+
buffer << line
|
86
|
+
quote = !quote if line.start_with?('```')
|
87
|
+
end
|
88
|
+
ary << origin.() if buffer.any?
|
89
|
+
}
|
90
|
+
end
|
91
|
+
|
92
|
+
def parse_treenode(text)
|
93
|
+
head, *tail = text.lines
|
94
|
+
m = head.match(/^(#+)(.*)/)
|
95
|
+
title = m[2]&.strip || ''
|
96
|
+
|
97
|
+
m = tail.join.match(/^({{([\s\S]*?)}})?(.*)?$/m)
|
98
|
+
meta = parse_metadata(m[2]&.strip || '')
|
99
|
+
body = m[3]&.strip || ''
|
100
|
+
|
101
|
+
TreeNode.new(title, body, **meta)
|
102
|
+
end
|
103
|
+
|
104
|
+
def parse_metadata(text)
|
105
|
+
return {} if text.empty?
|
106
|
+
atrbfn = method(:parse_attribute).to_proc
|
107
|
+
text
|
108
|
+
.split(/[;,\n]/)
|
109
|
+
.map(&:strip)
|
110
|
+
.reject(&:empty?)
|
111
|
+
.map(&atrbfn)
|
112
|
+
.to_h
|
113
|
+
end
|
114
|
+
|
115
|
+
def parse_attribute(text)
|
116
|
+
atr, val = text.split(?:)
|
117
|
+
[atr.strip.to_sym, val&.strip || 'true']
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "psych"
|
3
|
+
require 'fileutils'
|
4
|
+
require "securerandom"
|
5
|
+
require_relative "../storage"
|
6
|
+
|
7
|
+
module Marko
|
8
|
+
module Markup
|
9
|
+
|
10
|
+
# File Storage
|
11
|
+
class Storage < Marko::Storage
|
12
|
+
include FileUtils
|
13
|
+
|
14
|
+
Failure = Class.new(StandardError)
|
15
|
+
|
16
|
+
# @see Storage#create
|
17
|
+
def punch(storage)
|
18
|
+
fail Failure, "Directory already exists" if Dir.exist?(storage)
|
19
|
+
Dir.mkdir(storage)
|
20
|
+
Dir.chdir(storage) {
|
21
|
+
marko_directories.each{|dir| Dir.mkdir dir }
|
22
|
+
src = File.join(Marko.root, 'lib', 'assets', 'init', '.')
|
23
|
+
cp_r src, Dir.pwd
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
27
|
+
# create demo project
|
28
|
+
def punch_demo
|
29
|
+
demo = File.join(Dir.home, DEMO)
|
30
|
+
unless Dir.exist?(demo)
|
31
|
+
punch(demo)
|
32
|
+
assets = File.join(Marko.root, 'lib', 'assets', 'demo', '.')
|
33
|
+
cp_r assets, demo
|
34
|
+
end
|
35
|
+
demo
|
36
|
+
end
|
37
|
+
|
38
|
+
# @see Storage#sources
|
39
|
+
def sources
|
40
|
+
marko_home!
|
41
|
+
ptrn = File.join(SOURCE, '**', '*.md')
|
42
|
+
Dir.glob ptrn
|
43
|
+
end
|
44
|
+
|
45
|
+
# @see Storage#content
|
46
|
+
def content(source)
|
47
|
+
marko_home!
|
48
|
+
File.read(source)
|
49
|
+
end
|
50
|
+
|
51
|
+
def write(filename, content = '')
|
52
|
+
backup(filename)
|
53
|
+
File.open(filename, 'w') do |f|
|
54
|
+
f.puts(content) unless content.empty?
|
55
|
+
yield(f) if block_given?
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def marko_home?
|
60
|
+
marko_directories.all?{ Dir.exist? _1 }
|
61
|
+
end
|
62
|
+
|
63
|
+
# @see Marko::Strorage#artifact
|
64
|
+
def artifact
|
65
|
+
return Psych.load_file(ARTIFACT).freeze if File.exist?(ARTIFACT)
|
66
|
+
art = Artifact.new(SecureRandom.uuid,
|
67
|
+
'Marko Artifact',
|
68
|
+
'tt/artifact.md.tt',
|
69
|
+
'tt/marko-artifact.md'
|
70
|
+
)
|
71
|
+
File.write(ARTIFACT, Psych.dump(art))
|
72
|
+
art.freeze
|
73
|
+
end
|
74
|
+
|
75
|
+
protected
|
76
|
+
|
77
|
+
ARTIFACT = 'marko.yml'.freeze
|
78
|
+
SOURCE = 'src'.freeze
|
79
|
+
BINARY = 'bin'.freeze
|
80
|
+
SAMPLE = 'tt'.freeze
|
81
|
+
ASSETS = File.join(BINARY, 'assets').freeze
|
82
|
+
DEMO = 'marko_demo'.freeze
|
83
|
+
|
84
|
+
def marko_directories
|
85
|
+
[SOURCE, BINARY, ASSETS, SAMPLE]
|
86
|
+
end
|
87
|
+
|
88
|
+
|
89
|
+
def marko_home!
|
90
|
+
fail Failure, "Marko project required!" unless marko_home?
|
91
|
+
end
|
92
|
+
|
93
|
+
def backup(filename)
|
94
|
+
cp(filename, filename + ?~) if File.exist?(filename)
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../validator"
|
4
|
+
|
5
|
+
module Marko
|
6
|
+
module Markup
|
7
|
+
class Validator < Marko::Validator
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@inspectors = []
|
11
|
+
@inspectors << TheSameId.new
|
12
|
+
@inspectors << LostParent.new
|
13
|
+
@inspectors << LostIndex.new
|
14
|
+
@inspectors << LostLink.new
|
15
|
+
end
|
16
|
+
|
17
|
+
# see Marko::Validator#call
|
18
|
+
def call(tree)
|
19
|
+
[].tap{|errors|
|
20
|
+
@inspectors.each{|spectr| errors.concat(spectr.(tree)) }
|
21
|
+
}
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
class Inspector
|
27
|
+
def call(tree)
|
28
|
+
select(tree).then{ report(_1) }
|
29
|
+
end
|
30
|
+
|
31
|
+
def select(tree)
|
32
|
+
fail "the abstract method must be overriden"
|
33
|
+
end
|
34
|
+
|
35
|
+
def report(errs)
|
36
|
+
fail "the abstract method must be overriden"
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
class TheSameId < Inspector
|
42
|
+
def select(tree)
|
43
|
+
fn = proc{|n, memo| memo[n.id] ||= []; memo[n.id] << n}
|
44
|
+
tree
|
45
|
+
.each_with_object({}, &fn)
|
46
|
+
.select{|_, v| v.size > 1}
|
47
|
+
end
|
48
|
+
|
49
|
+
def report(errs)
|
50
|
+
# the same id [ref] found twice
|
51
|
+
# src/source2.md:22 >> ## header
|
52
|
+
# src/source3.md:11 >> ## header
|
53
|
+
errs.map{|id, nodes|
|
54
|
+
sources = nodes.map{|n| " #{n[:origin]}\n"}.join
|
55
|
+
"the same id [#{id}] found in\n#{sources}"
|
56
|
+
}
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
class LostParent < Inspector
|
61
|
+
def select(tree)
|
62
|
+
tree.select{|n| n[:parent] && n.parent_id != n[:parent]}
|
63
|
+
end
|
64
|
+
|
65
|
+
def report(errs)
|
66
|
+
# lost parent [ref] src/source2.md:22 >> ## header
|
67
|
+
errs.map{|n| "lost parent [#{n[:parent]}] found in #{n[:origin]}\n"}
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
class LostIndex < Inspector
|
72
|
+
def select(tree)
|
73
|
+
lost = proc{|n| n.order_index.reject{|i| n.find_item(i)}}
|
74
|
+
tree
|
75
|
+
.select{|n| n.order_index.any? && lost.(n).any?}
|
76
|
+
.map{|n| [n[:origin], lost.(n)]} # @todo second time calculation
|
77
|
+
end
|
78
|
+
|
79
|
+
def report(errs)
|
80
|
+
# lost index [a, b] src/source2.md:22 >> ## header
|
81
|
+
errs.map{|orig, lost| "lost index [#{lost.join(', ')}] in #{orig}\n" }
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
class LostLink < Inspector
|
86
|
+
def select(tree)
|
87
|
+
lost = proc{|n| n.links.reject{|i| n.find_node(i)}}
|
88
|
+
tree
|
89
|
+
.select{|n| lost.(n).any? }
|
90
|
+
.map{|n| [n[:origin], lost.(n)]} # @todo second time #lost calculation
|
91
|
+
end
|
92
|
+
|
93
|
+
# @todo report link line number in the origin
|
94
|
+
def report(errs)
|
95
|
+
# lost links [ref] src/source2.md:22 >> ## header
|
96
|
+
errs.map{|orig, lost| "lost link [#{lost.join(', ')}] in #{orig}\n"}
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
end
|
data/lib/marko/markup.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require_relative "markup/parser"
|
2
|
+
require_relative "markup/storage"
|
3
|
+
require_relative "markup/validator"
|
4
|
+
require_relative "markup/macro"
|
5
|
+
require_relative "markup/decorator"
|
6
|
+
require_relative "markup/compiler"
|
7
|
+
require_relative "config"
|
8
|
+
|
9
|
+
|
10
|
+
module Marko
|
11
|
+
|
12
|
+
ParserPlug.plug Markup::Parser
|
13
|
+
StoragePlug.plug Markup::Storage
|
14
|
+
CompilerPlug.plug Markup::Compiler
|
15
|
+
ValidatorPlug.plug Markup::Validator
|
16
|
+
|
17
|
+
# ParserPlug = Markup::Parser.plug
|
18
|
+
# StoragePlug = Markup::Storage.plug
|
19
|
+
# @todo let Decorator know
|
20
|
+
MacroProcPlug = Markup::MacroProcessor.plug
|
21
|
+
# ValidatorPlug = Markup::Validator.plug
|
22
|
+
# CompilerPlug = Markup::Compiler.plug
|
23
|
+
|
24
|
+
end
|
data/lib/marko/parser.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "gadgets"
|
4
|
+
|
5
|
+
module Marko
|
6
|
+
|
7
|
+
# The class for pasing content into TreeNode
|
8
|
+
class Parser
|
9
|
+
extend Pluggable
|
10
|
+
|
11
|
+
# @param content [String] content to parse
|
12
|
+
# @param source [String] content source
|
13
|
+
# @return [Array<TreeNode>, Array<String>] parsed nodes, errors
|
14
|
+
def call(content, source, &block)
|
15
|
+
fail "the abstract method must be overriden"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require_relative "../gadgets"
|
2
|
+
require_relative "../assembler"
|
3
|
+
|
4
|
+
module Marko
|
5
|
+
module Services
|
6
|
+
|
7
|
+
# Assemblage service
|
8
|
+
# @todo assemble projects bu url
|
9
|
+
class Assemble < Service
|
10
|
+
def call
|
11
|
+
Assembler.(&@block)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require_relative "../gadgets"
|
2
|
+
require_relative "../assembler"
|
3
|
+
|
4
|
+
module Marko
|
5
|
+
module Services
|
6
|
+
|
7
|
+
# Compitation service
|
8
|
+
class Compile < Service
|
9
|
+
def initialize(tree: nil, template: '', filename: '', &block)
|
10
|
+
@tree = MustbeTreeNode.(tree) if tree
|
11
|
+
@template = MustbeString.(template)
|
12
|
+
@filename = MustbeString.(filename)
|
13
|
+
@block = block
|
14
|
+
|
15
|
+
art = Marko.artifact
|
16
|
+
@template = art.template if @template.empty?
|
17
|
+
@filename = art.filename if @filename.empty?
|
18
|
+
end
|
19
|
+
|
20
|
+
def call
|
21
|
+
storage = StoragePlug.plugged
|
22
|
+
compiler = CompilerPlug.plugged
|
23
|
+
erb = storage.content(@template)
|
24
|
+
@tree = Assembler.(&@block) unless @tree
|
25
|
+
compiler.(@tree, erb, @filename, &@block) # => filename
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "gadgets"
|
4
|
+
require_relative "artifact"
|
5
|
+
|
6
|
+
module Marko
|
7
|
+
|
8
|
+
# The base class that represents sources repository
|
9
|
+
class Storage
|
10
|
+
extend Pluggable
|
11
|
+
|
12
|
+
# create a new repository
|
13
|
+
# @param repository [String] the name for the repository
|
14
|
+
def punch(repository)
|
15
|
+
fail "the abstract method must be overriden"
|
16
|
+
end
|
17
|
+
|
18
|
+
# @retrun [Array<String>] array of sources inside the repository
|
19
|
+
def sources
|
20
|
+
fail "the abstract method must be overriden"
|
21
|
+
end
|
22
|
+
|
23
|
+
# @param source [Striing] source to retrieve content
|
24
|
+
# @return [String] content of :source
|
25
|
+
def content(source)
|
26
|
+
fail "the abstract method must be overriden"
|
27
|
+
end
|
28
|
+
|
29
|
+
# @return [Artifact] artifact settings
|
30
|
+
def artifact
|
31
|
+
fail "the abstract method must be overriden"
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|