nin 1.0.1 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|