taskwarrior-web 1.0.7 → 1.0.8
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/CHANGELOG.md +4 -0
- data/Rakefile +8 -0
- data/lib/taskwarrior-web.rb +12 -3
- data/lib/taskwarrior-web/app.rb +111 -115
- data/lib/taskwarrior-web/command.rb +8 -15
- data/lib/taskwarrior-web/command_builder.rb +16 -16
- data/lib/taskwarrior-web/command_builders/base.rb +44 -50
- data/lib/taskwarrior-web/command_builders/v1.rb +0 -2
- data/lib/taskwarrior-web/command_builders/v2.rb +0 -2
- data/lib/taskwarrior-web/config.rb +12 -16
- data/lib/taskwarrior-web/helpers.rb +43 -47
- data/lib/taskwarrior-web/parser.rb +8 -12
- data/lib/taskwarrior-web/public/css/styles.css +46 -0
- data/lib/taskwarrior-web/public/js/application.js +16 -7
- data/lib/taskwarrior-web/public/js/bootstrap.js +2025 -0
- data/lib/taskwarrior-web/runner.rb +5 -11
- data/lib/taskwarrior-web/task.rb +1 -9
- data/lib/taskwarrior-web/views/_subnav.erb +13 -13
- data/lib/taskwarrior-web/views/layout.erb +2 -2
- data/lib/taskwarrior-web/views/listing.erb +1 -1
- data/lib/taskwarrior-web/views/projects.erb +51 -35
- data/spec/app/app_spec.rb +1 -1
- data/spec/app/helpers_spec.rb +1 -1
- data/spec/models/command_builder_spec.rb +0 -2
- data/spec/models/task_spec.rb +3 -17
- data/taskwarrior-web.gemspec +1 -1
- metadata +22 -22
- data/lib/taskwarrior-web/public/js/bootstrap.min.js +0 -6
data/CHANGELOG.md
CHANGED
data/Rakefile
CHANGED
@@ -4,4 +4,12 @@ require 'rspec/core/rake_task'
|
|
4
4
|
Bundler::GemHelper.install_tasks
|
5
5
|
RSpec::Core::RakeTask.new(:spec)
|
6
6
|
|
7
|
+
desc 'Uninstalls the current version of taskwarrior-web'
|
8
|
+
task :uninstall do
|
9
|
+
`gem uninstall -x taskwarrior-web`
|
10
|
+
end
|
7
11
|
|
12
|
+
desc 'Reloads the gem (useful for local development)'
|
13
|
+
task :refresh => [:uninstall, :install] do
|
14
|
+
`task-web -F`
|
15
|
+
end
|
data/lib/taskwarrior-web.rb
CHANGED
@@ -3,6 +3,15 @@ $:.unshift(File.dirname(__FILE__)) unless
|
|
3
3
|
|
4
4
|
require 'rubygems'
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
6
|
+
module TaskwarriorWeb
|
7
|
+
autoload :App, 'taskwarrior-web/app'
|
8
|
+
autoload :Helpers, 'taskwarrior-web/helpers'
|
9
|
+
autoload :Task, 'taskwarrior-web/task'
|
10
|
+
autoload :Config, 'taskwarrior-web/config'
|
11
|
+
autoload :CommandBuilder, 'taskwarrior-web/command_builder'
|
12
|
+
autoload :Command, 'taskwarrior-web/command'
|
13
|
+
autoload :Runner, 'taskwarrior-web/runner'
|
14
|
+
autoload :Parser, 'taskwarrior-web/parser'
|
15
|
+
|
16
|
+
class UnrecognizedTaskVersion < Exception; end
|
17
|
+
end
|
data/lib/taskwarrior-web/app.rb
CHANGED
@@ -4,145 +4,141 @@ require 'sinatra'
|
|
4
4
|
require 'erb'
|
5
5
|
require 'time'
|
6
6
|
require 'rinku'
|
7
|
-
require 'taskwarrior-web/config'
|
8
|
-
require 'taskwarrior-web/helpers'
|
9
7
|
require 'digest'
|
10
8
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
9
|
+
class TaskwarriorWeb::App < Sinatra::Base
|
10
|
+
autoload :Helpers, 'taskwarrior-web/helpers'
|
11
|
+
|
12
|
+
@@root = File.expand_path(File.join(File.dirname(__FILE__), '..', '..'))
|
13
|
+
set :root, @@root
|
14
|
+
set :app_file, __FILE__
|
15
|
+
set :public_folder, File.dirname(__FILE__) + '/public'
|
16
|
+
set :views, File.dirname(__FILE__) + '/views'
|
17
|
+
|
18
|
+
def protected!
|
19
|
+
response['WWW-Authenticate'] = %(Basic realm="Taskworrior Web") and throw(:halt, [401, "Not authorized\n"]) and return unless authorized?
|
20
|
+
end
|
23
21
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
22
|
+
def authorized?
|
23
|
+
@auth ||= Rack::Auth::Basic::Request.new(request.env)
|
24
|
+
values = [TaskwarriorWeb::Config.property('task-web.user'), TaskwarriorWeb::Config.property('task-web.passwd')]
|
25
|
+
@auth.provided? && @auth.basic? && @auth.credentials && @auth.credentials == values
|
26
|
+
end
|
29
27
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
28
|
+
# Before filter
|
29
|
+
before do
|
30
|
+
@current_page = request.path_info
|
31
|
+
protected! if TaskwarriorWeb::Config.property('task-web.user')
|
32
|
+
end
|
35
33
|
|
36
|
-
|
37
|
-
|
34
|
+
# Helpers
|
35
|
+
helpers Helpers
|
38
36
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
37
|
+
# Redirects
|
38
|
+
get '/' do
|
39
|
+
redirect '/tasks/pending'
|
40
|
+
end
|
41
|
+
get '/tasks/?' do
|
42
|
+
redirect '/tasks/pending'
|
43
|
+
end
|
46
44
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
45
|
+
# Task routes
|
46
|
+
get '/tasks/:status/?' do
|
47
|
+
pass unless ['pending', 'waiting', 'completed', 'deleted'].include?(params[:status])
|
48
|
+
@title = "Tasks"
|
49
|
+
@subnav = subnav('tasks')
|
50
|
+
@tasks = TaskwarriorWeb::Task.find_by_status(params[:status]).sort_by! { |x| [x.priority.nil?.to_s, x.priority.to_s, x.due.nil?.to_s, x.due.to_s, x.project.to_s] }
|
51
|
+
erb :listing
|
52
|
+
end
|
53
|
+
|
54
|
+
get '/tasks/new/?' do
|
55
|
+
@title = 'New Task'
|
56
|
+
@date_format = TaskwarriorWeb::Config.dateformat || 'm/d/yy'
|
57
|
+
@date_format.gsub!('Y', 'yy')
|
58
|
+
erb :task_form
|
59
|
+
end
|
55
60
|
|
56
|
-
|
61
|
+
post '/tasks/?' do
|
62
|
+
results = passes_validation(params[:task], :task)
|
63
|
+
if results.empty?
|
64
|
+
task = TaskwarriorWeb::Task.new(params[:task])
|
65
|
+
task.save!.to_s
|
66
|
+
redirect '/tasks'
|
67
|
+
else
|
68
|
+
@task = params[:task]
|
57
69
|
@title = 'New Task'
|
58
70
|
@date_format = TaskwarriorWeb::Config.dateformat || 'm/d/yy'
|
59
71
|
@date_format.gsub!('Y', 'yy')
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
post '/tasks/?' do
|
64
|
-
results = passes_validation(params[:task], :task)
|
65
|
-
if results.empty?
|
66
|
-
task = TaskwarriorWeb::Task.new(params[:task])
|
67
|
-
task.save!.to_s
|
68
|
-
redirect '/tasks'
|
69
|
-
else
|
70
|
-
@task = params[:task]
|
71
|
-
@title = 'New Task'
|
72
|
-
@date_format = TaskwarriorWeb::Config.dateformat || 'm/d/yy'
|
73
|
-
@date_format.gsub!('Y', 'yy')
|
74
|
-
@messages = []
|
75
|
-
results.each do |result|
|
76
|
-
@messages << { :severity => 'alert-error', :message => result }
|
77
|
-
end
|
78
|
-
erb :task_form
|
72
|
+
@messages = []
|
73
|
+
results.each do |result|
|
74
|
+
@messages << { :severity => 'alert-error', :message => result }
|
79
75
|
end
|
76
|
+
erb :task_form
|
80
77
|
end
|
78
|
+
end
|
81
79
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
get '/projects/overview/?' do
|
88
|
-
@title = 'Projects'
|
89
|
-
@subnav = subnav('projects')
|
90
|
-
@tasks = TaskwarriorWeb::Task.query('status.not' => 'deleted', 'project.not' => '').sort_by! { |x| [x.priority.nil?.to_s, x.priority.to_s, x.due.nil?.to_s, x.due.to_s] }.group_by { |x| x.project.to_s }
|
91
|
-
erb :projects
|
92
|
-
end
|
80
|
+
# Projects
|
81
|
+
get '/projects' do
|
82
|
+
redirect '/projects/overview'
|
83
|
+
end
|
93
84
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
end
|
85
|
+
get '/projects/overview/?' do
|
86
|
+
@title = 'Projects'
|
87
|
+
@subnav = subnav('projects')
|
88
|
+
@tasks = TaskwarriorWeb::Task.query('status.not' => 'deleted', 'project.not' => '').sort_by! { |x| [x.priority.nil?.to_s, x.priority.to_s, x.due.nil?.to_s, x.due.to_s] }.group_by { |x| x.project.to_s }
|
89
|
+
erb :projects
|
90
|
+
end
|
101
91
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
92
|
+
get '/projects/:name/?' do
|
93
|
+
@subnav = subnav('projects')
|
94
|
+
subbed = params[:name].gsub('--', '.')
|
95
|
+
@tasks = TaskwarriorWeb::Task.query('status.not' => 'deleted', :project => subbed).sort_by! { |x| [x.priority.nil?.to_s, x.priority.to_s, x.due.nil?.to_s, x.due.to_s] }
|
96
|
+
@title = @tasks.select { |t| t.project.match(/^#{subbed}$/i) }.first.project
|
97
|
+
erb :project
|
98
|
+
end
|
106
99
|
|
107
|
-
|
108
|
-
|
109
|
-
|
100
|
+
# AJAX callbacks
|
101
|
+
get '/ajax/projects/?' do
|
102
|
+
TaskwarriorWeb::Command.new(:projects).run.split("\n").to_json
|
103
|
+
end
|
110
104
|
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
end
|
105
|
+
get '/ajax/tags/?' do
|
106
|
+
tags = TaskwarriorWeb::Command.new(:tags).run.split("\n")
|
107
|
+
tags.keep_if { |tag| tag.include?(params[:query]) }
|
115
108
|
|
116
|
-
|
109
|
+
json = []
|
110
|
+
tags.each do |tag|
|
111
|
+
json << { :name => tag, :id => tag }
|
117
112
|
end
|
118
113
|
|
119
|
-
|
120
|
-
|
121
|
-
end
|
114
|
+
json.to_json
|
115
|
+
end
|
122
116
|
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
TaskwarriorWeb::Command.new(:complete, params[:id]).run
|
127
|
-
end
|
117
|
+
get '/ajax/count/?' do
|
118
|
+
TaskwarriorWeb::Task.count(:status => :pending).to_s
|
119
|
+
end
|
128
120
|
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
end
|
121
|
+
post '/ajax/task-complete/:id/?' do
|
122
|
+
# Bummer that we have to directly use Command here, but apparently tasks
|
123
|
+
# cannot be filtered by UUID.
|
124
|
+
TaskwarriorWeb::Command.new(:complete, params[:id]).run
|
125
|
+
end
|
135
126
|
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
end
|
143
|
-
end
|
144
|
-
results
|
145
|
-
end
|
127
|
+
# Error handling
|
128
|
+
not_found do
|
129
|
+
@title = 'Page Not Found'
|
130
|
+
@referrer = request.referrer
|
131
|
+
erb :'404'
|
132
|
+
end
|
146
133
|
|
134
|
+
def passes_validation(item, method)
|
135
|
+
results = []
|
136
|
+
case method
|
137
|
+
when :task
|
138
|
+
if item['description'].empty?
|
139
|
+
results << 'You must provide a description'
|
140
|
+
end
|
141
|
+
end
|
142
|
+
results
|
147
143
|
end
|
148
144
|
end
|
@@ -1,19 +1,12 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
class TaskwarriorWeb::Command
|
2
|
+
include TaskwarriorWeb::CommandBuilder
|
3
|
+
include TaskwarriorWeb::Runner
|
3
4
|
|
4
|
-
|
5
|
-
class Command
|
6
|
-
|
7
|
-
include TaskwarriorWeb::CommandBuilder
|
8
|
-
include TaskwarriorWeb::Runner
|
9
|
-
|
10
|
-
attr_accessor :command, :id, :params, :built, :command_string
|
11
|
-
|
12
|
-
def initialize(command, id = nil, *args)
|
13
|
-
@command = command if command
|
14
|
-
@id = id if id
|
15
|
-
@params = args.last.is_a?(::Hash) ? args.pop : {}
|
16
|
-
end
|
5
|
+
attr_accessor :command, :id, :params, :built, :command_string
|
17
6
|
|
7
|
+
def initialize(command, id = nil, *args)
|
8
|
+
@command = command if command
|
9
|
+
@id = id if id
|
10
|
+
@params = args.last.is_a?(::Hash) ? args.pop : {}
|
18
11
|
end
|
19
12
|
end
|
@@ -1,22 +1,22 @@
|
|
1
|
-
|
1
|
+
module TaskwarriorWeb::CommandBuilder
|
2
|
+
autoload :Base, 'taskwarrior-web/command_builders/base'
|
3
|
+
autoload :V1, 'taskwarrior-web/command_builders/v1'
|
4
|
+
autoload :V2, 'taskwarrior-web/command_builders/v2'
|
2
5
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
raise TaskwarriorWeb::UnrecognizedTaskVersion
|
16
|
-
end
|
6
|
+
class InvalidCommandError < Exception; end
|
7
|
+
class MissingTaskIDError < Exception; end
|
8
|
+
|
9
|
+
def self.included(class_name)
|
10
|
+
class_name.class_eval do
|
11
|
+
case TaskwarriorWeb::Config.version.major
|
12
|
+
when 2
|
13
|
+
include TaskwarriorWeb::CommandBuilder::V2
|
14
|
+
when 1
|
15
|
+
include TaskwarriorWeb::CommandBuilder::V1
|
16
|
+
else
|
17
|
+
raise TaskwarriorWeb::UnrecognizedTaskVersion
|
17
18
|
end
|
18
19
|
end
|
19
20
|
end
|
20
21
|
|
21
|
-
class UnrecognizedTaskVersion < Exception; end
|
22
22
|
end
|
@@ -1,66 +1,60 @@
|
|
1
1
|
require 'shellwords'
|
2
2
|
|
3
|
-
module TaskwarriorWeb::CommandBuilder
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
task_command
|
18
|
-
substitute_parts if @command_string =~ /:id/
|
19
|
-
end
|
20
|
-
parse_params
|
21
|
-
@built = "#{@command_string}#{@params}"
|
3
|
+
module TaskwarriorWeb::CommandBuilder::Base
|
4
|
+
|
5
|
+
TASK_COMMANDS = {
|
6
|
+
:add => 'add',
|
7
|
+
:query => TaskwarriorWeb::Config.version > Versionomy.parse('1.9.2') ? '_query' : 'export',
|
8
|
+
:complete => ':id done',
|
9
|
+
:projects => '_projects',
|
10
|
+
:tags => '_tags'
|
11
|
+
}
|
12
|
+
|
13
|
+
def build
|
14
|
+
unless @command_string
|
15
|
+
task_command
|
16
|
+
substitute_parts if @command_string =~ /:id/
|
22
17
|
end
|
18
|
+
parse_params
|
19
|
+
@built = "#{@command_string}#{@params}"
|
20
|
+
end
|
23
21
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
end
|
22
|
+
def task_command
|
23
|
+
if TASK_COMMANDS.has_key?(@command.to_sym)
|
24
|
+
@command_string = TASK_COMMANDS[@command.to_sym].clone
|
25
|
+
else
|
26
|
+
raise InvalidCommandError
|
30
27
|
end
|
28
|
+
end
|
31
29
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
end
|
30
|
+
def substitute_parts
|
31
|
+
if @id
|
32
|
+
@command_string.gsub!(':id', "uuid:#{@id.to_s}")
|
33
|
+
return self
|
34
|
+
else
|
35
|
+
raise TaskwarriorWeb::CommandBuilder::MissingTaskIDError
|
39
36
|
end
|
37
|
+
end
|
40
38
|
|
41
|
-
|
42
|
-
|
43
|
-
|
39
|
+
def parse_params
|
40
|
+
string = ''
|
41
|
+
string << %Q( #{@params.delete(:description).shellescape}) if @params.has_key?(:description)
|
44
42
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
43
|
+
if tags = @params.delete(:tags)
|
44
|
+
tag_indicator = TaskwarriorWeb::Config.property('tag.indicator') || '+'
|
45
|
+
tags.each { |tag| string << %Q( #{tag_indicator}#{tag.to_s.shellescape}) }
|
46
|
+
end
|
49
47
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
end
|
48
|
+
@params.each do |attr, value|
|
49
|
+
if value.respond_to? :each
|
50
|
+
value.each { |val| string << %Q( #{attr.to_s}:\\"#{val.to_s.shellescape}\\") }
|
51
|
+
else
|
52
|
+
string << %Q( #{attr.to_s}:\\"#{value.to_s.shellescape}\\")
|
56
53
|
end
|
57
|
-
|
58
|
-
@params = string
|
59
|
-
return self
|
60
54
|
end
|
61
55
|
|
56
|
+
@params = string
|
57
|
+
return self
|
62
58
|
end
|
63
59
|
|
64
|
-
class InvalidCommandError < Exception; end
|
65
|
-
class MissingTaskIDError < Exception; end
|
66
60
|
end
|