nin 0.0.0 → 1.3.0
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 +4 -4
- data/.gitignore +4 -0
- data/.travis.yml +5 -0
- data/Gemfile +9 -0
- data/LICENSE +21 -0
- data/README.md +112 -0
- data/Rakefile +17 -0
- data/bin/nin +17 -0
- data/demo.gif +0 -0
- data/lib/nin.rb +38 -57
- data/lib/nin/command.rb +116 -0
- data/lib/nin/error.rb +5 -0
- data/lib/nin/extensions/array_extensions.rb +41 -0
- data/lib/nin/extensions/date_extensions.rb +20 -0
- data/lib/nin/extensions/integer_extensions.rb +5 -0
- data/lib/nin/extensions/nil_class_extensions.rb +5 -0
- data/lib/nin/extensions/object_extensions.rb +5 -0
- data/lib/nin/extensions/string_extensions.rb +5 -0
- data/lib/nin/integration/service.rb +9 -0
- data/lib/nin/integration/service/todoist.rb +94 -0
- data/lib/nin/integration/synchronizer.rb +12 -0
- data/lib/nin/integration/synchronizer/todoist.rb +112 -0
- data/lib/nin/integration/todoist.rb +5 -0
- data/lib/nin/integration/todoist/client.rb +25 -0
- data/lib/nin/integration/todoist/client/base_client.rb +13 -0
- data/lib/nin/integration/todoist/client/item.rb +25 -0
- data/lib/nin/integration/todoist/client/project.rb +25 -0
- data/lib/nin/integration/todoist/client/sync.rb +33 -0
- data/lib/nin/item.rb +70 -0
- data/lib/nin/parser.rb +43 -0
- data/lib/nin/presenters/item_presenter.rb +53 -0
- data/lib/nin/presenters/todo_presenter.rb +17 -0
- data/lib/nin/todo.rb +214 -0
- data/lib/nin/version.rb +3 -0
- data/lib/nin/yaml_store.rb +31 -0
- data/nin.gemspec +27 -0
- metadata +117 -12
@@ -0,0 +1,20 @@
|
|
1
|
+
class Date
|
2
|
+
def self.parse_or_return(unparsed_date)
|
3
|
+
return unparsed_date unless unparsed_date.is_a?(String)
|
4
|
+
|
5
|
+
Date.parse(unparsed_date)
|
6
|
+
end
|
7
|
+
|
8
|
+
def humanize
|
9
|
+
case self
|
10
|
+
when Date.today.prev_day
|
11
|
+
'yesterday'
|
12
|
+
when Date.today
|
13
|
+
'today'
|
14
|
+
when Date.today.succ
|
15
|
+
'tomorrow'
|
16
|
+
else
|
17
|
+
self.to_s
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
module Nin
|
2
|
+
module Integration
|
3
|
+
class Service
|
4
|
+
class Todoist < Service
|
5
|
+
def projects
|
6
|
+
@projects ||= Project.new(@client)
|
7
|
+
end
|
8
|
+
|
9
|
+
def items
|
10
|
+
@items ||= Item.new(@client)
|
11
|
+
end
|
12
|
+
|
13
|
+
class Project < Service
|
14
|
+
def all
|
15
|
+
@client
|
16
|
+
.sync
|
17
|
+
.read_resources(['projects'])
|
18
|
+
.fetch('projects').reduce({}) { |projects, p| projects.update(p["id"] => p["name"]) }
|
19
|
+
end
|
20
|
+
|
21
|
+
def add(project)
|
22
|
+
commands = [
|
23
|
+
{
|
24
|
+
"type": "project_add",
|
25
|
+
"temp_id": SecureRandom.uuid,
|
26
|
+
"uuid": SecureRandom.uuid,
|
27
|
+
"args": project
|
28
|
+
}
|
29
|
+
].to_json
|
30
|
+
|
31
|
+
@client.sync.write_resources(commands).fetch('temp_id_mapping').values.first
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class Item < Service
|
36
|
+
def all
|
37
|
+
data = @client.sync.read_resources(['projects', 'items'])
|
38
|
+
|
39
|
+
projects = data.fetch('projects').reduce({}) do |projects, p|
|
40
|
+
projects.update(p["id"] => p["name"])
|
41
|
+
end
|
42
|
+
|
43
|
+
data.fetch('items').reduce([]) do |tasks, t|
|
44
|
+
tasks << [
|
45
|
+
t["content"],
|
46
|
+
(t["due"] || {}).fetch('date', nil),
|
47
|
+
projects.fetch(t["project_id"]),
|
48
|
+
t["id"],
|
49
|
+
(t["checked"].to_i == 1)
|
50
|
+
]
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def add(item)
|
55
|
+
commands = [
|
56
|
+
{
|
57
|
+
"type": "item_add",
|
58
|
+
"temp_id": SecureRandom.uuid,
|
59
|
+
"uuid": SecureRandom.uuid,
|
60
|
+
"args": item
|
61
|
+
}
|
62
|
+
].to_json
|
63
|
+
|
64
|
+
@client.sync.write_resources(commands).fetch('temp_id_mapping').values.first
|
65
|
+
end
|
66
|
+
|
67
|
+
def update(items)
|
68
|
+
commands = items.ensure_array.map do |item|
|
69
|
+
{
|
70
|
+
"type": "item_update",
|
71
|
+
"uuid": SecureRandom.uuid,
|
72
|
+
"args": item
|
73
|
+
}
|
74
|
+
end.to_json
|
75
|
+
|
76
|
+
@client.sync.write_resources(commands)
|
77
|
+
end
|
78
|
+
|
79
|
+
def delete(ids)
|
80
|
+
commands = ids.ensure_array.map do |id|
|
81
|
+
{
|
82
|
+
"type": "item_delete",
|
83
|
+
"uuid": SecureRandom.uuid,
|
84
|
+
"args": { 'id': id }
|
85
|
+
}
|
86
|
+
end.to_json
|
87
|
+
|
88
|
+
@client.sync.write_resources(commands)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
module Nin
|
2
|
+
module Integration
|
3
|
+
class Synchronizer
|
4
|
+
class Todoist < Synchronizer
|
5
|
+
def sync(op, params = {})
|
6
|
+
@service = Integration::Service::Todoist.new(@client)
|
7
|
+
|
8
|
+
case op
|
9
|
+
when :read
|
10
|
+
sync_read(params)
|
11
|
+
when :add
|
12
|
+
sync_add(params)
|
13
|
+
when :edit
|
14
|
+
sync_edit(params)
|
15
|
+
when :edit_completed
|
16
|
+
sync_edit(params, :checked)
|
17
|
+
when :delete
|
18
|
+
sync_delete(params)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def sync_read(params)
|
25
|
+
items = params.fetch(:items)
|
26
|
+
next_id = params.fetch(:next_id)
|
27
|
+
|
28
|
+
id = next_id
|
29
|
+
synced_uids = @service.items.all.map do |t|
|
30
|
+
item = Item.new(id, *t)
|
31
|
+
|
32
|
+
if existing_item = items.find_by(:uid, item.uid)
|
33
|
+
existing_item.edit(item.desc, item.date, item.tags, item.completed)
|
34
|
+
else
|
35
|
+
items << item
|
36
|
+
id += 1
|
37
|
+
end
|
38
|
+
|
39
|
+
item.uid
|
40
|
+
end
|
41
|
+
|
42
|
+
unsynced_uids = items.where(:uid) { |item_uid| !item_uid.nil? }.map(&:uid) - synced_uids
|
43
|
+
unsynced_uids.each do |uid|
|
44
|
+
item = items.find_by(:uid, uid)
|
45
|
+
t = @client.items.get(uid)
|
46
|
+
|
47
|
+
items.delete(item) and next if t.nil? || t.fetch("is_deleted") == 1
|
48
|
+
item.completed = (t.fetch("checked") == 1)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def sync_add(params)
|
53
|
+
item = params.fetch(:item)
|
54
|
+
|
55
|
+
payload = _get_item_write_payload(item)
|
56
|
+
|
57
|
+
uid = @service.items.add(payload)
|
58
|
+
item.uid = uid
|
59
|
+
end
|
60
|
+
|
61
|
+
def sync_edit(params, type = :full)
|
62
|
+
payload = params.fetch(:items).ensure_array.map do |item|
|
63
|
+
item_payload = if type == :checked
|
64
|
+
{ checked: item.completed }
|
65
|
+
else
|
66
|
+
_get_item_write_payload(item)
|
67
|
+
end
|
68
|
+
item_payload[:id] = item.uid
|
69
|
+
|
70
|
+
item_payload
|
71
|
+
end
|
72
|
+
|
73
|
+
@service.items.update(payload)
|
74
|
+
end
|
75
|
+
|
76
|
+
def sync_delete(params)
|
77
|
+
@service.items.delete(params.fetch(:ids))
|
78
|
+
end
|
79
|
+
|
80
|
+
def _get_item_write_payload(item)
|
81
|
+
payload = {
|
82
|
+
content: item.desc,
|
83
|
+
due: { date: item.date },
|
84
|
+
checked: item.completed
|
85
|
+
}
|
86
|
+
|
87
|
+
if project_name = item.tags.first
|
88
|
+
@projects ||= @service.projects.all
|
89
|
+
@project_names ||= @projects.values
|
90
|
+
|
91
|
+
_new_project = false
|
92
|
+
project_id = unless @project_names.include?(project_name)
|
93
|
+
@service.projects.add(name: project_name)
|
94
|
+
_new_project = true
|
95
|
+
else
|
96
|
+
@projects.find { |k, v| v == project_name }.first
|
97
|
+
end
|
98
|
+
|
99
|
+
if _new_project
|
100
|
+
@projects[project_id] = project_name
|
101
|
+
@project_names.push(project_name)
|
102
|
+
end
|
103
|
+
|
104
|
+
payload[:project_id] = project_id
|
105
|
+
end
|
106
|
+
|
107
|
+
payload
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Nin
|
2
|
+
module Integration
|
3
|
+
module Todoist
|
4
|
+
class Client
|
5
|
+
BASE_URI = 'https://api.todoist.com/sync/v8'.freeze
|
6
|
+
|
7
|
+
def initialize(token)
|
8
|
+
@token = token
|
9
|
+
end
|
10
|
+
|
11
|
+
def sync
|
12
|
+
@sync ||= Client::Sync.new(@token)
|
13
|
+
end
|
14
|
+
|
15
|
+
def projects
|
16
|
+
@projects ||= Client::Project.new(@token)
|
17
|
+
end
|
18
|
+
|
19
|
+
def items
|
20
|
+
@items ||= Client::Item.new(@token)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Nin
|
2
|
+
module Integration
|
3
|
+
module Todoist
|
4
|
+
class Client
|
5
|
+
class Item < BaseClient
|
6
|
+
API_URI = "#{BASE_URI}/items".freeze
|
7
|
+
|
8
|
+
def get(id)
|
9
|
+
res = HTTP.headers(accept: "application/json")
|
10
|
+
.get("#{API_URI}/get", params: { token: @token, item_id: id })
|
11
|
+
|
12
|
+
JSON.parse(res.body.to_s)['item']
|
13
|
+
end
|
14
|
+
|
15
|
+
def add(item)
|
16
|
+
res = HTTP.headers(accept: "application/json")
|
17
|
+
.get("#{API_URI}/add", params: { token: @token, **item })
|
18
|
+
|
19
|
+
JSON.parse(res.body.to_s)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Nin
|
2
|
+
module Integration
|
3
|
+
module Todoist
|
4
|
+
class Client
|
5
|
+
class Project < BaseClient
|
6
|
+
API_URI = "#{BASE_URI}/projects".freeze
|
7
|
+
|
8
|
+
def get(id)
|
9
|
+
res = HTTP.headers(accept: "application/json")
|
10
|
+
.get("#{API_URI}/get", params: { token: @token, project_id: id })
|
11
|
+
|
12
|
+
JSON.parse(res.body.to_s)['project']
|
13
|
+
end
|
14
|
+
#
|
15
|
+
# def add(project)
|
16
|
+
# res = HTTP.headers(accept: "application/json")
|
17
|
+
# .get("#{API_URI}/add", params: { token: @token, **project })
|
18
|
+
#
|
19
|
+
# JSON.parse(res.body.to_s)
|
20
|
+
# end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Nin
|
2
|
+
module Integration
|
3
|
+
module Todoist
|
4
|
+
class Client
|
5
|
+
class Sync < BaseClient
|
6
|
+
API_URI = "#{BASE_URI}/sync".freeze
|
7
|
+
|
8
|
+
def read_resources(resource_types = ['all'], sync_token = '*')
|
9
|
+
res = HTTP.headers(accept: "application/json")
|
10
|
+
.get("#{BASE_URI}/sync", params: { token: @token,
|
11
|
+
sync_token: sync_token,
|
12
|
+
resource_types: resource_types.to_json })
|
13
|
+
|
14
|
+
data = JSON.parse(res.body.to_s)
|
15
|
+
unless resource_types == ['all']
|
16
|
+
data.slice(*resource_types)
|
17
|
+
else
|
18
|
+
data
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def write_resources(commands)
|
23
|
+
res = HTTP.headers(accept: "application/json")
|
24
|
+
.get("#{BASE_URI}/sync", params: { token: @token,
|
25
|
+
commands: commands })
|
26
|
+
|
27
|
+
JSON.parse(res.body.to_s)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/lib/nin/item.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
module Nin
|
2
|
+
class Item
|
3
|
+
attr_accessor :id, :desc, :date, :tags, :uid, :completed, :archived
|
4
|
+
|
5
|
+
def initialize(id,
|
6
|
+
desc,
|
7
|
+
date = Date.today,
|
8
|
+
tags = [],
|
9
|
+
uid = nil,
|
10
|
+
completed = false,
|
11
|
+
archived = false,
|
12
|
+
formatter = Presenter::ItemPresenter)
|
13
|
+
|
14
|
+
@id = id
|
15
|
+
@desc = desc
|
16
|
+
@date = Date.parse_or_return(date) || Date.today
|
17
|
+
@tags = tags.ensure_array
|
18
|
+
@uid = uid
|
19
|
+
@completed = completed
|
20
|
+
@archived = archived
|
21
|
+
@formatter = formatter.new(self)
|
22
|
+
end
|
23
|
+
|
24
|
+
def edit(desc, date = nil, tags = [], completed = nil)
|
25
|
+
self.desc = desc
|
26
|
+
self.date = Date.parse_or_return(date) unless date.nil?
|
27
|
+
self.tags.concat(tags.ensure_array).uniq!
|
28
|
+
self.completed = completed.nil? ? self.completed : completed
|
29
|
+
end
|
30
|
+
|
31
|
+
def toggle_completed!
|
32
|
+
@completed = !@completed
|
33
|
+
end
|
34
|
+
|
35
|
+
def toggle_archived!
|
36
|
+
@archived = !@archived
|
37
|
+
end
|
38
|
+
|
39
|
+
def completed?
|
40
|
+
@completed
|
41
|
+
end
|
42
|
+
|
43
|
+
def archived?
|
44
|
+
@archived
|
45
|
+
end
|
46
|
+
|
47
|
+
def past?
|
48
|
+
@date < Date.today
|
49
|
+
end
|
50
|
+
|
51
|
+
def today?
|
52
|
+
@date == Date.today
|
53
|
+
end
|
54
|
+
|
55
|
+
def to_s
|
56
|
+
@formatter.call
|
57
|
+
end
|
58
|
+
|
59
|
+
def to_h
|
60
|
+
{
|
61
|
+
'id' => id,
|
62
|
+
'desc' => desc,
|
63
|
+
'tags' => tags,
|
64
|
+
'completed' => completed,
|
65
|
+
'archived' => archived,
|
66
|
+
'uid' => uid
|
67
|
+
}
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|