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.
@@ -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
@@ -95,7 +95,7 @@ module CodelessCode
95
95
 
96
96
  # Ask an external application how wide our terminal is
97
97
  class TermWidth
98
- def initialize(cmd = 'tputs cols')
98
+ def initialize(cmd = 'tput cols')
99
99
  @cmd = cmd
100
100
  end
101
101
 
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
- # Custom parsers for the syntax tree generated by the +MediaCloth+ gem.
44
- module Parsers
45
- autoload :Base, 'codeless_code/formats/parsers/base'
46
- autoload :Plain, 'codeless_code/formats/parsers/plain'
47
- autoload :Term, 'codeless_code/formats/parsers/term'
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.
@@ -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 'mediacloth'
18
+ require 'helper'
19
19
 
20
- module CodelessCode
21
- module Formats
22
- module Parsers
23
- # A custom parser for the syntax tree generated by the +MediaCloth+ gem
24
- # which removes all formatting such that the rendered document should
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 # rubocop:disable Metrics/MethodLength
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