codeless_code 0.1.7 → 0.1.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/.gitmodules +1 -1
  3. data/.reek.yml +14 -0
  4. data/.rubocop.yml +18 -0
  5. data/.ruby-version +1 -0
  6. data/{test/test_codeless_code.rb → .travis.yml} +12 -10
  7. data/Gemfile +5 -0
  8. data/Gemfile.lock +21 -0
  9. data/Guardfile +13 -1
  10. data/README.md +6 -0
  11. data/Rakefile +26 -11
  12. data/VERSION +1 -1
  13. data/bin/codeless_code +3 -1
  14. data/codeless_code.gemspec +33 -12
  15. data/lib/codeless_code/catalog.rb +3 -1
  16. data/lib/codeless_code/cli.rb +56 -24
  17. data/lib/codeless_code/commands/filter_fables.rb +15 -10
  18. data/lib/codeless_code/commands/list_translations.rb +3 -0
  19. data/lib/codeless_code/fable.rb +42 -53
  20. data/lib/codeless_code/fable_set.rb +8 -6
  21. data/lib/codeless_code/filters/builders.rb +4 -2
  22. data/lib/codeless_code/filters/composite.rb +3 -1
  23. data/lib/codeless_code/filters/date.rb +40 -30
  24. data/lib/codeless_code/filters/from_options.rb +45 -28
  25. data/lib/codeless_code/filters/headers/base.rb +61 -0
  26. data/lib/codeless_code/filters/{header_integer.rb → headers/integer.rb} +16 -19
  27. data/lib/codeless_code/filters/{header_string.rb → headers/string.rb} +12 -22
  28. data/lib/codeless_code/filters/lang.rb +3 -0
  29. data/lib/codeless_code/filters/translator.rb +7 -2
  30. data/lib/codeless_code/filters.rb +16 -8
  31. data/lib/codeless_code/formats/base.rb +20 -7
  32. data/lib/codeless_code/formats/parsers/base.rb +106 -0
  33. data/lib/codeless_code/formats/parsers/plain.rb +49 -0
  34. data/lib/codeless_code/formats/parsers/term.rb +79 -0
  35. data/lib/codeless_code/formats/plain.rb +21 -12
  36. data/lib/codeless_code/formats/raw.rb +2 -0
  37. data/lib/codeless_code/formats/term.rb +21 -15
  38. data/lib/codeless_code/language_set.rb +7 -4
  39. data/lib/codeless_code/options.rb +5 -3
  40. data/lib/codeless_code/renderers/fable.rb +13 -8
  41. data/lib/codeless_code/renderers/term_page.rb +46 -31
  42. data/lib/codeless_code.rb +119 -106
  43. data/test/codeless_code/commands/test_filter_fables.rb +89 -0
  44. data/test/codeless_code/filters/headers/test_integer.rb +86 -0
  45. data/test/codeless_code/filters/headers/test_string.rb +86 -0
  46. data/test/codeless_code/filters/test_builders.rb +4 -2
  47. data/test/codeless_code/filters/test_composite.rb +70 -0
  48. data/test/codeless_code/filters/test_date.rb +99 -0
  49. data/test/codeless_code/filters/test_langs.rb +50 -0
  50. data/test/codeless_code/filters/test_translator.rb +51 -0
  51. data/test/codeless_code/renderers/test_fable.rb +98 -0
  52. data/test/codeless_code/renderers/test_term_page.rb +87 -0
  53. data/test/codeless_code/test_catalog.rb +12 -5
  54. data/test/codeless_code/test_cli.rb +85 -0
  55. data/test/codeless_code/test_fable.rb +19 -10
  56. data/test/codeless_code/test_fable_set.rb +17 -5
  57. data/test/codeless_code/test_language_set.rb +16 -3
  58. data/test/codeless_code/test_options.rb +3 -11
  59. data/test/helper.rb +36 -10
  60. data/test/support/fs.rb +103 -0
  61. metadata +65 -11
  62. data/.document +0 -5
  63. data/data/README.md +0 -34
  64. data/lib/codeless_code/formats/plain_generator.rb +0 -157
  65. data/lib/codeless_code/formats/term_generator.rb +0 -167
  66. data/test/codeless_code/filters/test_header_integer.rb +0 -82
  67. data/test/codeless_code/filters/test_header_string.rb +0 -82
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # codeless_code filters and prints fables from http://thecodelesscode.com
2
4
  # Copyright (C) 2018 Jon Sangster
3
5
  #
@@ -14,15 +16,21 @@
14
16
  # You should have received a copy of the GNU General Public License along with
15
17
  # this program. If not, see <https://www.gnu.org/licenses/>.
16
18
  module CodelessCode
19
+ # Functors which test whether a given {Fable} matches some criteria.
17
20
  module Filters
18
- autoload :Builders, 'codeless_code/filters/builders'
19
- autoload :Composite, 'codeless_code/filters/composite'
20
- autoload :Date, 'codeless_code/filters/date'
21
- autoload :FromOptions, 'codeless_code/filters/from_options'
22
- autoload :HeaderInteger, 'codeless_code/filters/header_integer'
23
- autoload :Lang, 'codeless_code/filters/lang'
24
- autoload :HeaderString, 'codeless_code/filters/header_string'
25
- autoload :Translator, 'codeless_code/filters/translator'
21
+ autoload :Builders, 'codeless_code/filters/builders'
22
+ autoload :Composite, 'codeless_code/filters/composite'
23
+ autoload :Date, 'codeless_code/filters/date'
24
+ autoload :FromOptions, 'codeless_code/filters/from_options'
25
+ autoload :Lang, 'codeless_code/filters/lang'
26
+ autoload :Translator, 'codeless_code/filters/translator'
27
+
28
+ # Filters which inspect a {Fable fable's} headers
29
+ module Headers
30
+ autoload :Base, 'codeless_code/filters/headers/base'
31
+ autoload :Integer, 'codeless_code/filters/headers/integer'
32
+ autoload :String, 'codeless_code/filters/headers/string'
33
+ end
26
34
 
27
35
  extend Builders
28
36
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # codeless_code filters and prints fables from http://thecodelesscode.com
2
4
  # Copyright (C) 2018 Jon Sangster
3
5
  #
@@ -18,8 +20,9 @@ require 'nokogiri'
18
20
 
19
21
  module CodelessCode
20
22
  module Formats
23
+ # Abstract base class for all formats.
21
24
  class Base
22
- attr_accessor :raw
25
+ attr_reader :raw
23
26
 
24
27
  def initialize(raw)
25
28
  @raw = raw
@@ -27,14 +30,24 @@ module CodelessCode
27
30
 
28
31
  protected
29
32
 
30
- def to_xhtml(str)
31
- # MediaCloth expects XHTML-ish pages and chokes on ommited end tags
32
- Nokogiri::HTML(str).css('body > *').to_xhtml
33
+ def from_wiki(doc, parser_name)
34
+ parser = Parsers.const_get(parser_name).new(self)
35
+ MediaCloth.wiki_to_html(doc.to_s, generator: parser)
36
+ end
37
+ end
38
+
39
+ # MediaCloth expects XHTML-ish pages and chokes on ommited end tags
40
+ class XhtmlDoc
41
+ def self.parse(html)
42
+ new(Nokogiri::HTML(html))
43
+ end
44
+
45
+ def initialize(doc)
46
+ @doc = doc
33
47
  end
34
48
 
35
- def from_wiki(str, generator)
36
- return "" if str.length == 0
37
- MediaCloth::wiki_to_html(str, generator: generator)
49
+ def to_s
50
+ @doc.css('body > *').to_xhtml
38
51
  end
39
52
  end
40
53
  end
@@ -0,0 +1,106 @@
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 'mediacloth'
19
+
20
+ module CodelessCode
21
+ module Formats
22
+ module Parsers
23
+ # An abstract base class for the custom parsers for the syntax tree
24
+ # generated by the +MediaCloth+ gem.
25
+ class Base < MediaWikiWalker
26
+ # Reimplement these
27
+ #
28
+ # @example
29
+ # def parse_paragraph(ast)
30
+ # parse_wiki_ast(ast)
31
+ # end
32
+ ABSTRACT_METHODS = %i[
33
+ parse_paragraph
34
+ parse_paste
35
+ parse_formatted
36
+ parse_list_item
37
+ parse_list_term
38
+ parse_list_definition
39
+ parse_preformatted
40
+ parse_section
41
+ parse_link
42
+ parse_internal_link
43
+ parse_internal_link_item
44
+ parse_table
45
+ parse_table_row
46
+ parse_table_cell
47
+ parse_element
48
+ parse_template
49
+ parse_category
50
+ parse_keyword
51
+ ].freeze
52
+
53
+ attr_reader :ctx
54
+
55
+ def initialize(ctx)
56
+ @ctx = ctx
57
+ end
58
+
59
+ protected
60
+
61
+ def parse_wiki_ast(ast)
62
+ super(ast).join
63
+ end
64
+
65
+ ABSTRACT_METHODS.each do |meth|
66
+ define_method(meth) { |ast| parse_wiki_ast(ast) }
67
+ end
68
+
69
+ # Reimplement this
70
+ # :reek:UtilityFunction
71
+ def parse_text(ast)
72
+ ast.contents
73
+ end
74
+
75
+ # Reimplement this
76
+ def parse_list(ast)
77
+ ast.children.map(&method(:parse_list_child))
78
+ end
79
+
80
+ # Reimplement this
81
+ def parse_resource_link(ast)
82
+ ast.children.map do |child|
83
+ parse_internal_link_item(child) if child.is_a?(InternalLinkItemAST)
84
+ end
85
+ end
86
+
87
+ # Reimplement this
88
+ def parse_category_link(ast)
89
+ ast.children.map do |child|
90
+ parse_internal_link_item(child) if child.is_a?(InternalLinkItemAST)
91
+ end
92
+ end
93
+
94
+ private
95
+
96
+ def parse_list_child(child)
97
+ case child
98
+ when ListItemAST then parse_list_item(child)
99
+ when ListTermAST then parse_list_term(child)
100
+ when ListDefinitionAST then parse_list_definition(child)
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,49 @@
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 'mediacloth'
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
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,79 @@
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 'mediacloth'
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 formats a document using ANSI codes for color, bold, italics, and
25
+ # other styles.
26
+ class Term < Base
27
+ protected
28
+
29
+ def parse_formatted(ast)
30
+ if ast.formatting == :Bold
31
+ color(parse_wiki_ast(ast)).bold
32
+ else
33
+ color(parse_wiki_ast(ast)).italic
34
+ end
35
+ end
36
+
37
+ def parse_preformatted(ast)
38
+ color(parse_wiki_ast(ast)).green
39
+ end
40
+
41
+ def parse_section(ast)
42
+ color(parse_wiki_ast(ast).strip).blue
43
+ end
44
+
45
+ def parse_link(ast)
46
+ color(parse_wiki_ast(ast)).underline
47
+ end
48
+
49
+ def parse_internal_link(ast)
50
+ text = parse_wiki_ast(ast)
51
+ color(!text.empty? ? text : ast.locator).underline
52
+ end
53
+
54
+ def parse_element(ast)
55
+ str = parse_wiki_ast(ast)
56
+ if ast.name == 'pre'
57
+ color(ctx.generate(str.gsub(/\A\n*(.*?)\n*\z/m, '\1'))).red
58
+ else
59
+ str
60
+ end
61
+ end
62
+
63
+ def parse_template(ast)
64
+ color(ast.template_name).yellow
65
+ end
66
+
67
+ def parse_keyword(ast)
68
+ color(parse_wiki_ast(ast)).underline
69
+ end
70
+
71
+ private
72
+
73
+ def color(str)
74
+ ctx.color(str)
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # codeless_code filters and prints fables from http://thecodelesscode.com
2
4
  # Copyright (C) 2018 Jon Sangster
3
5
  #
@@ -21,24 +23,31 @@ module CodelessCode
21
23
  class Plain < Base
22
24
  def call
23
25
  raw.split("\n\n")
24
- .map { |str| from_wiki(to_xhtml(regex(str))) }
26
+ .map { |str| from_wiki(CleanupBody.new(str)) }
25
27
  .join("\n\n")
26
28
  end
27
29
 
28
- private
30
+ protected
29
31
 
30
- def regex(str)
31
- [
32
- [/\/\/\w*$/, ''],
33
- [/<i>([^<]+)<\/i>/mi, '\1'],
34
- [/<b>([^<]+)<\/b>/mi, '\1'],
35
- [/<a[^>]+>([^<]+)<\/a>/mi, '\1'],
36
- [/\/(\w+)\//, '\1'],
37
- ].inject(str) { |str, args| str = str.gsub(*args) }
32
+ def from_wiki(str)
33
+ super(XhtmlDoc.parse(str.to_s), :Plain)
34
+ end
35
+ end
36
+
37
+ # Tidies up the mixed syntax found in fables
38
+ class CleanupBody
39
+ def initialize(body)
40
+ @body = body
38
41
  end
39
42
 
40
- def from_wiki(str)
41
- super(str, PlainGenerator.new(self))
43
+ def to_s
44
+ [
45
+ [%r{//\w*$}, ''],
46
+ [%r{<i>([^<]+)</i>}mi, '\1'],
47
+ [%r{<b>([^<]+)</b>}mi, '\1'],
48
+ [%r{<a[^>]+>([^<]+)</a>}mi, '\1'],
49
+ [%r{/(\w+)/}, '\1']
50
+ ].inject(@body) { |str, args| str.gsub(*args) }
42
51
  end
43
52
  end
44
53
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # codeless_code filters and prints fables from http://thecodelesscode.com
2
4
  # Copyright (C) 2018 Jon Sangster
3
5
  #
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # codeless_code filters and prints fables from http://thecodelesscode.com
2
4
  # Copyright (C) 2018 Jon Sangster
3
5
  #
@@ -15,7 +17,6 @@
15
17
  # this program. If not, see <https://www.gnu.org/licenses/>.
16
18
  require 'colorized_string'
17
19
  require 'mediacloth'
18
- require 'nokogiri'
19
20
 
20
21
  module CodelessCode
21
22
  module Formats
@@ -23,26 +24,31 @@ module CodelessCode
23
24
  # colors, etc.
24
25
  class Term < Base
25
26
  def call
26
- from_wiki(to_xhtml(regex(raw)))
27
+ from_wiki
27
28
  end
28
29
 
29
- def regex(str)
30
- [
31
- [/\/\/\w*$/, ''],
32
- [/^\| .*/, c('\\0').green],
33
- [/<i>([^<]+)<\/i>/mi, "''\\1''"],
34
- [/<b>([^<]+)<\/b>/mi, "'''\\1'''"],
35
- [/<a[^>]+>([^<]+)<\/a>/mi, '[[\1]]'],
36
- [/\/(\w+)\//, "''\\1''"],
37
- ].inject(str) { |str, args| str = str.gsub(*args) }
30
+ # :reek:UtilityFunction
31
+ def color(str)
32
+ ColorizedString.new(str)
38
33
  end
39
34
 
40
- def from_wiki(str)
41
- super(str, TermGenerator.new(self))
35
+ protected
36
+
37
+ def from_wiki
38
+ super(XhtmlDoc.parse(regex_raw), :Term)
42
39
  end
43
40
 
44
- def c(str)
45
- ColorizedString.new(str)
41
+ private
42
+
43
+ def regex_raw
44
+ [
45
+ [%r{//\w*$}, ''],
46
+ [/^\| .*/, ColorizedString.new('\\0').green],
47
+ [%r{<i>([^<]+)</i>}mi, "''\\1''"],
48
+ [%r{<b>([^<]+)</b>}mi, "'''\\1'''"],
49
+ [%r{<a[^>]+>([^<]+)</a>}mi, '[[\1]]'],
50
+ [%r{/(\w+)/}, "''\\1''"]
51
+ ].inject(raw) { |str, args| str.gsub(*args) }
46
52
  end
47
53
  end
48
54
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # codeless_code filters and prints fables from http://thecodelesscode.com
2
4
  # Copyright (C) 2018 Jon Sangster
3
5
  #
@@ -22,16 +24,17 @@ module CodelessCode
22
24
  extend Forwardable
23
25
  include Enumerable
24
26
 
27
+ # If the requested language set doesn't exist
25
28
  NotFoundError = Class.new(StandardError)
26
29
 
27
- LANG_PATTERN = '%s-*'.freeze
30
+ LANG_PATTERN = '%s-*'
28
31
 
29
- attr_accessor :lang, :root_dir
32
+ attr_reader :lang, :root_dir
30
33
  def_delegator :fable_sets, :each
31
34
 
32
35
  def initialize(lang, root_dir:)
33
- self.lang = lang
34
- self.root_dir = root_dir
36
+ @lang = lang
37
+ @root_dir = root_dir
35
38
  end
36
39
 
37
40
  def fable_sets
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # codeless_code filters and prints fables from http://thecodelesscode.com
2
4
  # Copyright (C) 2018 Jon Sangster
3
5
  #
@@ -62,9 +64,9 @@ module CodelessCode
62
64
  args = [@command_name] + @argv
63
65
  opts = CodelessCode::OPTIONS.curry[@command_name]
64
66
 
65
- Slop.parse(args, suppress_errors: suppress_errors, &opts).tap do |o|
66
- if !suppress_errors && o.arguments.size > MAX_ARGS + 1
67
- raise format('too many arguments: %p', o.arguments[1..-1])
67
+ Slop.parse(args, suppress_errors: suppress_errors, &opts).tap do |opt|
68
+ if !suppress_errors && opt.arguments.size > MAX_ARGS + 1
69
+ raise format('too many arguments: %p', opt.arguments[1..-1])
68
70
  end
69
71
  end
70
72
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # codeless_code filters and prints fables from http://thecodelesscode.com
2
4
  # Copyright (C) 2018 Jon Sangster
3
5
  #
@@ -24,7 +26,7 @@ module CodelessCode
24
26
  page.title = best_title
25
27
  page.body = render_with(format, fallback: fallback)
26
28
 
27
- headers_no_best_title.map { |k, v| page.add_header(k, v) }
29
+ headers_no_best_title.map { |key, val| page.add_header(key, val) }
28
30
  end
29
31
  end
30
32
 
@@ -42,14 +44,17 @@ module CodelessCode
42
44
  private
43
45
 
44
46
  def render_slice(keys)
45
- headers.slice(*keys).map {|k, v| format('%s: %p', k, v) }.join(', ')
47
+ headers.slice(*keys)
48
+ .map { |key, val| format('%s: %p', key, val) }
49
+ .join(', ')
46
50
  end
47
51
 
48
52
  def render_with(format, fallback:)
49
53
  format.new(body).call
50
- rescue => e
51
- raise e unless fallback
52
- warn format('Error parsing %s: %s', file, e.message.strip)
54
+ rescue StandardError => err
55
+ raise err unless fallback
56
+
57
+ warn format('Error parsing %s: %s', file, err.message.strip)
53
58
  fallback.new(body).call
54
59
  end
55
60
 
@@ -58,13 +63,13 @@ module CodelessCode
58
63
  end
59
64
 
60
65
  def headers_no_best_title
61
- sorted_headers.dup.delete_if { |_, v| v&.strip == title&.strip }
66
+ sorted_headers.dup.delete_if { |_, val| val&.strip == title&.strip }
62
67
  end
63
68
 
64
69
  def sorted_headers
65
70
  Hash[
66
- headers.each_with_index.sort_by do |(k,_), i|
67
- HEADER_SORT.index(k) || HEADER_SORT.size + i
71
+ headers.each_with_index.sort_by do |(key, _), index|
72
+ HEADER_SORT.index(key) || HEADER_SORT.size + index
68
73
  end.map(&:first)
69
74
  ]
70
75
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # codeless_code filters and prints fables from http://thecodelesscode.com
2
4
  # Copyright (C) 2018 Jon Sangster
3
5
  #
@@ -24,16 +26,21 @@ module CodelessCode
24
26
  '%<title>s', '%<sep1>s', '%<headers>s', '%<sep2>s', '', '%<body>s'
25
27
  ].join("\n").freeze
26
28
 
29
+ # :reek:Attribute
27
30
  attr_accessor :title, :body
31
+ attr_reader :headers
32
+
33
+ # :reek:ControlParameter
34
+ def initialize(max_width: nil, width_func: nil)
35
+ @max_width = max_width
36
+ @max_width ||= (width_func || TermWidth.new).call
28
37
 
29
- def initialize(max_width: nil)
30
- @max_width = max_width || term_width
31
38
  @headers = {}
32
39
  @key_width = 0
33
40
  end
34
41
 
35
42
  def to_s
36
- format(PAGER_FORMAT, title: title.center(width),
43
+ format(PAGER_FORMAT, title: title.center(width).rstrip,
37
44
  sep1: seperator('='), sep2: seperator('-'),
38
45
  headers: header_section, body: body)
39
46
  end
@@ -45,38 +52,31 @@ module CodelessCode
45
52
 
46
53
  private
47
54
 
48
- def term_width
49
- if (w = %x[tput cols].strip&.to_i)&.positive?
50
- w
51
- end
52
- rescue Errno::ENOENT
53
- nil
54
- end
55
-
56
- def seperator(ch = '=')
57
- (@seperator ||= {})[ch] ||= ch * width
55
+ def seperator(char = '=')
56
+ (@seperator ||= {})[char] ||= char * width
58
57
  end
59
58
 
60
59
  def header_section
61
- lines = @headers.map { |k, v| format_header(k, v, wrap: true) }
62
- max_line_width = lines.join("\n").lines.map { |s| s.size }.max
60
+ lines = format_header_section
61
+ max_line_width = lines.join("\n").lines.map(&:size).max || 0
63
62
  padding = [0, (width - max_line_width)].max / 2
64
63
 
65
- lines.map { |s| [' ' * padding, s].join }.join("\n")
64
+ lines.map { |line| [' ' * padding, line].join }.join("\n")
66
65
  end
67
66
 
68
- def format_header(k, v, wrap: false)
69
- line =
70
- if wrap
71
- v.chars
72
- .each_slice(width - @key_width - 2)
73
- .map { |s| s.join.strip }
74
- .inject { |str, s| str << "\n" << ' ' * (@key_width + 2) << s }
75
- else
76
- v
77
- end
78
-
79
- format("% #{@key_width}s: %s", k, line)
67
+ def format_header_section
68
+ headers.map { |key, value| format_header(key, wrap_header(value)) }
69
+ end
70
+
71
+ def format_header(key, value)
72
+ format("% #{@key_width}s: %s", key, value)
73
+ end
74
+
75
+ def wrap_header(head)
76
+ head.chars
77
+ .each_slice(width - @key_width - 2)
78
+ .map { |str| str.join.strip }
79
+ .inject { |str, part| str + "\n" + ' ' * (@key_width + 2) + part }
80
80
  end
81
81
 
82
82
  def width
@@ -85,12 +85,27 @@ module CodelessCode
85
85
 
86
86
  def content_width
87
87
  [
88
- body.lines.map do |s|
89
- ColorizedString[s].uncolorize.strip.size
88
+ body.lines.map do |line|
89
+ ColorizedString[line].uncolorize.strip.size
90
90
  end.max || 0,
91
- @headers.map(&method(:format_header)).map(&:size).max || 0
91
+ headers.map(&method(:format_header)).map(&:size).max || 0
92
92
  ].max
93
93
  end
94
94
  end
95
+
96
+ # Ask an external application how wide our terminal is
97
+ class TermWidth
98
+ def initialize(cmd = 'tputs cols')
99
+ @cmd = cmd
100
+ end
101
+
102
+ def call
103
+ if (tput_width = `#{@cmd}`.strip.to_i).positive?
104
+ tput_width
105
+ end
106
+ rescue Errno::ENOENT
107
+ nil
108
+ end
109
+ end
95
110
  end
96
111
  end