redpomo-reloaded 0.0.13
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.
- checksums.yaml +7 -0
- data/Dockerfile +5 -0
- data/Gemfile +9 -0
- data/Gemfile.lock +139 -0
- data/Guardfile +23 -0
- data/LICENSE +22 -0
- data/README.md +176 -0
- data/Rakefile +6 -0
- data/bin/redpomo +7 -0
- data/docker-compose.yml +7 -0
- data/lib/redpomo.rb +10 -0
- data/lib/redpomo/cli.rb +172 -0
- data/lib/redpomo/config.rb +26 -0
- data/lib/redpomo/entries_printer.rb +33 -0
- data/lib/redpomo/entry.rb +65 -0
- data/lib/redpomo/file_cache.rb +44 -0
- data/lib/redpomo/fuzzy_converter.rb +68 -0
- data/lib/redpomo/issue.rb +38 -0
- data/lib/redpomo/null_cache.rb +9 -0
- data/lib/redpomo/numeric_ext.rb +30 -0
- data/lib/redpomo/task.rb +103 -0
- data/lib/redpomo/task_list.rb +61 -0
- data/lib/redpomo/templates/config.yml +47 -0
- data/lib/redpomo/templates/issue_stub.textile +7 -0
- data/lib/redpomo/tracker.rb +175 -0
- data/lib/redpomo/ui.rb +73 -0
- data/lib/redpomo/version.rb +3 -0
- data/redpomo.gemspec +33 -0
- data/spec/file_cache_spec.rb +22 -0
- data/spec/fixtures/add_results.txt +4 -0
- data/spec/fixtures/cassettes/cli_add.yml +102 -0
- data/spec/fixtures/cassettes/cli_close.yml +50 -0
- data/spec/fixtures/cassettes/cli_pull.yml +1222 -0
- data/spec/fixtures/cassettes/cli_push.yml +297 -0
- data/spec/fixtures/cassettes/close_issue.yml +50 -0
- data/spec/fixtures/cassettes/create_issue.yml +102 -0
- data/spec/fixtures/cassettes/issues.yml +449 -0
- data/spec/fixtures/cassettes/push_entry.yml +55 -0
- data/spec/fixtures/close_results.txt +2 -0
- data/spec/fixtures/config.yml +16 -0
- data/spec/fixtures/printer_output.txt +16 -0
- data/spec/fixtures/proper_timelog.csv +4 -0
- data/spec/fixtures/pull_results.txt +20 -0
- data/spec/fixtures/tasks.txt +3 -0
- data/spec/fixtures/timelog.csv +6 -0
- data/spec/integration/add_spec.rb +29 -0
- data/spec/integration/init_spec.rb +33 -0
- data/spec/lib/redpomo/cli_spec.rb +91 -0
- data/spec/lib/redpomo/entry_spec.rb +23 -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 +72 -0
- data/spec/spec_helper.rb +28 -0
- data/spec/support/cli_helpers.rb +76 -0
- data/spec/support/fixtures.rb +24 -0
- data/spec/support/ruby_ext.rb +20 -0
- data/spec/tmp/REDME.md +0 -0
- metadata +296 -0
@@ -0,0 +1,47 @@
|
|
1
|
+
---
|
2
|
+
todo: "~/Documents/todo/todo.txt" # Path of your local Todo.txt
|
3
|
+
|
4
|
+
trackers:
|
5
|
+
|
6
|
+
my_tracker: # This is the name of the context you want
|
7
|
+
# to use for this tracker (--> @my_tracker)
|
8
|
+
|
9
|
+
url: "http://my_tracker.com" # The URL of your Redmine instance
|
10
|
+
|
11
|
+
token: "" # Find this at the following URL:
|
12
|
+
# http://mytracker.com/my/account
|
13
|
+
|
14
|
+
default_activity_id: 9 # Choose a timetrack activity ID from one
|
15
|
+
# of those specified at:
|
16
|
+
# http://mytracker.com/enumerations
|
17
|
+
#
|
18
|
+
# -- 9 is "Development" on clean Redmine
|
19
|
+
|
20
|
+
default_project_id: "project" # If timetracks you're pushing don't have
|
21
|
+
# a +project specified, use this.
|
22
|
+
|
23
|
+
default_priority_id: 4 # If issues you're adding don't have a
|
24
|
+
# priority specified, then use this.
|
25
|
+
#
|
26
|
+
# -- 4 is "Normal" on clean Redmine
|
27
|
+
|
28
|
+
closed_status_id: 5 # Identifier of the status used to mark
|
29
|
+
# issues as closed. Find this ID here:
|
30
|
+
# http://mytracker.com/issue_statuses
|
31
|
+
#
|
32
|
+
# -- 5 is "Closed" on clean Redmine
|
33
|
+
|
34
|
+
priority_ids: # Map a Todo.txt priority with a Redmine
|
35
|
+
# priority ID.
|
36
|
+
|
37
|
+
- 7 # -> (A)
|
38
|
+
- 6 # -> (B)
|
39
|
+
- 5 # -> (C)
|
40
|
+
# ... and so on
|
41
|
+
#
|
42
|
+
# Find those IDs at the following URL:
|
43
|
+
# http://mytracker.com/enumerations
|
44
|
+
#
|
45
|
+
# -- 7, 6 and 5 are "Immediate", "Urgent"
|
46
|
+
# and "High" on clean Redmine
|
47
|
+
|
@@ -0,0 +1,7 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
# Please enter the description of your new task on the first line.
|
4
|
+
# Following lines will be used as issue descripton (textile allowed).
|
5
|
+
#
|
6
|
+
# Make sure to add a project and context. Lines starting with '#'
|
7
|
+
# will be ignored, and an empty message aborts the creation.
|
@@ -0,0 +1,175 @@
|
|
1
|
+
require 'active_support/core_ext/hash'
|
2
|
+
require 'redpomo/issue'
|
3
|
+
require 'redpomo/config'
|
4
|
+
|
5
|
+
require 'rest_client'
|
6
|
+
require 'json'
|
7
|
+
|
8
|
+
module Redpomo
|
9
|
+
class Tracker
|
10
|
+
|
11
|
+
def self.find(name)
|
12
|
+
if data = Config.trackers_data[name.to_sym]
|
13
|
+
Tracker.new(name.to_sym, data)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.all
|
18
|
+
Config.trackers_data.map do |name, data|
|
19
|
+
Tracker.new(name.to_sym, data)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
attr_reader :name, :base_url
|
24
|
+
|
25
|
+
def initialize(name, options)
|
26
|
+
options.symbolize_keys!
|
27
|
+
@name = name
|
28
|
+
@base_url = options[:url]
|
29
|
+
@api_key = options[:token]
|
30
|
+
@default_project_id = options[:default_project_id]
|
31
|
+
@default_priority_id = options[:default_priority_id]
|
32
|
+
@closed_status_id = options[:closed_status_id].to_i
|
33
|
+
@default_activity_id = options[:default_activity_id]
|
34
|
+
@priorities = options[:priority_ids]
|
35
|
+
end
|
36
|
+
|
37
|
+
def todo_priority(priority_name)
|
38
|
+
return nil if @priorities.nil?
|
39
|
+
alphabet = ('A' .. 'Z').to_a.join
|
40
|
+
if index = @priorities.index(priority_name)
|
41
|
+
"(#{alphabet[index]})"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def issue_priority_id(priority_val)
|
46
|
+
if index = ('A' .. 'Z').to_a.index(priority_val)
|
47
|
+
@priorities[index]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def issues
|
52
|
+
data = get("/issues", assigned_to_id: current_user_id, status_id: "open", limit: 100)
|
53
|
+
data["issues"].map do |issue|
|
54
|
+
issue["project_id"] = project_identifier_for(issue["project"]["id"])
|
55
|
+
Issue.new(self, issue)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def create_issue!(issue)
|
60
|
+
data = {
|
61
|
+
subject: issue.subject,
|
62
|
+
description: issue.description,
|
63
|
+
due_date: issue.due_date,
|
64
|
+
project_id: issue.project_id || @default_project_id,
|
65
|
+
priority_id: issue.priority_id || @default_priority_id,
|
66
|
+
assigned_to_id: current_user_id
|
67
|
+
}
|
68
|
+
|
69
|
+
post("/issues", issue: data)
|
70
|
+
end
|
71
|
+
|
72
|
+
def pushable_entry?(entry)
|
73
|
+
data = entry_data(entry)
|
74
|
+
if data[:project_id]
|
75
|
+
project_exists?(data[:project_id])
|
76
|
+
else
|
77
|
+
issue_exists?(data[:issue_id])
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def push_entry!(entry)
|
82
|
+
post("/time_entries", time_entry: entry_data(entry))
|
83
|
+
end
|
84
|
+
|
85
|
+
def close_issue!(id, message = nil)
|
86
|
+
issue = { status_id: @closed_status_id }
|
87
|
+
issue[:notes] = message if message.present?
|
88
|
+
put("/issues/#{id}", issue: issue)
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
def project_exists?(project_id)
|
94
|
+
project_identifier_for(project_id).present?
|
95
|
+
end
|
96
|
+
|
97
|
+
def issue_exists?(issue_id)
|
98
|
+
issue_identifier_for(issue_id).present?
|
99
|
+
end
|
100
|
+
|
101
|
+
def entry_data(entry)
|
102
|
+
task = entry.to_task
|
103
|
+
time_entry = {}
|
104
|
+
|
105
|
+
if issue = task.issue
|
106
|
+
time_entry[:issue_id] = issue
|
107
|
+
elsif project = task.project
|
108
|
+
time_entry[:project_id] = project
|
109
|
+
else
|
110
|
+
time_entry[:project_id] = @default_project_id
|
111
|
+
end
|
112
|
+
|
113
|
+
time_entry[:spent_on] = entry.datetime
|
114
|
+
time_entry[:hours] = entry.duration / 3600.0
|
115
|
+
time_entry[:comments] = task.text
|
116
|
+
|
117
|
+
if @default_activity_id.present?
|
118
|
+
time_entry[:activity_id] = @default_activity_id.to_i
|
119
|
+
end
|
120
|
+
|
121
|
+
time_entry
|
122
|
+
end
|
123
|
+
|
124
|
+
def issue_identifier_for(issue_id)
|
125
|
+
Config.cache.get("#{@name}:issue:#{issue_id}:identifier") do
|
126
|
+
data = get("/issues/#{issue_id}")
|
127
|
+
data["issue"]["id"]
|
128
|
+
end
|
129
|
+
rescue RestClient::ResourceNotFound => e
|
130
|
+
nil
|
131
|
+
end
|
132
|
+
|
133
|
+
def project_identifier_for(project_id)
|
134
|
+
Config.cache.get("#{@name}:project:#{project_id}:identifier") do
|
135
|
+
data = get("/projects/#{project_id}")
|
136
|
+
data["project"]["identifier"]
|
137
|
+
end
|
138
|
+
rescue RestClient::ResourceNotFound => e
|
139
|
+
nil
|
140
|
+
end
|
141
|
+
|
142
|
+
def current_user_id
|
143
|
+
get("/users/current")["user"]["id"]
|
144
|
+
end
|
145
|
+
|
146
|
+
def get(url, params = {})
|
147
|
+
request(:get, url, params)
|
148
|
+
end
|
149
|
+
|
150
|
+
def post(url, data)
|
151
|
+
request(:post, url, body: data)
|
152
|
+
end
|
153
|
+
|
154
|
+
def put(url, data)
|
155
|
+
request(:put, url, body: data)
|
156
|
+
end
|
157
|
+
|
158
|
+
def request(type, url, params = {})
|
159
|
+
args = []
|
160
|
+
args << @base_url + url + ".json"
|
161
|
+
args << JSON.generate(params.delete(:body)) unless type == :get
|
162
|
+
args << {
|
163
|
+
accept: :json,
|
164
|
+
content_type: :json,
|
165
|
+
params: params.merge(key: @api_key)
|
166
|
+
}
|
167
|
+
parse RestClient.send(type, *args)
|
168
|
+
end
|
169
|
+
|
170
|
+
def parse(json)
|
171
|
+
json && json.length >= 2 ? ::JSON.parse(json) : nil
|
172
|
+
end
|
173
|
+
|
174
|
+
end
|
175
|
+
end
|
data/lib/redpomo/ui.rb
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
module Redpomo
|
2
|
+
|
3
|
+
class UI
|
4
|
+
def warn(message, newline = nil)
|
5
|
+
end
|
6
|
+
|
7
|
+
def debug(message, newline = nil)
|
8
|
+
end
|
9
|
+
|
10
|
+
def error(message, newline = nil)
|
11
|
+
end
|
12
|
+
|
13
|
+
def info(message, newline = nil)
|
14
|
+
end
|
15
|
+
|
16
|
+
def confirm(message, newline = nil)
|
17
|
+
end
|
18
|
+
|
19
|
+
def debug?
|
20
|
+
false
|
21
|
+
end
|
22
|
+
|
23
|
+
class Shell < UI
|
24
|
+
attr_writer :shell
|
25
|
+
|
26
|
+
def initialize(shell)
|
27
|
+
@shell = shell
|
28
|
+
@quiet = false
|
29
|
+
@debug = ENV['DEBUG']
|
30
|
+
end
|
31
|
+
|
32
|
+
def info(msg, newline = nil)
|
33
|
+
tell_me(msg, nil, newline) if !@quiet
|
34
|
+
end
|
35
|
+
|
36
|
+
def confirm(msg, newline = nil)
|
37
|
+
tell_me(msg, :green, newline) if !@quiet
|
38
|
+
end
|
39
|
+
|
40
|
+
def warn(msg, newline = nil)
|
41
|
+
tell_me(msg, :yellow, newline)
|
42
|
+
end
|
43
|
+
|
44
|
+
def error(msg, newline = nil)
|
45
|
+
tell_me(msg, :red, newline)
|
46
|
+
end
|
47
|
+
|
48
|
+
def be_quiet!
|
49
|
+
@quiet = true
|
50
|
+
end
|
51
|
+
|
52
|
+
def debug?
|
53
|
+
# needs to be false instead of nil to be newline param to other methods
|
54
|
+
!!@debug && !@quiet
|
55
|
+
end
|
56
|
+
|
57
|
+
def debug!
|
58
|
+
@debug = true
|
59
|
+
end
|
60
|
+
|
61
|
+
def debug(msg, newline = nil)
|
62
|
+
tell_me(msg, nil, newline) if debug?
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
# valimism
|
67
|
+
def tell_me(msg, color = nil, newline = nil)
|
68
|
+
newline.nil? ? @shell.say(msg, color) : @shell.say(msg, color, newline)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
end
|
data/redpomo.gemspec
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/redpomo/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Stefano Verna", "Yuri Freire"]
|
6
|
+
gem.email = ["yurifrl@outlook.com"]
|
7
|
+
gem.description = %q{A nice little gem that integrates Redmine, Todo.txt and Pomodoro.app}
|
8
|
+
gem.summary = %q{A nice little gem that integrates Redmine, Todo.txt and Pomodoro.app}
|
9
|
+
gem.homepage = ""
|
10
|
+
|
11
|
+
gem.files = Dir.glob("./**/*")
|
12
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
13
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
14
|
+
gem.name = "redpomo-reloaded"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = Redpomo::VERSION
|
17
|
+
|
18
|
+
gem.add_dependency "activesupport"
|
19
|
+
gem.add_dependency "thor"
|
20
|
+
gem.add_dependency "todo-txt", '~> 0.11'
|
21
|
+
gem.add_dependency "rest-client"
|
22
|
+
gem.add_dependency "launchy"
|
23
|
+
gem.add_dependency "applescript"
|
24
|
+
gem.add_dependency "terminal-table"
|
25
|
+
|
26
|
+
gem.add_development_dependency "rspec"
|
27
|
+
gem.add_development_dependency "guard-rspec"
|
28
|
+
gem.add_development_dependency "vcr"
|
29
|
+
gem.add_development_dependency "webmock"
|
30
|
+
gem.add_development_dependency "mocha"
|
31
|
+
gem.add_development_dependency "simplecov"
|
32
|
+
gem.add_development_dependency "rake"
|
33
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'redpomo/file_cache'
|
3
|
+
require 'tempfile'
|
4
|
+
|
5
|
+
describe Redpomo::FileCache do
|
6
|
+
|
7
|
+
describe "#get" do
|
8
|
+
it "executes the block on cache miss" do
|
9
|
+
old_cache_path = Redpomo::FileCache.instance.cache_path
|
10
|
+
Redpomo::FileCache.instance.cache_path = tmp_path("cache_file")
|
11
|
+
|
12
|
+
counter = Redpomo::FileCache.get("foobar") { 5 }
|
13
|
+
counter.should == 5
|
14
|
+
|
15
|
+
counter = Redpomo::FileCache.get("foobar") { 10 }
|
16
|
+
counter.should == 5
|
17
|
+
|
18
|
+
Redpomo::FileCache.instance.cache_path = old_cache_path
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
---
|
2
|
+
http_interactions:
|
3
|
+
- request:
|
4
|
+
method: get
|
5
|
+
uri: http://code.welaika.com/users/current.json?key=WELAIKA_TOKEN
|
6
|
+
body:
|
7
|
+
encoding: US-ASCII
|
8
|
+
string: ''
|
9
|
+
headers:
|
10
|
+
Accept:
|
11
|
+
- application/json
|
12
|
+
Accept-Encoding:
|
13
|
+
- gzip, deflate
|
14
|
+
Content-Type:
|
15
|
+
- application/json
|
16
|
+
User-Agent:
|
17
|
+
- Ruby
|
18
|
+
response:
|
19
|
+
status:
|
20
|
+
code: 200
|
21
|
+
message: OK
|
22
|
+
headers:
|
23
|
+
Date:
|
24
|
+
- Sun, 13 May 2012 15:49:32 GMT
|
25
|
+
Server:
|
26
|
+
- Apache/2.2.9 (Debian) PHP/5.2.6-1+lenny9 with Suhosin-Patch mod_ssl/2.2.9
|
27
|
+
OpenSSL/0.9.8g Phusion_Passenger/3.0.0
|
28
|
+
X-Powered-By:
|
29
|
+
- Phusion Passenger (mod_rails/mod_rack) 3.0.0
|
30
|
+
Etag:
|
31
|
+
- ! '"210f37abc00e2352ce9a7ebfc82e684d"'
|
32
|
+
X-Runtime:
|
33
|
+
- '48'
|
34
|
+
Cache-Control:
|
35
|
+
- private, max-age=0, must-revalidate
|
36
|
+
Content-Length:
|
37
|
+
- '196'
|
38
|
+
Status:
|
39
|
+
- '200'
|
40
|
+
Content-Type:
|
41
|
+
- application/json; charset=utf-8
|
42
|
+
body:
|
43
|
+
encoding: US-ASCII
|
44
|
+
string: ! '{"user":{"mail":"stefano.verna@welaika.com","last_login_on":"2012/05/13
|
45
|
+
11:17:28 +0200","lastname":"Verna","login":"steffoz","created_on":"2010/02/11
|
46
|
+
09:34:28 +0100","firstname":"Stefano","id":4}}'
|
47
|
+
http_version:
|
48
|
+
recorded_at: Sun, 13 May 2012 15:49:33 GMT
|
49
|
+
- request:
|
50
|
+
method: post
|
51
|
+
uri: http://code.welaika.com/issues.json?key=WELAIKA_TOKEN
|
52
|
+
body:
|
53
|
+
encoding: UTF-8
|
54
|
+
string: ! '{"issue":{"subject":"foobar","description":null,"due_date":null,"project_id":"olasagasti","priority_id":4,"assigned_to_id":4}}'
|
55
|
+
headers:
|
56
|
+
Accept:
|
57
|
+
- application/json
|
58
|
+
Accept-Encoding:
|
59
|
+
- gzip, deflate
|
60
|
+
Content-Type:
|
61
|
+
- application/json
|
62
|
+
Content-Length:
|
63
|
+
- '126'
|
64
|
+
User-Agent:
|
65
|
+
- Ruby
|
66
|
+
response:
|
67
|
+
status:
|
68
|
+
code: 201
|
69
|
+
message: Created
|
70
|
+
headers:
|
71
|
+
Date:
|
72
|
+
- Sun, 13 May 2012 15:49:33 GMT
|
73
|
+
Server:
|
74
|
+
- Apache/2.2.9 (Debian) PHP/5.2.6-1+lenny9 with Suhosin-Patch mod_ssl/2.2.9
|
75
|
+
OpenSSL/0.9.8g Phusion_Passenger/3.0.0
|
76
|
+
X-Powered-By:
|
77
|
+
- Phusion Passenger (mod_rails/mod_rack) 3.0.0
|
78
|
+
X-Runtime:
|
79
|
+
- '288'
|
80
|
+
Cache-Control:
|
81
|
+
- no-cache
|
82
|
+
Set-Cookie:
|
83
|
+
- _redmine_session=BAh7BzoPc2Vzc2lvbl9pZCIlMGE5Y2M5NGZmNjRiYjA4NjE2MTY1MDk3ZDE2MmUzN2IiCmZsYXNoSUM6J0FjdGlvbkNvbnRyb2xsZXI6OkZsYXNoOjpGbGFzaEhhc2h7BjoLbm90aWNlIhpDcmVhemlvbmUgZWZmZXR0dWF0YS4GOgpAdXNlZHsGOwdG--4f2ec2b1d5f84985b5475828990ec5b42bdd9c17;
|
84
|
+
path=/; HttpOnly
|
85
|
+
Location:
|
86
|
+
- http://code.welaika.com/issues/3397
|
87
|
+
Content-Length:
|
88
|
+
- '408'
|
89
|
+
Status:
|
90
|
+
- '201'
|
91
|
+
Content-Type:
|
92
|
+
- application/json; charset=utf-8
|
93
|
+
body:
|
94
|
+
encoding: US-ASCII
|
95
|
+
string: ! '{"issue":{"status":{"name":"New","id":1},"updated_on":"2012/05/13
|
96
|
+
17:49:33 +0200","assigned_to":{"name":"Stefano Verna","id":4},"done_ratio":0,"tracker":{"name":"Bug","id":1},"start_date":"2012/05/13","created_on":"2012/05/13
|
97
|
+
17:49:33 +0200","spent_hours":0.0,"subject":"foobar","author":{"name":"Stefano
|
98
|
+
Verna","id":4},"id":3397,"priority":{"name":"Normal","id":4},"project":{"name":"Olasagasti
|
99
|
+
","id":52}}}'
|
100
|
+
http_version:
|
101
|
+
recorded_at: Sun, 13 May 2012 15:49:33 GMT
|
102
|
+
recorded_with: VCR 2.0.1
|