eskimo-ascii 3.0.0.pre.1

Sign up to get free protection for your applications and to get access to all the features.
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