redpomo 0.0.5 → 0.0.6

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