spinning_cursor 0.1.2 → 0.2.0

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