nin 1.0.1 → 1.2.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/README.md +10 -1
- data/Rakefile +1 -0
- data/bin/nin +6 -1
- data/lib/nin.rb +23 -3
- data/lib/nin/command.rb +33 -6
- data/lib/nin/error.rb +1 -0
- data/lib/nin/extensions/array_extensions.rb +4 -0
- data/lib/nin/extensions/nil_class_extensions.rb +5 -0
- data/lib/nin/extensions/object_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 +9 -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 +10 -6
- data/lib/nin/parser.rb +7 -5
- data/lib/nin/todo.rb +43 -10
- data/lib/nin/version.rb +1 -1
- data/nin.gemspec +1 -0
- metadata +31 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 00b4da872337ca4ab8b11be39d559f398f029e617683437dde721bc61dd10517
|
|
4
|
+
data.tar.gz: 9230fd0f962c4a3a3e6e3ce38059508e404e66deec956406c6147006642463ac
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8e5591b0e4cd06c15e5248089b87c52d77f4483b033d97eab6c37be16b283314251e556059360a15c8df0cede4cf38757a964d28e49e77f2cdc73e88f80d5873
|
|
7
|
+
data.tar.gz: 614a0cdd1365036d91019d086c128884fd789bbe4b685539f9e82ab16a142c0b4eea2fed45b8dbfe442efaa5ba526993444411226b502f4991ba09f33dd29069
|
data/README.md
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
- Simple, intuitive, and easy-to-use CLI
|
|
16
16
|
- Currently supports: listing, adding, editing, deleting, completing,
|
|
17
17
|
archiving, prioritizing, and analyzing todo items
|
|
18
|
+
- Integration and synchronization with [Todoist](https://todoist.com/) (With potential integration with more platforms)
|
|
18
19
|
- Smart colored output
|
|
19
20
|
- Uses YAML for storage by default (There's the option to add other stores but no configuration for it, yet)
|
|
20
21
|
- Modular code covered by unit tests
|
|
@@ -35,7 +36,7 @@ USAGE:
|
|
|
35
36
|
nin COMMAND [arguments...]
|
|
36
37
|
|
|
37
38
|
COMMANDS:
|
|
38
|
-
l | list [a]
|
|
39
|
+
l | list [a|l] List all unarchived todos. Pass optional argument `a` to list all todos or `l` to list local todos only
|
|
39
40
|
a | add desc Add a todo. Prepend due date by a @. Prepend each tag by a \#
|
|
40
41
|
e | edit id desc Edit a todo. Prepend due date by a @. Prepend each tag by a \#
|
|
41
42
|
p | prioritize id step Prioritize a todo by either a positive or negative step within its date group
|
|
@@ -72,6 +73,14 @@ COMMANDS:
|
|
|
72
73
|
the need to call `nin` every time and can be triggered by calling `nin i` or
|
|
73
74
|
`nin repl`
|
|
74
75
|
|
|
76
|
+
## Integration
|
|
77
|
+
|
|
78
|
+
### Todoist
|
|
79
|
+
|
|
80
|
+
For Todoist integration, two environment variables must be set:
|
|
81
|
+
- `NIN_INTEGRATION_CLIENT=todoist`
|
|
82
|
+
- `NIN_INTEGRATION_CLIENT_TOKEN=token`. Token can be found in your [integration settings page](https://todoist.com/prefs/integrations)
|
|
83
|
+
|
|
75
84
|
## Development
|
|
76
85
|
|
|
77
86
|
- Install a recent version of `Ruby` and `Bundler`
|
data/Rakefile
CHANGED
data/bin/nin
CHANGED
|
@@ -2,8 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative '../lib/nin'
|
|
4
4
|
|
|
5
|
+
config = {
|
|
6
|
+
integrated_client: ENV['NIN_INTEGRATION_CLIENT'],
|
|
7
|
+
integrated_client_token: ENV['NIN_INTEGRATION_CLIENT_TOKEN']
|
|
8
|
+
}
|
|
9
|
+
|
|
5
10
|
begin
|
|
6
|
-
Nin::Command.new(ARGV[0], ARGV[1..-1]).call
|
|
11
|
+
Nin::Command.new(ARGV[0], ARGV[1..-1], config).call
|
|
7
12
|
rescue Nin::ItemNotFoundError
|
|
8
13
|
puts "Todo item does not exist"
|
|
9
14
|
rescue Nin::EmptyCommandArgumentError
|
data/lib/nin.rb
CHANGED
|
@@ -1,11 +1,21 @@
|
|
|
1
1
|
require 'psych'
|
|
2
|
-
require 'chronic'
|
|
3
|
-
require 'colored2'
|
|
4
2
|
require 'date'
|
|
5
3
|
require 'delegate'
|
|
6
4
|
require 'readline'
|
|
5
|
+
require 'securerandom'
|
|
6
|
+
|
|
7
|
+
require 'chronic'
|
|
8
|
+
require 'colored2'
|
|
9
|
+
require 'http'
|
|
10
|
+
|
|
11
|
+
if RUBY_PLATFORM =~ /mri|mingw|x66_mingw/i
|
|
12
|
+
require 'byebug'
|
|
13
|
+
end
|
|
7
14
|
|
|
8
15
|
require_relative 'nin/version'
|
|
16
|
+
require_relative 'nin/extensions/object_extensions'
|
|
17
|
+
require_relative 'nin/extensions/nil_class_extensions'
|
|
18
|
+
require_relative 'nin/extensions/array_extensions'
|
|
9
19
|
require_relative 'nin/extensions/array_extensions'
|
|
10
20
|
require_relative 'nin/extensions/string_extensions'
|
|
11
21
|
require_relative 'nin/extensions/integer_extensions'
|
|
@@ -14,7 +24,17 @@ require_relative 'nin/error'
|
|
|
14
24
|
require_relative 'nin/yaml_store'
|
|
15
25
|
require_relative 'nin/parser'
|
|
16
26
|
require_relative 'nin/item'
|
|
17
|
-
require_relative 'nin/todo'
|
|
18
27
|
require_relative 'nin/presenters/item_presenter'
|
|
19
28
|
require_relative 'nin/presenters/todo_presenter'
|
|
29
|
+
require_relative 'nin/integration/todoist/client'
|
|
30
|
+
require_relative 'nin/integration/todoist/client/base_client'
|
|
31
|
+
require_relative 'nin/integration/todoist/client/sync'
|
|
32
|
+
require_relative 'nin/integration/todoist/client/project'
|
|
33
|
+
require_relative 'nin/integration/todoist/client/item'
|
|
34
|
+
require_relative 'nin/integration/service'
|
|
35
|
+
require_relative 'nin/integration/service/todoist'
|
|
36
|
+
require_relative 'nin/integration/todoist'
|
|
37
|
+
require_relative 'nin/integration/synchronizer'
|
|
38
|
+
require_relative 'nin/integration/synchronizer/todoist'
|
|
39
|
+
require_relative 'nin/todo'
|
|
20
40
|
require_relative 'nin/command'
|
data/lib/nin/command.rb
CHANGED
|
@@ -2,10 +2,11 @@ module Nin
|
|
|
2
2
|
class Command
|
|
3
3
|
COMMANDS_WITH_ARGS = %w(a e c ac d)
|
|
4
4
|
|
|
5
|
-
def initialize(command, args)
|
|
5
|
+
def initialize(command, args, config = {})
|
|
6
6
|
@command = command
|
|
7
7
|
@args = args
|
|
8
|
-
@
|
|
8
|
+
@config = config
|
|
9
|
+
@todo = Todo.new(collect_config, collect_options)
|
|
9
10
|
|
|
10
11
|
validate_args
|
|
11
12
|
end
|
|
@@ -15,10 +16,10 @@ module Nin
|
|
|
15
16
|
when 'l', 'list'
|
|
16
17
|
@todo.list
|
|
17
18
|
when 'a', 'add'
|
|
18
|
-
desc, date, tags =
|
|
19
|
+
desc, date, tags = parse_item_desc(@args.join(' '))
|
|
19
20
|
@todo.add(desc, date, tags)
|
|
20
21
|
when 'e', 'edit'
|
|
21
|
-
desc, date, tags =
|
|
22
|
+
desc, date, tags = parse_item_desc(@args[1..-1].join(' '))
|
|
22
23
|
@todo.edit(@args[0].to_i, desc, date, tags)
|
|
23
24
|
when 'p', 'prioritize'
|
|
24
25
|
@todo.prioritize(@args[0].to_i, @args[1].to_i)
|
|
@@ -40,7 +41,7 @@ module Nin
|
|
|
40
41
|
puts "NAME:\n\tnin - a simple, full-featured command line todo app"
|
|
41
42
|
puts "\nUSAGE:\n\tnin COMMAND [arguments...]"
|
|
42
43
|
puts "\nCOMMANDS:"
|
|
43
|
-
puts "\tl | list [a]
|
|
44
|
+
puts "\tl | list [a|l] List all unarchived todos. Pass optional argument `a` to list all todos or `l` to list local todos only"
|
|
44
45
|
puts "\ta | add desc Add a todo. Prepend due date by a @. Prepend each tag by a \\#"
|
|
45
46
|
puts "\te | edit id desc Edit a todo. Prepend due date by a @. Prepend each tag by a \\#"
|
|
46
47
|
puts "\tp | prioritize id step Prioritize a todo by either a positive or negative step within its date group"
|
|
@@ -56,16 +57,42 @@ module Nin
|
|
|
56
57
|
|
|
57
58
|
private
|
|
58
59
|
|
|
60
|
+
def collect_config
|
|
61
|
+
config = { store: YamlStore.new }
|
|
62
|
+
|
|
63
|
+
_client_name = @config.fetch(:integrated_client, nil)
|
|
64
|
+
_client_credentials = @config.fetch(:integrated_client_token, nil)
|
|
65
|
+
if _client_name && _client_credentials
|
|
66
|
+
_client_klass = Object.const_get("Nin::Integration::#{_client_name.capitalize}::Client")
|
|
67
|
+
_synchronizer_klass = Object.const_get("Nin::Integration::Synchronizer::#{_client_name.capitalize}")
|
|
68
|
+
|
|
69
|
+
config[:integration_syncrhonizer] = _synchronizer_klass.new(_client_klass.new(_client_credentials))
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
config
|
|
73
|
+
end
|
|
74
|
+
|
|
59
75
|
def collect_options
|
|
60
|
-
options = { archived: false }
|
|
76
|
+
options = { archived: false, local: false }
|
|
61
77
|
|
|
62
78
|
if @command == 'l' && @args[0] == 'a'
|
|
63
79
|
options[:archived] = true
|
|
80
|
+
elsif @command == 'l' && @args[0] == 'l'
|
|
81
|
+
options[:local] = true
|
|
64
82
|
end
|
|
65
83
|
|
|
66
84
|
options
|
|
67
85
|
end
|
|
68
86
|
|
|
87
|
+
def parse_item_desc(desc)
|
|
88
|
+
begin
|
|
89
|
+
Parser.new(desc).call
|
|
90
|
+
rescue InvalidDateFormatError
|
|
91
|
+
puts "Invalid date format."
|
|
92
|
+
exit
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
69
96
|
def validate_args
|
|
70
97
|
COMMANDS_WITH_ARGS.each do |command|
|
|
71
98
|
raise EmptyCommandArgumentError if @command == command && @args.empty?
|
data/lib/nin/error.rb
CHANGED
|
@@ -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
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
module Nin
|
|
2
2
|
class Item
|
|
3
|
-
attr_accessor :id, :desc, :date, :tags, :completed, :archived
|
|
3
|
+
attr_accessor :id, :desc, :date, :tags, :uid, :completed, :archived
|
|
4
4
|
|
|
5
5
|
def initialize(id,
|
|
6
6
|
desc,
|
|
7
7
|
date = Date.today,
|
|
8
8
|
tags = [],
|
|
9
|
+
uid = nil,
|
|
9
10
|
completed = false,
|
|
10
11
|
archived = false,
|
|
11
12
|
formatter = Presenter::ItemPresenter)
|
|
@@ -13,16 +14,18 @@ module Nin
|
|
|
13
14
|
@id = id
|
|
14
15
|
@desc = desc
|
|
15
16
|
@date = Date.parse_or_return(date) || Date.today
|
|
16
|
-
@tags = tags
|
|
17
|
+
@tags = tags.ensure_array
|
|
18
|
+
@uid = uid
|
|
17
19
|
@completed = completed
|
|
18
20
|
@archived = archived
|
|
19
21
|
@formatter = formatter.new(self)
|
|
20
22
|
end
|
|
21
23
|
|
|
22
|
-
def edit(desc, date = nil, tags = [])
|
|
24
|
+
def edit(desc, date = nil, tags = [], completed = nil)
|
|
23
25
|
self.desc = desc
|
|
24
|
-
self.date = date unless date.nil?
|
|
25
|
-
self.tags.concat(tags)
|
|
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
|
|
26
29
|
end
|
|
27
30
|
|
|
28
31
|
def toggle_completed!
|
|
@@ -59,7 +62,8 @@ module Nin
|
|
|
59
62
|
'desc' => desc,
|
|
60
63
|
'tags' => tags,
|
|
61
64
|
'completed' => completed,
|
|
62
|
-
'archived' => archived
|
|
65
|
+
'archived' => archived,
|
|
66
|
+
'uid' => uid
|
|
63
67
|
}
|
|
64
68
|
end
|
|
65
69
|
end
|
data/lib/nin/parser.rb
CHANGED
|
@@ -2,18 +2,16 @@ module Nin
|
|
|
2
2
|
class Parser
|
|
3
3
|
def initialize(desc)
|
|
4
4
|
@desc = desc
|
|
5
|
-
@date = extract_date
|
|
6
|
-
@tags = extract_tags
|
|
7
5
|
end
|
|
8
6
|
|
|
9
7
|
def call
|
|
10
|
-
[@desc,
|
|
8
|
+
[@desc, extract_date, extract_tags]
|
|
11
9
|
end
|
|
12
10
|
|
|
13
11
|
private
|
|
14
12
|
|
|
15
13
|
def extract_date
|
|
16
|
-
date_pattern = /@[A-Z0-9
|
|
14
|
+
date_pattern = /@[A-Z0-9,-\/]+/i
|
|
17
15
|
date = @desc.scan(date_pattern).last
|
|
18
16
|
|
|
19
17
|
return nil if date.nil?
|
|
@@ -21,7 +19,11 @@ module Nin
|
|
|
21
19
|
date.gsub!('@', '')
|
|
22
20
|
strip_tags(date_pattern)
|
|
23
21
|
|
|
24
|
-
|
|
22
|
+
begin
|
|
23
|
+
Chronic.parse(date).to_date
|
|
24
|
+
rescue NoMethodError
|
|
25
|
+
raise InvalidDateFormatError
|
|
26
|
+
end
|
|
25
27
|
end
|
|
26
28
|
|
|
27
29
|
def extract_tags
|
data/lib/nin/todo.rb
CHANGED
|
@@ -3,13 +3,16 @@ module Nin
|
|
|
3
3
|
attr_accessor :items
|
|
4
4
|
attr_reader :store
|
|
5
5
|
|
|
6
|
-
def initialize(
|
|
7
|
-
@store
|
|
8
|
-
@
|
|
9
|
-
@
|
|
6
|
+
def initialize(config, options = {})
|
|
7
|
+
@store = config.fetch(:store)
|
|
8
|
+
@integration_syncrhonizer = config.fetch(:integration_syncrhonizer, nil)
|
|
9
|
+
@options = options
|
|
10
|
+
@items = load_items_sorted
|
|
10
11
|
end
|
|
11
12
|
|
|
12
13
|
def list
|
|
14
|
+
sync(:read, true, items: @items, next_id: next_id) unless @options[:local]
|
|
15
|
+
|
|
13
16
|
items_to_list = if @options[:archived]
|
|
14
17
|
@items
|
|
15
18
|
else
|
|
@@ -20,16 +23,20 @@ module Nin
|
|
|
20
23
|
end
|
|
21
24
|
|
|
22
25
|
def add(desc, date, tags)
|
|
23
|
-
|
|
26
|
+
item = Item.new(next_id, desc, date, tags)
|
|
27
|
+
@items << item
|
|
28
|
+
|
|
29
|
+
fork_sync(:add, true, item: item)
|
|
24
30
|
|
|
25
31
|
@store.write(to_hash)
|
|
26
32
|
end
|
|
27
33
|
|
|
28
34
|
def edit(id, desc, date, tags)
|
|
29
35
|
item = find_by_id(id)
|
|
30
|
-
|
|
31
36
|
item.edit(desc, date, tags)
|
|
32
37
|
|
|
38
|
+
fork_sync(:edit, false, items: item)
|
|
39
|
+
|
|
33
40
|
@store.write(to_hash)
|
|
34
41
|
end
|
|
35
42
|
|
|
@@ -51,11 +58,15 @@ module Nin
|
|
|
51
58
|
end
|
|
52
59
|
|
|
53
60
|
def complete(*ids)
|
|
54
|
-
ids.
|
|
61
|
+
items = ids.map do |id|
|
|
55
62
|
item = find_by_id(id.to_i)
|
|
56
63
|
item.toggle_completed!
|
|
64
|
+
|
|
65
|
+
item
|
|
57
66
|
end
|
|
58
67
|
|
|
68
|
+
fork_sync(:edit_completed, false, items: items)
|
|
69
|
+
|
|
59
70
|
@store.write(to_hash)
|
|
60
71
|
end
|
|
61
72
|
|
|
@@ -70,16 +81,18 @@ module Nin
|
|
|
70
81
|
|
|
71
82
|
def delete_archived
|
|
72
83
|
delete(*archived_items.map(&:id))
|
|
73
|
-
|
|
74
|
-
reset_item_indices!
|
|
75
84
|
end
|
|
76
85
|
|
|
77
86
|
def delete(*ids)
|
|
78
|
-
ids.
|
|
87
|
+
uids = ids.map do |id|
|
|
79
88
|
item = find_by_id(id.to_i)
|
|
80
89
|
@items.delete(item)
|
|
90
|
+
|
|
91
|
+
item.uid
|
|
81
92
|
end
|
|
82
93
|
|
|
94
|
+
fork_sync(:delete, false, ids: uids)
|
|
95
|
+
|
|
83
96
|
reset_item_indices!
|
|
84
97
|
end
|
|
85
98
|
|
|
@@ -92,6 +105,25 @@ module Nin
|
|
|
92
105
|
end
|
|
93
106
|
end
|
|
94
107
|
|
|
108
|
+
def sync(op, store_write = false, params = {})
|
|
109
|
+
return unless @integration_syncrhonizer
|
|
110
|
+
|
|
111
|
+
@integration_syncrhonizer.sync(op, params)
|
|
112
|
+
reset_item_indices! if store_write
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def fork_sync(op, store_write = false, params = {})
|
|
116
|
+
return unless @integration_syncrhonizer
|
|
117
|
+
|
|
118
|
+
pid = fork do
|
|
119
|
+
@integration_syncrhonizer.sync(op, params)
|
|
120
|
+
@store.write(to_hash) if store_write
|
|
121
|
+
|
|
122
|
+
exit
|
|
123
|
+
end
|
|
124
|
+
Process.detach(pid)
|
|
125
|
+
end
|
|
126
|
+
|
|
95
127
|
private
|
|
96
128
|
|
|
97
129
|
def load_items_sorted
|
|
@@ -107,6 +139,7 @@ module Nin
|
|
|
107
139
|
item.fetch('desc'),
|
|
108
140
|
date,
|
|
109
141
|
item.fetch('tags'),
|
|
142
|
+
item.fetch('uid', nil),
|
|
110
143
|
item.fetch('completed'),
|
|
111
144
|
item.fetch('archived'))
|
|
112
145
|
end
|
data/lib/nin/version.rb
CHANGED
data/nin.gemspec
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: nin
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0
|
|
4
|
+
version: 1.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ahmed Saleh
|
|
8
|
-
autorequire:
|
|
8
|
+
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2020-07-26 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: chronic
|
|
@@ -38,6 +38,20 @@ dependencies:
|
|
|
38
38
|
- - ">="
|
|
39
39
|
- !ruby/object:Gem::Version
|
|
40
40
|
version: '0'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: http
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - ">="
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '0'
|
|
48
|
+
type: :runtime
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - ">="
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '0'
|
|
41
55
|
- !ruby/object:Gem::Dependency
|
|
42
56
|
name: rake
|
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -87,7 +101,19 @@ files:
|
|
|
87
101
|
- lib/nin/extensions/array_extensions.rb
|
|
88
102
|
- lib/nin/extensions/date_extensions.rb
|
|
89
103
|
- lib/nin/extensions/integer_extensions.rb
|
|
104
|
+
- lib/nin/extensions/nil_class_extensions.rb
|
|
105
|
+
- lib/nin/extensions/object_extensions.rb
|
|
90
106
|
- lib/nin/extensions/string_extensions.rb
|
|
107
|
+
- lib/nin/integration/service.rb
|
|
108
|
+
- lib/nin/integration/service/todoist.rb
|
|
109
|
+
- lib/nin/integration/synchronizer.rb
|
|
110
|
+
- lib/nin/integration/synchronizer/todoist.rb
|
|
111
|
+
- lib/nin/integration/todoist.rb
|
|
112
|
+
- lib/nin/integration/todoist/client.rb
|
|
113
|
+
- lib/nin/integration/todoist/client/base_client.rb
|
|
114
|
+
- lib/nin/integration/todoist/client/item.rb
|
|
115
|
+
- lib/nin/integration/todoist/client/project.rb
|
|
116
|
+
- lib/nin/integration/todoist/client/sync.rb
|
|
91
117
|
- lib/nin/item.rb
|
|
92
118
|
- lib/nin/parser.rb
|
|
93
119
|
- lib/nin/presenters/item_presenter.rb
|
|
@@ -100,7 +126,7 @@ homepage: https://github.com/aonemd/nin
|
|
|
100
126
|
licenses:
|
|
101
127
|
- MIT
|
|
102
128
|
metadata: {}
|
|
103
|
-
post_install_message:
|
|
129
|
+
post_install_message:
|
|
104
130
|
rdoc_options: []
|
|
105
131
|
require_paths:
|
|
106
132
|
- lib
|
|
@@ -116,7 +142,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
116
142
|
version: '0'
|
|
117
143
|
requirements: []
|
|
118
144
|
rubygems_version: 3.0.3
|
|
119
|
-
signing_key:
|
|
145
|
+
signing_key:
|
|
120
146
|
specification_version: 4
|
|
121
147
|
summary: A command line todo app that provides an easy to use CLI for everyday todo
|
|
122
148
|
management
|