redpomo-reloaded 0.0.13

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +7 -0
  2. data/Dockerfile +5 -0
  3. data/Gemfile +9 -0
  4. data/Gemfile.lock +139 -0
  5. data/Guardfile +23 -0
  6. data/LICENSE +22 -0
  7. data/README.md +176 -0
  8. data/Rakefile +6 -0
  9. data/bin/redpomo +7 -0
  10. data/docker-compose.yml +7 -0
  11. data/lib/redpomo.rb +10 -0
  12. data/lib/redpomo/cli.rb +172 -0
  13. data/lib/redpomo/config.rb +26 -0
  14. data/lib/redpomo/entries_printer.rb +33 -0
  15. data/lib/redpomo/entry.rb +65 -0
  16. data/lib/redpomo/file_cache.rb +44 -0
  17. data/lib/redpomo/fuzzy_converter.rb +68 -0
  18. data/lib/redpomo/issue.rb +38 -0
  19. data/lib/redpomo/null_cache.rb +9 -0
  20. data/lib/redpomo/numeric_ext.rb +30 -0
  21. data/lib/redpomo/task.rb +103 -0
  22. data/lib/redpomo/task_list.rb +61 -0
  23. data/lib/redpomo/templates/config.yml +47 -0
  24. data/lib/redpomo/templates/issue_stub.textile +7 -0
  25. data/lib/redpomo/tracker.rb +175 -0
  26. data/lib/redpomo/ui.rb +73 -0
  27. data/lib/redpomo/version.rb +3 -0
  28. data/redpomo.gemspec +33 -0
  29. data/spec/file_cache_spec.rb +22 -0
  30. data/spec/fixtures/add_results.txt +4 -0
  31. data/spec/fixtures/cassettes/cli_add.yml +102 -0
  32. data/spec/fixtures/cassettes/cli_close.yml +50 -0
  33. data/spec/fixtures/cassettes/cli_pull.yml +1222 -0
  34. data/spec/fixtures/cassettes/cli_push.yml +297 -0
  35. data/spec/fixtures/cassettes/close_issue.yml +50 -0
  36. data/spec/fixtures/cassettes/create_issue.yml +102 -0
  37. data/spec/fixtures/cassettes/issues.yml +449 -0
  38. data/spec/fixtures/cassettes/push_entry.yml +55 -0
  39. data/spec/fixtures/close_results.txt +2 -0
  40. data/spec/fixtures/config.yml +16 -0
  41. data/spec/fixtures/printer_output.txt +16 -0
  42. data/spec/fixtures/proper_timelog.csv +4 -0
  43. data/spec/fixtures/pull_results.txt +20 -0
  44. data/spec/fixtures/tasks.txt +3 -0
  45. data/spec/fixtures/timelog.csv +6 -0
  46. data/spec/integration/add_spec.rb +29 -0
  47. data/spec/integration/init_spec.rb +33 -0
  48. data/spec/lib/redpomo/cli_spec.rb +91 -0
  49. data/spec/lib/redpomo/entry_spec.rb +23 -0
  50. data/spec/lib/redpomo/fuzzy_converter_spec.rb +65 -0
  51. data/spec/lib/redpomo/task_spec.rb +39 -0
  52. data/spec/lib/redpomo/tracker_spec.rb +72 -0
  53. data/spec/spec_helper.rb +28 -0
  54. data/spec/support/cli_helpers.rb +76 -0
  55. data/spec/support/fixtures.rb +24 -0
  56. data/spec/support/ruby_ext.rb +20 -0
  57. data/spec/tmp/REDME.md +0 -0
  58. 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
@@ -0,0 +1,3 @@
1
+ module Redpomo
2
+ VERSION = "0.0.13"
3
+ 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,4 @@
1
+ Make screenshot #838 @cantiere
2
+ Fix #3290 @welaika
3
+ Another task @home
4
+ foobar #3397 +olasagasti @welaika
@@ -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