ruby-pomodoro 0.4.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.
Files changed (41) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/ruby.yml +24 -0
  3. data/.gitignore +12 -0
  4. data/.rspec +3 -0
  5. data/.ruby-version +1 -0
  6. data/CHANGELOG.md +78 -0
  7. data/CODE_OF_CONDUCT.md +74 -0
  8. data/Gemfile +8 -0
  9. data/Gemfile.lock +67 -0
  10. data/LICENSE.txt +21 -0
  11. data/README.md +46 -0
  12. data/Rakefile +6 -0
  13. data/bin/console +14 -0
  14. data/bin/setup +8 -0
  15. data/exe/ruby-pomodoro +5 -0
  16. data/lib/ruby/pomodoro/cmd/base.rb +40 -0
  17. data/lib/ruby/pomodoro/cmd/choose_task.rb +33 -0
  18. data/lib/ruby/pomodoro/cmd/edit_list.rb +15 -0
  19. data/lib/ruby/pomodoro/cmd/error_handler.rb +17 -0
  20. data/lib/ruby/pomodoro/cmd/main.rb +15 -0
  21. data/lib/ruby/pomodoro/cmd/pause.rb +27 -0
  22. data/lib/ruby/pomodoro/cmd/quit.rb +19 -0
  23. data/lib/ruby/pomodoro/cmd/stop.rb +12 -0
  24. data/lib/ruby/pomodoro/command_controller.rb +42 -0
  25. data/lib/ruby/pomodoro/notification.rb +40 -0
  26. data/lib/ruby/pomodoro/notification_observer.rb +32 -0
  27. data/lib/ruby/pomodoro/printer.rb +56 -0
  28. data/lib/ruby/pomodoro/progressbar.rb +32 -0
  29. data/lib/ruby/pomodoro/task.rb +25 -0
  30. data/lib/ruby/pomodoro/tasks/editor.rb +65 -0
  31. data/lib/ruby/pomodoro/tasks/entity.rb +27 -0
  32. data/lib/ruby/pomodoro/tasks/resource.rb +42 -0
  33. data/lib/ruby/pomodoro/terminal_notifier_channel.rb +13 -0
  34. data/lib/ruby/pomodoro/time_converter.rb +27 -0
  35. data/lib/ruby/pomodoro/time_helpers.rb +11 -0
  36. data/lib/ruby/pomodoro/version.rb +5 -0
  37. data/lib/ruby/pomodoro/worker.rb +78 -0
  38. data/lib/ruby/pomodoro.rb +86 -0
  39. data/lib/view/main.txt.erb +13 -0
  40. data/ruby-pomodoro.gemspec +31 -0
  41. metadata +156 -0
@@ -0,0 +1,32 @@
1
+ module Ruby
2
+ module Pomodoro
3
+ class NotificationObserver
4
+ # @param stop [Ruby::Pomodoro::Notification]
5
+ # @param pause [Ruby::Pomodoro::Notification]
6
+ # @param time [Numeric] repeat notifications interval in seconds
7
+ def initialize(stop:, pause:, time:, printer: Printer.new)
8
+ @stop_notification = stop
9
+ @pause_notification = pause
10
+ @time = time
11
+ @printer = printer
12
+ end
13
+
14
+ # @param [Symbol] event
15
+ def update(event)
16
+ case event
17
+ when :finish
18
+ @stop_notification.notify(@time)
19
+ @printer.print_line "_Task #{Worker.instance.current_task.name} was stopped, type [\u21e7 + R] for resume\r",
20
+ color: :red
21
+ when :pause
22
+ @pause_notification.notify(@time, skip_now: true)
23
+ when :stop, :start
24
+ @pause_notification.stop
25
+ @stop_notification.stop
26
+ else
27
+ @pause_notification.stop
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,56 @@
1
+ module Ruby
2
+ module Pomodoro
3
+ class Printer
4
+ def initialize(stream: $stdout, cursor: TTY::Cursor, palette: Pastel.new)
5
+ @stream = stream
6
+ @cursor = cursor
7
+ @palette = palette
8
+ end
9
+
10
+ def print_template(template_name, cmd_binding)
11
+ @__cmd_binding = cmd_binding
12
+ stream.print(render(template_name))
13
+ end
14
+
15
+ def clear_terminal
16
+ stream.print(cursor.up(100))
17
+ stream.print(cursor.clear_screen_down)
18
+ end
19
+
20
+ def print(text, color: nil)
21
+ if color
22
+ stream.print(palette.send(color, text))
23
+ else
24
+ stream.print(text)
25
+ end
26
+ stream.flush
27
+ end
28
+
29
+ def print_line(text, color: nil)
30
+ cursor.clear_line
31
+ print(text, color: color)
32
+ end
33
+
34
+
35
+ def render(file_name)
36
+ ERB.new(read_file(file_name)).result(@__cmd_binding)
37
+ end
38
+
39
+ private
40
+
41
+ attr_reader :stream, :cursor, :palette
42
+
43
+
44
+ def read_file(file_name_or_path)
45
+ if file_name_or_path.kind_of?(Symbol)
46
+ path = File.expand_path(
47
+ "../../../#{File.join("view", "#{file_name_or_path}.txt.erb")}", __FILE__
48
+ )
49
+ File.read(path)
50
+ else
51
+ File.read(file_name_or_path)
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,32 @@
1
+ module Ruby
2
+ module Pomodoro
3
+ class Progressbar
4
+ def initialize(seconds:, printer: Ruby::Pomodoro::Printer.new)
5
+ @all_seconds = seconds
6
+ @spent_seconds = 0
7
+ @printer = printer
8
+ end
9
+
10
+ def start(text)
11
+ @text = text
12
+ end
13
+
14
+ def increment
15
+ @spent_seconds += 1
16
+ print
17
+ end
18
+
19
+ private
20
+
21
+ def print
22
+ @printer.print " In progress: #{@text} [#{strftime}]\r", color: :green
23
+ end
24
+
25
+ def strftime
26
+ seconds = @all_seconds - @spent_seconds
27
+ minutes = seconds / 60
28
+ "#{minutes} m #{seconds - minutes * 60} s"
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,25 @@
1
+ module Ruby
2
+ module Pomodoro
3
+ # Task for [Ruby::Pomodoro::Worker]
4
+ class Task
5
+ attr_reader :name, :pomodors, :spent_time
6
+
7
+ def initialize(name, spent_time: 0)
8
+ @name = name
9
+ @spent_time = spent_time
10
+ @pomodors = 0
11
+ end
12
+
13
+ # @return [Integer]
14
+ def add_pomodoro
15
+ @pomodors += 1
16
+ end
17
+
18
+ # @param [Integer] seconds
19
+ # @return [Integer]
20
+ def track(seconds)
21
+ @spent_time += seconds
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,65 @@
1
+ module Ruby
2
+ module Pomodoro
3
+ module Tasks
4
+ class Editor
5
+ attr_reader :tasks_repo, :editor, :file_path
6
+
7
+ # @param [Array<Ruby::Pomodoro::Task>] tasks_repo
8
+ # @param [String] file_path
9
+ # @param editor [Class, Object] Editor with method open, for create and open file
10
+ def initialize(tasks_repo: Resource, file_path: Ruby::Pomodoro.tasks_file_path, editor: TTY::Editor)
11
+ @tasks_repo = tasks_repo
12
+ @file_path = file_path
13
+ @editor = editor
14
+ end
15
+
16
+ # Open editor and save tasks from tmp file to task_repo
17
+ # @return [TrueClass]
18
+ def edit
19
+ save
20
+ editor.open(file_path)
21
+ create_tasks
22
+ true
23
+ end
24
+
25
+ # save list to disc
26
+ # @return [TrueClass]
27
+ def save
28
+ File.open(file_path, "w") do |f|
29
+ if @tasks_repo.size > 0
30
+ f.puts(@tasks_repo.all.map {|task| print_task(task) }.join("\n"))
31
+ end
32
+ end
33
+ true
34
+ end
35
+
36
+ # Load tasks form file
37
+ # @return [TrueClass]
38
+ def load
39
+ create_tasks
40
+ true
41
+ end
42
+
43
+ private
44
+
45
+ def read_tmp_file
46
+ File.readlines(file_path)
47
+ end
48
+
49
+ def create_tasks
50
+ tasks_repo.delete_all
51
+ read_tmp_file.each do |line|
52
+ next unless line
53
+
54
+ name, time = line.split("|").compact.map {|l| l.chomp.strip }
55
+ tasks_repo.create(name: name, spent_time: TimeConverter.to_seconds(time))
56
+ end
57
+ end
58
+
59
+ def print_task(task)
60
+ "#{task.name} | #{TimeConverter.to_format_string(task.spent_time)}"
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,27 @@
1
+ module Ruby
2
+ module Pomodoro
3
+ module Tasks
4
+ class Entity
5
+ attr_reader :name, :pomodors, :spent_time, :id
6
+
7
+ def initialize(name:, spent_time: 0, id: nil)
8
+ @name = name
9
+ @spent_time = spent_time
10
+ @pomodors = 0
11
+ @id = id
12
+ end
13
+
14
+ # @return [Integer]
15
+ def add_pomodoro
16
+ @pomodors += 1
17
+ end
18
+
19
+ # @param [Integer] seconds
20
+ # @return [Integer]
21
+ def track(seconds)
22
+ @spent_time += seconds
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,42 @@
1
+ module Ruby
2
+ module Pomodoro
3
+ module Tasks
4
+ class Resource
5
+ class << self
6
+ extend Forwardable
7
+
8
+ delegate size: :store
9
+
10
+ def create(attributes)
11
+ id = store.size.next
12
+ Entity.new(id: id, **attributes.slice(:name, :spent_time)).tap do |task|
13
+ store[id] = task
14
+ end
15
+ end
16
+
17
+ def find(id)
18
+ store[id]
19
+ end
20
+
21
+ def all
22
+ store.values
23
+ end
24
+
25
+ def delete_all
26
+ store.clear
27
+ end
28
+
29
+ def sum(attribute)
30
+ all.inject(0) { |sum, task| sum + task.public_send(attribute).to_i }
31
+ end
32
+
33
+ private
34
+
35
+ def store
36
+ @store ||= {}
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,13 @@
1
+ module Ruby
2
+ module Pomodoro
3
+ module TerminalNotifierChannel
4
+ module_function
5
+
6
+ def call(message)
7
+ TerminalNotifier.notify(
8
+ message, title: "RubyPomodoro", sound: "default"
9
+ )
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,27 @@
1
+ module Ruby
2
+ module Pomodoro
3
+ module TimeConverter
4
+ class << self
5
+ def to_format_string(seconds)
6
+ days = seconds / 60 / 60 / 24
7
+ hours = (seconds - days * 24 * 60 * 60) / 60 / 60
8
+ minutes = (seconds - (hours * 60 * 60) - (days * 24 * 60 * 60)) / 60
9
+ {d: days, h: hours, m: minutes}.select { |_k, v| v.positive? }.each.with_object(String.new) do |item, acc|
10
+ acc << item.reverse.join(":") + " "
11
+ end.strip
12
+ end
13
+
14
+ def to_seconds(format_string)
15
+ return 0 if format_string.nil?
16
+
17
+ match = format_string.split(" ").compact.map do |item|
18
+ if item
19
+ item.split(":").reverse.then { |res| res.size == 2 ? res : nil }
20
+ end
21
+ end.compact.to_h.select { |k| %w[m h d].include?(k) }
22
+ (match["d"].to_i * 24 * 60 * 60) + (match["h"].to_i * 60 * 60) + (match["m"].to_i * 60)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,11 @@
1
+ module Ruby
2
+ module Pomodoro
3
+ module TimeHelpers
4
+ # @param [Integer] seconds
5
+ # @return [String] as N:d N:h N:m
6
+ def to_format_string(seconds)
7
+ Ruby::Pomodoro::TimeConverter.to_format_string(seconds)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,5 @@
1
+ module Ruby
2
+ module Pomodoro
3
+ VERSION = "0.4.0"
4
+ end
5
+ end
@@ -0,0 +1,78 @@
1
+ module Ruby
2
+ module Pomodoro
3
+ # Singleton for work with tasks in application
4
+ class Worker
5
+ include Observable
6
+ include AASM
7
+ include Singleton
8
+
9
+ attr_reader :current_task
10
+ attr_accessor :time_interval, :pomodoro_size, :progressbar
11
+
12
+ aasm do
13
+ state :sleeping, initial: true
14
+ state :working, :paused
15
+
16
+ after_all_transitions :notify
17
+
18
+ event :start, before: :changed do
19
+ transitions from: :sleeping, to: :working, after: ->(task) { run_task(task) }
20
+ end
21
+
22
+ event :resume, before: :changed do
23
+ transitions from: :paused, to: :working
24
+ end
25
+
26
+ event :pause, before: :changed do
27
+ transitions from: :working, to: :paused
28
+ end
29
+
30
+
31
+ event :finish, before: :changed do
32
+ after { @do&.kill }
33
+
34
+ transitions from: :working, to: :sleeping
35
+ end
36
+
37
+ event :stop, before: :changed do
38
+ after do
39
+ @do&.kill
40
+ @current_task = nil
41
+ end
42
+
43
+ transitions to: :sleeping
44
+ end
45
+ end
46
+
47
+ private
48
+
49
+ def notify
50
+ notify_observers(aasm.current_event)
51
+ end
52
+
53
+ # @param [Ruby::Pomodoro::Task] task
54
+ # @return [TrueClass]
55
+ def run_task(task)
56
+ raise Error, "Setup pomodoro_size" if pomodoro_size.nil?
57
+
58
+ @current_task = task
59
+ @do = Thread.new do
60
+ progress = progressbar || Ruby::Pomodoro::Progressbar.new(seconds: pomodoro_size)
61
+ progress.start(task.name)
62
+ count = 0
63
+ loop do
64
+ seconds = time_interval || 1
65
+ sleep seconds
66
+ unless paused?
67
+ task.track(seconds)
68
+ count += seconds
69
+ progress.increment
70
+ finish if count >= pomodoro_size.to_i && working?
71
+ end
72
+ end
73
+ end
74
+ true
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,86 @@
1
+ require 'terminal-notifier'
2
+ require 'observer'
3
+ require 'tty-cursor'
4
+ require 'tty-editor'
5
+ require 'tty-reader'
6
+ require 'logger'
7
+ require 'aasm'
8
+ require "erb"
9
+ require "ruby/pomodoro/command_controller"
10
+ require "ruby/pomodoro/time_helpers"
11
+ require "ruby/pomodoro/notification_observer"
12
+ require "ruby/pomodoro/printer"
13
+ require "ruby/pomodoro/cmd/base"
14
+ require "ruby/pomodoro/cmd/main"
15
+ require "ruby/pomodoro/cmd/quit"
16
+ require "ruby/pomodoro/cmd/choose_task"
17
+ require "ruby/pomodoro/cmd/edit_list"
18
+ require "ruby/pomodoro/cmd/pause"
19
+ require "ruby/pomodoro/cmd/stop"
20
+ require "ruby/pomodoro/cmd/error_handler"
21
+ require "ruby/pomodoro/time_converter"
22
+ require "ruby/pomodoro/version"
23
+ require "ruby/pomodoro/tasks/resource"
24
+ require "ruby/pomodoro/tasks/editor"
25
+ require "ruby/pomodoro/task"
26
+ require "ruby/pomodoro/tasks/entity"
27
+ require "ruby/pomodoro/worker"
28
+ require "ruby/pomodoro/progressbar"
29
+ require "ruby/pomodoro/notification"
30
+ require "ruby/pomodoro/terminal_notifier_channel"
31
+
32
+ module Ruby
33
+ module Pomodoro
34
+ class Error < StandardError; end
35
+
36
+ REPEAT_ALERT_TIME = 60 * 5
37
+ POMODORO_SIZE = 60 * 30
38
+
39
+ class << self
40
+ attr_reader :editor, :tasks_file_path
41
+
42
+ def start
43
+ Worker.instance.then do |worker|
44
+ add_observer(worker)
45
+ worker.pomodoro_size = POMODORO_SIZE
46
+ end
47
+
48
+ @tasks_file_path = File.join(init_app_folder, "tasks")
49
+
50
+ @editor = Tasks::Editor.new(file_path: tasks_file_path)
51
+ editor.load
52
+ Cmd::Main.new.call
53
+ reader = TTY::Reader.new
54
+ reader.subscribe(CommandController)
55
+ loop do
56
+ reader.read_char
57
+ rescue => e
58
+ Cmd::ErrorHandler.new.call(e)
59
+ end
60
+ end
61
+
62
+ private
63
+
64
+ def add_observer(worker)
65
+ notify_ch = TerminalNotifierChannel
66
+ pause_notification = Notification.new("Task is paused, resume?", notify_ch)
67
+ stop_notification =
68
+ Notification.new("Work is stopped, choose task for resume", notify_ch)
69
+
70
+ worker.add_observer(
71
+ NotificationObserver.new(
72
+ stop: stop_notification, pause: pause_notification, time: REPEAT_ALERT_TIME
73
+ )
74
+ )
75
+ end
76
+
77
+ def init_app_folder
78
+ path = File.join(Dir.home, ".ruby-pomodoro")
79
+ unless Dir.exists?(path)
80
+ Dir.mkdir(path)
81
+ end
82
+ path
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,13 @@
1
+ Total time: <%= to_format_string(Ruby::Pomodoro::Tasks::Resource.sum(:spent_time)) %>
2
+ List of tasks:
3
+ <% if @tasks %>
4
+ <% @tasks.each do |task| %>
5
+ <%= task.id %>. <%= task.name %> <%= task.spent_time.zero? ? nil : to_format_string(task.spent_time) %>
6
+ <% end %>
7
+ <% end %>
8
+
9
+ [c] - Choose task to work
10
+ [e] - Edit list of tasks
11
+ [p] - Pause work
12
+ [s] - Stop work
13
+ [q] - Quit
@@ -0,0 +1,31 @@
1
+ require_relative 'lib/ruby/pomodoro/version'
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = "ruby-pomodoro"
5
+ spec.version = Ruby::Pomodoro::VERSION
6
+ spec.authors = ["Alexander Shvaykin"]
7
+ spec.email = ["skiline.alex@gmail.com"]
8
+
9
+ spec.summary = %q{CLI pomodoro-tracker}
10
+ spec.homepage = "https://github.com/AlexanderShvaykin"
11
+ spec.license = "MIT"
12
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
13
+
14
+ spec.metadata["homepage_uri"] = spec.homepage
15
+ spec.metadata["source_code_uri"] = "https://github.com/AlexanderShvaykin"
16
+ spec.metadata["changelog_uri"] = "https://github.com/AlexanderShvaykin"
17
+
18
+ # Specify which files should be added to the gem when it is released.
19
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
20
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
21
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
22
+ end
23
+ spec.bindir = "exe"
24
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
25
+ spec.require_paths = ["lib"]
26
+ spec.add_runtime_dependency 'terminal-notifier'
27
+ spec.add_runtime_dependency 'tty-cursor', '~> 0.6'
28
+ spec.add_runtime_dependency 'tty-editor', '~> 0.7'
29
+ spec.add_runtime_dependency 'pastel', '~> 0.8'
30
+ spec.add_runtime_dependency 'aasm', '~> 5'
31
+ end