todo.txt 0.0.3
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.
- checksums.yaml +7 -0
- data/Gemfile +7 -0
- data/Gemfile.lock +29 -0
- data/MIT-LICENSE +21 -0
- data/NOTES.md +0 -0
- data/README.md +11 -0
- data/lib/todo.rb +20 -0
- data/lib/todo/cli.rb +39 -0
- data/lib/todo/cli/archive.rb +39 -0
- data/lib/todo/cli/cmd.rb +26 -0
- data/lib/todo/cli/list.rb +42 -0
- data/lib/todo/cli/push.rb +42 -0
- data/lib/todo/cli/toggle.rb +22 -0
- data/lib/todo/data/item.rb +58 -0
- data/lib/todo/data/list.rb +64 -0
- data/lib/todo/data/matcher.rb +42 -0
- data/lib/todo/data/parser.rb +41 -0
- data/lib/todo/helpers/hash/compact.rb +13 -0
- data/lib/todo/helpers/hash/format.rb +13 -0
- data/lib/todo/helpers/hash/slice.rb +17 -0
- data/lib/todo/helpers/object/presence.rb +26 -0
- data/lib/todo/helpers/string/camelize.rb +16 -0
- data/lib/todo/src/file.rb +24 -0
- data/lib/todo/src/idonethis.rb +52 -0
- data/lib/todo/src/io.rb +29 -0
- data/lib/todo/support/http.rb +31 -0
- data/lib/todo/support/options_parser.rb +31 -0
- data/lib/todo/version.rb +3 -0
- data/lib/todo/view.rb +49 -0
- data/spec/spec_helper.rb +9 -0
- data/spec/support/io.rb +9 -0
- data/spec/todo/cli/archive_spec.rb +16 -0
- data/spec/todo/cli/list_spec.rb +13 -0
- data/spec/todo/cli/toggle_spec.rb +25 -0
- data/spec/todo/data/item_spec.rb +94 -0
- data/spec/todo/data/list_spec.rb +59 -0
- data/spec/todo/data/parser_spec.rb +27 -0
- data/spec/todo/helpers/hash/format_spec.rb +9 -0
- data/spec/todo/helpers/hash/slice_spec.rb +8 -0
- data/spec/todo/helpers/object/presence_spec.rb +42 -0
- data/spec/todo/helpers/string/string_spec.rb +7 -0
- data/spec/todo/src/file_spec.rb +33 -0
- data/spec/todo/src/io_spec.rb +18 -0
- data/spec/todo/view_spec.rb +17 -0
- data/todo.gemspec +20 -0
- data/todo.md +10 -0
- metadata +89 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
module Todo
|
|
2
|
+
module Data
|
|
3
|
+
class Matcher < Struct.new(:item, :data)
|
|
4
|
+
def matches?
|
|
5
|
+
return true if data.empty?
|
|
6
|
+
data[:id] ? match_id : match_data
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
private
|
|
10
|
+
|
|
11
|
+
def match_data
|
|
12
|
+
data.keys.inject(true) do |result, key|
|
|
13
|
+
result && send(:"match_#{key}")
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def match_id
|
|
18
|
+
item.id.to_i == data[:id].to_i
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def match_text
|
|
22
|
+
item.text.include?(data[:text])
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def match_status
|
|
26
|
+
item.status && item.status.to_sym == normalize_status(data[:status].to_sym)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def match_since
|
|
30
|
+
item.done_date.to_s >= data[:since].to_s
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def match_before
|
|
34
|
+
item.done_date.to_s < data[:before].to_s
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def normalize_status(status)
|
|
38
|
+
status == :pending ? :pend : status
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
require 'todo'
|
|
2
|
+
require 'todo/helpers/hash/compact'
|
|
3
|
+
|
|
4
|
+
module Todo
|
|
5
|
+
module Data
|
|
6
|
+
class Parser < Struct.new(:line)
|
|
7
|
+
STATUS = /^(#{STATUSES.values.join('|')}){1}\s*/
|
|
8
|
+
ID = /\s*\[(\d+)\]/
|
|
9
|
+
TAG = /\s*([^\s]+):([^\s]+)/
|
|
10
|
+
|
|
11
|
+
include Helpers::Hash::Compact
|
|
12
|
+
|
|
13
|
+
def parse
|
|
14
|
+
compact(
|
|
15
|
+
id: id,
|
|
16
|
+
text: text,
|
|
17
|
+
status: status,
|
|
18
|
+
tags: tags,
|
|
19
|
+
)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def status
|
|
25
|
+
STATUSES.invert[line.match(STATUS) && $1]
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def id
|
|
29
|
+
line =~ ID && $1.to_i
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def text
|
|
33
|
+
line.sub(STATUS, '').gsub(ID, '').gsub(TAG, '').rstrip
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def tags
|
|
37
|
+
Hash[line.scan(TAG).map { |key, value| [key.to_sym, value] }]
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module Todo
|
|
2
|
+
module Helpers
|
|
3
|
+
module Hash
|
|
4
|
+
module Slice
|
|
5
|
+
def slice(hash, *keys)
|
|
6
|
+
hash.select { |key, value| keys.include?(key) }
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def except(hash, *keys)
|
|
10
|
+
hash.reject { |key, value| keys.include?(key) }
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
extend self
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module Todo
|
|
2
|
+
module Helpers
|
|
3
|
+
module Object
|
|
4
|
+
module Presence
|
|
5
|
+
def present?(obj)
|
|
6
|
+
case obj
|
|
7
|
+
when NilClass, FalseClass
|
|
8
|
+
false
|
|
9
|
+
when TrueClass, Symbol, Numeric
|
|
10
|
+
true
|
|
11
|
+
when ::String, ::Array, ::Hash
|
|
12
|
+
!obj.empty?
|
|
13
|
+
else
|
|
14
|
+
fail "Unexpected object type #{obj.inspect}"
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def blank?(obj)
|
|
19
|
+
not present?(obj)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
extend self
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module Todo
|
|
2
|
+
module Helpers
|
|
3
|
+
module String
|
|
4
|
+
module Camelize
|
|
5
|
+
def camelize(string)
|
|
6
|
+
string.to_s.
|
|
7
|
+
sub(/^[a-z\d]*/) { $&.capitalize }.
|
|
8
|
+
gsub(/(?:_|(\/))([a-z\d]*)/i) { "#{$1}#{$2.capitalize}" }.
|
|
9
|
+
gsub('/', '::')
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
extend self
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module Todo
|
|
2
|
+
module Src
|
|
3
|
+
class File
|
|
4
|
+
attr_reader :path, :mode
|
|
5
|
+
|
|
6
|
+
def initialize(path, opts = {})
|
|
7
|
+
@path = path
|
|
8
|
+
@mode = opts[:mode] || 'w+'
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def exists?
|
|
12
|
+
::File.exists?(path)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def read
|
|
16
|
+
exists? ? ::File.readlines(path).map(&:rstrip) : []
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def write(lines)
|
|
20
|
+
::File.open(path, mode) { |f| f.puts(lines.join("\n")) }
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
require 'todo/support/http'
|
|
3
|
+
|
|
4
|
+
module Todo
|
|
5
|
+
module Src
|
|
6
|
+
class Idonthis < Struct.new(:config, :opts)
|
|
7
|
+
URL = 'https://idonethis.com/api/v0.1/dones/'
|
|
8
|
+
|
|
9
|
+
def include?(item)
|
|
10
|
+
texts.include?(item.text) or ids.include?(item.id)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def write(lines)
|
|
14
|
+
lines.each { |line| post(line) }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def texts
|
|
20
|
+
items.map(&:text)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def ids
|
|
24
|
+
items.map(&:id).compact
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def items
|
|
28
|
+
@items ||= fetch.map { |item| Item.new(nil, item['raw_text']) }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def fetch
|
|
32
|
+
JSON.parse(get.body)['results'] || []
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def get
|
|
36
|
+
Http.new(URL, headers: headers, params: params).get
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def post(text)
|
|
40
|
+
Http.new(URL, headers: headers).post(raw_text: text, team: config[:team])
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def headers
|
|
44
|
+
{ 'Authorization' => "Token #{config[:token]}" }
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def params
|
|
48
|
+
{ owner: config[:username], team: config[:team], done_date_after: opts[:since], page_size: 100 }
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
data/lib/todo/src/io.rb
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module Todo
|
|
2
|
+
module Src
|
|
3
|
+
class Io
|
|
4
|
+
attr_reader :opts
|
|
5
|
+
|
|
6
|
+
def initialize(opts = {})
|
|
7
|
+
@opts = opts
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def read
|
|
11
|
+
@lines ||= input.readlines.map(&:rstrip)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def write(lines)
|
|
15
|
+
output.puts(lines.join("\n"))
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def input
|
|
21
|
+
opts[:in] || $stdin
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def output
|
|
25
|
+
opts[:out] || $stdout
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
require 'net/http'
|
|
2
|
+
require 'uri'
|
|
3
|
+
|
|
4
|
+
module Todo
|
|
5
|
+
module Support
|
|
6
|
+
class Http
|
|
7
|
+
attr_reader :uri, :headers
|
|
8
|
+
|
|
9
|
+
def initialize(url, opts)
|
|
10
|
+
@uri = URI(url).tap { |url| url.query = URI.encode_www_form(opts[:params] || {}) }
|
|
11
|
+
@headers = opts[:headers] || {}
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def get
|
|
15
|
+
client = Net::HTTP.new(url.host, url.port)
|
|
16
|
+
client.use_ssl = true
|
|
17
|
+
response = client.get(uri, headers)
|
|
18
|
+
raise "Failed to get #{url.to_s}" unless response.code.to_s[0] == '2'
|
|
19
|
+
response
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def post(data)
|
|
23
|
+
request = Net::HTTP::Post.new(url, headers)
|
|
24
|
+
request.form_data = data
|
|
25
|
+
response = http.request(request)
|
|
26
|
+
raise "Failed to post to #{url.to_s}: #{response.body}" unless response.code.to_s[0] == '2'
|
|
27
|
+
response
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
require 'optparse'
|
|
2
|
+
|
|
3
|
+
module Todo
|
|
4
|
+
module Support
|
|
5
|
+
module OptionsParser
|
|
6
|
+
def opt(*args, &block)
|
|
7
|
+
rules << [args, block]
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def parse(args)
|
|
11
|
+
opts = {}
|
|
12
|
+
args = parser(opts).parse(args)
|
|
13
|
+
[args, opts]
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
def rules
|
|
19
|
+
@rules ||= (superclass.instance_variable_get(:@rules) || []).dup
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def parser(opts)
|
|
23
|
+
OptionParser.new do |parser|
|
|
24
|
+
rules.each do |args, block|
|
|
25
|
+
parser.on(*args) { |value| block.call(opts, value.strip) }
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
data/lib/todo/version.rb
ADDED
data/lib/todo/view.rb
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
require 'todo'
|
|
2
|
+
require 'todo/helpers/hash/format'
|
|
3
|
+
require 'todo/helpers/object/presence'
|
|
4
|
+
|
|
5
|
+
module Todo
|
|
6
|
+
class View < Struct.new(:items, :cols)
|
|
7
|
+
COLS = [:status, :text, :tags, :id]
|
|
8
|
+
|
|
9
|
+
include Helpers::Hash::Format, Helpers::Object::Presence
|
|
10
|
+
|
|
11
|
+
def render
|
|
12
|
+
items.map { |item| format(item) }
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
def format(item)
|
|
18
|
+
cols.map { |col| format_col(col, item.send(col)) }.compact.join(' ')
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def format_col(col, value)
|
|
22
|
+
send(:"format_#{col}", value) if present?(value)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def format_status(status)
|
|
26
|
+
STATUSES[status]
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def format_id(id)
|
|
30
|
+
"[#{id}]"
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def format_text(text)
|
|
34
|
+
text
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def format_done_date(date)
|
|
38
|
+
date
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def format_tags(tags)
|
|
42
|
+
to_pairs(tags, ':') if tags.any?
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def cols
|
|
46
|
+
super || COLS
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
data/spec/spec_helper.rb
ADDED
data/spec/support/io.rb
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
require 'todo/cli/archive'
|
|
2
|
+
require 'fileutils'
|
|
3
|
+
|
|
4
|
+
describe Todo::Cli::Archive do
|
|
5
|
+
let(:lines) { ['- foo [1]', 'x bar done:2015-11-01 [2]', 'x baz done:2015-12-01 [3]'] }
|
|
6
|
+
let(:io) { Support::Io.new(lines.join("\n")) }
|
|
7
|
+
let(:out) { Support::Io.new }
|
|
8
|
+
let(:opts) { { archive: '/tmp/todo.archive.txt', in: io, out: out } }
|
|
9
|
+
|
|
10
|
+
subject { described_class.new(nil, opts) }
|
|
11
|
+
before { subject.run }
|
|
12
|
+
after { FileUtils.rm_f(opts[:archive]) }
|
|
13
|
+
|
|
14
|
+
it { expect(out.readlines).to eq ['- foo [1]', 'x baz done:2015-12-01 [3]'] }
|
|
15
|
+
it { expect(File.read(opts[:archive]).strip).to eq 'x bar done:2015-11-01 [2]' }
|
|
16
|
+
end
|