codeless_code 0.1.8 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitmodules +1 -1
- data/.reek.yml +2 -0
- data/Gemfile +0 -1
- data/Gemfile.lock +0 -2
- data/Guardfile +15 -13
- data/VERSION +1 -1
- data/codeless_code.gemspec +11 -10
- data/data/the-codeless-code/de-andrebogus/case-193.txt +1 -1
- data/data/the-codeless-code/de-andrebogus/case-74.txt +1 -1
- data/data/the-codeless-code/en-qi/case-194.txt +1 -1
- data/data/the-codeless-code/en-qi/case-74.txt +1 -1
- data/lib/codeless_code/cli.rb +1 -1
- data/lib/codeless_code/fable.rb +1 -1
- data/lib/codeless_code/formats/base.rb +2 -19
- data/lib/codeless_code/formats/plain.rb +34 -21
- data/lib/codeless_code/formats/term.rb +48 -18
- data/lib/codeless_code/markup/converter.rb +78 -0
- data/lib/codeless_code/markup/nodes.rb +64 -0
- data/lib/codeless_code/markup/parser.rb +152 -0
- data/lib/codeless_code/renderers/term_page.rb +1 -1
- data/lib/codeless_code.rb +7 -7
- data/test/codeless_code/filters/{test_langs.rb → test_lang.rb} +0 -0
- data/test/codeless_code/formats/test_plain.rb +74 -0
- data/{lib/codeless_code/formats/parsers/plain.rb → test/codeless_code/formats/test_raw.rb} +6 -28
- data/test/codeless_code/formats/test_term.rb +72 -0
- data/test/codeless_code/markup/test_parser.rb +177 -0
- data/test/codeless_code/test_cli.rb +75 -2
- data/test/support/fs.rb +15 -0
- metadata +10 -20
- data/lib/codeless_code/formats/parsers/base.rb +0 -106
- data/lib/codeless_code/formats/parsers/term.rb +0 -79
@@ -0,0 +1,152 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# codeless_code filters and prints fables from http://thecodelesscode.com
|
4
|
+
# Copyright (C) 2018 Jon Sangster
|
5
|
+
#
|
6
|
+
# This program is free software: you can redistribute it and/or modify it under
|
7
|
+
# the terms of the GNU General Public License as published by the Free Software
|
8
|
+
# Foundation, either version 3 of the License, or (at your option) any later
|
9
|
+
# version.
|
10
|
+
#
|
11
|
+
# This program is distributed in the hope that it will be useful, but WITHOUT
|
12
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
13
|
+
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
14
|
+
# details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU General Public License along with
|
17
|
+
# this program. If not, see <https://www.gnu.org/licenses/>.
|
18
|
+
require 'nokogiri'
|
19
|
+
|
20
|
+
module CodelessCode
|
21
|
+
module Markup
|
22
|
+
# Parses the body of a {Fable}, including HTML, MediaWiki syntax, and custom
|
23
|
+
# syntax, and returns it as an HTML DOM.
|
24
|
+
class Parser
|
25
|
+
# [[href|title]] or [[title]]
|
26
|
+
LINK_PATTERN = /\[\[(?:([^|]+)\|)?([^\]]+)\]\]/.freeze
|
27
|
+
|
28
|
+
ITALIC_PATTERN = %r{/([^/]+)/}.freeze # /some text/
|
29
|
+
SUP_PATTERN = /{{([^}]+)}}/.freeze # {{*}}
|
30
|
+
HR_PATTERN = /^- - -(?: -)*$/.freeze # - - -
|
31
|
+
BR_PATTERN = %r{\s*//\s*(?:\n|$)}m.freeze # end of line //
|
32
|
+
|
33
|
+
attr_reader :doc
|
34
|
+
|
35
|
+
def initialize(str)
|
36
|
+
@doc = Nokogiri::HTML(format('<main>%s</main>', str))
|
37
|
+
end
|
38
|
+
|
39
|
+
# @return [Nokogiri::XML::Element] The body of the fable, with non-HTML
|
40
|
+
# markup converted into HTML
|
41
|
+
def call
|
42
|
+
new_elem(:main).tap do |main|
|
43
|
+
paragraphs.each do |para|
|
44
|
+
main << parse_paragraph(para) unless para.inner_html.empty?
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def paragraphs
|
52
|
+
@paragraphs ||=
|
53
|
+
doc.css('main')
|
54
|
+
.flat_map(&method(:split_double_newline))
|
55
|
+
.reject { |node| node.inner_html.empty? }
|
56
|
+
end
|
57
|
+
|
58
|
+
def split_double_newline(para)
|
59
|
+
body = para.text? ? para.to_s : para.inner_html
|
60
|
+
|
61
|
+
case body.split(/\n\n+/).size
|
62
|
+
when 0 then new_elem(:span) << ''
|
63
|
+
when 1 then split_single_line(para)
|
64
|
+
else
|
65
|
+
str_node_set(format('<p>%s</p>', body.gsub(/\n\n+/, '</p><p>')))
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def split_single_line(para)
|
70
|
+
case para.name
|
71
|
+
when 'p' then para
|
72
|
+
when 'main' then new_elem(:p) << para.children
|
73
|
+
else
|
74
|
+
new_elem(:p) << para
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def new_elem(name)
|
79
|
+
Nokogiri::XML::Element.new(name.to_s, doc)
|
80
|
+
end
|
81
|
+
|
82
|
+
def str_node_set(str)
|
83
|
+
doc = Nokogiri::HTML(format('<main>%s</main>', str))
|
84
|
+
doc.css('body > main').tap { |ns| ns.document = doc }.children
|
85
|
+
end
|
86
|
+
|
87
|
+
def parse_paragraph(para)
|
88
|
+
html = para.inner_html
|
89
|
+
|
90
|
+
if HR_PATTERN.match?(html)
|
91
|
+
new_elem(:hr)
|
92
|
+
elsif blockquote?(html)
|
93
|
+
new_blockquote(html)
|
94
|
+
elsif html.lstrip.start_with?('== ')
|
95
|
+
new_elem(:h2) << html.lstrip[3..-1]
|
96
|
+
else
|
97
|
+
parse_node(para)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# Does every line start with the same number of spaces?
|
102
|
+
# :reek:UtilityFunction
|
103
|
+
def blockquote?(html)
|
104
|
+
match = /^\s+/.match(html)
|
105
|
+
return unless match
|
106
|
+
|
107
|
+
lines = html.lines
|
108
|
+
lines.size > 1 && lines.all? { |line| line.start_with?(match[0]) }
|
109
|
+
end
|
110
|
+
|
111
|
+
def new_blockquote(html)
|
112
|
+
match = /^\s+/.match(html)
|
113
|
+
len = match[0].length
|
114
|
+
span = new_elem(:span) << html.lines.map { |line| line[len..-1] }.join
|
115
|
+
new_elem(:blockquote) << parse_node(span.child)
|
116
|
+
end
|
117
|
+
|
118
|
+
# @return [NodeSet]
|
119
|
+
def parse_node(node)
|
120
|
+
return parse_text(node) if node.text?
|
121
|
+
|
122
|
+
node.children
|
123
|
+
.map(&method(:parse_node))
|
124
|
+
.inject(new_elem(node.name)) { |elem, child| elem << child }
|
125
|
+
end
|
126
|
+
|
127
|
+
def parse_text(text_node)
|
128
|
+
str_node_set(
|
129
|
+
gsub_links(
|
130
|
+
parse_slashes(text_node.content.dup)
|
131
|
+
.gsub(BR_PATTERN) { new_elem(:br) }
|
132
|
+
.gsub(SUP_PATTERN) { new_elem(:sup) << Regexp.last_match(1) }
|
133
|
+
).tr("\n", ' ')
|
134
|
+
)
|
135
|
+
end
|
136
|
+
|
137
|
+
def parse_slashes(text)
|
138
|
+
text.split(BR_PATTERN).map do |str|
|
139
|
+
str.gsub(ITALIC_PATTERN) { new_elem(:em) << Regexp.last_match(1) }
|
140
|
+
end.join("//\n")
|
141
|
+
end
|
142
|
+
|
143
|
+
def gsub_links(text)
|
144
|
+
text.gsub(LINK_PATTERN) do
|
145
|
+
(new_elem(:a) << Regexp.last_match(2)).tap do |link|
|
146
|
+
link['href'] = Regexp.last_match(1) if Regexp.last_match(1)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
data/lib/codeless_code.rb
CHANGED
@@ -27,7 +27,7 @@ module CodelessCode
|
|
27
27
|
autoload :LanguageSet, 'codeless_code/language_set'
|
28
28
|
autoload :Options, 'codeless_code/options'
|
29
29
|
|
30
|
-
# The "main" methods this applications supports
|
30
|
+
# The "main" methods this applications supports.
|
31
31
|
module Commands
|
32
32
|
autoload :FilterFables, 'codeless_code/commands/filter_fables'
|
33
33
|
autoload :ListTranslations, 'codeless_code/commands/list_translations'
|
@@ -39,13 +39,13 @@ module CodelessCode
|
|
39
39
|
autoload :Plain, 'codeless_code/formats/plain'
|
40
40
|
autoload :Raw, 'codeless_code/formats/raw'
|
41
41
|
autoload :Term, 'codeless_code/formats/term'
|
42
|
+
end
|
42
43
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
end
|
44
|
+
# Parses the markup in a {Fable}
|
45
|
+
module Markup
|
46
|
+
autoload :Converter, 'codeless_code/markup/converter'
|
47
|
+
autoload :Nodes, 'codeless_code/markup/nodes'
|
48
|
+
autoload :Parser, 'codeless_code/markup/parser'
|
49
49
|
end
|
50
50
|
|
51
51
|
# The methods in which a {Fable fables's} body may be rendered as text.
|
File without changes
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# codeless_code filters and prints fables from http://thecodelesscode.com
|
4
|
+
# Copyright (C) 2018 Jon Sangster
|
5
|
+
#
|
6
|
+
# This program is free software: you can redistribute it and/or modify it under
|
7
|
+
# the terms of the GNU General Public License as published by the Free Software
|
8
|
+
# Foundation, either version 3 of the License, or (at your option) any later
|
9
|
+
# version.
|
10
|
+
#
|
11
|
+
# This program is distributed in the hope that it will be useful, but WITHOUT
|
12
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
13
|
+
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
14
|
+
# details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU General Public License along with
|
17
|
+
# this program. If not, see <https://www.gnu.org/licenses/>.
|
18
|
+
require 'helper'
|
19
|
+
|
20
|
+
module Formats
|
21
|
+
class TestPlain < UnitTest
|
22
|
+
def test_basic_html
|
23
|
+
assert_equal 'italic text', plain('<i>italic</i> text')
|
24
|
+
assert_equal 'bold text', plain('<b>bold</b> text')
|
25
|
+
assert_equal 'link text', plain('<a href="url">link</a> text')
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_basic_html__not_across_paragraphs
|
29
|
+
assert_equal ['not italic', 'text across paragraphs'].join("\n\n"),
|
30
|
+
plain(['not <i>italic',
|
31
|
+
'text</i> across paragraphs'].join("\n\n"))
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_custom_syntax
|
35
|
+
assert_equal 'italic text', plain('/italic/ text')
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_custom_syntax__not_across_paragraphs
|
39
|
+
input = ['not/italic', 'text/across paragraphs'].join("\n\n")
|
40
|
+
assert_equal input, plain(input)
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_line_breaks
|
44
|
+
assert_equal "line\nbreaks", plain("line //\nbreaks")
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_remove_bad_html
|
48
|
+
assert_equal 'bad html', plain('<a>bad html</b>')
|
49
|
+
assert_equal 'bad html', plain('<a>bad html')
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_rule
|
53
|
+
assert_equal '- - - - - - - - -', plain('- - - - -')
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_reference
|
57
|
+
assert_equal '[ref]', plain('{{ref}}')
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_header
|
61
|
+
assert_equal "Some Header\n-----------", plain('== Some Header')
|
62
|
+
end
|
63
|
+
|
64
|
+
def test_quote
|
65
|
+
assert_equal "\tQuote Lines", plain(" Quote\n Lines")
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def plain(body)
|
71
|
+
Formats::Plain.new(body).call
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -15,35 +15,13 @@
|
|
15
15
|
#
|
16
16
|
# You should have received a copy of the GNU General Public License along with
|
17
17
|
# this program. If not, see <https://www.gnu.org/licenses/>.
|
18
|
-
require '
|
18
|
+
require 'helper'
|
19
19
|
|
20
|
-
module
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
# appear as plain text.
|
26
|
-
class Plain < Base
|
27
|
-
protected
|
28
|
-
|
29
|
-
def parse_section(ast)
|
30
|
-
parse_wiki_ast(ast).strip
|
31
|
-
end
|
32
|
-
|
33
|
-
def parse_internal_link(ast)
|
34
|
-
text = parse_wiki_ast(ast)
|
35
|
-
!text.empty? ? text : ast.locator
|
36
|
-
end
|
37
|
-
|
38
|
-
def parse_element(ast)
|
39
|
-
str = parse_wiki_ast(ast)
|
40
|
-
if ast.name == 'pre'
|
41
|
-
ctx.generate(str.gsub(/\A\n*(.*?)\n*\z/m, '\1'))
|
42
|
-
else
|
43
|
-
str
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|
20
|
+
module Formats
|
21
|
+
class TestRaw < UnitTest
|
22
|
+
def test_call
|
23
|
+
input = 'Some body content'
|
24
|
+
assert_same input, Formats::Raw.new(input).call
|
47
25
|
end
|
48
26
|
end
|
49
27
|
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# codeless_code filters and prints fables from http://thecodelesscode.com
|
4
|
+
# Copyright (C) 2018 Jon Sangster
|
5
|
+
#
|
6
|
+
# This program is free software: you can redistribute it and/or modify it under
|
7
|
+
# the terms of the GNU General Public License as published by the Free Software
|
8
|
+
# Foundation, either version 3 of the License, or (at your option) any later
|
9
|
+
# version.
|
10
|
+
#
|
11
|
+
# This program is distributed in the hope that it will be useful, but WITHOUT
|
12
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
13
|
+
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
14
|
+
# details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU General Public License along with
|
17
|
+
# this program. If not, see <https://www.gnu.org/licenses/>.
|
18
|
+
require 'helper'
|
19
|
+
require 'colorized_string'
|
20
|
+
|
21
|
+
module Formats
|
22
|
+
class TestTerm < UnitTest
|
23
|
+
def test_basic_html
|
24
|
+
assert_equal color('italic').italic + ' text',
|
25
|
+
term('<i>italic</i> text')
|
26
|
+
assert_equal color('bold').bold + ' text',
|
27
|
+
term('<b>bold</b> text')
|
28
|
+
assert_equal color('link').underline + ' text',
|
29
|
+
term('<a href="url">link</a> text')
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_custom_syntax
|
33
|
+
assert_equal color('italic').italic + ' text', term('/italic/ text')
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_custom_syntax__not_across_paragraphs
|
37
|
+
input = ['not/italic', 'text/across paragraphs'].join("\n\n")
|
38
|
+
assert_equal input, term(input)
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_line_breaks
|
42
|
+
assert_equal "line\nbreaks", term("line //\nbreaks")
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_rule
|
46
|
+
assert_equal color('- - - - - - - - -').yellow, term('- - - - -')
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_reference
|
50
|
+
assert_equal color('ref').yellow, term('{{ref}}')
|
51
|
+
end
|
52
|
+
|
53
|
+
def test_header
|
54
|
+
assert_equal color("Some Header\n-----------").blue,
|
55
|
+
term('== Some Header')
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_quote
|
59
|
+
assert_equal color('Quote Lines').green, term(" Quote\n Lines")
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def term(body)
|
65
|
+
Formats::Term.new(body).call.to_s
|
66
|
+
end
|
67
|
+
|
68
|
+
def color(str)
|
69
|
+
ColorizedString.new(str)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,177 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# codeless_code filters and prints fables from http://thecodelesscode.com
|
4
|
+
# Copyright (C) 2018 Jon Sangster
|
5
|
+
#
|
6
|
+
# This program is free software: you can redistribute it and/or modify it under
|
7
|
+
# the terms of the GNU General Public License as published by the Free Software
|
8
|
+
# Foundation, either version 3 of the License, or (at your option) any later
|
9
|
+
# version.
|
10
|
+
#
|
11
|
+
# This program is distributed in the hope that it will be useful, but WITHOUT
|
12
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
13
|
+
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
14
|
+
# details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU General Public License along with
|
17
|
+
# this program. If not, see <https://www.gnu.org/licenses/>.
|
18
|
+
require 'helper'
|
19
|
+
|
20
|
+
module Markup
|
21
|
+
class TestParser < UnitTest # rubocop:disable Metrics/ClassLength
|
22
|
+
def test_name_is_main
|
23
|
+
assert_equal 'main', parse.name
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_anchor
|
27
|
+
para = parse('[[label]]').child
|
28
|
+
|
29
|
+
assert_equal 'p', para.name
|
30
|
+
assert_equal 'a', para.child.name
|
31
|
+
assert_equal 'label', para.child.content
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_link
|
35
|
+
link = parse('[[url|label]]').child.child
|
36
|
+
|
37
|
+
assert_equal 'a', link.name
|
38
|
+
assert_equal 'label', link.content
|
39
|
+
assert_equal 'url', link['href']
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_italic
|
43
|
+
para = parse('/italic/').child
|
44
|
+
|
45
|
+
assert_equal 'p', para.name
|
46
|
+
assert_equal 'em', para.child.name
|
47
|
+
assert_equal 'italic', para.child.content
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_sup
|
51
|
+
para = parse('{{ref}}').child
|
52
|
+
|
53
|
+
assert_equal 'p', para.name
|
54
|
+
assert_equal 'sup', para.child.name
|
55
|
+
assert_equal 'ref', para.child.content
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_hr
|
59
|
+
rule = parse('- - - -').child
|
60
|
+
|
61
|
+
assert_equal 'hr', rule.name
|
62
|
+
end
|
63
|
+
|
64
|
+
def test_br
|
65
|
+
para = parse("first //\nsecond").child
|
66
|
+
|
67
|
+
assert_equal 'p', para.name
|
68
|
+
children = para.children
|
69
|
+
assert_equal 'first', children[0].content
|
70
|
+
assert_equal 'br', children[1].name
|
71
|
+
assert_equal 'second', children[2].content
|
72
|
+
end
|
73
|
+
|
74
|
+
def test_quote
|
75
|
+
quote = parse(" Quote\n Lines").child
|
76
|
+
|
77
|
+
assert_equal 'blockquote', quote.name
|
78
|
+
assert_equal 'Quote Lines', quote.content
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
def parse(body = nil)
|
84
|
+
Markup::Parser.new(body || fable.body).call
|
85
|
+
end
|
86
|
+
|
87
|
+
def fable(dir = 'en-test', fable = 'case-123.txt', root: fake_fs)
|
88
|
+
(@fable ||= {})["#{dir}/#{fable}"] ||=
|
89
|
+
Fable.new(root.glob(dir).first.glob(fable).first)
|
90
|
+
end
|
91
|
+
|
92
|
+
def fake_fs
|
93
|
+
FakeDir.new('/').tap do |fs|
|
94
|
+
fs.create_path('en-test/case-123.txt', fable_text)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def fable_text
|
99
|
+
<<-FABLE.gsub(/^ {8}/, '')
|
100
|
+
Date: 2013-12-28
|
101
|
+
Number: 125
|
102
|
+
Geekiness: 0
|
103
|
+
Title: Power
|
104
|
+
Names: Subashikoi, Shinpuru, Spider Clan
|
105
|
+
Topics: power, promotion, management, work-life balance
|
106
|
+
Illus.0.src: Vine.jpg
|
107
|
+
Illus.0.title: Even a garden needs a little debugging now and then.
|
108
|
+
|
109
|
+
It is the function of the Temple abbots to direct the
|
110
|
+
activities of their respective clans: choosing projects,
|
111
|
+
setting deadlines, apportioning tasks, and employing
|
112
|
+
whatever means are necessary to ensure that schedules are
|
113
|
+
met. It is for these powers that the abbots are both envied
|
114
|
+
and despised. Indeed, it is rare for abbot and monk to
|
115
|
+
cross paths without the latter finding himself more
|
116
|
+
miserable for the experience.
|
117
|
+
|
118
|
+
Thus it was with no great joy that the elder monk
|
119
|
+
[[Shinpuru]] found himself visited by the new head abbot of
|
120
|
+
the [[Spider Clan]].
|
121
|
+
|
122
|
+
- - -
|
123
|
+
|
124
|
+
Shinpuru was in the temple greenhouse, tending the plants of
|
125
|
+
a small winter garden that he kept as a hobby, when the
|
126
|
+
head abbot approached and bowed low, saying: "Have I the good
|
127
|
+
fortune of being in the presence of the monk Shinpuru, whose
|
128
|
+
code is admired throughout the Temple?"
|
129
|
+
|
130
|
+
"This miserable soul is he," said Shinpuru, returning the bow.
|
131
|
+
|
132
|
+
"I have come to ask if you have given any thought to the
|
133
|
+
future," said the abbot.
|
134
|
+
|
135
|
+
"Tomorrow I expect the sun shall rise," answered Shinpuru.
|
136
|
+
"Unless I am wrong, in which case it will not."
|
137
|
+
|
138
|
+
"I was thinking of your future, specifically," replied the
|
139
|
+
abbot.
|
140
|
+
|
141
|
+
The head abbot frowned. "What would Shinpuru think of a
|
142
|
+
seed that refused to sprout, or a tree that refused to yield
|
143
|
+
fruit? What else should I think of a monk who so quickly
|
144
|
+
declines an opportunity for growth, for command, for power?"
|
145
|
+
|
146
|
+
Shinpuru set aside his shears to tie up the vine. "Define
|
147
|
+
power," he said.
|
148
|
+
|
149
|
+
"The ability to do as one wishes," said the abbot.
|
150
|
+
|
151
|
+
"Well, then," said Shinpuru. "Tomorrow I wish to greet the
|
152
|
+
sunrise with my little bowl. Then I wish to take some hot
|
153
|
+
tea at my workstation as I read the technical sites I find
|
154
|
+
most illuminating, after which I look forward to a fruitful
|
155
|
+
day of coding interrupted only by some pleasant exchanges
|
156
|
+
with my fellows and a midday meal at this very spot, tending
|
157
|
+
my garden. When night falls I wish to find myself in my
|
158
|
+
cozy room with a belly full of rice, a cup full of hot sake,
|
159
|
+
a purse full of coins sufficient to buy more seeds, and a
|
160
|
+
mind empty of all other cares."
|
161
|
+
|
162
|
+
The abbot bowed. "I expect that Shinpuru has all the power
|
163
|
+
he could desire, then. Unless he is wrong."
|
164
|
+
|
165
|
+
"I am seldom wrong about such things," said Shinpuru,
|
166
|
+
picking up his shears again as another yellow leaf caught
|
167
|
+
his eye. "In a world where even the sunrise is uncertain, a
|
168
|
+
man may be excused for not knowing a great many things. But
|
169
|
+
to not know my own heart? I hope to never be so hopeless
|
170
|
+
a fool."
|
171
|
+
|
172
|
+
|
173
|
+
{{*}} As documented in cases [[#61|61]], [[#62|62]], [[#67|67]], [[#120|120]], and probably others besides. Abbots in the Spider Clan have the average life expectancy of a dolphin in the Gobi desert.
|
174
|
+
FABLE
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
@@ -18,7 +18,7 @@
|
|
18
18
|
require 'helper'
|
19
19
|
require 'minitest/mock'
|
20
20
|
|
21
|
-
class TestCli < UnitTest
|
21
|
+
class TestCli < UnitTest # rubocop:disable Metrics/ClassLength
|
22
22
|
def setup
|
23
23
|
@pager = ENV.delete('PAGER')
|
24
24
|
end
|
@@ -27,11 +27,29 @@ class TestCli < UnitTest
|
|
27
27
|
ENV['PAGER'] = @pager
|
28
28
|
end
|
29
29
|
|
30
|
+
def test_bad_arguments
|
31
|
+
assert_output(nil, /unknown option `--unknown-flag'/) do
|
32
|
+
cli('--unknown-flag').call
|
33
|
+
end
|
34
|
+
assert_output(nil, /Usage: test_app/) do
|
35
|
+
cli('--unknown-flag').call
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_bad_number
|
40
|
+
assert_raises(ArgumentError) { cli('--random-set', 'string').call }
|
41
|
+
end
|
42
|
+
|
30
43
|
def test_call_default
|
31
44
|
expected = "00123 Test Case\n00234 Test Case 2\n"
|
32
45
|
assert_output(expected) { cli.call }
|
33
46
|
end
|
34
47
|
|
48
|
+
def test_force_stdout
|
49
|
+
expected = "00123 Test Case\n00234 Test Case 2\n"
|
50
|
+
assert_output(expected) { cli('-o', '-').call }
|
51
|
+
end
|
52
|
+
|
35
53
|
def test_call_single_by_number
|
36
54
|
[
|
37
55
|
" Test Case\n" \
|
@@ -56,6 +74,42 @@ class TestCli < UnitTest
|
|
56
74
|
assert_output("en test\n") { cli('--list-translations').call }
|
57
75
|
end
|
58
76
|
|
77
|
+
def test_random_stability
|
78
|
+
create_cases(10)
|
79
|
+
cli = cli('--random')
|
80
|
+
|
81
|
+
first = capture_io { Random.srand(0) && cli.call }
|
82
|
+
second = capture_io { Random.srand(1) && cli.call }
|
83
|
+
third = capture_io { Random.srand(0) && cli.call }
|
84
|
+
|
85
|
+
assert_equal first, third
|
86
|
+
refute_equal first, second
|
87
|
+
end
|
88
|
+
|
89
|
+
def test_random_set_stability
|
90
|
+
create_cases(10)
|
91
|
+
cli = cli('--random-set', '10')
|
92
|
+
|
93
|
+
first = capture_io { Random.srand(0) && cli.call }
|
94
|
+
second = capture_io { Random.srand(1) && cli.call }
|
95
|
+
third = capture_io { Random.srand(0) && cli.call }
|
96
|
+
|
97
|
+
assert_equal first, third
|
98
|
+
refute_equal first, second
|
99
|
+
end
|
100
|
+
|
101
|
+
def test_daily_stability
|
102
|
+
create_cases(10)
|
103
|
+
cli = cli('--daily')
|
104
|
+
|
105
|
+
first = capture_io { stub_today('2018-12-23') { cli.call } }
|
106
|
+
second = capture_io { stub_today('2000-11-11') { cli.call } }
|
107
|
+
third = capture_io { stub_today('2018-12-23') { cli.call } }
|
108
|
+
|
109
|
+
assert_equal first, third
|
110
|
+
refute_equal first, second
|
111
|
+
end
|
112
|
+
|
59
113
|
private
|
60
114
|
|
61
115
|
def cli(*args)
|
@@ -64,7 +118,11 @@ class TestCli < UnitTest
|
|
64
118
|
end
|
65
119
|
end
|
66
120
|
|
67
|
-
def fake_fs
|
121
|
+
def fake_fs
|
122
|
+
@fake_fs ||= create_fake_fs
|
123
|
+
end
|
124
|
+
|
125
|
+
def create_fake_fs # rubocop:disable Metrics/MethodLength
|
68
126
|
FakeDir.new('/').tap do |fs|
|
69
127
|
fs.create_path('en-test/case-123.txt', <<-FABLE)
|
70
128
|
Title: Test Case
|
@@ -82,4 +140,19 @@ class TestCli < UnitTest
|
|
82
140
|
FABLE
|
83
141
|
end
|
84
142
|
end
|
143
|
+
|
144
|
+
def create_cases(count)
|
145
|
+
(100..(100 + count)).each do |num|
|
146
|
+
fake_fs.create_path("en/test/case-#{num}.txt", <<-FABLE)
|
147
|
+
Title: Case #{num}
|
148
|
+
Number: #{num}
|
149
|
+
|
150
|
+
Case #{num}
|
151
|
+
FABLE
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def stub_today(date, &blk)
|
156
|
+
Date.stub(:today, Date.parse(date), &blk)
|
157
|
+
end
|
85
158
|
end
|
data/test/support/fs.rb
CHANGED
@@ -1,5 +1,20 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# codeless_code filters and prints fables from http://thecodelesscode.com
|
4
|
+
# Copyright (C) 2018 Jon Sangster
|
5
|
+
#
|
6
|
+
# This program is free software: you can redistribute it and/or modify it under
|
7
|
+
# the terms of the GNU General Public License as published by the Free Software
|
8
|
+
# Foundation, either version 3 of the License, or (at your option) any later
|
9
|
+
# version.
|
10
|
+
#
|
11
|
+
# This program is distributed in the hope that it will be useful, but WITHOUT
|
12
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
13
|
+
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
14
|
+
# details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU General Public License along with
|
17
|
+
# this program. If not, see <https://www.gnu.org/licenses/>.
|
3
18
|
module Support
|
4
19
|
class FakeFile
|
5
20
|
attr_reader :name, :path, :body, :parent
|