rainbow_formatter 0.1.5

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,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'formatter/custom/car'
4
+ require 'formatter/custom/dog'
5
+ require 'formatter/custom/monkey'
6
+ require 'formatter/custom/tina_bike'
7
+ require 'formatter/custom/tina_dream'
8
+
9
+ module Formatter
10
+ class Configuration
11
+ attr_accessor :formatter
12
+
13
+ def initialize
14
+ @formatter = :tina_bike
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Formatter
4
+ module Custom
5
+ module Car
6
+ # rubocop:disable Metrics/LineLength
7
+ def ascii_array
8
+ [" - __\r\n -- ~( @\\ \\ \r\n--- _________]_[__/_>________\r\n / ____ \\ <> | ____ \\\r\n =\\_/ __ \\_\\_______|_/ __ \\__D\r\n________(__)_____________(__)____",
9
+ " - __\r\n -- ~( @\\ \\\r\n --- _________]_[__/_>________\r\n / ____ \\ <> | ____ \\\r\n =\\_/ __ \\_\\_______|_/ __ \\__D\r\n________(__)_____________(__)____"]
10
+ end
11
+ # rubocop:enable Metrics/LineLength
12
+
13
+ def rainbow_mp3
14
+ File.expand_path('../../../data/tina_dream.mp3', __dir__)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Formatter
4
+ module Custom
5
+ module Dog
6
+ # rubocop:disable Metrics/LineLength
7
+ def ascii_array
8
+ [" ;~~,__\r\n :-....,-------'`-'._.'\r\n `-,,, , ,'~~'\r\n ; ,'~.__; /\r\n :| :|\r\n `-' `-'",
9
+ " ;~~,__\r\n :-....,-------'`-'._.'\r\n `-,,, , ,'~~'\r\n ; ,'~.__; /--.\r\n :| :| :|``(;\r\n `-'`-' `-'",
10
+ " ;~~,__\r\n :-....,-------'`-'._.'\r\n `-,,, , ;'~~'\r\n ,'_,'~.__; '--.\r\n //' ````(;\r\n `-'",
11
+ " .--~~,__\r\n :-....,-------`~~'._.'\r\n `-,,, ,_ ;'~U'\r\n _,-' ,'`-__; '--.\r\n (_/'~~ ''''(;"]
12
+ end
13
+ # rubocop:enable Metrics/LineLength
14
+
15
+ def rainbow_mp3
16
+ File.expand_path('../../../data/tina_dream.mp3', __dir__)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Formatter
4
+ module Custom
5
+ module Monkey
6
+ # rubocop:disable Metrics/LineLength
7
+ def ascii_array
8
+ [" .-\"-. \r\n _/.-.-.\\\r\n /|( o o )|\\\r\n | // \" \\\\ |\r\n / / \\'---'/ \\ \\\r\n \\ \\_/`\"\"\"`\\_/ /\r\n \\ /",
9
+ " .-\"-.\r\n _/_-.-_\\\r\n / __} {__ \\\r\n / // \" \\\\ \\\r\n / / \\'---'/ \\ \\\r\n \\ \\_/`\"\"\"`\\_/ /\r\n \\ /"]
10
+ end
11
+ # rubocop:enable Metrics/LineLength
12
+
13
+ def rainbow_mp3
14
+ File.expand_path('../../../data/tina_dream.mp3', __dir__)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Formatter
4
+ module Custom
5
+ module TinaBike
6
+ # rubocop:disable Metrics/LineLength
7
+ def ascii_array
8
+ [" - /\\**/\\ \r\n -- _( o_o ) \r\n --- (_/ u--u) \r\n /==( ___||)\r\n /d\\\"P\\ )=\\d\\\"Pb, \r\n 8 || ===/8=== || 8| \r\n P, ,d P, ,d` \r\n `\"\"\" `\"\"\"",
9
+ " - /\\**/\\ \n -- _( o_o ) \n --- (_/ u--u) \n /==( ___||)\n /d\\\"P\\ )=\\d\\\"Pb, \n 8 || ===/8=== || 8|\n P, ,d P, ,d`\n `\"\"\" `\"\"\""]
10
+ end
11
+ # rubocop:enable Metrics/LineLength
12
+
13
+ def rainbow_mp3
14
+ File.expand_path('../../../data/tina_bike.mp3', __dir__)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Formatter
4
+ module Custom
5
+ module TinaDream
6
+ # rubocop:disable Metrics/LineLength
7
+ def ascii_array
8
+ [" \r\n /\\_____/\\\r\n / o o \\ \r\n ( == ^ == )\r\n ) (\r\n ( )\r\n ( ( ) ( ) )\r\n (__(__)___(__)__)",
9
+ " \r\n /\\_____/\\\r\n / -- -- \\ \r\n ( == ^ == )\r\n ) (\r\n ( )\r\n ( ( ) ( ) )\r\n (__(__)___(__)__)",
10
+ " \r\n /\\_____/\\\r\n / -- -- \\ \r\n ( == o == )\r\n ) (\r\n ( )\r\n ( ( ) ( ) )\r\n (__(__)___(__)__)",
11
+ " ZzzZ \r\n /\\_____/\\\r\n / -- -- \\ \r\n ( == o == )\r\n ) (\r\n ( )\r\n ( ( ) ( ) )\r\n (__(__)___(__)__)",
12
+ " ZzzZz \r\n /\\_____/\\\r\n / -- -- \\ \r\n ( == o == )\r\n ) (\r\n ( )\r\n ( ( ) ( ) )\r\n (__(__)___(__)__)",
13
+ " ZzzZzz \r\n /\\_____/\\\r\n / -- -- \\ \r\n ( == o == )\r\n ) (\r\n ( )\r\n ( ( ) ( ) )\r\n (__(__)___(__)__)",
14
+ " ZzzZzzZ \r\n /\\_____/\\\r\n / -- -- \\ \r\n ( == o == )\r\n ) (\r\n ( )\r\n ( ( ) ( ) )\r\n (__(__)___(__)__)",
15
+ " ZzzZzzZz \r\n /\\_____/\\\r\n / -- -- \\ \r\n ( == o == )\r\n ) (\r\n ( )\r\n ( ( ) ( ) )\r\n (__(__)___(__)__)",
16
+ " ZzzZzzZzz \r\n /\\_____/\\\r\n / -- -- \\ \r\n ( == o == )\r\n ) (\r\n ( )\r\n ( ( ) ( ) )\r\n (__(__)___(__)__)",
17
+ " \r\n /\\_____/\\\r\n / 0 0 \\ \r\n ( == o == )\r\n ) (\r\n ( )\r\n ( ( ) ( ) )\r\n (__(__)___(__)__)"]
18
+ end
19
+ # rubocop:enable Metrics/LineLength
20
+
21
+ def rainbow_mp3
22
+ File.expand_path('../../../data/tina_dream.mp3', __dir__)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Formatter
4
+ module InstaFail
5
+ def progress_lines
6
+ # Display last failed example output
7
+ errors_dump = []
8
+ errors_dump.concat(@failed_examples.last.fully_formatted(@failure_count).split("\n")) if @failed_examples.size.positive?
9
+ super.concat(errors_dump)
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Formatter
4
+ module Music
5
+ def osx?
6
+ platform.downcase.include?('darwin')
7
+ end
8
+
9
+ def linux?
10
+ platform.downcase.include?('linux')
11
+ end
12
+
13
+ def kernel=(kernel)
14
+ @kernel = kernel
15
+ end
16
+
17
+ def kernel
18
+ @kernel ||= Kernel
19
+ end
20
+
21
+ def platform=(platform)
22
+ @platform = platform
23
+ end
24
+
25
+ def platform
26
+ @platform ||= RUBY_PLATFORM
27
+ end
28
+
29
+ def start(input)
30
+ super
31
+ @music_thread = Thread.new { start_music_or_kill(Thread.current) }
32
+ wait_for_music_to_start(@music_thread)
33
+ end
34
+
35
+ def dump_summary(*args)
36
+ kill_music
37
+ super
38
+ end
39
+
40
+ private
41
+
42
+ def kill_music
43
+ if @music_thread && @music_thread['music_pid']
44
+ @music_thread.kill
45
+ Process.kill('KILL', @music_thread['music_pid'])
46
+ end
47
+ end
48
+
49
+ def linux_player
50
+ %w[mpg321 mpg123].find do |player|
51
+ kernel.system("which #{player} >/dev/null 2>&1 && type #{player} >/dev/null 2>&1")
52
+ end
53
+ end
54
+
55
+ def music_command
56
+ # this isn't really threadsafe but it'll work if we're careful
57
+ return @music_command if @music_command
58
+
59
+ if osx?
60
+ @music_command = "afplay #{rainbow_mp3}"
61
+ elsif linux? && linux_player
62
+ @music_command = "#{linux_player} #{rainbow_mp3} >/dev/null 2>&1"
63
+ end
64
+ end
65
+
66
+ def start_music_or_kill(thread)
67
+ thread.exit unless File.exist?(rainbow_mp3) && music_command
68
+ loop do
69
+ thread['music_pid'] = kernel.spawn(music_command)
70
+ thread['started_playing'] ||= true
71
+ Process.wait(thread['music_pid'])
72
+ end
73
+ end
74
+
75
+ def wait_for_music_to_start(music_thread)
76
+ sleep 0.001 while !music_thread['started_playing'] && music_thread.status
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Formatter
4
+ module Verbose
5
+ def progress_lines
6
+ label = "running: #{example_name}"
7
+ label = label.slice(0, terminal_width - 3).concat('...') if label.size > terminal_width
8
+ super.concat(
9
+ [
10
+ format("%-#{terminal_width}s", finished? ? '' : label)
11
+ ]
12
+ )
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Formatter
4
+ VERSION = '0.1.5'
5
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'formatter/configuration'
4
+ require 'formatter/common'
5
+ require 'ostruct'
6
+ require 'pry'
7
+
8
+ class RainbowFormatter
9
+ include Formatter::Common
10
+
11
+ attr_reader :example_name, :ascii_array, :output
12
+
13
+ RSpec::Core::Formatters.register self, :start, :example_started, :example_passed, :example_pending, :example_failed,
14
+ :start_dump, :dump_summary
15
+
16
+ BUNDLED_MODES = {
17
+ tina_bike: Formatter::Custom::TinaBike,
18
+ tina_dream: Formatter::Custom::TinaDream,
19
+ car: Formatter::Custom::Car,
20
+ dog: Formatter::Custom::Dog,
21
+ monkey: Formatter::Custom::Monkey
22
+ }.freeze
23
+
24
+ def initialize(output)
25
+ @output = output
26
+ @current = @color_index = @passing_count = @failure_count = @pending_count = @animation_index = 0
27
+ @example_results = []
28
+ @failed_examples = []
29
+ @pending_examples = []
30
+ setup_formatter
31
+ end
32
+
33
+ def setup_formatter
34
+ formatter = RainbowFormatter.configuration.formatter
35
+ formatter = BUNDLED_MODES.dig(formatter) unless formatter.is_a?(Module)
36
+ singleton_class.send(:include, formatter)
37
+ end
38
+
39
+ def self.configuration
40
+ @configuration ||= Formatter::Configuration.new
41
+ end
42
+
43
+ def self.configure
44
+ yield configuration if block_given?
45
+ end
46
+
47
+ def start(start_notification)
48
+ # TODO: Lazy fix for specs.
49
+ @example_count = if start_notification.is_a?(Integer)
50
+ start_notification
51
+ else
52
+ start_notification.count
53
+ end
54
+ end
55
+
56
+ def start_dump(_notification)
57
+ @current = @example_count
58
+ end
59
+
60
+ def example_started(notification)
61
+ notification = notification.example if notification.respond_to?(:example)
62
+ @example_name = notification.full_description
63
+ end
64
+
65
+ def example_passed(_notification)
66
+ tick
67
+ end
68
+
69
+ def example_pending(notification)
70
+ @pending_examples << notification
71
+ @pending_count += 1
72
+ tick(mark: PENDING)
73
+ end
74
+
75
+ def example_failed(notification)
76
+ @failed_examples << notification
77
+ @failure_count += 1
78
+ tick(mark: FAIL)
79
+ end
80
+
81
+ def dump_summary(notification)
82
+ duration = notification.duration
83
+ summary = "\nYou've rainbowified for #{format_duration(duration)}\n".split(//).map { |c| rainbowify(c) }
84
+ output.puts summary.join
85
+ output.puts notification.fully_formatted
86
+ dump_commands_to_rerun_failed_examples if respond_to?(:dump_commands_to_rerun_failed_examples)
87
+ end
88
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rainbow_formatter'
4
+ require 'formatter/insta_fail'
5
+
6
+ RainbowInstaFailFormatter = Class.new(RainbowFormatter) do
7
+ include Formatter::InstaFail
8
+
9
+ RSpec::Core::Formatters.register(self, :example_passed, :example_pending, :example_failed, :start_dump, :start)
10
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rainbow_formatter'
4
+ require 'formatter/music'
5
+
6
+ RainbowMusicFormatter = Class.new(RainbowFormatter) do
7
+ include Formatter::Music
8
+
9
+ RSpec::Core::Formatters.register(self, :example_passed, :example_pending, :example_failed, :start_dump, :start)
10
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rainbow_formatter'
4
+ require 'formatter/verbose'
5
+
6
+ RainbowVerboseFormatter = Class.new(RainbowFormatter) do
7
+ include Formatter::Verbose
8
+
9
+ RSpec::Core::Formatters.register(self, :example_passed, :example_pending, :example_failed, :start_dump, :start)
10
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ $LOAD_PATH.push File.expand_path('lib', __dir__)
4
+ require 'formatter/version'
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = 'rainbow_formatter'
8
+ s.version = Formatter::VERSION
9
+ s.authors = ['Federico Farina']
10
+ s.email = ['federicojosefarina@gmail.com']
11
+ s.platform = Gem::Platform::RUBY
12
+ s.homepage = 'https://github.com/fedefa/rainbow-formatter'
13
+ s.summary = 'Customizable RSpec formatter'
14
+ s.description = 'Customizable ascii-music RSpec formattter'
15
+ s.license = 'MIT'
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
+ s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
20
+ s.require_paths = ['lib']
21
+
22
+ s.add_dependency 'rspec', '>= 3', '< 4'
23
+
24
+ s.add_development_dependency 'pry'
25
+ s.add_development_dependency 'rake'
26
+ end
@@ -0,0 +1,156 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+ require 'stringio'
5
+
6
+ describe RainbowFormatter do
7
+ before do
8
+ rspec_bin = $PROGRAM_NAME.split('/').last
9
+ @output = StringIO.new
10
+ if rspec_bin == 'rspec'
11
+ @formatter = RainbowFormatter.new(@output)
12
+ @example = RSpec::Core::ExampleGroup.describe.example
13
+ else
14
+ formatter_options = OpenStruct.new(colour: true, dry_run: false, autospec: nil)
15
+ @formatter = RainbowFormatter.new(formatter_options, @output)
16
+ @example = Spec::Example::ExampleProxy.new('should pass')
17
+ @formatter.instance_variable_set(:@example_group, OpenStruct.new(description: 'group'))
18
+ end
19
+ @formatter.start(2)
20
+ @formatter.example_started(@example)
21
+ end
22
+
23
+ shared_examples 'a test' do
24
+ it 'should call the increment method' do
25
+ expect(@formatter).to receive(:tick)
26
+ @formatter.example_passed(@example)
27
+ end
28
+ end
29
+
30
+ describe 'passed, pending and failed' do
31
+ before do
32
+ allow(@formatter).to receive(:tick)
33
+ end
34
+
35
+ describe 'example_passed' do
36
+ it_behaves_like 'a test'
37
+ it 'should update the scoreboard' do
38
+ expect(@formatter.scoreboard.size).to eq(4)
39
+ end
40
+ end
41
+
42
+ describe 'example_pending' do
43
+ it_behaves_like 'a test'
44
+
45
+ it 'should increment the pending count' do
46
+ expect do
47
+ @formatter.example_pending(@example)
48
+ end.to change(@formatter, :pending_count).by(1)
49
+ end
50
+ end
51
+
52
+ describe 'example_failed' do
53
+ it_behaves_like 'a test'
54
+
55
+ it 'should increment the failure count' do
56
+ expect do
57
+ @formatter.example_failed(@example)
58
+ end.to change(@formatter, :failure_count).by(1)
59
+ end
60
+
61
+ it 'should return finished if the specs are finished' do
62
+ @formatter.example_failed(@example)
63
+ allow(@formatter).to receive(:finished?).and_return(true)
64
+ end
65
+ end
66
+ end
67
+
68
+ describe 'tick' do
69
+ before do
70
+ allow(@formatter).to receive(:current).and_return(1)
71
+ allow(@formatter).to receive(:example_count).and_return(2)
72
+ @formatter.tick
73
+ end
74
+
75
+ it 'should increment the current' do
76
+ expect(@formatter.current).to eql(1)
77
+ end
78
+
79
+ it 'should store the marks in an array' do
80
+ expect(@formatter.example_results).to include('=')
81
+ end
82
+ end
83
+
84
+ describe 'rainbowify' do
85
+ it 'should increment the color index count' do
86
+ expect do
87
+ @formatter.rainbowify('=')
88
+ end.to change(@formatter, :color_index).by(1)
89
+ end
90
+ end
91
+
92
+ describe 'highlight' do
93
+ it 'should rainbowify passing examples' do
94
+ expect(@formatter.highlight('=')).to eq("\e[38;5;154m-\e[0m")
95
+ end
96
+
97
+ it 'should mark failing examples as red' do
98
+ expect(@formatter.highlight('*')).to eq("\e[31m*\e[0m")
99
+ end
100
+
101
+ it 'should mark pending examples as yellow' do
102
+ expect(@formatter.highlight('!')).to eq("\e[33m!\e[0m")
103
+ end
104
+ end
105
+
106
+ describe 'start' do
107
+ it 'should set the total amount of specs' do
108
+ expect(@formatter.example_count).to eq(2)
109
+ end
110
+
111
+ it 'should set the current to 0' do
112
+ expect(@formatter.current).to eq(0)
113
+ end
114
+ end
115
+
116
+ describe '#format_duration' do
117
+ it 'should return just seconds for sub 60 seconds' do
118
+ expect(@formatter.format_duration(5.3)).to eq('5.3 seconds')
119
+ end
120
+
121
+ it 'should remove that extra zero if it is not needed' do
122
+ expect(@formatter.format_duration(1.0)).to eq('1 second')
123
+ end
124
+
125
+ it 'should plurlaize seconds' do
126
+ expect(@formatter.format_duration(1.1)).to eq('1.1 seconds')
127
+ end
128
+
129
+ it 'add a minute if it is just over 60 seconds' do
130
+ expect(@formatter.format_duration(63.2543456456)).to eq('1 minute and 3.25 seconds')
131
+ end
132
+
133
+ it 'should pluralize minutes' do
134
+ expect(@formatter.format_duration(987.34)).to eq('16 minutes and 27.34 seconds')
135
+ end
136
+ end
137
+
138
+ describe 'example width' do
139
+ [15, 36, 60].each do |n|
140
+ context "for #{n} examples" do
141
+ before { @formatter.start(n) }
142
+
143
+ [0.25, 0.5, 0.75].each do |p|
144
+ i = (n * p).to_i
145
+ before { i.times { @formatter.tick } }
146
+
147
+ context "when in example #{i}" do
148
+ it 'should return 1 as the example width' do
149
+ expect(@formatter.example_width).to eq(1)
150
+ end
151
+ end
152
+ end
153
+ end
154
+ end
155
+ end
156
+ end