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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d8d6d3094654b2846aad88459d296d5f80806095ed1e4c904ffac4de3d0c06d9
4
- data.tar.gz: 3f8b51f4d5cbfe4458866bb2cf2298f3ebc953de04b69ac0381fe3ca859ddef4
3
+ metadata.gz: 00b4da872337ca4ab8b11be39d559f398f029e617683437dde721bc61dd10517
4
+ data.tar.gz: 9230fd0f962c4a3a3e6e3ce38059508e404e66deec956406c6147006642463ac
5
5
  SHA512:
6
- metadata.gz: a8f6db899bad0219bb28b7ed1d86c6e948b29ca28d4b017106f7ffc379bfefc82794a2dca45e8872468f3be556a31432d5e5b76004b91498239d428f56ae9491
7
- data.tar.gz: d81b09f774728f8ec0f1df871b7cab86c3049e8fa372cfea2fda0fa88f812abc276c576fe79f6a621c70c58bee3489d3f9d2d67a650ee8c762251212713a1801
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] List all unarchived todos. Pass optional argument `a` to list all todos
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
@@ -8,6 +8,7 @@ end
8
8
 
9
9
  task :default => :test
10
10
 
11
+ desc "Make a new build and publish it to RubyGems"
11
12
  task :publish do
12
13
  build_name_and_version = "nin-#{Nin::VERSION}.gem"
13
14
 
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'
@@ -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
- @todo = Todo.new(YamlStore.new, collect_options)
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 = Parser.new(@args.join(' ')).call
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 = Parser.new(@args[1..-1].join(' ')).call
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] List all unarchived todos. Pass optional argument `a` to list all todos"
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?
@@ -1,4 +1,5 @@
1
1
  module Nin
2
2
  class ItemNotFoundError < StandardError; end
3
3
  class EmptyCommandArgumentError < StandardError; end
4
+ class InvalidDateFormatError < StandardError; end
4
5
  end
@@ -34,4 +34,8 @@ class Array
34
34
  block_given? ? block.call(key_eval) : (key_eval == val)
35
35
  end
36
36
  end
37
+
38
+ def ensure_array
39
+ self.to_a
40
+ end
37
41
  end
@@ -0,0 +1,5 @@
1
+ class NilClass
2
+ def ensure_array
3
+ self.to_a
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class Object
2
+ def ensure_array
3
+ [self]
4
+ end
5
+ end
@@ -0,0 +1,9 @@
1
+ module Nin
2
+ module Integration
3
+ class Service
4
+ def initialize(client)
5
+ @client = client
6
+ end
7
+ end
8
+ end
9
+ 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,9 @@
1
+ module Nin
2
+ module Integration
3
+ class Synchronizer
4
+ def initialize(client)
5
+ @client = client
6
+ end
7
+ end
8
+ end
9
+ 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,5 @@
1
+ module Nin
2
+ module Integration
3
+ module Todoist; end;
4
+ end
5
+ 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,13 @@
1
+ module Nin
2
+ module Integration
3
+ module Todoist
4
+ class Client
5
+ class BaseClient
6
+ def initialize(token)
7
+ @token = token
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
13
+ 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
@@ -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
@@ -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, @date, @tags]
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.,-]+/i
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
- Chronic.parse(date).strftime('%Y-%m-%d')
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
@@ -3,13 +3,16 @@ module Nin
3
3
  attr_accessor :items
4
4
  attr_reader :store
5
5
 
6
- def initialize(store = YamlStore.new, options = {})
7
- @store = store
8
- @options = options
9
- @items = load_items_sorted
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
- @items << Item.new(next_id, desc, date, tags)
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.each do |id|
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.each do |id|
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
@@ -1,3 +1,3 @@
1
1
  module Nin
2
- VERSION='1.0.1'
2
+ VERSION='1.2.0'
3
3
  end
@@ -20,6 +20,7 @@ Gem::Specification.new do |gem|
20
20
 
21
21
  gem.add_dependency 'chronic'
22
22
  gem.add_dependency 'colored2'
23
+ gem.add_dependency 'http'
23
24
 
24
25
  gem.add_development_dependency 'rake'
25
26
  gem.add_development_dependency 'minitest'
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.1
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: 2019-12-03 00:00:00.000000000 Z
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