codeless_code 0.1.8 → 0.2.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 +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
|