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