eskimo-ascii 3.0.0.pre.1

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.
Files changed (43) hide show
  1. checksums.yaml +7 -0
  2. data/lib/eskimo/ascii.rb +34 -0
  3. data/lib/eskimo/ascii/component.rb +35 -0
  4. data/lib/eskimo/ascii/components/did_you_mean.rb +30 -0
  5. data/lib/eskimo/ascii/components/either.rb +26 -0
  6. data/lib/eskimo/ascii/components/gutter.rb +29 -0
  7. data/lib/eskimo/ascii/components/highlight.rb +27 -0
  8. data/lib/eskimo/ascii/components/highlight_column.rb +57 -0
  9. data/lib/eskimo/ascii/components/indent.rb +21 -0
  10. data/lib/eskimo/ascii/components/line_break.rb +14 -0
  11. data/lib/eskimo/ascii/components/soft_break.rb +12 -0
  12. data/lib/eskimo/ascii/components/spacer.rb +29 -0
  13. data/lib/eskimo/ascii/components/squeeze.rb +39 -0
  14. data/lib/eskimo/ascii/components/strip.rb +11 -0
  15. data/lib/eskimo/ascii/components/strip_left.rb +11 -0
  16. data/lib/eskimo/ascii/components/strip_right.rb +11 -0
  17. data/lib/eskimo/ascii/components/style.rb +21 -0
  18. data/lib/eskimo/ascii/components/truncate.rb +27 -0
  19. data/lib/eskimo/ascii/components/truncate_rear.rb +21 -0
  20. data/lib/eskimo/ascii/components/wrap.rb +24 -0
  21. data/lib/eskimo/ascii/constants.rb +5 -0
  22. data/lib/eskimo/ascii/version.rb +7 -0
  23. data/spec/component_spec.rb +13 -0
  24. data/spec/components/did_you_mean_spec.rb +21 -0
  25. data/spec/components/either_spec.rb +21 -0
  26. data/spec/components/gutter_spec.rb +25 -0
  27. data/spec/components/highlight_column_spec.rb +63 -0
  28. data/spec/components/highlight_spec.rb +25 -0
  29. data/spec/components/indent_spec.rb +15 -0
  30. data/spec/components/line_break_spec.rb +17 -0
  31. data/spec/components/soft_break_spec.rb +17 -0
  32. data/spec/components/spacer_spec.rb +42 -0
  33. data/spec/components/squeeze_spec.rb +61 -0
  34. data/spec/components/strip_left_spec.rb +15 -0
  35. data/spec/components/strip_right_spec.rb +15 -0
  36. data/spec/components/strip_spec.rb +15 -0
  37. data/spec/components/style_spec.rb +25 -0
  38. data/spec/components/truncate_rear_spec.rb +35 -0
  39. data/spec/components/truncate_spec.rb +35 -0
  40. data/spec/components/wrap_spec.rb +25 -0
  41. data/spec/spec_helper.rb +41 -0
  42. data/spec/support/component_suite.rb +15 -0
  43. metadata +188 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: b68fb7c3d713145484543bb2e096e01327e510e9055e7349b9f1e0449415915e
4
+ data.tar.gz: 5475bd6c450f2fc8038578b9b26a1cf3d3020ef38141c0e644c6cfe6ad3c8315
5
+ SHA512:
6
+ metadata.gz: 0d786f05a438ab22fe52a457fc95095cd6612badcdc0506b6101a14a214470ff7ff18967786bd51fdfb685e7df3be4ca39a0dcceb50122ac9961451b19986fd3
7
+ data.tar.gz: 870c65a3904e4456a64c0f1f9df061af84a02b8b5052b6db6df8b80cd308e34c4408a77b7da3672cec54a89874cf8535b401742a4417127d19ed89c55ae2f324
@@ -0,0 +1,34 @@
1
+ module Eskimo
2
+ module ASCII
3
+ end
4
+ end
5
+
6
+ begin
7
+ require 'did_you_mean'
8
+ rescue LoadError
9
+ end
10
+
11
+ require 'pastel'
12
+ require 'strings'
13
+ require 'tty-screen'
14
+
15
+ require 'eskimo/ascii/version'
16
+ require 'eskimo/ascii/constants'
17
+ require 'eskimo/ascii/component'
18
+ require 'eskimo/ascii/components/did_you_mean'
19
+ require 'eskimo/ascii/components/either'
20
+ require 'eskimo/ascii/components/gutter'
21
+ require 'eskimo/ascii/components/highlight'
22
+ require 'eskimo/ascii/components/highlight_column'
23
+ require 'eskimo/ascii/components/indent'
24
+ require 'eskimo/ascii/components/line_break'
25
+ require 'eskimo/ascii/components/soft_break'
26
+ require 'eskimo/ascii/components/spacer'
27
+ require 'eskimo/ascii/components/squeeze'
28
+ require 'eskimo/ascii/components/strip'
29
+ require 'eskimo/ascii/components/strip_left'
30
+ require 'eskimo/ascii/components/strip_right'
31
+ require 'eskimo/ascii/components/style'
32
+ require 'eskimo/ascii/components/truncate'
33
+ require 'eskimo/ascii/components/truncate_rear'
34
+ require 'eskimo/ascii/components/wrap'
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ # A base component class that renders child components defined in a block into
4
+ # a String for further formatting.
5
+ #
6
+ # class MyComponent < Eskimo::ASCII::Component
7
+ # def render(**)
8
+ # text = super
9
+ # text.is_a?(String) # => true
10
+ # end
11
+ # end
12
+ #
13
+ # Use of this class is optional. What's happening under the hood is:
14
+ #
15
+ # 1. Component maintains a reference to the Proc passed to {#initialize}. That
16
+ # Proc can potentially return a list of child components to render.
17
+ #
18
+ # 2. {Component#render} (called via "super" from sub-classes) invokes the
19
+ # `render` prop provided by {Core::Renderer#apply} with the tracked children
20
+ # which converts them to a String and returns them.
21
+ class Eskimo::ASCII::Component
22
+ def initialize(*, **, &children_gen)
23
+ @children = children_gen
24
+ end
25
+
26
+ def render(render:, **)
27
+ render[@children]
28
+ end
29
+
30
+ protected
31
+
32
+ def pastel
33
+ @pastel ||= Pastel.new
34
+ end
35
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Present the user with the closest possible correction, if any.
4
+ #
5
+ # DidYouMean.new(dictionary: [ 'abc', 'bca' ], item: 'abd')
6
+ # # => "hint: Did you mean? abc"
7
+ #
8
+ # DidYouMean.new(dictionary: [ 'abc', 'bca' ], item: 'asdfasdf')
9
+ # # => ""
10
+ #
11
+ # See https://github.com/yuki24/did_you_mean#using-the-didyoumeanspellchecker
12
+ class Eskimo::ASCII::DidYouMean < Eskimo::ASCII::Component
13
+ attr_reader :corrections, :separator
14
+
15
+ def initialize(dictionary:, item:, separator: " or ", &children)
16
+ @corrections = ::DidYouMean::SpellChecker.new(
17
+ dictionary: dictionary
18
+ ).correct(item)
19
+
20
+ @separator = separator
21
+
22
+ super
23
+ end
24
+
25
+ def render(**)
26
+ if corrections.any?
27
+ "Did you mean? #{corrections.join(separator)}"
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Render a fallback component in case the first one evaluates to an empty
4
+ # String.
5
+ #
6
+ # Either.new(->(**) { false }, "Fallback here")
7
+ # # => "Fallback here"
8
+ #
9
+ # Either.new(->(**) { "Hi!" }, "Fallback here")
10
+ # # => "Hi!"
11
+ #
12
+ class Eskimo::ASCII::Either
13
+ attr_reader :children
14
+
15
+ def initialize(primary, fallback)
16
+ @children = [ primary, fallback ]
17
+ end
18
+
19
+ def render(render:, **)
20
+ for child in children do
21
+ rendered = render[child]
22
+
23
+ return rendered unless rendered.empty?
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Prepend each line with a character or symbol.
4
+ #
5
+ # Gutter.new(char: '| ') do
6
+ # [ "Hello", "\n", "World!" ]
7
+ # end
8
+ # # => "| Hello"
9
+ # # "| World!"
10
+ class Eskimo::ASCII::Gutter < Eskimo::ASCII::Component
11
+ attr_reader :char, :spacing
12
+
13
+ def initialize(char:, spacing: 0, &children)
14
+ @char = char
15
+ @spacing = spacing
16
+
17
+ super
18
+ end
19
+
20
+ def render(**)
21
+ spacer = Array.new(spacing, char)
22
+
23
+ [
24
+ *spacer,
25
+ super.lines.map { |s| s.prepend(char) }.join,
26
+ *spacer
27
+ ].join("\n")
28
+ end
29
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Highlight a substring with ASCII arrows.
4
+ #
5
+ # Highlight.new(pattern: /lol/) do
6
+ # "- include: lol://file.yml"
7
+ # end
8
+ # # => "- include: lol://file.yml"
9
+ # # ^^^
10
+ #
11
+ class Eskimo::ASCII::Highlight < Eskimo::ASCII::Component
12
+ attr_reader :pastel, :pattern, :style
13
+
14
+ def initialize(pattern:, style: [:red, :bold, :underline], &children)
15
+ @pastel = Pastel.new
16
+ @pattern = pattern
17
+ @style = style
18
+
19
+ super(&children)
20
+ end
21
+
22
+ def render(**)
23
+ super.sub(pattern) do |substring|
24
+ pastel.decorate(substring, *style)
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,57 @@
1
+ # Highlight a particular character of a string with an ASCII arrow.
2
+ #
3
+ # HighlightColumn.new(line: 0, column: 14) do
4
+ # "- include: lol://wut.yml"
5
+ # end
6
+ # => "- include: lol://wut.yml"
7
+ # " ^ "
8
+ # " here "
9
+ class Eskimo::ASCII::HighlightColumn < Eskimo::ASCII::Component
10
+ def initialize(
11
+ column:,
12
+ line:,
13
+ markers: ['^', 'here'],
14
+ style: [:bold, :red],
15
+ &children
16
+ )
17
+ pastel = Pastel.new
18
+
19
+ @colorize = ->(str) { pastel.decorate(str, *style) }
20
+ @column = column
21
+ @line = line
22
+ @marker_padding = ' ' * @column
23
+ @markers = markers
24
+
25
+ super
26
+ end
27
+
28
+ def render(**)
29
+ lines = super.lines
30
+ line = lines[@line]
31
+
32
+ unless line.nil? || line[@column].nil?
33
+ lines[@line] = transform_line!(line, @column, &@colorize)
34
+ end
35
+
36
+ lines.join
37
+ end
38
+
39
+ protected
40
+
41
+ def create_markers()
42
+ buf = ''
43
+
44
+ for marker in @markers do
45
+ buf << @marker_padding + @colorize[marker] + "\n"
46
+ end
47
+
48
+ buf
49
+ end
50
+
51
+ def transform_line!(line, column, &fn)
52
+ line[column] = fn[line[column]]
53
+ line << "\n" unless line.end_with?("\n")
54
+ line << create_markers
55
+ line
56
+ end
57
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Indent text from the left.
4
+ #
5
+ # Indent.new(width: 8) do
6
+ # [ "Hello", SoftBreak.new, "World!" ]
7
+ # end
8
+ # # => " Hello"
9
+ # # " World!"
10
+ class Eskimo::ASCII::Indent < Eskimo::ASCII::Component
11
+ attr_reader :width
12
+
13
+ def initialize(width: 4, &children)
14
+ @width = width
15
+ super
16
+ end
17
+
18
+ def render(**)
19
+ Strings.pad(super, [0,0,0,width])
20
+ end
21
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Insert a paragraph-like line-break.
4
+ #
5
+ # [ "Hello", LineBreak.new, "World!" ]
6
+ # # => "Hello"
7
+ # # ""
8
+ # # ""
9
+ # # "World!"
10
+ class Eskimo::ASCII::LineBreak
11
+ def render(**)
12
+ "\n \n"
13
+ end
14
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Insert a soft line-break.
4
+ #
5
+ # [ "Hello", SoftBreak.new, "World!" ]
6
+ # # => "Hello"
7
+ # # "World!"
8
+ class Eskimo::ASCII::SoftBreak
9
+ def render(**)
10
+ "\n"
11
+ end
12
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Space consecutive components with soft breaks.
4
+ #
5
+ # Spacer.new([
6
+ # "Hello",
7
+ # "World!"
8
+ # ])
9
+ # # => "Hello"
10
+ # # "World!"
11
+ #
12
+ # The soft breaks for each conditional component will be preserved only
13
+ # if they do render some content.
14
+ class Eskimo::ASCII::Spacer
15
+ def initialize(children)
16
+ if !children.is_a?(Array) || block_given?
17
+ raise ArgumentError.new("Spacer works only with an Array of components")
18
+ end
19
+
20
+ @children = children
21
+ end
22
+
23
+ def render(**props)
24
+ rendered = @children.map(&props[:render])
25
+
26
+ without_blanks = rendered.reject(&:empty?)
27
+ without_blanks.join("\n")
28
+ end
29
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Squeeze immediate consecutive soft breaks.
4
+ #
5
+ # Squeeze.new do
6
+ # [
7
+ # SoftBreak.new,
8
+ # ConditionalComponent.new,
9
+ #
10
+ # SoftBreak.new,
11
+ # false && SomeOtherComponent.new,
12
+ #
13
+ # SoftBreak.new,
14
+ # 'hello',
15
+ # ]
16
+ # end
17
+ # # => ""
18
+ # # "hello"
19
+ #
20
+ # The soft breaks for each conditional component will be preserved only
21
+ # if they do render some content.
22
+ class Eskimo::ASCII::Squeeze
23
+ def initialize(children)
24
+ if !children.is_a?(Array) || block_given?
25
+ raise ArgumentError.new("Squeeze works only with an Array of components")
26
+ end
27
+
28
+ @children = children
29
+ end
30
+
31
+ def render(**props)
32
+ rendered = @children.map(&props[:render])
33
+
34
+ without_blanks = rendered.reject(&:empty?)
35
+ without_blanks.reject.with_index do |element, index|
36
+ element == "\n" && without_blanks[index+1] == "\n"
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Remove surrounding whitespace.
4
+ #
5
+ # Strip.new { " hello world " }
6
+ # # => "hello world"
7
+ class Eskimo::ASCII::Strip < Eskimo::ASCII::Component
8
+ def render(**)
9
+ super.strip
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Remove whitespace from the beginning.
4
+ #
5
+ # StripLeft.new { " hello world " }
6
+ # # => "hello world "
7
+ class Eskimo::ASCII::StripLeft < Eskimo::ASCII::Component
8
+ def render(**)
9
+ super.lstrip
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Remove whitespace from the end.
4
+ #
5
+ # StripRight.new { " hello world " }
6
+ # # => " hello world"
7
+ class Eskimo::ASCII::StripRight < Eskimo::ASCII::Component
8
+ def render(**)
9
+ super.rstrip
10
+ end
11
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Style text with colors and custom formatting.
4
+ #
5
+ # See [Pastel's documentation][pastel] for the accepted styles.
6
+ #
7
+ # [pastel]: https://github.com/piotrmurach/pastel
8
+ class Eskimo::ASCII::Style < Eskimo::ASCII::Component
9
+ attr_reader :pastel, :style
10
+
11
+ def initialize(*style, &children)
12
+ @style = style.flatten
13
+ @pastel = Pastel.new
14
+
15
+ super
16
+ end
17
+
18
+ def render(**)
19
+ pastel.decorate(super, *style)
20
+ end
21
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Truncate text from the beginning if it exceeds a certain width.
4
+ #
5
+ # Truncate.new(width: 3) do
6
+ # "foo bar"
7
+ # end
8
+ # # => "... bar"
9
+ class Eskimo::ASCII::Truncate < Eskimo::ASCII::Component
10
+ attr_reader :maxlen
11
+
12
+ def initialize(reserve: 0, width: Constants::SCREEN_COLUMNS, &children)
13
+ @maxlen = [0, width - reserve].max
14
+
15
+ super
16
+ end
17
+
18
+ def render(**)
19
+ text = super
20
+
21
+ if text.length >= maxlen
22
+ '...' + text[text.length - maxlen - 1 .. -1]
23
+ else
24
+ text
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'eskimo/ascii/components/truncate'
4
+
5
+ # Truncate text from the rear if it exceeds a certain width.
6
+ #
7
+ # TruncateRear.new(width: 3) do
8
+ # "foo bar"
9
+ # end
10
+ # # => "foo..."
11
+ class Eskimo::ASCII::TruncateRear < Eskimo::ASCII::Truncate
12
+ def render(render:, **)
13
+ text = render[@children]
14
+
15
+ if text.length >= maxlen
16
+ text[0..maxlen - 1] + '...'
17
+ else
18
+ text
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Wrap a block text with newlines at a certain threshold.
4
+ #
5
+ # Wrap.new(width: 3) do
6
+ # "foobar"
7
+ # end
8
+ # # => "foo"
9
+ # # "bar"
10
+ #
11
+ module Eskimo::ASCII
12
+ class Wrap < Component
13
+ attr_reader :width
14
+
15
+ def initialize(width: Constants::SCREEN_COLUMNS, &children)
16
+ @width = width
17
+ super
18
+ end
19
+
20
+ def render(**)
21
+ Strings.wrap(super, width)
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Eskimo::ASCII::Constants
4
+ SCREEN_COLUMNS = [TTY::Screen.width, 72].min
5
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Eskimo
4
+ module ASCII
5
+ VERSION = '3.0.0.pre.1'
6
+ end
7
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe Eskimo::ASCII::Component do
6
+ it 'accepts a block of children' do
7
+ expect(
8
+ Eskimo::Core::Renderer.new.apply {
9
+ Eskimo::ASCII::Component.new { ['hello', "\n", 'world!'] }
10
+ }
11
+ ).to eq("hello\nworld!")
12
+ end
13
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe Eskimo::ASCII::DidYouMean do
6
+ it 'renders a suggestion' do
7
+ expect(
8
+ renderer.apply {
9
+ described_class.new(dictionary: [ 'abc', 'bca' ], item: 'abd')
10
+ }
11
+ ).to eq("Did you mean? abc")
12
+ end
13
+
14
+ it 'renders nothing if no suggestions were found' do
15
+ expect(
16
+ renderer.apply {
17
+ described_class.new(dictionary: [ 'abc', 'bca' ], item: 'asdf')
18
+ }
19
+ ).to eq("")
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe Eskimo::ASCII::Either do
6
+ it 'renders the primary component' do
7
+ expect(
8
+ renderer.apply {
9
+ described_class.new(->(**) { false }, "Fallback here")
10
+ }
11
+ ).to eq("Fallback here")
12
+ end
13
+
14
+ it 'renders the fallback if the primary does not' do
15
+ expect(
16
+ renderer.apply {
17
+ described_class.new(->(**) { "Hi!" }, "Fallback here")
18
+ }
19
+ ).to eq("Hi!")
20
+ end
21
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe Eskimo::ASCII::Gutter do
6
+ it 'prepends each line with the character' do
7
+ expect(
8
+ renderer.apply do
9
+ Eskimo::ASCII::Gutter.new(char: '| ') do
10
+ "hello\nworld"
11
+ end
12
+ end
13
+ ).to eq("| hello\n| world")
14
+ end
15
+
16
+ it 'inserts surrounding whitespace' do
17
+ expect(
18
+ renderer.apply do
19
+ Eskimo::ASCII::Gutter.new(char: '| ', spacing: 2) do
20
+ "hello\nworld"
21
+ end
22
+ end
23
+ ).to eq("| \n| \n| hello\n| world\n| \n| ")
24
+ end
25
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe Eskimo::ASCII::HighlightColumn do
6
+ it 'highlights the character' do
7
+ style = [ :bold, :red ]
8
+
9
+ expect(
10
+ renderer.apply {
11
+ described_class.new(column: 1, line: 1, style: style) { "number:\nfour" }
12
+ }
13
+ ).to include("f#{pastel.decorate('o', *style)}ur")
14
+ end
15
+
16
+ it 'highlights the first character' do
17
+ style = [ :bold, :red ]
18
+
19
+ expect(
20
+ renderer.apply {
21
+ described_class.new(column: 0, line: 0, style: style) { "four" }
22
+ }
23
+ ).to include("#{pastel.decorate('f', *style)}our")
24
+ end
25
+
26
+ it 'highlights the last character' do
27
+ style = [ :bold, :red ]
28
+
29
+ expect(
30
+ renderer.apply {
31
+ described_class.new(column: 3, line: 0, style: style) { "four" }
32
+ }
33
+ ).to include("fou#{pastel.decorate('r', *style)}")
34
+ end
35
+
36
+ it 'highlights nothing' do
37
+ expect(
38
+ renderer.apply {
39
+ described_class.new(column: 1234, line: 0) { "four" }
40
+ }
41
+ ).to eq("four")
42
+
43
+ expect(
44
+ renderer.apply {
45
+ described_class.new(column: 0, line: 1234) { "four" }
46
+ }
47
+ ).to eq("four")
48
+ end
49
+
50
+ it 'inserts the arrow' do
51
+ expect(
52
+ strip_styles(
53
+ renderer.apply {
54
+ described_class.new(column: 1, line: 0) { "four" }
55
+ }
56
+ )
57
+ ).to eq(
58
+ "four" + "\n" +
59
+ " ^" + "\n" +
60
+ " here" + "\n"
61
+ )
62
+ end
63
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe Eskimo::ASCII::Highlight do
6
+ it 'styles a substring' do
7
+ style = [ :bold, :red ]
8
+
9
+ expect(
10
+ renderer.apply {
11
+ described_class.new(pattern: /ou/, style: style) { 'four' }
12
+ }
13
+ ).to eq("f#{Pastel.new.decorate('ou', *style)}r")
14
+ end
15
+
16
+ it 'is a no-op if no match' do
17
+ input = 'four'
18
+
19
+ expect(
20
+ renderer.apply {
21
+ described_class.new(pattern: /asdf/) { input }
22
+ }
23
+ ).to eq(input)
24
+ end
25
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe Eskimo::ASCII::Indent do
6
+ it 'indents each line' do
7
+ expect(
8
+ renderer.apply do
9
+ Eskimo::ASCII::Indent.new(width: 2) do
10
+ "hello\nworld"
11
+ end
12
+ end
13
+ ).to eq(" hello\n world")
14
+ end
15
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe Eskimo::ASCII::LineBreak do
6
+ it 'inserts a hard linebreak' do
7
+ expect(
8
+ renderer.apply {
9
+ [
10
+ 'hello',
11
+ Eskimo::ASCII::LineBreak.new,
12
+ 'world'
13
+ ]
14
+ }
15
+ ).to eq("hello\n \nworld")
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe Eskimo::ASCII::SoftBreak do
6
+ it 'inserts a soft linebreak' do
7
+ expect(
8
+ renderer.apply {
9
+ [
10
+ 'hello',
11
+ Eskimo::ASCII::SoftBreak.new,
12
+ 'world'
13
+ ]
14
+ }
15
+ ).to eq("hello\nworld")
16
+ end
17
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe Eskimo::ASCII::Spacer do
6
+ it 'inserts newlines between components' do
7
+ expect(
8
+ renderer.apply do
9
+ Eskimo::ASCII::Spacer.new([
10
+ "Hello",
11
+ "World!"
12
+ ])
13
+ end
14
+ ).to eq("Hello\nWorld!")
15
+ end
16
+
17
+ it 'ignores falsey components' do
18
+ expect(
19
+ renderer.apply do
20
+ Eskimo::ASCII::Spacer.new([
21
+ "Hello",
22
+ false && 'hi',
23
+ false && 'ho',
24
+ "World!",
25
+ false && 'yup'
26
+ ])
27
+ end
28
+ ).to eq("Hello\nWorld!")
29
+ end
30
+
31
+ it 'bails if not given an array of children' do
32
+ expect { Eskimo::ASCII::Spacer.new('') }.to raise_error(ArgumentError,
33
+ "Spacer works only with an Array of components"
34
+ )
35
+ end
36
+
37
+ it 'bails if given a block' do
38
+ expect { Eskimo::ASCII::Spacer.new([]) {} }.to raise_error(ArgumentError,
39
+ "Spacer works only with an Array of components"
40
+ )
41
+ end
42
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe Eskimo::ASCII::Squeeze do
6
+ it 'swallows consecutive newlines' do
7
+ expect(
8
+ renderer.apply do
9
+ Eskimo::ASCII::Squeeze.new([
10
+ Eskimo::ASCII::SoftBreak.new,
11
+ false && '1',
12
+
13
+ Eskimo::ASCII::SoftBreak.new,
14
+ false && '2',
15
+
16
+ Eskimo::ASCII::SoftBreak.new,
17
+ '3',
18
+
19
+ Eskimo::ASCII::SoftBreak.new,
20
+ '4',
21
+ ])
22
+ end
23
+ ).to eq("\n3\n4")
24
+ end
25
+
26
+ it 'does not swallow consecutive newlines in inner children' do
27
+ expect(
28
+ renderer.apply do
29
+ Eskimo::ASCII::Squeeze.new([
30
+ Eskimo::ASCII::Indent.new(width: 0) do
31
+ [
32
+ Eskimo::ASCII::SoftBreak.new,
33
+ false && '1',
34
+
35
+ Eskimo::ASCII::SoftBreak.new,
36
+ false && '2',
37
+
38
+ Eskimo::ASCII::SoftBreak.new,
39
+ '3',
40
+
41
+ Eskimo::ASCII::SoftBreak.new,
42
+ '4',
43
+ ]
44
+ end
45
+ ])
46
+ end
47
+ ).to eq("\n\n\n3\n4")
48
+ end
49
+
50
+ it 'bails if not given an array of children' do
51
+ expect { Eskimo::ASCII::Squeeze.new('') }.to raise_error(ArgumentError,
52
+ "Squeeze works only with an Array of components"
53
+ )
54
+ end
55
+
56
+ it 'bails if given a block' do
57
+ expect { Eskimo::ASCII::Squeeze.new([]) {} }.to raise_error(ArgumentError,
58
+ "Squeeze works only with an Array of components"
59
+ )
60
+ end
61
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe Eskimo::ASCII::StripLeft do
6
+ it 'strips whitespace from the head' do
7
+ expect(
8
+ renderer.apply do
9
+ Eskimo::ASCII::StripLeft.new do
10
+ " hello world "
11
+ end
12
+ end
13
+ ).to eq("hello world ")
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe Eskimo::ASCII::StripRight do
6
+ it 'strips whitespace from the rear' do
7
+ expect(
8
+ renderer.apply do
9
+ Eskimo::ASCII::StripRight.new do
10
+ " hello world "
11
+ end
12
+ end
13
+ ).to eq(" hello world")
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe Eskimo::ASCII::Strip do
6
+ it 'strips surrounding whitespace' do
7
+ expect(
8
+ renderer.apply do
9
+ Eskimo::ASCII::Strip.new do
10
+ " hello world "
11
+ end
12
+ end
13
+ ).to eq("hello world")
14
+ end
15
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe Eskimo::ASCII::Style do
6
+ it 'applies ANSI styling to text' do
7
+ expect(
8
+ renderer.apply {
9
+ [
10
+ Eskimo::ASCII::Style.new([:bold, :green]) { 'hai' },
11
+ ]
12
+ }
13
+ ).to eq("\e[1;32mhai\e[0m")
14
+ end
15
+
16
+ it 'accepts a splat' do
17
+ expect(
18
+ renderer.apply {
19
+ [
20
+ Eskimo::ASCII::Style.new(:bold, :green) { 'hai' },
21
+ ]
22
+ }
23
+ ).to eq("\e[1;32mhai\e[0m")
24
+ end
25
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe Eskimo::ASCII::TruncateRear do
6
+ it 'truncates a string from the rear if it exceeds the length' do
7
+ expect(
8
+ renderer.apply do
9
+ Eskimo::ASCII::TruncateRear.new(width: 5) do
10
+ "hello world"
11
+ end
12
+ end
13
+ ).to eq("hello...")
14
+ end
15
+
16
+ it 'reserves chars from the width' do
17
+ expect(
18
+ renderer.apply do
19
+ Eskimo::ASCII::TruncateRear.new(width: 10, reserve: 5) do
20
+ ("hello world")
21
+ end
22
+ end
23
+ ).to eq("hello...")
24
+ end
25
+
26
+ it 'is a no-op if text fits' do
27
+ expect(
28
+ renderer.apply do
29
+ Eskimo::ASCII::TruncateRear.new(width: 62, reserve: 5) do
30
+ ("hello world")
31
+ end
32
+ end
33
+ ).to eq("hello world")
34
+ end
35
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe Eskimo::ASCII::Truncate do
6
+ it 'truncates a string from the beginning if it exceeds the length' do
7
+ expect(
8
+ renderer.apply do
9
+ Eskimo::ASCII::Truncate.new(width: 5) do
10
+ ("hello world")
11
+ end
12
+ end
13
+ ).to eq("... world")
14
+ end
15
+
16
+ it 'reserves chars from the width' do
17
+ expect(
18
+ renderer.apply do
19
+ Eskimo::ASCII::Truncate.new(width: 10, reserve: 5) do
20
+ ("hello world")
21
+ end
22
+ end
23
+ ).to eq("... world")
24
+ end
25
+
26
+ it 'is a no-op if text fits' do
27
+ expect(
28
+ renderer.apply do
29
+ Eskimo::ASCII::Truncate.new(width: 62, reserve: 5) do
30
+ ("hello world")
31
+ end
32
+ end
33
+ ).to eq("hello world")
34
+ end
35
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe Eskimo::ASCII::Wrap do
6
+ it 'wraps text' do
7
+ expect(
8
+ renderer.apply do
9
+ Eskimo::ASCII::Wrap.new(width: 5) do
10
+ 'hello world'
11
+ end
12
+ end
13
+ ).to eq("\nhello \nworld")
14
+ end
15
+
16
+ it 'defaults to the screen width' do
17
+ expect(
18
+ renderer.apply do
19
+ Eskimo::ASCII::Wrap.new do
20
+ 'hello world'
21
+ end
22
+ end
23
+ ).to eq("hello world")
24
+ end
25
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'simplecov'
4
+
5
+ SimpleCov.start do
6
+ add_filter(%r{/spec/})
7
+ coverage_dir("#{ENV.fetch('COVERAGE_DIR', 'coverage')}")
8
+ end
9
+
10
+ require 'eskimo/core'
11
+ require 'eskimo/ascii'
12
+ require_relative './support/component_suite'
13
+
14
+ RSpec.configure do |config|
15
+ config.expect_with :rspec do |expectations|
16
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
17
+ end
18
+
19
+ config.mock_with :rspec do |mocks|
20
+ mocks.verify_partial_doubles = true
21
+ end
22
+
23
+ config.shared_context_metadata_behavior = :apply_to_host_groups
24
+
25
+ config.filter_run_when_matching :focus
26
+
27
+ config.disable_monkey_patching!
28
+
29
+ config.warnings = true
30
+
31
+ config.default_formatter = 'doc' if config.files_to_run.one?
32
+
33
+ config.order = :random
34
+ config.define_derived_metadata(file_path: %r{/spec/components/}) do |metadata|
35
+ metadata[:type] = :component
36
+ end
37
+
38
+ config.include EskimoASCIITest::ComponentSuite, type: :component
39
+
40
+ Kernel.srand config.seed
41
+ end
@@ -0,0 +1,15 @@
1
+ module EskimoASCIITest
2
+ module ComponentSuite
3
+ def renderer
4
+ @renderer ||= Eskimo::Core::Renderer.new
5
+ end
6
+
7
+ def strip_styles(string)
8
+ pastel.strip(string)
9
+ end
10
+
11
+ def pastel
12
+ @pastel ||= Pastel.new
13
+ end
14
+ end
15
+ end
metadata ADDED
@@ -0,0 +1,188 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: eskimo-ascii
3
+ version: !ruby/object:Gem::Version
4
+ version: 3.0.0.pre.1
5
+ platform: ruby
6
+ authors:
7
+ - Ahmad Amireh
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-04-18 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: eskimo-core
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '3'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: pastel
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.7'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.7'
41
+ - !ruby/object:Gem::Dependency
42
+ name: strings
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0.1'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0.1'
55
+ - !ruby/object:Gem::Dependency
56
+ name: tty-screen
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0.6'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0.6'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.8'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.8'
83
+ - !ruby/object:Gem::Dependency
84
+ name: simplecov
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '0.16'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '0.16'
97
+ description: Format text for humans using a declarative, extensible API.
98
+ email: ahmad@instructure.com
99
+ executables: []
100
+ extensions: []
101
+ extra_rdoc_files: []
102
+ files:
103
+ - lib/eskimo/ascii.rb
104
+ - lib/eskimo/ascii/component.rb
105
+ - lib/eskimo/ascii/components/did_you_mean.rb
106
+ - lib/eskimo/ascii/components/either.rb
107
+ - lib/eskimo/ascii/components/gutter.rb
108
+ - lib/eskimo/ascii/components/highlight.rb
109
+ - lib/eskimo/ascii/components/highlight_column.rb
110
+ - lib/eskimo/ascii/components/indent.rb
111
+ - lib/eskimo/ascii/components/line_break.rb
112
+ - lib/eskimo/ascii/components/soft_break.rb
113
+ - lib/eskimo/ascii/components/spacer.rb
114
+ - lib/eskimo/ascii/components/squeeze.rb
115
+ - lib/eskimo/ascii/components/strip.rb
116
+ - lib/eskimo/ascii/components/strip_left.rb
117
+ - lib/eskimo/ascii/components/strip_right.rb
118
+ - lib/eskimo/ascii/components/style.rb
119
+ - lib/eskimo/ascii/components/truncate.rb
120
+ - lib/eskimo/ascii/components/truncate_rear.rb
121
+ - lib/eskimo/ascii/components/wrap.rb
122
+ - lib/eskimo/ascii/constants.rb
123
+ - lib/eskimo/ascii/version.rb
124
+ - spec/component_spec.rb
125
+ - spec/components/did_you_mean_spec.rb
126
+ - spec/components/either_spec.rb
127
+ - spec/components/gutter_spec.rb
128
+ - spec/components/highlight_column_spec.rb
129
+ - spec/components/highlight_spec.rb
130
+ - spec/components/indent_spec.rb
131
+ - spec/components/line_break_spec.rb
132
+ - spec/components/soft_break_spec.rb
133
+ - spec/components/spacer_spec.rb
134
+ - spec/components/squeeze_spec.rb
135
+ - spec/components/strip_left_spec.rb
136
+ - spec/components/strip_right_spec.rb
137
+ - spec/components/strip_spec.rb
138
+ - spec/components/style_spec.rb
139
+ - spec/components/truncate_rear_spec.rb
140
+ - spec/components/truncate_spec.rb
141
+ - spec/components/wrap_spec.rb
142
+ - spec/spec_helper.rb
143
+ - spec/support/component_suite.rb
144
+ homepage: https://github.com/amireh/eskimo
145
+ licenses:
146
+ - MIT
147
+ metadata: {}
148
+ post_install_message:
149
+ rdoc_options: []
150
+ require_paths:
151
+ - lib
152
+ required_ruby_version: !ruby/object:Gem::Requirement
153
+ requirements:
154
+ - - ">="
155
+ - !ruby/object:Gem::Version
156
+ version: '0'
157
+ required_rubygems_version: !ruby/object:Gem::Requirement
158
+ requirements:
159
+ - - ">"
160
+ - !ruby/object:Gem::Version
161
+ version: 1.3.1
162
+ requirements: []
163
+ rubyforge_project:
164
+ rubygems_version: 2.7.6
165
+ signing_key:
166
+ specification_version: 4
167
+ summary: Declarative text formatting
168
+ test_files:
169
+ - spec/spec_helper.rb
170
+ - spec/component_spec.rb
171
+ - spec/components/strip_spec.rb
172
+ - spec/components/soft_break_spec.rb
173
+ - spec/components/did_you_mean_spec.rb
174
+ - spec/components/truncate_rear_spec.rb
175
+ - spec/components/wrap_spec.rb
176
+ - spec/components/strip_left_spec.rb
177
+ - spec/components/squeeze_spec.rb
178
+ - spec/components/either_spec.rb
179
+ - spec/components/style_spec.rb
180
+ - spec/components/indent_spec.rb
181
+ - spec/components/highlight_column_spec.rb
182
+ - spec/components/strip_right_spec.rb
183
+ - spec/components/gutter_spec.rb
184
+ - spec/components/truncate_spec.rb
185
+ - spec/components/line_break_spec.rb
186
+ - spec/components/spacer_spec.rb
187
+ - spec/components/highlight_spec.rb
188
+ - spec/support/component_suite.rb