redpomo 0.0.5 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -5,7 +5,8 @@
5
5
 
6
6
  Redpomo is the classic "scratch your own itch" project:
7
7
 
8
- * It makes it really easy to manage Redmine issues from the command-line;
8
+ * It makes it really easy to manage (create, close, browse) Redmine issues
9
+ from the command-line;
9
10
  * It is able to sync Redmine issues with your local Todo.txt tasks;
10
11
  * It can start Pomodoro.app timer on a specific Todo.txt task, and is
11
12
  able to "push" the logged pomodoros as Redmine timetracks.
@@ -14,26 +15,28 @@ Redpomo is the classic "scratch your own itch" project:
14
15
 
15
16
  › redpomo help
16
17
  Tasks:
17
- redpomo close TASK # Marks a todo.txt task as complete, and closes the related Redmine issue
18
+ redpomo add # creates a new task on Todo.txt, forwarding it to the remote tracker
19
+ redpomo close TASK # marks a todo.txt task as complete, and closes the related Redmine issue
18
20
  redpomo help [TASK] # Describe available tasks or one specific task
19
- redpomo open TASK # Opens up the Redmine issue page of the selected task
20
- redpomo pull # Imports Redmine open issues into local todo.txt
21
- redpomo push LOGFILE # Parses Pomodoro export file and imports to Redmine clients
22
- redpomo start TASK # Starts a Pomodoro session for the selected task
21
+ redpomo init # generates a .redpomo configuration file on your home directory
22
+ redpomo open TASK # opens up the Redmine issue page of the selected task
23
+ redpomo pull # imports Redmine open issues into local todo.txt
24
+ redpomo push LOGFILE # parses Pomodoro export file and imports to Redmine clients
25
+ redpomo start TASK # starts a Pomodoro session for the selected task
23
26
 
24
- ## Installation
25
-
26
- Add this line to your application's Gemfile:
27
+ Options:
28
+ -c, [--config=CONFIG] # Default: ~/.redpomo
29
+ [--no-color]
27
30
 
28
- gem 'redpomo'
31
+ ## Installation
29
32
 
30
- And then execute:
33
+ Install it yourself as:
31
34
 
32
- $ bundle
35
+ $ gem install redpomo
33
36
 
34
- Or install it yourself as:
37
+ And then configure it:
35
38
 
36
- $ gem install redpomo
39
+ $ redpomo init
37
40
 
38
41
  ## Contributing
39
42
 
data/lib/redpomo/cli.rb CHANGED
@@ -1,81 +1,133 @@
1
1
  require 'thor'
2
+ require 'tempfile'
2
3
 
3
4
  require 'redpomo/config'
4
5
  require 'redpomo/task_list'
5
6
  require 'redpomo/entry'
6
7
  require 'redpomo/entries_printer'
7
8
  require 'redpomo/fuzzy_converter'
8
- require 'redpomo/config_generator'
9
+ # require 'redpomo/issue_generator'
9
10
 
10
11
  module Redpomo
11
12
  class CLI < ::Thor
12
- map "p" => :pull
13
+ include Thor::Actions
14
+
15
+ def initialize(*)
16
+ super
17
+ the_shell = (options["no-color"] ? Thor::Shell::Basic.new : shell)
18
+ Redpomo.ui = UI::Shell.new(the_shell)
19
+ configure!
20
+ end
21
+
22
+ default_task :pull
23
+ class_option "config", :aliases => "-c", default: '~/.redpomo'
24
+ class_option "no-color", :type => :boolean
25
+
13
26
  map "o" => :open
14
27
  map "c" => :close
28
+ map "a" => :add
29
+
30
+ desc "add", "creates a new task on Todo.txt, forwarding it to the remote tracker"
31
+ method_option :description, aliases: "-d"
32
+ def add(subject = nil)
33
+ description = @options[:description]
34
+
35
+ if subject.blank?
36
+ message_path = Tempfile.new('issue').path + ".textile"
37
+ template "issue_stub.textile", message_path, verbose: false
38
+ subject, description = parse_message edit(message_path)
39
+ end
40
+
41
+ issue = Task.new(nil, subject).to_issue
42
+ issue.description = description
43
+ issue.create!
44
+
45
+ task = issue.to_task
46
+ task.add!
47
+
48
+ Redpomo.ui.info "Issue created, see it at #{task.url}"
49
+ end
15
50
 
16
51
  desc "pull", "imports Redmine open issues into local todo.txt"
17
- method_option :config, aliases: "-c", default: '~/.redpomo'
18
52
  def pull
19
- configure!
20
53
  TaskList.pull_from_trackers!
21
54
  end
22
55
 
23
56
  desc "push LOGFILE", "parses Pomodoro export file and imports to Redmine clients"
24
- method_option :config, aliases: "-c", default: '~/.redpomo'
25
57
  method_option :fuzzy, aliases: "-f", type: :boolean
26
58
  method_option :dry_run, aliases: "-n", type: :boolean
27
59
  def push(path)
28
- configure!
29
-
30
60
  entries = Entry.load_from_csv(path)
31
61
  entries = FuzzyConverter.convert(entries) if @options[:fuzzy]
32
62
 
33
- unless @options[:dry_run]
63
+ if @options[:dry_run]
64
+ EntriesPrinter.print(entries)
65
+ else
34
66
  entries.each(&:push!)
67
+ Redpomo.ui.info "Pushed #{entries.count} time entries!"
35
68
  end
36
-
37
- EntriesPrinter.print(entries)
38
69
  end
39
70
 
40
71
  desc "open TASK", "opens up the Redmine issue page of the selected task"
41
- method_option :config, aliases: "-c", default: '~/.redpomo'
42
72
  def open(task_number)
43
- configure!
44
-
45
73
  task = TaskList.find(task_number)
46
74
  task.open_in_browser!
47
75
  end
48
76
 
49
77
  desc "start TASK", "starts a Pomodoro session for the selected task"
50
- method_option :config, aliases: "-c", default: '~/.redpomo'
51
78
  def start(task_number)
52
- configure!
53
-
54
79
  task = TaskList.find(task_number)
55
80
  task.start_pomodoro!
56
81
  end
57
82
 
58
83
  desc "close TASK", "marks a todo.txt task as complete, and closes the related Redmine issue"
59
- method_option :config, aliases: "-c", default: '~/.redpomo'
60
84
  method_option :message, aliases: "-m"
61
85
  def close(task_number)
62
- configure!
63
-
64
86
  task = TaskList.find(task_number)
65
87
  task.done!
66
- task.close_issue!
88
+ task.close_issue!(@options[:message])
89
+
90
+ Redpomo.ui.info "Issue updated, see it at #{task.url}"
67
91
  end
68
92
 
69
93
  desc "init", "generates a .redpomo configuration file on your home directory"
70
- method_option :path, aliases: "-p", default: '~/.redpomo'
71
- def init
72
- ConfigGenerator.start([ @options[:path] ])
94
+ def init(path = '~/.redpomo')
95
+ path = File.expand_path(path)
96
+ template "config.yml", path, verbose: false
97
+
98
+ editor = [ENV['REDPOMO_EDITOR'], ENV['VISUAL'], ENV['EDITOR']].find{|e| !e.nil? && !e.empty? }
99
+ if editor
100
+ command = "#{editor} #{path}"
101
+ system(command)
102
+ File.read(path)
103
+ else
104
+ Redpomo.ui.info("To open the .redpomo config file, set $EDITOR or $REDPOMO_EDITOR")
105
+ end
73
106
  end
74
107
 
75
108
  private
76
109
 
77
110
  def configure!
78
- Config.load_from_yaml(@options[:config])
111
+ Config.load_from_yaml(options["config"])
112
+ end
113
+
114
+ def self.source_root
115
+ File.dirname(__FILE__) + "/templates"
116
+ end
117
+
118
+ def parse_message(message)
119
+ # remove comments
120
+ message.gsub! /#.*$/, ''
121
+ # remove empty lines at beginning of string
122
+ message.gsub! /\A\n*/, ''
123
+
124
+ # first line is the subject, the rest is description
125
+ subject, description = message.split(/\n/, 2)
126
+
127
+ subject ||= ''
128
+ description ||= ''
129
+
130
+ [subject.strip, description.strip]
79
131
  end
80
132
 
81
133
  end
@@ -13,9 +13,11 @@ module Redpomo
13
13
  @@cache = NullCache
14
14
 
15
15
  def self.load_from_yaml(path)
16
- data = YAML::load_file(File.expand_path(path))
16
+ config_path = File.expand_path(path)
17
+ return unless File.exists?(config_path)
18
+ data = YAML::load_file(config_path)
17
19
 
18
- @@todo_path = File.expand_path(data["todo"])
20
+ @@todo_path = File.expand_path(data["todo"], File.dirname(config_path))
19
21
  @@trackers_data = data["trackers"].symbolize_keys!
20
22
  @@cache = data["cache"] ? FileCache : NullCache
21
23
  end
data/lib/redpomo/issue.rb CHANGED
@@ -1,30 +1,37 @@
1
1
  module Redpomo
2
2
  class Issue
3
3
 
4
- attr_reader :title, :issue_id, :project, :tracker, :due_date
4
+ attr_accessor :subject, :description
5
+ attr_accessor :issue_id, :project_id, :tracker
6
+ attr_accessor :due_date, :priority_id
5
7
 
6
- def initialize(tracker, data)
7
- @title = data["subject"]
8
+ def initialize(tracker, data = {})
9
+ @subject = data["subject"]
8
10
  @issue_id = data["id"]
9
- @project = data["project"]
10
- @priority = data["priority"]["name"]
11
+ @project_id = data["project_id"]
12
+ @priority_id = data["priority"]["id"] if data["priority"].present?
11
13
  @due_date = Date.parse(data["due_date"]) if data["due_date"].present?
12
14
  @tracker = tracker
13
15
  end
14
16
 
15
17
  def to_task
16
18
  label = []
17
- if @priority.present?
18
- if priority = @tracker.todo_priority(@priority)
19
+ if @priority_id.present?
20
+ if priority = @tracker.todo_priority(@priority_id)
19
21
  label << priority
20
22
  end
21
23
  end
22
- label << @title
23
24
  label << @due_date.strftime("%Y-%m-%d") if @due_date.present?
25
+ label << @subject
24
26
  label << "##{issue_id}"
25
- label << "+#{project}"
27
+ label << "+#{project_id}"
26
28
  label << "@#{tracker.name}"
27
- Todo::Task.new(label.join(" "))
29
+ Task.new(nil, label.join(" "))
30
+ end
31
+
32
+ def create!
33
+ data = tracker.create_issue!(self)
34
+ @issue_id = data["issue"]["id"]
28
35
  end
29
36
 
30
37
  end
data/lib/redpomo/task.rb CHANGED
@@ -16,6 +16,8 @@ module Redpomo
16
16
  class Task
17
17
 
18
18
  delegate :orig, to: :@task
19
+ delegate :priority, to: :@task
20
+ delegate :date, to: :@task
19
21
 
20
22
  ISSUES_REGEXP = /(?:\s+|^)#[0-9]+/
21
23
 
@@ -54,6 +56,10 @@ module Redpomo
54
56
  @list.remove!(self)
55
57
  end
56
58
 
59
+ def add!
60
+ TaskList.add!(self)
61
+ end
62
+
57
63
  def open_in_browser!
58
64
  require 'launchy'
59
65
  Launchy.open(url)
@@ -82,6 +88,15 @@ module Redpomo
82
88
  Tracker.find(context)
83
89
  end
84
90
 
91
+ def to_issue
92
+ issue = Issue.new(tracker)
93
+ issue.subject = text
94
+ issue.project_id = project
95
+ issue.due_date = date
96
+ issue.priority_id = tracker.issue_priority_id(priority)
97
+ issue
98
+ end
99
+
85
100
  end
86
101
  end
87
102
 
@@ -14,6 +14,11 @@ module Redpomo
14
14
  list.pull_from_trackers!
15
15
  end
16
16
 
17
+ def self.add!(task)
18
+ list = TaskList.new(Config.todo_path)
19
+ list.add!(task)
20
+ end
21
+
17
22
  def initialize(path)
18
23
  @path = path
19
24
  File.read(path).split("\n").each do |line|
@@ -30,6 +35,11 @@ module Redpomo
30
35
  write!
31
36
  end
32
37
 
38
+ def add!(task)
39
+ push task
40
+ write!
41
+ end
42
+
33
43
  def pull_from_trackers!
34
44
  issue_tasks = Tracker.all.map(&:issues).flatten.map(&:to_task)
35
45
  delete_if do |task|
@@ -38,6 +48,7 @@ module Redpomo
38
48
  self << issue_tasks
39
49
  self.flatten!
40
50
  write!
51
+ Redpomo.ui.info "Pulled #{issue_tasks.count} issues."
41
52
  end
42
53
 
43
54
  def write!
@@ -1,9 +1,40 @@
1
1
  ---
2
- todo: "~/Documents/todo/todo.txt" # path of your local todo.txt
2
+ todo: "~/Documents/todo/todo.txt" # Path of your local Todo.txt
3
+
3
4
  trackers:
4
- my_tracker: # this is the name of the context you want to use i.e. @my_tracker
5
- url: "http://my_tracker.com" # the URL of your Redmine instance
6
- token: "" # find the API key in "http://mytracker.com/my/account"
7
- default_project: "project" # if timetracks you need to push don't have a +project specified, use this
8
- closed_status: 5 # identifier of the status used to mark issues as closed (5 should be "Closed" on most Redmine instances)
9
- priorities: [ "Immediate", "Urgent", "High" ] # list of priorities sorted by.. uhm.. priority. The first one with get (A), then (B), (C), etc.
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_project_id: "project" # If timetracks you're pushing don't have
15
+ # a +project specified, use this.
16
+
17
+ default_priority_id: 4 # If issues you're adding don't have a
18
+ # priority specified, then use this.
19
+ #
20
+ # -- 4 should be "Normal" on most Redmines
21
+
22
+ closed_status_id: 5 # Identifier of the status used to mark
23
+ # issues as closed. Find this ID here:
24
+ # http://mytracker.com/issue_statuses
25
+ #
26
+ # -- 5 should be "Closed" on most Redmines
27
+
28
+ priority_ids: # Map a Todo.txt priority with a Redmine
29
+ # priority ID.
30
+
31
+ - 7 # -> (A)
32
+ - 6 # -> (B)
33
+ - 5 # -> (C)
34
+ # ... and so on
35
+ #
36
+ # Find those IDs at the following URL:
37
+ # http://mytracker.com/enumerations
38
+ #
39
+ # -- 7, 6 and 5 should be "Immediate",
40
+ # "Urgent" and "High" on most Redmines
@@ -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.
@@ -2,6 +2,9 @@ require 'active_support/core_ext/hash'
2
2
  require 'redpomo/issue'
3
3
  require 'redpomo/config'
4
4
 
5
+ require 'rest_client'
6
+ require 'json'
7
+
5
8
  module Redpomo
6
9
  class Tracker
7
10
 
@@ -24,28 +27,47 @@ module Redpomo
24
27
  @name = name
25
28
  @base_url = options[:url]
26
29
  @api_key = options[:token]
27
- @default_project = options[:default_project]
28
- @closed_status_id = options[:closed_status].to_i
29
- @priorities = options[:priorities]
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
+ @priorities = options[:priority_ids]
30
34
  end
31
35
 
32
36
  def todo_priority(priority_name)
33
37
  return nil if @priorities.nil?
38
+ alphabet = ('A' .. 'Z').to_a.join
34
39
  if index = @priorities.index(priority_name)
35
- "(#{"ABCDE"[index]})"
36
- else
37
- nil
40
+ "(#{alphabet[index]})"
41
+ end
42
+ end
43
+
44
+ def issue_priority_id(priority_val)
45
+ if index = ('A' .. 'Z').to_a.index(priority_val)
46
+ @priorities[index]
38
47
  end
39
48
  end
40
49
 
41
50
  def issues
42
51
  data = get("/issues", assigned_to_id: current_user_id, status_id: "open", limit: 100)
43
52
  data["issues"].map do |issue|
44
- issue["project"] = project_identifier_for(issue["project"]["id"])
53
+ issue["project_id"] = project_identifier_for(issue["project"]["id"])
45
54
  Issue.new(self, issue)
46
55
  end
47
56
  end
48
57
 
58
+ def create_issue!(issue)
59
+ data = {
60
+ subject: issue.subject,
61
+ description: issue.description,
62
+ due_date: issue.due_date,
63
+ project_id: issue.project_id || @default_project_id,
64
+ priority_id: issue.priority_id || @default_priority_id,
65
+ assigned_to_id: current_user_id
66
+ }
67
+
68
+ post("/issues", issue: data)
69
+ end
70
+
49
71
  def push_entry!(entry)
50
72
  task = entry.to_task
51
73
  time_entry = {}
@@ -55,7 +77,7 @@ module Redpomo
55
77
  elsif project = task.project
56
78
  time_entry[:project_id] = project
57
79
  else
58
- time_entry[:project_id] = @default_project
80
+ time_entry[:project_id] = @default_project_id
59
81
  end
60
82
 
61
83
  time_entry[:spent_on] = entry.datetime
@@ -97,8 +119,6 @@ module Redpomo
97
119
  end
98
120
 
99
121
  def request(type, url, params = {})
100
- require 'rest_client'
101
- require 'json'
102
122
  args = []
103
123
  args << @base_url + url + ".json"
104
124
  args << params.delete(:body).to_json unless type == :get
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
@@ -1,3 +1,3 @@
1
1
  module Redpomo
2
- VERSION = "0.0.5"
2
+ VERSION = "0.0.6"
3
3
  end
data/lib/redpomo.rb CHANGED
@@ -1,15 +1,10 @@
1
- # require "active_support/core_ext"
2
- # require "rest-client"
3
- # require "todo-txt"
4
- # require "yaml"
5
- # require "json"
6
- # require "csv"
7
- # require "launchy"
8
- # require "applescript"
9
-
1
+ require 'active_support/core_ext/module/attribute_accessors'
10
2
  require "redpomo/version"
11
- # require "redpomo/ext"
12
- # require "redpomo/cli"
3
+ require "redpomo/ui"
13
4
 
14
5
  module Redpomo
6
+
7
+ mattr_accessor :ui
8
+ self.ui = UI.new
9
+
15
10
  end
@@ -0,0 +1,4 @@
1
+ Make screenshot #838 @cantiere
2
+ Fix #3290 @welaika
3
+ Another task @home
4
+ foobar #3397 +olasagasti @welaika