progress_monitor 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 4ebee08ed5906ff4ba92f40d53ad0355d82e33c0
4
+ data.tar.gz: f83435c6cb2c46d8b1501628cb41add64b3d8e3a
5
+ SHA512:
6
+ metadata.gz: e176d59cc435ba148b8b763384ae369057e26ecd9485f327ec0a34b5b36ca10c3377c9c4332c8acbac2f5d26e729b6e27ca01b9683d8703c4214f8ab61ded8f4
7
+ data.tar.gz: 7f71b6a7219b7d2b7509a34fa6ea25cf977f34124acd59484952e7b4571426ea33970c2f540da2b56e3f396b784320d9a90b89a6184723c7b306c464d099ecf8
@@ -0,0 +1,23 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
23
+ .idea
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
@@ -0,0 +1,3 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.1.2
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in progress_monitor.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Tobias Cohen
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,29 @@
1
+ # ProgressMonitor
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'progress_monitor'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install progress_monitor
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it ( https://github.com/[my-github-username]/progress_monitor/fork )
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create a new Pull Request
@@ -0,0 +1,7 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
7
+
@@ -0,0 +1,12 @@
1
+ require "observer"
2
+ require "io/console"
3
+
4
+ require "progress_monitor/version"
5
+
6
+ require "progress_monitor/task"
7
+ require "progress_monitor/display"
8
+ require "progress_monitor/utils"
9
+
10
+ module ProgressMonitor
11
+
12
+ end
@@ -0,0 +1,32 @@
1
+ require "progress_monitor/display/task_observer"
2
+ require "progress_monitor/display/input_loop"
3
+ require "progress_monitor/display/message_loop"
4
+ require "progress_monitor/display/timer_loop"
5
+
6
+ require "progress_monitor/display/renderer"
7
+ require "progress_monitor/display/progress_bar"
8
+
9
+ module ProgressMonitor
10
+ class Display
11
+ attr :task, :queue
12
+
13
+ def initialize(task)
14
+ @task = task
15
+ @queue = Queue.new
16
+ end
17
+
18
+ def display
19
+ @current_task = task
20
+
21
+ @renderer = Renderer.new(task)
22
+ @renderer.refresh
23
+
24
+ task.add_observer TaskObserver.new(queue)
25
+
26
+ @main_thread = Thread.current
27
+ Thread.new { InputLoop.new(queue).perform }
28
+ Thread.new { TimerLoop.new(queue).perform }
29
+ Thread.new { MessageLoop.new(queue, @main_thread, @renderer).perform }
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,46 @@
1
+ module ProgressMonitor
2
+ class Display
3
+ class InputLoop
4
+ attr :queue
5
+
6
+ def initialize(queue)
7
+ @queue = queue
8
+ @status = :normal
9
+ end
10
+
11
+ def perform
12
+ loop { get_key }
13
+ end
14
+
15
+ private
16
+
17
+ def get_key
18
+ key = STDIN.getch
19
+ case @status
20
+ when :normal
21
+ if key == "\e"
22
+ @status = :escape
23
+ else
24
+ send_key(key)
25
+ end
26
+ when :escape
27
+ if key == "["
28
+ @status = :special
29
+ else
30
+ send_key("\e#{key}")
31
+ @status = :normal
32
+ end
33
+ when :special
34
+ send_key("\e[#{key}")
35
+ @status = :normal
36
+ end
37
+ rescue => e
38
+ puts e.inspect
39
+ end
40
+
41
+ def send_key(key)
42
+ @queue << { type: :input, key: key }
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,56 @@
1
+ module ProgressMonitor
2
+ class Display
3
+ class MessageLoop
4
+ attr :queue, :main_thread, :renderer
5
+
6
+ def initialize(queue, main_thread, renderer)
7
+ @queue = queue
8
+ @main_thread = main_thread
9
+ @renderer = renderer
10
+ end
11
+
12
+ def perform
13
+ loop do
14
+ message = queue.pop
15
+ case message[:type]
16
+ when :update
17
+ on_update(message[:task], message[:event], message[:details])
18
+ when :timer
19
+ on_timer
20
+ when :input
21
+ on_input message[:key]
22
+ end
23
+ end
24
+ rescue => error
25
+ main_thread.raise error
26
+ end
27
+
28
+ private
29
+
30
+ def on_update(task, event, details)
31
+ case event
32
+ when :status
33
+ renderer.task = task
34
+ renderer.refresh
35
+ end
36
+ end
37
+
38
+ def on_input(key)
39
+ case key
40
+ when ?\C-c
41
+ main_thread.raise Interrupt
42
+ when "\e[A"
43
+ collapse
44
+ when "\e[B"
45
+ expand
46
+ else
47
+ puts key.inspect
48
+ end
49
+ end
50
+
51
+ def on_timer
52
+ @renderer.refresh
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,34 @@
1
+ module ProgressMonitor
2
+ class Display
3
+ class ProgressBar
4
+ attr_accessor :size, :completion_percent
5
+
6
+ def initialize(size: 20, completion_percent: 0)
7
+ @size = size
8
+ @completion_percent = completion_percent
9
+ end
10
+
11
+ BAR_PROGRESS = "█"
12
+ BAR_SPACE = " "
13
+ BAR_PARTIAL_PROGRESS = [BAR_SPACE, '▏', '▎', '▍', '▌', '▋', '▊', '▉']
14
+
15
+ def render
16
+ if completion_percent == :unknown
17
+ '╍' * size
18
+ else
19
+ completion_float = completion_percent.to_f / 100 * size
20
+ completion_size = completion_float.floor
21
+ remainder = (completion_float % 1 * 8).floor
22
+ if completion_size < size
23
+ BAR_PROGRESS * completion_size + BAR_PARTIAL_PROGRESS[remainder] + BAR_SPACE * (size - completion_size - 1)
24
+ else
25
+ BAR_PROGRESS * size
26
+ end
27
+ end
28
+ rescue => e
29
+ puts e
30
+ ""
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,53 @@
1
+ module ProgressMonitor
2
+ class Display
3
+ class Renderer
4
+ attr_accessor :task
5
+
6
+ def initialize(task)
7
+ @task = task
8
+ @current_state = nil
9
+ end
10
+
11
+ def refresh
12
+ new_state = task_state
13
+
14
+ if @current_state != new_state
15
+ render task_state
16
+ @current_state = new_state
17
+ end
18
+
19
+ rescue => e
20
+ puts "\r\n", e.inspect, e.backtrace
21
+ nil
22
+ end
23
+
24
+ private
25
+
26
+ def task_state
27
+ {
28
+ name: task.name,
29
+ completion_percent: task.completion_percent,
30
+ }
31
+ end
32
+
33
+ def render(state)
34
+ left = state[:name]
35
+ progress_bar = ProgressBar.new(size: 20)
36
+ progress_bar.completion_percent = state[:completion_percent]
37
+ right = progress_bar.render
38
+ spacing = " " * (columns - left.length - right.length)
39
+
40
+ print clear_code, left, spacing, "\e[48;5;235m\e[32m", right, "\e[0m\r\e[#{left.length}C"
41
+ end
42
+
43
+ def clear_code
44
+ "\e[1K\r"
45
+ end
46
+
47
+ def columns
48
+ STDIN.winsize[1]
49
+ end
50
+ end
51
+ end
52
+ end
53
+
@@ -0,0 +1,15 @@
1
+ module ProgressMonitor
2
+ class Display
3
+ class TaskObserver
4
+ attr :queue
5
+
6
+ def initialize(queue)
7
+ @queue = queue
8
+ end
9
+
10
+ def update(task, event, details)
11
+ @queue << {type: :update, task: task, event: event, details: details}
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,18 @@
1
+ module ProgressMonitor
2
+ class Display
3
+ class TimerLoop
4
+ attr :queue
5
+
6
+ def initialize(queue)
7
+ @queue = queue
8
+ end
9
+
10
+ def perform
11
+ loop do
12
+ sleep 0.1
13
+ queue << {type: :timer}
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,94 @@
1
+ require "progress_monitor/task/percentage_calculation/status"
2
+ require "progress_monitor/task/percentage_calculation/io_position"
3
+ require "progress_monitor/task/percentage_calculation/average_completion_of_subtasks"
4
+
5
+ module ProgressMonitor
6
+ class Task
7
+ include Observable
8
+
9
+ attr :name, :parent, :subtasks, :status, :started_at, :finished_at
10
+ attr_accessor :io
11
+
12
+ PERCENTAGE_CALCULATORS = [
13
+ PercentageCalculation::Status,
14
+ PercentageCalculation::IoPosition,
15
+ PercentageCalculation::AverageCompletionOfSubtasks,
16
+ ]
17
+
18
+ def initialize(name, parent=nil)
19
+ @name = name
20
+ @parent = parent
21
+ @subtasks = []
22
+ @status = :new
23
+ end
24
+
25
+ def start
26
+ return unless status == :new
27
+
28
+ parent.start if parent
29
+ @status = :started
30
+ @started_at = Time.now
31
+ notify_status
32
+ end
33
+
34
+ def finish
35
+ return unless status == :started
36
+
37
+ parent.finish_if_subtasks_finished
38
+ @status = :finished
39
+ @finished_at = Time.now
40
+ notify_status
41
+ end
42
+
43
+ def duration
44
+ finished_at - started_at
45
+ end
46
+
47
+ def add_subtask(name)
48
+ Task.new(name, self).tap do |task|
49
+ @subtasks << task
50
+ end
51
+ end
52
+
53
+ def add_subtasks(*names)
54
+ names.map &method(:add_subtask)
55
+ end
56
+
57
+ def add_and_run_subtasks(items, name_fn, &run_fn)
58
+ items.map do |item|
59
+ [item, add_subtask(name_fn.call(item))]
60
+ end.each do |args|
61
+ run_fn.call(*args)
62
+ end
63
+ end
64
+
65
+ def completion_percent
66
+ PERCENTAGE_CALCULATORS.each do |calculator|
67
+ result = calculator.new(self).perform
68
+ return result if result
69
+ end
70
+ :unknown
71
+ rescue => e
72
+ puts e.inspect
73
+ :unknown
74
+ end
75
+
76
+ protected
77
+
78
+ def notify(task, event, details)
79
+ parent.notify task, event, details if parent
80
+ changed
81
+ notify_observers task, event, details
82
+ end
83
+
84
+ def finish_if_subtasks_finished
85
+ finish if subtasks.all?{|st| st.status == :finished}
86
+ end
87
+
88
+ private
89
+
90
+ def notify_status
91
+ notify self, :status, {status: status}
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,21 @@
1
+ module ProgressMonitor
2
+ class Task
3
+ module PercentageCalculation
4
+ class AverageCompletionOfSubtasks
5
+ attr :task
6
+
7
+ def initialize(task)
8
+ @task = task
9
+ end
10
+
11
+ def perform
12
+ if task.subtasks.any?
13
+ Utils.average task.subtasks.map(&:completion_percent)
14
+ end
15
+ rescue
16
+ nil
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ module ProgressMonitor
2
+ class Task
3
+ module PercentageCalculation
4
+ class IoPosition
5
+ attr :task
6
+
7
+ def initialize(task)
8
+ @task = task
9
+ end
10
+
11
+ def perform
12
+ if task.io && task.io.size > 0
13
+ (100.0 * task.io.pos / task.io.size).to_i
14
+ end
15
+ rescue
16
+ nil
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,24 @@
1
+ module ProgressMonitor
2
+ class Task
3
+ module PercentageCalculation
4
+ class Status
5
+ attr :task
6
+
7
+ def initialize(task)
8
+ @task = task
9
+ end
10
+
11
+ def perform
12
+ case task.status
13
+ when :new
14
+ 0
15
+ when :finished
16
+ 100
17
+ end
18
+ rescue
19
+ nil
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,11 @@
1
+ module Utils
2
+ def self.average(items)
3
+ count = items.count
4
+ if count > 0
5
+ sum = items.reduce(:+)
6
+ sum / count
7
+ else
8
+ 0
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,3 @@
1
+ module ProgressMonitor
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,23 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'progress_monitor/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "progress_monitor"
8
+ spec.version = ProgressMonitor::VERSION
9
+ spec.authors = ["Tobias Cohen"]
10
+ spec.email = ["me@tobiascohen.com"]
11
+ spec.summary = %q{Interactively display progress in the terminal for long running multi-step processes}
12
+ spec.homepage = ""
13
+ spec.license = "MIT"
14
+
15
+ spec.files = `git ls-files -z`.split("\x0")
16
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.add_development_dependency "bundler", "~> 1.6"
21
+ spec.add_development_dependency "rake"
22
+ spec.add_development_dependency "rspec"
23
+ end
@@ -0,0 +1,93 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+
4
+ Bundler.load
5
+ require 'progress_monitor'
6
+
7
+ class Example
8
+
9
+ class CreateUsers
10
+
11
+ NAMES = %w(Joe Mary Steve Alexis Kate Stephanie Daniel Cassandra Keith Tyrion)
12
+
13
+ attr :task
14
+
15
+ def initialize(task)
16
+ @task = task
17
+ end
18
+
19
+ def perform
20
+ task.start
21
+ task.add_and_run_subtasks NAMES, -> (name) { "Create user #{name}" } do |name, user_task|
22
+ create_record, upload_photo, send_email = user_task.add_subtasks("Create user record", "Upload profile photo for #{name}", "Send welcome email")
23
+ CreateRecord.new(create_record).perform
24
+ UploadPhoto.new(name, upload_photo).perform
25
+ SendEmail.new(send_email).perform
26
+ end
27
+ task.finish
28
+ end
29
+
30
+ class CreateRecord
31
+ attr :task
32
+
33
+ def initialize(task)
34
+ @task = task
35
+ end
36
+
37
+ def perform
38
+ task.start
39
+ sleep 0.5
40
+ task.finish
41
+ end
42
+ end
43
+
44
+ class UploadPhoto
45
+ attr :name, :task
46
+
47
+ def initialize(name, task)
48
+ @name = name
49
+ @task = task
50
+ end
51
+
52
+ def perform
53
+ task.start
54
+
55
+ file = File.open(File.join(__dir__, 'sample.txt'))
56
+
57
+ task.io = file
58
+
59
+ until file.eof?
60
+ file.getc
61
+ sleep 0.05
62
+ end
63
+ file.close
64
+
65
+ task.finish
66
+ end
67
+ end
68
+
69
+ class SendEmail
70
+ attr :task
71
+
72
+ def initialize(task)
73
+ @task = task
74
+ end
75
+
76
+ def perform
77
+ task.start
78
+ sleep 1
79
+ task.finish
80
+ end
81
+ end
82
+ end
83
+
84
+ def perform
85
+ task = ProgressMonitor::Task.new("Initialize data")
86
+ ProgressMonitor::Display.new(task).display
87
+
88
+ CreateUsers.new(task.add_subtask("Create users")).perform
89
+ end
90
+
91
+ end
92
+
93
+ Example.new.perform
@@ -0,0 +1 @@
1
+ The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog.
@@ -0,0 +1,7 @@
1
+ require 'spec_helper'
2
+
3
+ describe ProgressMonitor do
4
+ it 'has a version number' do
5
+ expect(ProgressMonitor::VERSION).not_to be nil
6
+ end
7
+ end
@@ -0,0 +1,2 @@
1
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
2
+ require 'progress_monitor'
metadata ADDED
@@ -0,0 +1,115 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: progress_monitor
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Tobias Cohen
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-02-15 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.6'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.6'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description:
56
+ email:
57
+ - me@tobiascohen.com
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - ".gitignore"
63
+ - ".rspec"
64
+ - ".travis.yml"
65
+ - Gemfile
66
+ - LICENSE.txt
67
+ - README.md
68
+ - Rakefile
69
+ - lib/progress_monitor.rb
70
+ - lib/progress_monitor/display.rb
71
+ - lib/progress_monitor/display/input_loop.rb
72
+ - lib/progress_monitor/display/message_loop.rb
73
+ - lib/progress_monitor/display/progress_bar.rb
74
+ - lib/progress_monitor/display/renderer.rb
75
+ - lib/progress_monitor/display/task_observer.rb
76
+ - lib/progress_monitor/display/timer_loop.rb
77
+ - lib/progress_monitor/task.rb
78
+ - lib/progress_monitor/task/percentage_calculation/average_completion_of_subtasks.rb
79
+ - lib/progress_monitor/task/percentage_calculation/io_position.rb
80
+ - lib/progress_monitor/task/percentage_calculation/status.rb
81
+ - lib/progress_monitor/utils.rb
82
+ - lib/progress_monitor/version.rb
83
+ - progress_monitor.gemspec
84
+ - script/example.rb
85
+ - script/sample.txt
86
+ - spec/progress_monitor_spec.rb
87
+ - spec/spec_helper.rb
88
+ homepage: ''
89
+ licenses:
90
+ - MIT
91
+ metadata: {}
92
+ post_install_message:
93
+ rdoc_options: []
94
+ require_paths:
95
+ - lib
96
+ required_ruby_version: !ruby/object:Gem::Requirement
97
+ requirements:
98
+ - - ">="
99
+ - !ruby/object:Gem::Version
100
+ version: '0'
101
+ required_rubygems_version: !ruby/object:Gem::Requirement
102
+ requirements:
103
+ - - ">="
104
+ - !ruby/object:Gem::Version
105
+ version: '0'
106
+ requirements: []
107
+ rubyforge_project:
108
+ rubygems_version: 2.2.2
109
+ signing_key:
110
+ specification_version: 4
111
+ summary: Interactively display progress in the terminal for long running multi-step
112
+ processes
113
+ test_files:
114
+ - spec/progress_monitor_spec.rb
115
+ - spec/spec_helper.rb