progress_monitor 0.0.1

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,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