redpomo 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/.rspec +1 -0
- data/Gemfile +8 -0
- data/LICENSE +22 -0
- data/README.md +29 -0
- data/Rakefile +2 -0
- data/bin/redpomo +5 -0
- data/lib/redpomo/cli.rb +72 -0
- data/lib/redpomo/config.rb +50 -0
- data/lib/redpomo/entries_printer.rb +33 -0
- data/lib/redpomo/entry.rb +49 -0
- data/lib/redpomo/file_cache.rb +40 -0
- data/lib/redpomo/fuzzy_converter.rb +68 -0
- data/lib/redpomo/issue.rb +24 -0
- data/lib/redpomo/null_cache.rb +9 -0
- data/lib/redpomo/numeric_ext.rb +29 -0
- data/lib/redpomo/puller.rb +35 -0
- data/lib/redpomo/pusher.rb +59 -0
- data/lib/redpomo/task.rb +88 -0
- data/lib/redpomo/task_list.rb +50 -0
- data/lib/redpomo/tracker.rb +108 -0
- data/lib/redpomo/version.rb +3 -0
- data/lib/redpomo.rb +15 -0
- data/redpomo.gemspec +31 -0
- data/spec/file_cache_spec.rb +17 -0
- data/spec/fixtures/cassettes/cli_close.yml +51 -0
- data/spec/fixtures/cassettes/cli_pull.yml +1222 -0
- data/spec/fixtures/cassettes/cli_push.yml +109 -0
- data/spec/fixtures/cassettes/close_issue.yml +50 -0
- data/spec/fixtures/cassettes/issues.yml +449 -0
- data/spec/fixtures/close_results.txt +2 -0
- data/spec/fixtures/printer_output.txt +16 -0
- data/spec/fixtures/pull_results.txt +20 -0
- data/spec/fixtures/timelog.csv +6 -0
- data/spec/lib/redpomo/cli_spec.rb +101 -0
- data/spec/lib/redpomo/fuzzy_converter_spec.rb +65 -0
- data/spec/lib/redpomo/task_spec.rb +39 -0
- data/spec/lib/redpomo/tracker_spec.rb +38 -0
- data/spec/spec_helper.rb +23 -0
- data/spec/support/capture.rb +13 -0
- metadata +235 -0
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--colour --format d
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Stefano Verna
|
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.
|
data/README.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# Redpomo
|
2
|
+
|
3
|
+
TODO: Write a gem description
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'redpomo'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install redpomo
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
TODO: Write usage instructions here
|
22
|
+
|
23
|
+
## Contributing
|
24
|
+
|
25
|
+
1. Fork it
|
26
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
27
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
28
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
29
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/bin/redpomo
ADDED
data/lib/redpomo/cli.rb
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'thor'
|
2
|
+
|
3
|
+
require 'redpomo/config'
|
4
|
+
require 'redpomo/task_list'
|
5
|
+
require 'redpomo/entry'
|
6
|
+
require 'redpomo/entries_printer'
|
7
|
+
require 'redpomo/fuzzy_converter'
|
8
|
+
|
9
|
+
module Redpomo
|
10
|
+
class CLI < ::Thor
|
11
|
+
|
12
|
+
desc "pull", "imports Redmine open issues into local todo.txt"
|
13
|
+
method_option :config, aliases: "-c", default: '~/.redpomo'
|
14
|
+
def pull
|
15
|
+
configure!
|
16
|
+
TaskList.pull_from_trackers!
|
17
|
+
end
|
18
|
+
|
19
|
+
desc "push LOGFILE", "parses Pomodoro export file and imports to Redmine clients"
|
20
|
+
method_option :config, aliases: "-c", default: '~/.redpomo'
|
21
|
+
method_option :fuzzy, aliases: "-f", type: :boolean
|
22
|
+
method_option :dry_run, aliases: "-n", type: :boolean
|
23
|
+
def push(path)
|
24
|
+
configure!
|
25
|
+
|
26
|
+
entries = Entry.load_from_csv(path)
|
27
|
+
entries = FuzzyConverter.convert(entries) if @options[:fuzzy]
|
28
|
+
|
29
|
+
unless @options[:dry_run]
|
30
|
+
entries.each(&:push!)
|
31
|
+
end
|
32
|
+
|
33
|
+
EntriesPrinter.print(entries)
|
34
|
+
end
|
35
|
+
|
36
|
+
desc "open TASK", "opens up the Redmine issue page of the selected task"
|
37
|
+
method_option :config, aliases: "-c", default: '~/.redpomo'
|
38
|
+
def open(task_number)
|
39
|
+
configure!
|
40
|
+
|
41
|
+
task = TaskList.find(task_number)
|
42
|
+
task.open_in_browser!
|
43
|
+
end
|
44
|
+
|
45
|
+
desc "start TASK", "starts a Pomodoro session for the selected task"
|
46
|
+
method_option :config, aliases: "-c", default: '~/.redpomo'
|
47
|
+
def start(task_number)
|
48
|
+
configure!
|
49
|
+
|
50
|
+
task = TaskList.find(task_number)
|
51
|
+
task.start_pomodoro!
|
52
|
+
end
|
53
|
+
|
54
|
+
desc "close TASK", "marks a todo.txt task as complete, and closes the related Redmine issue"
|
55
|
+
method_option :config, aliases: "-c", default: '~/.redpomo'
|
56
|
+
method_option :message, aliases: "-m"
|
57
|
+
def close(task_number)
|
58
|
+
configure!
|
59
|
+
|
60
|
+
task = TaskList.find(task_number)
|
61
|
+
task.done!
|
62
|
+
task.close_issue!
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def configure!
|
68
|
+
Config.load_from_yaml(@options[:config])
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'active_support/core_ext/module/attribute_accessors'
|
2
|
+
require 'active_support/core_ext/hash'
|
3
|
+
require 'yaml'
|
4
|
+
|
5
|
+
require 'redpomo/file_cache'
|
6
|
+
require 'redpomo/null_cache'
|
7
|
+
|
8
|
+
module Redpomo
|
9
|
+
module Config
|
10
|
+
|
11
|
+
mattr_reader :todo_path, :trackers_data, :cache
|
12
|
+
|
13
|
+
@@cache = NullCache
|
14
|
+
|
15
|
+
def self.load_from_yaml(path)
|
16
|
+
data = YAML::load_file(File.expand_path(path))
|
17
|
+
|
18
|
+
@@todo_path = File.expand_path(data["todo"])
|
19
|
+
@@trackers_data = data["trackers"].symbolize_keys!
|
20
|
+
@@cache = data["cache"] ? FileCache : NullCache
|
21
|
+
end
|
22
|
+
|
23
|
+
# def todo_path
|
24
|
+
# File.expand_path(@data["todo"])
|
25
|
+
# end
|
26
|
+
|
27
|
+
# def tasks
|
28
|
+
# @tasks ||= Todo::List.new(todo_path)
|
29
|
+
# end
|
30
|
+
|
31
|
+
# def find_task(number)
|
32
|
+
# tasks[number.to_i - 1]
|
33
|
+
# end
|
34
|
+
|
35
|
+
# def trackers
|
36
|
+
# @trackers ||= @data["trackers"].map do |key, data|
|
37
|
+
# Tracker.new(data.merge(name: key))
|
38
|
+
# end
|
39
|
+
# end
|
40
|
+
|
41
|
+
# def issues
|
42
|
+
# [].tap do |issues|
|
43
|
+
# trackers.each do |tracker|
|
44
|
+
# issues << tracker.issues
|
45
|
+
# end
|
46
|
+
# end.flatten
|
47
|
+
# end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'redpomo/numeric_ext'
|
2
|
+
|
3
|
+
module Redpomo
|
4
|
+
class EntriesPrinter
|
5
|
+
|
6
|
+
def self.print(entries)
|
7
|
+
require 'terminal-table'
|
8
|
+
entries.group_by(&:date).each do |date, entries|
|
9
|
+
duration = 0
|
10
|
+
rows = entries.map do |entry|
|
11
|
+
task = entry.to_task
|
12
|
+
duration += entry.duration
|
13
|
+
[
|
14
|
+
task.context,
|
15
|
+
task.project,
|
16
|
+
task.issue,
|
17
|
+
task.text,
|
18
|
+
entry.duration.seconds_in_words,
|
19
|
+
I18n.l(entry.time, format: "%H:%M"),
|
20
|
+
I18n.l(entry.end_time, :format => "%H:%M")
|
21
|
+
]
|
22
|
+
end
|
23
|
+
puts Terminal::Table.new(
|
24
|
+
title: "#{ I18n.l(date, format: "%A %x") } - #{ duration.seconds_in_words }",
|
25
|
+
headings: [ "Context", "Project", "Issue #", "Description", "Duration", "From", "To" ],
|
26
|
+
rows: rows
|
27
|
+
)
|
28
|
+
puts
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'csv'
|
2
|
+
|
3
|
+
module Redpomo
|
4
|
+
class Entry
|
5
|
+
|
6
|
+
def self.load_from_csv(path)
|
7
|
+
CSV.parse(File.read(path).split("\n")[4..-1].join("\n")).map do |data|
|
8
|
+
Entry.new(data[0], DateTime.parse(data[1]), data[2].to_i * 60.0)
|
9
|
+
end.sort_by { |entry| entry.datetime }
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_reader :text, :datetime, :duration
|
13
|
+
|
14
|
+
def initialize(text, datetime, duration)
|
15
|
+
@text = text
|
16
|
+
@datetime = datetime
|
17
|
+
@duration = duration
|
18
|
+
end
|
19
|
+
|
20
|
+
def date
|
21
|
+
datetime.to_date
|
22
|
+
end
|
23
|
+
|
24
|
+
def time
|
25
|
+
datetime.to_time
|
26
|
+
end
|
27
|
+
|
28
|
+
def end_time
|
29
|
+
time + duration
|
30
|
+
end
|
31
|
+
|
32
|
+
def same_date?(entry)
|
33
|
+
date == entry.date
|
34
|
+
end
|
35
|
+
|
36
|
+
def same_text?(entry)
|
37
|
+
text == entry.text
|
38
|
+
end
|
39
|
+
|
40
|
+
def to_task
|
41
|
+
Task.new(nil, text)
|
42
|
+
end
|
43
|
+
|
44
|
+
def push!
|
45
|
+
to_task.tracker.push_entry!(self)
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
require 'redpomo/config'
|
3
|
+
|
4
|
+
module Redpomo
|
5
|
+
class FileCache
|
6
|
+
|
7
|
+
include Singleton
|
8
|
+
attr_accessor :cache_path
|
9
|
+
|
10
|
+
def self.get(key, &block)
|
11
|
+
instance.get(key, &block)
|
12
|
+
end
|
13
|
+
|
14
|
+
def get(key, &block)
|
15
|
+
return existing_keys[key] if existing_keys.has_key?(key)
|
16
|
+
if block_given?
|
17
|
+
value = block.call
|
18
|
+
set(key, value)
|
19
|
+
value
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def inizialize
|
26
|
+
@cache_path = File.expand_path("~/.redpomo-cache~")
|
27
|
+
end
|
28
|
+
|
29
|
+
def existing_keys
|
30
|
+
File.exists?(cache_path) ? (YAML::load_file(cache_path) || {}) : {}
|
31
|
+
end
|
32
|
+
|
33
|
+
def set(key, val)
|
34
|
+
dict = existing_keys
|
35
|
+
dict[key] = val
|
36
|
+
File.open(cache_path, 'w') { |f| f.write(dict.to_yaml) }
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'active_support/core_ext/numeric/time'
|
2
|
+
require 'redpomo/entry'
|
3
|
+
|
4
|
+
module Redpomo
|
5
|
+
class FuzzyConverter
|
6
|
+
|
7
|
+
def self.convert(entries)
|
8
|
+
past_entry = nil
|
9
|
+
consecutive_entries = []
|
10
|
+
|
11
|
+
entries << nil
|
12
|
+
|
13
|
+
result = []
|
14
|
+
|
15
|
+
entries.each do |entry|
|
16
|
+
if past_entry.present?
|
17
|
+
if entry.present? && entry.same_date?(past_entry) && entry.same_text?(past_entry)
|
18
|
+
consecutive_entries << entry
|
19
|
+
else
|
20
|
+
result << self.fuzzy_entry(consecutive_entries, entry)
|
21
|
+
past_entry = entry
|
22
|
+
consecutive_entries = [ entry ]
|
23
|
+
end
|
24
|
+
else
|
25
|
+
past_entry = entry
|
26
|
+
consecutive_entries = [ entry ]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
result
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.fuzzy_entry(entries, next_entry)
|
34
|
+
first_entry = entries.first
|
35
|
+
last_entry = entries.last
|
36
|
+
total_duration = first_entry.time - last_entry.end_time
|
37
|
+
duration_till_next_entry = next_entry.time - last_entry.end_time if next_entry.present?
|
38
|
+
|
39
|
+
entry = if next_entry.nil? || duration_till_next_entry > 1.5 * 3600.0
|
40
|
+
duration = last_entry.end_time - first_entry.time
|
41
|
+
duration += 1.5 * 3600.0 if next_entry.present? && next_entry.same_date?(last_entry)
|
42
|
+
Entry.new(first_entry.text, first_entry.datetime, duration)
|
43
|
+
else
|
44
|
+
duration = next_entry.time - first_entry.time
|
45
|
+
entry = Entry.new(first_entry.text, first_entry.datetime, duration)
|
46
|
+
end
|
47
|
+
entry
|
48
|
+
end
|
49
|
+
|
50
|
+
# def strip!
|
51
|
+
# end_working_day = Time.new(time.year, time.month, time.day, 18, 30, 0, "+00:00")
|
52
|
+
# start_launch_time = Time.new(time.year, time.month, time.day, 13, 15, 0, "+00:00")
|
53
|
+
# end_launch_time = Time.new(time.year, time.month, time.day, 14, 15, 0, "+00:00")
|
54
|
+
# late_night_day = Time.new(time.year, time.month, time.day, 23, 59, 0, "+00:00")
|
55
|
+
|
56
|
+
# if time < end_working_day && end_time > end_working_day
|
57
|
+
# @duration = end_working_day - time
|
58
|
+
# elsif time < start_launch_time && end_time > end_launch_time
|
59
|
+
# @duration -= 3600.0
|
60
|
+
# elsif time < start_launch_time && end_time > start_launch_time
|
61
|
+
# @duration = start_launch_time - time
|
62
|
+
# elsif time > end_working_day && end_time > late_night_day
|
63
|
+
# @duration = late_night_day - time
|
64
|
+
# end
|
65
|
+
# end
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Redpomo
|
2
|
+
class Issue
|
3
|
+
|
4
|
+
attr_reader :title, :issue_id, :project, :tracker, :due_date
|
5
|
+
|
6
|
+
def initialize(tracker, data)
|
7
|
+
@title = data["subject"]
|
8
|
+
@issue_id = data["id"]
|
9
|
+
@project = data["project"]
|
10
|
+
@due_date = Date.parse(data["due_date"]) if data["due_date"].present?
|
11
|
+
@tracker = tracker
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_task
|
15
|
+
label = [ title ]
|
16
|
+
label << @due_date.strftime("%Y-%m-%d") if @due_date.present?
|
17
|
+
label << "##{issue_id}"
|
18
|
+
label << "+#{project}"
|
19
|
+
label << "@#{tracker.name}"
|
20
|
+
Todo::Task.new(label.join(" "))
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
class Numeric
|
2
|
+
module Units
|
3
|
+
Sec = 1
|
4
|
+
Min = Sec * 60
|
5
|
+
Hour = Min * 60
|
6
|
+
Day = Hour * 24
|
7
|
+
Week = Day * 7
|
8
|
+
Month = Week * 4
|
9
|
+
Year = Day * 365
|
10
|
+
Decade = Year * 10
|
11
|
+
Century = Decade * 10
|
12
|
+
Millennium = Century * 10
|
13
|
+
Eon = 1.0/0
|
14
|
+
end
|
15
|
+
|
16
|
+
def seconds_in_words
|
17
|
+
unit = get_unit(self)
|
18
|
+
unit_difference = self / Units.const_get(unit.capitalize)
|
19
|
+
unit = unit.to_s.downcase + ('s' if self > 1)
|
20
|
+
"#{unit_difference.to_i} #{unit}"
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
def get_unit(time_difference)
|
25
|
+
Units.constants.each_cons(2) do |con|
|
26
|
+
return con.first if (Units.const_get(con[0])...Units.const_get(con[1])) === time_difference
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'redpomo/config'
|
2
|
+
|
3
|
+
module Redpomo
|
4
|
+
class Puller
|
5
|
+
|
6
|
+
def initialize(options = {})
|
7
|
+
@options = options
|
8
|
+
end
|
9
|
+
|
10
|
+
def execute
|
11
|
+
list = Todo::List.new(
|
12
|
+
config.issues.map(&:to_task) +
|
13
|
+
unrelated_tasks
|
14
|
+
)
|
15
|
+
list.write_to(config.todo_path)
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def config
|
21
|
+
@config ||= Redpomo::Config.new(@options[:config])
|
22
|
+
end
|
23
|
+
|
24
|
+
def trackers_contexts
|
25
|
+
@trackers_context ||= config.trackers.map(&:context)
|
26
|
+
end
|
27
|
+
|
28
|
+
def unrelated_tasks
|
29
|
+
config.tasks.select do |task|
|
30
|
+
! task.include_contexts?(trackers_contexts)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'terminal-table'
|
2
|
+
|
3
|
+
require 'redpomo/entry'
|
4
|
+
require 'redpomo/config'
|
5
|
+
|
6
|
+
module Redpomo
|
7
|
+
class Pusher
|
8
|
+
|
9
|
+
def initialize(log_path, options = {})
|
10
|
+
@options = options
|
11
|
+
@log_path = File.expand_path(log_path)
|
12
|
+
end
|
13
|
+
|
14
|
+
def execute
|
15
|
+
entries_to_push = @options[:fuzzy] ? fuzzy_entries : entries
|
16
|
+
|
17
|
+
unless @options[:dry_run]
|
18
|
+
entries_to_push.each do |entry|
|
19
|
+
tracker = config.tracker_for_task(entry.to_task)
|
20
|
+
tracker.push_entry(entry)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
entries_to_push.group_by(&:date).each do |date, entries|
|
25
|
+
duration = 0
|
26
|
+
rows = entries.map do |entry|
|
27
|
+
task = entry.to_task
|
28
|
+
duration += entry.duration
|
29
|
+
[ task.contexts.first, task.projects.first, task.issues.first, task.text, entry.duration.seconds_in_words, I18n.l(entry.time, format: "%H:%M"), I18n.l(entry.end_time, :format => "%H:%M") ]
|
30
|
+
end
|
31
|
+
puts Terminal::Table.new(
|
32
|
+
title: "#{ I18n.l(date, format: "%A %x") } - #{ duration.seconds_in_words }",
|
33
|
+
headings: [ "Context", "Project", "Issue #", "Description", "Duration", "From", "To" ],
|
34
|
+
rows: rows
|
35
|
+
)
|
36
|
+
puts
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def config
|
44
|
+
@config ||= Redpomo::Config.new(@options[:config])
|
45
|
+
end
|
46
|
+
|
47
|
+
def entries
|
48
|
+
@entries ||= raw_log.map do |line_data|
|
49
|
+
Entry.from_csv(line_data)
|
50
|
+
end.sort_by { |entry| entry.datetime }
|
51
|
+
end
|
52
|
+
|
53
|
+
def raw_log
|
54
|
+
CSV.parse File.read(@log_path).split("\n")[4..-1].join("\n")
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|
data/lib/redpomo/task.rb
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'active_support/core_ext/module/delegation'
|
2
|
+
require 'todo-txt/task'
|
3
|
+
require 'redpomo/tracker'
|
4
|
+
|
5
|
+
module Todo
|
6
|
+
class Task
|
7
|
+
|
8
|
+
def self.projects_regex
|
9
|
+
/(?:\s+|^)\+[\w\-]+/
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
module Redpomo
|
16
|
+
class Task
|
17
|
+
|
18
|
+
delegate :orig, to: :@task
|
19
|
+
|
20
|
+
ISSUES_REGEXP = /(?:\s+|^)#[0-9]+/
|
21
|
+
|
22
|
+
def initialize(list, text)
|
23
|
+
@task = Todo::Task.new(text)
|
24
|
+
@list = list
|
25
|
+
end
|
26
|
+
|
27
|
+
def context
|
28
|
+
@task.contexts.map do |context|
|
29
|
+
context.gsub /^@/, ''
|
30
|
+
end.first
|
31
|
+
end
|
32
|
+
|
33
|
+
def project
|
34
|
+
@task.projects.map do |context|
|
35
|
+
context.gsub /^\+/, ''
|
36
|
+
end.first
|
37
|
+
end
|
38
|
+
|
39
|
+
def issue
|
40
|
+
orig.scan(ISSUES_REGEXP).map(&:strip).map do |issue|
|
41
|
+
issue.gsub(/^#/, '').to_i
|
42
|
+
end.first
|
43
|
+
end
|
44
|
+
|
45
|
+
def text
|
46
|
+
@task.text.gsub(ISSUES_REGEXP, '').strip
|
47
|
+
end
|
48
|
+
|
49
|
+
def close_issue!(message = nil)
|
50
|
+
tracker.close_issue!(issue, message)
|
51
|
+
end
|
52
|
+
|
53
|
+
def done!
|
54
|
+
@list.remove!(self)
|
55
|
+
end
|
56
|
+
|
57
|
+
def open_in_browser!
|
58
|
+
require 'launchy'
|
59
|
+
Launchy.open(url)
|
60
|
+
end
|
61
|
+
|
62
|
+
def start_pomodoro!
|
63
|
+
require 'applescript'
|
64
|
+
command = 'tell application "Pomodoro" to start "'
|
65
|
+
command << orig
|
66
|
+
command << '"'
|
67
|
+
AppleScript.execute(command)
|
68
|
+
end
|
69
|
+
|
70
|
+
def url
|
71
|
+
return nil unless tracker.present?
|
72
|
+
if issue.present?
|
73
|
+
"#{tracker.base_url}/issues/#{issue}"
|
74
|
+
elsif project.present?
|
75
|
+
"#{tracker.base_url}/projects/#{project}"
|
76
|
+
else
|
77
|
+
"#{tracker.base_url}/projects/#{tracker.default_project}"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def tracker
|
82
|
+
Tracker.find(context)
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
|