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 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