spinning_cursor 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/CHANGELOG CHANGED
@@ -1,6 +1,21 @@
1
1
  CHANGELOG
2
2
  ===============================================================================
3
3
 
4
+ v0.2.0 (2013-07-13)
5
+ - Several bug fixes/internal API improvements (#4 #8 #9 #10 #11 #13)
6
+ - Suppress output from action block (#7)
7
+ - Add `output` option to display inline or all at once at the end (#13)
8
+ - Add `delay` option to specify cursor speed (#2)
9
+ - Hide terminal cursor during cursor animation (#11)
10
+ - Better exception handling (#13)
11
+
12
+
13
+ v0.1.1 - v0.1.2 (2012-04-25)
14
+ - Task timing is more concentrated (times, yields, works out the difference)
15
+ - Trying to set the message/banner after a task has finished raises an
16
+ exception
17
+ - Exceptions are showing now (promise this time D;)
18
+
4
19
  v0.1.0 (2012-04-10)
5
20
  - First non-pre release
6
21
  - Exceptions in the task cause the cursor to stop and the exception is shown
@@ -15,4 +30,4 @@ v0.1.0.rc1 (2012-04-09)
15
30
  - Set the loading message, type of spinner and finished message
16
31
  - Pass in an action block to do the whole start stop loop, or don't and
17
32
  call stop yourself
18
- - Change the finish message within your task block
33
+ - Change the finish message within your task block
data/Gemfile CHANGED
@@ -7,9 +7,9 @@ source "http://rubygems.org"
7
7
  # Include everything needed to run rake, tests, features, etc.
8
8
  group :development do
9
9
  gem "shoulda", ">= 0"
10
- gem "bundler", "~> 1.1.3"
11
- gem "jeweler", "~> 1.8.3"
12
- gem "yard"
10
+ gem "bundler", "~> 1.3.5"
11
+ gem "jeweler", "~> 1.8.4"
12
+ gem "yard", "~> 0.8.6.2"
13
13
  gem "redcarpet"
14
14
  gem "github-markup"
15
15
  end
data/Gemfile.lock CHANGED
@@ -1,32 +1,77 @@
1
1
  GEM
2
2
  remote: http://rubygems.org/
3
3
  specs:
4
+ activesupport (4.0.0)
5
+ i18n (~> 0.6, >= 0.6.4)
6
+ minitest (~> 4.2)
7
+ multi_json (~> 1.3)
8
+ thread_safe (~> 0.1)
9
+ tzinfo (~> 0.3.37)
10
+ addressable (2.3.5)
11
+ atomic (1.1.10)
12
+ builder (3.2.2)
13
+ faraday (0.8.7)
14
+ multipart-post (~> 1.1)
4
15
  git (1.2.5)
5
- github-markup (0.7.2)
6
- jeweler (1.8.3)
16
+ github-markup (0.7.5)
17
+ github_api (0.10.1)
18
+ addressable
19
+ faraday (~> 0.8.1)
20
+ hashie (>= 1.2)
21
+ multi_json (~> 1.4)
22
+ nokogiri (~> 1.5.2)
23
+ oauth2
24
+ hashie (2.0.5)
25
+ highline (1.6.19)
26
+ httpauth (0.2.0)
27
+ i18n (0.6.4)
28
+ jeweler (1.8.6)
29
+ builder
7
30
  bundler (~> 1.0)
8
31
  git (>= 1.2.5)
32
+ github_api (= 0.10.1)
33
+ highline (>= 1.6.15)
34
+ nokogiri (= 1.5.10)
9
35
  rake
10
36
  rdoc
11
- json (1.6.6)
12
- rake (0.9.2.2)
13
- rdoc (3.12)
37
+ json (1.8.0)
38
+ jwt (0.1.8)
39
+ multi_json (>= 1.5)
40
+ minitest (4.7.5)
41
+ multi_json (1.7.7)
42
+ multi_xml (0.5.4)
43
+ multipart-post (1.2.0)
44
+ nokogiri (1.5.10)
45
+ oauth2 (0.9.2)
46
+ faraday (~> 0.8)
47
+ httpauth (~> 0.2)
48
+ jwt (~> 0.1.4)
49
+ multi_json (~> 1.0)
50
+ multi_xml (~> 0.5)
51
+ rack (~> 1.2)
52
+ rack (1.5.2)
53
+ rake (10.1.0)
54
+ rdoc (4.0.1)
14
55
  json (~> 1.4)
15
- redcarpet (2.1.1)
16
- shoulda (3.0.1)
17
- shoulda-context (~> 1.0.0)
18
- shoulda-matchers (~> 1.0.0)
19
- shoulda-context (1.0.0)
20
- shoulda-matchers (1.0.0)
21
- yard (0.7.5)
56
+ redcarpet (3.0.0)
57
+ shoulda (3.5.0)
58
+ shoulda-context (~> 1.0, >= 1.0.1)
59
+ shoulda-matchers (>= 1.4.1, < 3.0)
60
+ shoulda-context (1.1.4)
61
+ shoulda-matchers (2.2.0)
62
+ activesupport (>= 3.0.0)
63
+ thread_safe (0.1.0)
64
+ atomic
65
+ tzinfo (0.3.37)
66
+ yard (0.8.6.2)
22
67
 
23
68
  PLATFORMS
24
69
  ruby
25
70
 
26
71
  DEPENDENCIES
27
- bundler (~> 1.1.3)
72
+ bundler (~> 1.3.5)
28
73
  github-markup
29
- jeweler (~> 1.8.3)
74
+ jeweler (~> 1.8.4)
30
75
  redcarpet
31
76
  shoulda
32
- yard
77
+ yard (~> 0.8.6.2)
data/README.md CHANGED
@@ -56,6 +56,11 @@ It's as easy as that!
56
56
  * `action` - The stuff you want to do whilst the cursor is spinning.
57
57
  * `message` - The message you want to show the user once the task is finished.
58
58
  Defaults to "Done".
59
+ * `delay` - Sets the delay in seconds between the frames of the animation.
60
+ Defaults depend on type of cursor animation (spinner 0.5s, dots 1s)
61
+ * `output` - Sets how to display program output during cursor animation
62
+ (`:inline` or `:at_stop`).
63
+ Defaults to `:inline`.
59
64
 
60
65
  ### But the action block would get too messy!
61
66
 
@@ -82,7 +87,7 @@ SpinningCursor.stop
82
87
  ```
83
88
 
84
89
  **Notice** the absence of the `action` option. The start method will only keep
85
- the cursor running if an `action` block isn't passed into it.
90
+ the cursor running if an `action` block isn't passed to it.
86
91
 
87
92
  ### I want to be able to change the finish message conditionally!
88
93
 
@@ -147,7 +152,7 @@ The hash contains the following, self-explanatory keys:
147
152
  #### Suggestions
148
153
 
149
154
  There isn't much this library should do, but a good suggestion is always
150
- welcome. Make sure to use the issue track on GitHub to make suggestions -- and
155
+ welcome. Make sure to use the issue tracker on GitHub to make suggestions -- and
151
156
  fork & pull request if you want to implement it yourself, of course.
152
157
 
153
158
  #### More Cursors!
@@ -166,7 +171,7 @@ indebted to you. It's a learning experience for me!
166
171
 
167
172
  * Check out the latest master to make sure the feature hasn't been implemented
168
173
  or the bug hasn't been fixed yet.
169
- * Check out the issue tracker to make sure someone already hasn't requested it
174
+ * Check out the issue tracker to make sure someone hasn't already requested it
170
175
  and/or contributed it.
171
176
  * Fork the project.
172
177
  * Start a feature/bugfix branch.
@@ -177,3 +182,9 @@ indebted to you. It's a learning experience for me!
177
182
  to have your own version, or is otherwise necessary, that is fine, but
178
183
  please isolate to its own commit so I can cherry-pick around it.
179
184
 
185
+ ### List of contributors
186
+
187
+ A massive thanks to those who have taken some time out to help improve
188
+ Spinning Cursor!
189
+
190
+ * Abinoam P. Marques Jr. (@abinoam)
data/TODO CHANGED
@@ -1,4 +1,5 @@
1
- Suppress output and show later?
1
+ Suppress output and show later? (give option to produce log file, or just print it to the screen after it's finished),
2
+ default to hide though.
2
3
  deal with returns?
3
4
  test changes to spinning cursor, check diff for changes
4
5
  refactor tests to use minitest
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.2
1
+ 0.2.0
@@ -1,8 +1,12 @@
1
+ require "spinning_cursor/console_helpers"
1
2
  require "spinning_cursor/cursor"
2
3
  require "spinning_cursor/parser"
4
+ require "spinning_cursor/stop_watch"
3
5
 
4
6
  module SpinningCursor
5
7
  extend self
8
+ include self::ConsoleHelpers
9
+ extend self::ConsoleHelpers
6
10
 
7
11
  #
8
12
  # Sends passed block to Parser, and starts cursor thread
@@ -10,30 +14,36 @@ module SpinningCursor
10
14
  # thread if an action block is passed.
11
15
  #
12
16
  def start(&block)
13
- if not @curs.nil?
14
- if @curs.alive?
15
- stop
16
- end
17
- end
17
+ stop if alive?
18
18
 
19
- @parsed = Parser.new(block)
20
- @cursor = Cursor.new(@parsed.banner nil)
21
- @curs = Thread.new { @cursor.spin(@parsed.type nil) }
19
+ save_stdout_sync_status
20
+ capture_console
21
+ hide_cursor
22
22
 
23
- if @parsed.action.nil?
24
- # record start time
25
- do_exec_time
26
- return
23
+ @parsed = Parser.new(&block)
24
+ @cursor = Cursor.new(@parsed)
25
+ @spinner = Thread.new do
26
+ abort_on_exception = true
27
+ @cursor.spin
27
28
  end
28
- # The action
29
- begin
30
- do_exec_time do
31
- @parsed.originator.instance_eval &@parsed.action
29
+
30
+ @stop_watch = StopWatch.new
31
+
32
+ if @parsed.action
33
+ # The action
34
+ begin
35
+ @stop_watch.measure do
36
+ @parsed.outer_scope_object.instance_eval &@parsed.action
37
+ end
38
+ rescue StandardError => e
39
+ set_message "#{e.message}\n#{e.backtrace.join("\n")}"
40
+ raise
41
+ ensure
42
+ stop
32
43
  end
33
- rescue Exception => e
34
- set_message "#{e.message}\n#{e.backtrace.join("\n")}"
35
- ensure
36
- return stop
44
+ else
45
+ # record start time
46
+ @stop_watch.start
37
47
  end
38
48
  end
39
49
 
@@ -43,20 +53,28 @@ module SpinningCursor
43
53
  #
44
54
  def stop
45
55
  begin
46
- @curs.kill
56
+ restore_stdout_sync_status
57
+ if console_captured?
58
+ $console.print ESC_R_AND_CLR + $stdout.string
59
+ release_console
60
+ end
61
+ show_cursor
62
+
63
+ @spinner.kill
47
64
  # Wait for the cursor to die -- can cause problems otherwise
48
- while @curs.alive? ; end
65
+ @spinner.join
49
66
  # Set cursor to nil so set_banner method only works
50
67
  # when cursor is actually running.
51
68
  @cursor = nil
52
69
  reset_line
53
- puts (@parsed.message nil)
70
+ puts @parsed.message
54
71
  # Set parsed to nil so set_message method only works
55
72
  # when cursor is actually running.
56
73
  @parsed = nil
57
74
 
58
75
  # Return execution time
59
- get_exec_time
76
+ @stop_watch.stop
77
+ @stop_watch.timing
60
78
  rescue NameError
61
79
  raise CursorNotRunning.new "Can't stop, no cursor running."
62
80
  end
@@ -66,11 +84,7 @@ module SpinningCursor
66
84
  # Determines whether the cursor thread is still running
67
85
  #
68
86
  def alive?
69
- if @curs.nil?
70
- return false
71
- else
72
- @curs.alive?
73
- end
87
+ @spinner and @spinner.alive?
74
88
  end
75
89
 
76
90
  #
@@ -91,47 +105,15 @@ module SpinningCursor
91
105
  #
92
106
  def set_banner(banner)
93
107
  begin
94
- @cursor.banner = banner
108
+ @parsed.banner banner
95
109
  rescue NameError
96
110
  raise CursorNotRunning.new "Cursor isn't running... are you sure " +
97
111
  "you're calling this from an action block?"
98
112
  end
99
113
  end
100
114
 
101
- #
102
- # Retrieves execution time information
103
- #
104
- def get_exec_time
105
- if not @start.nil?
106
- if @finish.nil? && @curs.alive? == false
107
- do_exec_time
108
- end
109
- return { :started => @start, :finished => @finish,
110
- :elapsed => @elapsed }
111
- else
112
- raise NoTaskError.new "An execution hasn't started or finished."
113
- end
114
- end
115
-
116
115
  private
117
116
 
118
- #
119
- # Takes a block, and returns the start, finish and elapsed times
120
- #
121
- def do_exec_time
122
- if @curs.alive?
123
- @start = Time.now
124
- if block_given?
125
- yield
126
- @finish = Time.now
127
- @elapsed = @finish - @start
128
- end
129
- else
130
- @finish = Time.now
131
- @elapsed = @finish - @start
132
- end
133
- end
134
-
135
117
  class NoTaskError < Exception ; end
136
118
  class CursorNotRunning < NoTaskError ; end
137
- end
119
+ end
@@ -0,0 +1,88 @@
1
+ require 'stringio'
2
+
3
+ $console = STDOUT
4
+
5
+ module SpinningCursor
6
+ module ConsoleHelpers
7
+ if RUBY_PLATFORM =~ /(win|w)32$/
8
+ # DOS
9
+ # Contains a string to clear the line in the shell
10
+ CLR = " \r"
11
+ else
12
+ # Unix
13
+ # Contains a string to clear the line in the shell
14
+ CLR = "\e[0K"
15
+ end
16
+
17
+ # ANSI escape sequence for hiding terminal cursor
18
+ ESC_CURS_INVIS = "\e[?25l"
19
+ # ANSI escape sequence for showing terminal cursor
20
+ ESC_CURS_VIS = "\e[?25h"
21
+ # ANSI escape sequence for clearing line in terminal
22
+ ESC_R_AND_CLR = "\r#{CLR}"
23
+
24
+ #
25
+ # Manages line reset in the console
26
+ #
27
+ def reset_line(text = "")
28
+ $console.print "#{ESC_R_AND_CLR}#{text}"
29
+ end
30
+
31
+ #
32
+ # Stores current `STDOUT.sync` value and sets it to true
33
+ #
34
+ def save_stdout_sync_status
35
+ @stdout_sync_saved_status = STDOUT.sync
36
+ STDOUT.sync = true
37
+ end
38
+
39
+ #
40
+ # Restores the previously stored `STDOUT.sync` value
41
+ #
42
+ def restore_stdout_sync_status
43
+ STDOUT.sync = @stdout_sync_saved_status
44
+ end
45
+
46
+ #
47
+ # Sets `$stdout` global variable to a `StringIO` object to buffer output
48
+ #
49
+ def capture_console
50
+ $stdout = StringIO.new
51
+ end
52
+
53
+ #
54
+ # Resets `$stdout` global variable to `STDOUT`
55
+ #
56
+ def release_console
57
+ $stdout = $console
58
+ end
59
+
60
+ #
61
+ # Returns true if `$stdout` is a `StringIO` object
62
+ #
63
+ def console_captured?
64
+ $stdout.is_a?(StringIO)
65
+ end
66
+
67
+ #
68
+ # Returns true if the output buffer is currently empty
69
+ #
70
+ def captured_console_empty?
71
+ console_captured? and $stdout.string.empty?
72
+ end
73
+
74
+ #
75
+ # Hides the terminal cursor
76
+ #
77
+ def hide_cursor
78
+ $console.print ESC_CURS_INVIS
79
+ end
80
+
81
+ #
82
+ # Shows the terminal cursor
83
+ #
84
+ def show_cursor
85
+ $console.print ESC_CURS_VIS
86
+ end
87
+ end
88
+ end
@@ -1,43 +1,30 @@
1
1
  module SpinningCursor
2
- if RUBY_PLATFORM =~ /(win|w)32$/
3
- # DOS
4
- # Contains a string to clear the line in the shell
5
- CLR = " \r"
6
- else
7
- # Unix
8
- # Contains a string to clear the line in the shell
9
- CLR = "\e[0K"
10
- end
11
-
12
- #
13
- # Manages line reset in the console
14
- #
15
- def reset_line(text = "")
16
- print "\r#{CLR}#{text}"
17
- end
18
-
19
2
  #
20
3
  # This class contains the cursor types (and their helper methods)
21
4
  #
22
5
  class Cursor
23
- attr_accessor :banner
6
+ include SpinningCursor::ConsoleHelpers
24
7
 
25
8
  #
26
9
  # As of v0.1.0: only initializes the cursor class, use the spin
27
10
  # method to start the printing. Takes only the banner argument as
28
11
  # a result of this.
29
12
  #
30
- def initialize(banner = "Loading")
31
- @banner = banner
13
+ def initialize(parsed)
14
+ @parsed = parsed
32
15
  end
33
16
 
34
17
  #
35
- # Takes a cursor type symbol and starts the printing
18
+ # Takes a cursor type symbol and delay, and starts the printing
36
19
  #
37
- def spin(type = :spinner)
20
+ def spin
38
21
  $stdout.sync = true
39
- print @banner
40
- send type
22
+ $console.print @parsed.banner
23
+ if @parsed.delay
24
+ send @parsed.type, @parsed.delay
25
+ else
26
+ send @parsed.type
27
+ end
41
28
  end
42
29
 
43
30
  private
@@ -45,33 +32,30 @@ module SpinningCursor
45
32
  #
46
33
  # Prints three dots and clears the line
47
34
  #
48
- def dots
49
- i = 1
50
- loop do
51
- sleep 1
52
- if i % 4 == 0
53
- SpinningCursor.reset_line @banner
54
- i += 1
55
- next
56
- end
57
- i += 1
58
- print "."
59
- end
35
+ def dots(delay = 1)
36
+ cycle_through ['.', '..', '...', ''], delay
60
37
  end
61
38
 
62
39
  #
63
40
  # Cycles through '|', '/', '-', '\', resembling a spinning cursor
64
41
  #
65
- def spinner
66
- spinners = ['|', '/', '-', '\\']
67
- i = 0
68
- loop do
69
- print " " unless @banner.empty?
70
- print spinners[i % 4]
71
- sleep 0.5
72
- SpinningCursor.reset_line @banner
73
- i += 1
42
+ def spinner(delay = 0.5)
43
+ cycle_through ['|', '/', '-', '\\'], delay
44
+ end
45
+
46
+ def cycle_through(chars, delay)
47
+ chars.cycle do |char|
48
+ unless @parsed.output==:at_stop or captured_console_empty?
49
+ $console.print "#{ESC_R_AND_CLR}"
50
+ $console.print $stdout.string
51
+ $console.print "\n" unless $stdout.string[-1,1] == "\n"
52
+ $stdout.string = "" # TODO: Check for race condition.
53
+ end
54
+ $console.print "#{ESC_R_AND_CLR}#{@parsed.banner}"
55
+ $console.print " " unless @parsed.banner.empty?
56
+ $console.print "#{char}"
57
+ sleep delay
74
58
  end
75
59
  end
76
60
  end
77
- end
61
+ end
@@ -1,43 +1,50 @@
1
1
  module SpinningCursor
2
2
  class Parser
3
- attr_reader :originator
3
+ attr_accessor :outer_scope_object
4
4
 
5
5
  #
6
6
  # Parses proc
7
7
  #
8
- def initialize(proc)
9
- @banner = "Loading"
10
- @type = :spinner
8
+ def initialize(&block)
9
+ @banner = "Loading"
10
+ @type = :spinner
11
11
  @message = "Done"
12
+ @delay = nil
13
+ @action = nil
14
+ @output = :inline
12
15
 
13
- if not proc.nil?
14
- # Store the originating class for use in method_missing
15
- @originator = eval "self", proc.binding
16
- instance_eval &proc
16
+ if block_given?
17
+ @outer_scope_object = eval("self", block.binding)
18
+ instance_eval &block
17
19
  end
18
-
19
- self
20
20
  end
21
21
 
22
22
  #
23
23
  # Getter and setter for the action block
24
24
  #
25
25
  def action(&block)
26
- @action = block unless block.nil?
26
+ @action = block if block
27
27
 
28
28
  @action
29
29
  end
30
30
 
31
- #
32
- # Getters and setters for +banner+, +type+ and +message+
33
- # attributes.
34
- # Note:: for getting, pass nil e.g. <tt>banner nil</tt>
35
- #
36
- %w[banner type message].each do |method|
37
- define_method(method) do |string|
31
+ # @method banner(banner)
32
+ # @method type(type)
33
+ # @method message(message)
34
+ # @method delay(delay)
35
+ # @method output(output)
36
+ # Getters and setters for `banner`, `type`, `message`, `delay` and `output`
37
+ # attributes
38
+ # @note For getting, use method without arguments
39
+ # e.g. `banner`<br />
40
+ # For setting, use method with arguments
41
+ # e.g. `banner "my banner"`
42
+ #
43
+ %w[banner type message delay output].each do |method|
44
+ define_method(method) do |*args|
38
45
  var = "@#{method}"
39
- return instance_variable_get(var) if string.nil?
40
- instance_variable_set(var, string)
46
+ return instance_variable_get(var) unless args.first
47
+ instance_variable_set(var, args.first)
41
48
  end
42
49
  end
43
50
 
@@ -47,7 +54,7 @@ module SpinningCursor
47
54
  # Pass any other methods to the calling class
48
55
  #
49
56
  def method_missing(method, *args, &block)
50
- @originator.send method, *args, &block
57
+ @outer_scope_object.send method, *args, &block
51
58
  end
52
59
  end
53
- end
60
+ end
@@ -0,0 +1,78 @@
1
+ class StopWatch
2
+
3
+ #
4
+ # Instantiate class and call {#measure}
5
+ #
6
+ def self.measure(&block)
7
+ sw = self.new
8
+ sw.measure &block
9
+ end
10
+
11
+ #
12
+ # Measures the time taken to process the passed block and returns the
13
+ # measurment (see {timing})
14
+ #
15
+ def measure(&block)
16
+ start
17
+ yield
18
+ stop
19
+ timing
20
+ end
21
+
22
+ #
23
+ # Starts timer
24
+ #
25
+ def start
26
+ @start_time = Time.now
27
+ @stop_time = nil
28
+ self
29
+ end
30
+
31
+ #
32
+ # Stops timer
33
+ #
34
+ def stop
35
+ if @start_time
36
+ @stop_time = Time.now
37
+ end
38
+ self
39
+ end
40
+
41
+ #
42
+ # Resets timer
43
+ #
44
+ def reset
45
+ @start_time = @stop_time = nil
46
+ end
47
+
48
+ #
49
+ # Returns the elapsed time
50
+ # @note If the timer has not yet stopped, the time elapsed from the start of
51
+ # the timer till `Time.now` is returned
52
+ #
53
+ def elapsed_time
54
+ time_now = Time.now
55
+ (@stop_time || time_now ) - (@start_time || time_now)
56
+ end
57
+
58
+ alias old_inspect inspect
59
+
60
+ def inspect
61
+ puts "#{old_inspect} #{{:elapsed_time => elapsed_time}}"
62
+ end
63
+
64
+ #
65
+ # Returns the measurement in a hash containing
66
+ # * the start time
67
+ # * the stop time
68
+ # * the total elapsed time
69
+ #
70
+ def timing
71
+ { :start_time => @start_time,
72
+ :stop_time => @stop_time,
73
+ :elapsed_time => elapsed_time }
74
+ end
75
+
76
+ end
77
+
78
+
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "spinning_cursor"
8
- s.version = "0.1.2"
8
+ s.version = "0.2.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Adnan Abdulhussein"]
12
- s.date = "2012-04-25"
12
+ s.date = "2013-07-13"
13
13
  s.description = "Spinning Cursor is a flexible DSL that allows you to easily produce a customizable waiting/loading message for your Ruby command line program. Beautifully keep your users informed with what your program is doing when a more complex solution, such as a progress bar, doesn't fit your needs."
14
14
  s.email = "adnan@prydoni.us"
15
15
  s.extra_rdoc_files = [
@@ -29,8 +29,10 @@ Gem::Specification.new do |s|
29
29
  "TODO",
30
30
  "VERSION",
31
31
  "lib/spinning_cursor.rb",
32
+ "lib/spinning_cursor/console_helpers.rb",
32
33
  "lib/spinning_cursor/cursor.rb",
33
34
  "lib/spinning_cursor/parser.rb",
35
+ "lib/spinning_cursor/stop_watch.rb",
34
36
  "spinning_cursor.gemspec",
35
37
  "test/helper.rb",
36
38
  "test/test_cursors.rb",
@@ -49,24 +51,24 @@ Gem::Specification.new do |s|
49
51
 
50
52
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
51
53
  s.add_development_dependency(%q<shoulda>, [">= 0"])
52
- s.add_development_dependency(%q<bundler>, ["~> 1.1.3"])
53
- s.add_development_dependency(%q<jeweler>, ["~> 1.8.3"])
54
- s.add_development_dependency(%q<yard>, [">= 0"])
54
+ s.add_development_dependency(%q<bundler>, ["~> 1.3.5"])
55
+ s.add_development_dependency(%q<jeweler>, ["~> 1.8.4"])
56
+ s.add_development_dependency(%q<yard>, ["~> 0.8.6.2"])
55
57
  s.add_development_dependency(%q<redcarpet>, [">= 0"])
56
58
  s.add_development_dependency(%q<github-markup>, [">= 0"])
57
59
  else
58
60
  s.add_dependency(%q<shoulda>, [">= 0"])
59
- s.add_dependency(%q<bundler>, ["~> 1.1.3"])
60
- s.add_dependency(%q<jeweler>, ["~> 1.8.3"])
61
- s.add_dependency(%q<yard>, [">= 0"])
61
+ s.add_dependency(%q<bundler>, ["~> 1.3.5"])
62
+ s.add_dependency(%q<jeweler>, ["~> 1.8.4"])
63
+ s.add_dependency(%q<yard>, ["~> 0.8.6.2"])
62
64
  s.add_dependency(%q<redcarpet>, [">= 0"])
63
65
  s.add_dependency(%q<github-markup>, [">= 0"])
64
66
  end
65
67
  else
66
68
  s.add_dependency(%q<shoulda>, [">= 0"])
67
- s.add_dependency(%q<bundler>, ["~> 1.1.3"])
68
- s.add_dependency(%q<jeweler>, ["~> 1.8.3"])
69
- s.add_dependency(%q<yard>, [">= 0"])
69
+ s.add_dependency(%q<bundler>, ["~> 1.3.5"])
70
+ s.add_dependency(%q<jeweler>, ["~> 1.8.4"])
71
+ s.add_dependency(%q<yard>, ["~> 0.8.6.2"])
70
72
  s.add_dependency(%q<redcarpet>, [">= 0"])
71
73
  s.add_dependency(%q<github-markup>, [">= 0"])
72
74
  end
data/test/helper.rb CHANGED
@@ -18,16 +18,31 @@ require 'spinning_cursor'
18
18
  # (http://thinkingdigitally.com/archive/capturing-output-from-puts-in-ruby/)
19
19
 
20
20
  require 'stringio'
21
+
22
+ def kill_other_threads
23
+ other_threads = Thread.list - [Thread.current]
24
+ other_threads.each do |th|
25
+ th.kill
26
+ th.join
27
+ end
28
+ end
21
29
 
22
30
  module Kernel
23
31
  def capture_stdout
32
+ SpinningCursor.capture_console
24
33
  out = StringIO.new
25
- $stdout = out
34
+ $console = out
26
35
  yield out
27
36
  ensure
28
- $stdout = STDOUT
37
+ kill_other_threads
38
+ $console = STDOUT
39
+ SpinningCursor.release_console
29
40
  end
30
41
  end
31
42
 
43
+ include SpinningCursor
44
+
45
+ Thread.abort_on_exception=true
46
+
32
47
  class Test::Unit::TestCase
33
48
  end
data/test/test_cursors.rb CHANGED
@@ -2,40 +2,80 @@ require 'helper'
2
2
 
3
3
  class TestSpinningCursorCursor < Test::Unit::TestCase
4
4
  context "dots" do
5
- should "reset line after printing three dots" do
5
+ parsed = Parser.new { type :dots; delay 0.2; banner ""}
6
+ delay = parsed.delay
7
+ should "change 'frames' with correct delay" do
6
8
  capture_stdout do |out|
7
9
  dots = Thread.new do
8
- SpinningCursor::Cursor.new("").spin :dots
10
+ SpinningCursor::Cursor.new(parsed).spin
9
11
  end
10
- sleep 5
12
+ # slight delay to get things started
13
+ sleep (delay/4.0)
14
+ buffer = "#{ESC_R_AND_CLR}" << "."
15
+ assert_equal buffer, out.string
16
+
17
+ sleep delay
18
+ buffer << "#{ESC_R_AND_CLR}" << ".."
19
+ assert_equal buffer, out.string
20
+
21
+ sleep delay
22
+ buffer << "#{ESC_R_AND_CLR}" << "..."
23
+ assert_equal buffer, out.string
24
+
25
+ sleep delay
26
+ buffer << "#{ESC_R_AND_CLR}"
27
+ assert_equal buffer, out.string
28
+ # don't need to go through the whole thing, otherwise test will take
29
+ # too long
11
30
  dots.kill
12
- # \r\e[0K is move cursor to the start of the line and clear line
13
- # in bash
14
- assert_equal "...\r\e[0K", out.string
15
31
  end
16
32
  end
17
33
  end
18
34
 
19
35
  context "spinner" do
20
36
  should "cycle through correctly" do
37
+ parsed = Parser.new { type :spinner; delay 0.2; banner ""}
38
+ delay = parsed.delay
39
+ capture_stdout do |out|
40
+ spinner = Thread.new do
41
+ SpinningCursor::Cursor.new(parsed).spin
42
+ end
43
+ # slight delay to get things started
44
+ sleep (delay/3.0)
45
+ buffer = (ESC_R_AND_CLR + "|")
46
+ assert_equal buffer, out.string
47
+ buffer += (ESC_R_AND_CLR + "/")
48
+ sleep delay
49
+ assert_equal buffer, out.string
50
+ buffer += (ESC_R_AND_CLR + "-")
51
+ sleep delay
52
+ assert_equal buffer, out.string
53
+ buffer += (ESC_R_AND_CLR + "\\")
54
+ sleep delay
55
+ assert_equal buffer, out.string
56
+ sleep delay
57
+ buffer += (ESC_R_AND_CLR + "|")
58
+ assert_equal buffer, out.string
59
+ spinner.kill
60
+ end
61
+ end
62
+
63
+ should "changes 'frames' with correct delay" do
64
+ parsed = Parser.new { type :spinner; delay 0.2; banner ""}
65
+ delay = parsed.delay
21
66
  capture_stdout do |out|
22
67
  spinner = Thread.new do
23
- SpinningCursor::Cursor.new("").spin :spinner
68
+ SpinningCursor::Cursor.new(parsed).spin
24
69
  end
25
- sleep 0.1
26
- assert_equal "|", out.string
27
- buffer = "|\r\e[0K"
28
- sleep 0.5
29
- assert_equal "#{buffer}/", out.string
30
- buffer += "/\r\e[0K"
31
- sleep 0.5
32
- assert_equal "#{buffer}-", out.string
33
- buffer += "-\r\e[0K"
34
- sleep 0.5
35
- assert_equal "#{buffer}\\", out.string
36
- buffer += "\\\r\e[0K"
37
- sleep 0.5
38
- assert_equal "#{buffer}|", out.string
70
+ sleep (delay/4.0)
71
+ buffer = (ESC_R_AND_CLR + "|")
72
+ assert_equal buffer, out.string
73
+ buffer += (ESC_R_AND_CLR + "/")
74
+ sleep delay
75
+ # next frame after 'delay' second
76
+ assert_equal buffer, out.string
77
+ # don't need to go through the whole thing, otherwise test will take
78
+ # too long
39
79
  spinner.kill
40
80
  end
41
81
  end
@@ -17,12 +17,6 @@ class TestSpinningCursor < Test::Unit::TestCase
17
17
  end
18
18
  end
19
19
 
20
- should "a) raise NoTaskError when getting execution time if no task ran" do
21
- assert_raise SpinningCursor::NoTaskError do
22
- SpinningCursor.get_exec_time
23
- end
24
- end
25
-
26
20
  should "raise CursorNotRunning errors when cursor has run and finished" do
27
21
  SpinningCursor.start
28
22
  SpinningCursor.stop
data/test/test_parser.rb CHANGED
@@ -1,34 +1,78 @@
1
1
  require 'helper'
2
2
 
3
3
  class TestSpinningCursorParser < Test::Unit::TestCase
4
- context "parser" do
5
- should "check calling class for any missing methods" do
6
- def banner_text
7
- "Banner"
4
+ context "A Parser instance" do
5
+ should "always respond to outer_scope_object method" do
6
+ assert_respond_to Parser.new, :outer_scope_object
7
+ end
8
+
9
+ context "initialized WITHOUT a block" do
10
+ should "have outer_scope_object equal nil" do
11
+ assert_equal nil, Parser.new.outer_scope_object
12
+ end
13
+ end
14
+
15
+ context "initialized WITH a block" do
16
+ should "have outer_scope_object point to 'caller'" do
17
+ parsed = Parser.new {}
18
+ assert_equal self, parsed.outer_scope_object
19
+ end
20
+
21
+ context "having an 'action' block within this block" do
22
+ should "have this action block retrievable with Parser#action" do
23
+ action_block = Proc.new { }
24
+ parsed = Parser.new do
25
+ action &action_block
26
+ end
27
+ assert_equal action_block, parsed.action
28
+ end
29
+ end
30
+
31
+ context "and instance evaluating this block at Parser context" do
32
+ should "NOT have access to instance variables of outer_scope_object from inside the block" do
33
+ @outer_instance_variable = 1
34
+ parsed = Parser.new { @outer_instance_variable = 2 }
35
+ assert_not_equal 2, @outer_instance_variable
36
+ assert_equal 2, parsed.instance_variable_get(:@outer_instance_variable)
37
+ end
38
+
39
+ should "have direct access to instance variables of the Parser instance itself" do
40
+ parsed = Parser.new { @banner = "this is Parser instance"}
41
+ assert_equal parsed.banner, "this is Parser instance"
42
+ end
43
+
44
+ should "have access to methods of outer_scope_object from inside the block (thanks method_missing)" do
45
+ def outer_method
46
+ "Outer Method"
47
+ end
48
+ parsed = Parser.new { banner outer_method }
49
+ assert_equal outer_method, parsed.banner
50
+ end
8
51
  end
9
- parser = SpinningCursor::Parser.new Proc.new { banner banner_text }
10
- assert_equal banner_text, (parser.banner nil)
11
52
  end
12
53
  end
13
54
 
14
- context "banner, type, message and action methods" do
55
+ context "banner, type, message, delay and action methods" do
15
56
  setup do
16
- @parser = SpinningCursor::Parser.new Proc.new { }
57
+ @parser = SpinningCursor::Parser.new
17
58
  end
18
59
 
19
60
  should "act as getters and setters" do
20
61
  @parser.banner "a new banner"
21
- assert_equal "a new banner", (@parser.banner nil)
62
+ assert_equal "a new banner", @parser.banner
22
63
 
23
64
  @parser.type :dots
24
- assert_equal :dots, (@parser.type nil)
65
+ assert_equal :dots, @parser.type
25
66
 
26
67
  @parser.message "a message"
27
- assert_equal "a message", (@parser.message nil)
68
+ assert_equal "a message", @parser.message
69
+
70
+ @parser.delay 5
71
+ assert_equal 5, @parser.delay
28
72
 
29
73
  proc = Proc.new { }
30
74
  @parser.action &proc
31
75
  assert_equal proc, @parser.action
32
76
  end
33
77
  end
34
- end
78
+ end
@@ -6,13 +6,22 @@ class TestSpinningCursor < Test::Unit::TestCase
6
6
  # Hide any output
7
7
  capture_stdout do |out|
8
8
  SpinningCursor.start do
9
- action { sleep 1 }
9
+ action { sleep 0.1 }
10
10
  end
11
11
  # Give it some time to abort
12
12
  assert_equal false, SpinningCursor.alive?
13
13
  end
14
14
  end
15
15
 
16
+ should "Parser#outer_scope_object point to 'caller'" do
17
+ capture_stdout do |out|
18
+ SpinningCursor.start { }
19
+ parsed = SpinningCursor.instance_variable_get(:@parsed)
20
+ assert_equal self, parsed.outer_scope_object
21
+ SpinningCursor.stop
22
+ end
23
+ end
24
+
16
25
  should "evalute the block from the calling class" do
17
26
  @num = 1
18
27
  capture_stdout do |out|
@@ -23,6 +32,17 @@ class TestSpinningCursor < Test::Unit::TestCase
23
32
  assert_equal 2, @num
24
33
  end
25
34
  end
35
+
36
+ should "raise an exception if something wrong inside the 'action' block" do
37
+ class SomethingWrongHappened < StandardError; end
38
+ assert_raise SomethingWrongHappened do
39
+ capture_stdout do |out|
40
+ SpinningCursor.start do
41
+ action { raise SomethingWrongHappened }
42
+ end
43
+ end
44
+ end
45
+ end
26
46
  end
27
47
 
28
48
  context "when an action block isn't passed it" do
@@ -31,7 +51,7 @@ class TestSpinningCursor < Test::Unit::TestCase
31
51
  SpinningCursor.start do
32
52
  banner "no action block"
33
53
  end
34
- sleep 2
54
+ sleep 0.5
35
55
  assert_equal true, SpinningCursor.alive?
36
56
  SpinningCursor.stop
37
57
  sleep 0.1
@@ -56,12 +76,14 @@ class TestSpinningCursor < Test::Unit::TestCase
56
76
 
57
77
  should "stop and display error if an unmanaged exception is thrown" do
58
78
  capture_stdout do |out|
59
- SpinningCursor.start do
60
- action do
61
- raise "An exception!"
79
+ begin
80
+ SpinningCursor.start do
81
+ action do
82
+ raise "An exception!"
83
+ end
62
84
  end
85
+ rescue # Just to let the test go on
63
86
  end
64
-
65
87
  assert_equal true, (out.string.include? "An exception!")
66
88
  end
67
89
  end
@@ -85,18 +107,61 @@ class TestSpinningCursor < Test::Unit::TestCase
85
107
  should "allow you to change the banner" do
86
108
  capture_stdout do |out|
87
109
  SpinningCursor.start do
110
+ delay 0.2
88
111
  action do
89
112
  # Have to give it time to print the banners
90
113
  sleep 0.1
91
- assert_equal true, (out.string.include? "Loading")
114
+ assert_equal true, (out.string.include? "Loading"), "It should initialy show default banner"
92
115
  sleep 0.1
93
116
  SpinningCursor.set_banner "Finishing up"
94
- sleep 0.5
95
- assert_equal true, (out.string.include? "Finishing up")
117
+ sleep 0.2
118
+ assert_equal true, (out.string.include? "Finishing up"), "It should have changed banner"
96
119
  sleep 0.1
97
120
  end
98
121
  end
99
122
  end
100
123
  end
101
124
  end
125
+
126
+ context "when running for the second time" do
127
+ should "(without a block) return similar timing values" do
128
+ capture_stdout do |out|
129
+ SpinningCursor.start
130
+ sleep 0.2
131
+ result = SpinningCursor.stop
132
+ timing_1 = result[:elapsed_time]
133
+
134
+ SpinningCursor.start
135
+ sleep 0.2
136
+ result = SpinningCursor.stop
137
+ timing_2 = result[:elapsed_time]
138
+
139
+ assert_equal (timing_1*10).round, (timing_2*10).round,
140
+ "t1 #{timing_1} and t2 #{timing_2} should be equal"
141
+ end
142
+ end
143
+
144
+ should "(with a block) return similar timing values" do
145
+ capture_stdout do |out|
146
+ result =
147
+ SpinningCursor.start do
148
+ action do
149
+ sleep 0.2
150
+ end
151
+ end
152
+ timing_1 = result[:elapsed_time]
153
+
154
+ result =
155
+ SpinningCursor.start do
156
+ action do
157
+ sleep 0.2
158
+ end
159
+ end
160
+ timing_2 = result[:elapsed_time]
161
+
162
+ assert_equal (timing_1*10).round, (timing_2*10).round,
163
+ "t1 #{timing_1} and t2 #{timing_2} should be equal"
164
+ end
165
+ end
166
+ end
102
167
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spinning_cursor
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-04-25 00:00:00.000000000 Z
12
+ date: 2013-07-13 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: shoulda
@@ -34,7 +34,7 @@ dependencies:
34
34
  requirements:
35
35
  - - ~>
36
36
  - !ruby/object:Gem::Version
37
- version: 1.1.3
37
+ version: 1.3.5
38
38
  type: :development
39
39
  prerelease: false
40
40
  version_requirements: !ruby/object:Gem::Requirement
@@ -42,7 +42,7 @@ dependencies:
42
42
  requirements:
43
43
  - - ~>
44
44
  - !ruby/object:Gem::Version
45
- version: 1.1.3
45
+ version: 1.3.5
46
46
  - !ruby/object:Gem::Dependency
47
47
  name: jeweler
48
48
  requirement: !ruby/object:Gem::Requirement
@@ -50,7 +50,7 @@ dependencies:
50
50
  requirements:
51
51
  - - ~>
52
52
  - !ruby/object:Gem::Version
53
- version: 1.8.3
53
+ version: 1.8.4
54
54
  type: :development
55
55
  prerelease: false
56
56
  version_requirements: !ruby/object:Gem::Requirement
@@ -58,23 +58,23 @@ dependencies:
58
58
  requirements:
59
59
  - - ~>
60
60
  - !ruby/object:Gem::Version
61
- version: 1.8.3
61
+ version: 1.8.4
62
62
  - !ruby/object:Gem::Dependency
63
63
  name: yard
64
64
  requirement: !ruby/object:Gem::Requirement
65
65
  none: false
66
66
  requirements:
67
- - - ! '>='
67
+ - - ~>
68
68
  - !ruby/object:Gem::Version
69
- version: '0'
69
+ version: 0.8.6.2
70
70
  type: :development
71
71
  prerelease: false
72
72
  version_requirements: !ruby/object:Gem::Requirement
73
73
  none: false
74
74
  requirements:
75
- - - ! '>='
75
+ - - ~>
76
76
  - !ruby/object:Gem::Version
77
- version: '0'
77
+ version: 0.8.6.2
78
78
  - !ruby/object:Gem::Dependency
79
79
  name: redcarpet
80
80
  requirement: !ruby/object:Gem::Requirement
@@ -130,8 +130,10 @@ files:
130
130
  - TODO
131
131
  - VERSION
132
132
  - lib/spinning_cursor.rb
133
+ - lib/spinning_cursor/console_helpers.rb
133
134
  - lib/spinning_cursor/cursor.rb
134
135
  - lib/spinning_cursor/parser.rb
136
+ - lib/spinning_cursor/stop_watch.rb
135
137
  - spinning_cursor.gemspec
136
138
  - test/helper.rb
137
139
  - test/test_cursors.rb
@@ -153,7 +155,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
153
155
  version: '0'
154
156
  segments:
155
157
  - 0
156
- hash: 1437049553814384370
158
+ hash: 2712039492645402918
157
159
  required_rubygems_version: !ruby/object:Gem::Requirement
158
160
  none: false
159
161
  requirements: