nin 0.0.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +4 -0
- data/.travis.yml +5 -0
- data/Gemfile +9 -0
- data/LICENSE +21 -0
- data/README.md +112 -0
- data/Rakefile +17 -0
- data/bin/nin +17 -0
- data/demo.gif +0 -0
- data/lib/nin.rb +38 -57
- data/lib/nin/command.rb +116 -0
- data/lib/nin/error.rb +5 -0
- data/lib/nin/extensions/array_extensions.rb +41 -0
- data/lib/nin/extensions/date_extensions.rb +20 -0
- data/lib/nin/extensions/integer_extensions.rb +5 -0
- data/lib/nin/extensions/nil_class_extensions.rb +5 -0
- data/lib/nin/extensions/object_extensions.rb +5 -0
- data/lib/nin/extensions/string_extensions.rb +5 -0
- data/lib/nin/integration/service.rb +9 -0
- data/lib/nin/integration/service/todoist.rb +94 -0
- data/lib/nin/integration/synchronizer.rb +12 -0
- data/lib/nin/integration/synchronizer/todoist.rb +112 -0
- data/lib/nin/integration/todoist.rb +5 -0
- data/lib/nin/integration/todoist/client.rb +25 -0
- data/lib/nin/integration/todoist/client/base_client.rb +13 -0
- data/lib/nin/integration/todoist/client/item.rb +25 -0
- data/lib/nin/integration/todoist/client/project.rb +25 -0
- data/lib/nin/integration/todoist/client/sync.rb +33 -0
- data/lib/nin/item.rb +70 -0
- data/lib/nin/parser.rb +43 -0
- data/lib/nin/presenters/item_presenter.rb +53 -0
- data/lib/nin/presenters/todo_presenter.rb +17 -0
- data/lib/nin/todo.rb +214 -0
- data/lib/nin/version.rb +3 -0
- data/lib/nin/yaml_store.rb +31 -0
- data/nin.gemspec +27 -0
- metadata +117 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 39f9e7a10bb0b9d4433ec8e048f0f98a86b69c446df00b87f60011ed51656be2
|
4
|
+
data.tar.gz: f531e76fab7c0dee8883f8f05bf4f5b4f2ce8ecd8322203dac43bb4ecb6dd72b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 36dd67aa3390451a0c127c3fcfa8c053ba8976ce7c24da2c4a822c243dfdb6b5b70a2639e222e0b6378ea4f4bd3e1696a0ebb98025808ec7ff0bf95274852c27
|
7
|
+
data.tar.gz: 56953c2a4da2d7eab114f35fc195a0e9415958737b1dc8133edf658cf1cbe9c4f26ba1f903223ca861457b9da7e3230a2f715c1faace7a4e7a4e2de23e988995
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2018 Ahmed Saleh
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
<div align="center">
|
2
|
+
<h1>
|
3
|
+
nin
|
4
|
+
</h1>
|
5
|
+
|
6
|
+
A simple, full-featured command line todo app
|
7
|
+
|
8
|
+

|
9
|
+
</div>
|
10
|
+
|
11
|
+
[](https://travis-ci.com/aonemd/nin)
|
12
|
+
|
13
|
+
## Features
|
14
|
+
|
15
|
+
- Simple, intuitive, and easy-to-use CLI
|
16
|
+
- Currently supports: listing, adding, editing, deleting, completing,
|
17
|
+
archiving, prioritizing, and analyzing todo items
|
18
|
+
- Integration and synchronization with [Todoist](https://todoist.com/) (With potential integration with more platforms)
|
19
|
+
- Smart colored output
|
20
|
+
- Uses YAML for storage by default (There's the option to add other stores but no configuration for it, yet)
|
21
|
+
- Modular code covered by unit tests
|
22
|
+
|
23
|
+
## Installation
|
24
|
+
|
25
|
+
```bash
|
26
|
+
gem install nin
|
27
|
+
```
|
28
|
+
|
29
|
+
## Usage
|
30
|
+
|
31
|
+
```console
|
32
|
+
NAME:
|
33
|
+
nin - a simple, full-featured command line todo app
|
34
|
+
|
35
|
+
USAGE:
|
36
|
+
nin COMMAND [arguments...]
|
37
|
+
|
38
|
+
COMMANDS:
|
39
|
+
l | list [a|l] List all unarchived todos. Pass optional argument `a` to list all todos or `l` to list local todos only
|
40
|
+
a | add desc Add a todo. Prepend due date by a @. Prepend each tag by a \#
|
41
|
+
e | edit id desc Edit a todo. Prepend due date by a @. Prepend each tag by a \#
|
42
|
+
p | prioritize id step Prioritize a todo by either a positive or negative step within its date group
|
43
|
+
c | complete id(s) Un/complete todo(s)
|
44
|
+
ac | archive id(s)|c Un/archive todo(s) or pass `c` to archive all completed items
|
45
|
+
d | delete id(s) Delete todo(s)
|
46
|
+
gc | garbage Delete all archived todos. Resets item ids as a side effect
|
47
|
+
s | analyze Analyze tasks and print statistics
|
48
|
+
i | repl Open nin in REPL mode
|
49
|
+
o | open Open todo file in $EDITOR
|
50
|
+
v | version Print current version of nin
|
51
|
+
```
|
52
|
+
|
53
|
+
- Print the usage instructions by calling `nin` without commands or arguments
|
54
|
+
- Each command has a short and a long name, for example, `l` and `list`
|
55
|
+
- You can utilize the power of the CLI by using shell commands and tools to
|
56
|
+
help you do various tasks. For example, run `nin list | grep school` to
|
57
|
+
filter items tagged as school
|
58
|
+
- For adding a due date to an item, prefix the date by an `@`. If no date is
|
59
|
+
passed, the default is always the date of the current day
|
60
|
+
- For adding tags, you need to prefix a `#` by a `\` (e.g., `\#`) in order for
|
61
|
+
the shell to interpret it as an actual `#`. Please note that you don't need
|
62
|
+
to do this in the REPL mode
|
63
|
+
- The `edit` command edits the description of an item. If a date is passed, its
|
64
|
+
date will be updated. If one or more tags are passed, they will be added to
|
65
|
+
that item's tag list
|
66
|
+
- Commands `complete`, `archive`, and `delete` can update multiple items at
|
67
|
+
once by passing multiple id's as arguments
|
68
|
+
- The `prioritize` command can take a positive or a negative weight as a step
|
69
|
+
to either prioritize the item up or down, respectively. The step is always
|
70
|
+
bound to the smallest and largest id in the current items date group. For
|
71
|
+
example, passing a 1 as as step prioritizes the item by one item up and
|
72
|
+
passing -2 prioritizes the item by 2 items down
|
73
|
+
- REPL (interactive) mode is where you can pass commands and arguments without
|
74
|
+
the need to call `nin` every time and can be triggered by calling `nin i` or
|
75
|
+
`nin repl`
|
76
|
+
|
77
|
+
## Integration
|
78
|
+
|
79
|
+
### Todoist
|
80
|
+
|
81
|
+
For Todoist integration, two environment variables must be set:
|
82
|
+
- `NIN_INTEGRATION_CLIENT=todoist`
|
83
|
+
- `NIN_INTEGRATION_CLIENT_TOKEN=token`. Token can be found in your [integration settings page](https://todoist.com/prefs/integrations)
|
84
|
+
- `NIN_INTEGRATION_TIMEOUT=interval_in_seconds`. A timeout interval for when the synchronization times out. Defaults to 60 seconds
|
85
|
+
|
86
|
+
## Development
|
87
|
+
|
88
|
+
- Install a recent version of `Ruby` and `Bundler`
|
89
|
+
- Run `bundle install` to install the dependencies
|
90
|
+
- Run `bundle exec rake` to run the test suite
|
91
|
+
- Run `gem build nin.gemspec` to build a new version
|
92
|
+
- To push a new version to RubyGems, run `gem push nin-VERSION-NUMBER.gem`
|
93
|
+
|
94
|
+
## Why
|
95
|
+
|
96
|
+
Why write another todo app? I like to use the terminal for everything and I've
|
97
|
+
been using a markdown file to manage my todo list. I looked for something
|
98
|
+
simple and I found [Todo.rb](https://gist.github.com/mattsears/1259080) which
|
99
|
+
`nin` started as a spinoff from. However, I needed to add some more features. I
|
100
|
+
then found [Todolist](http://todolist.site/) which I took some inspiration from
|
101
|
+
but kept the CLI as simple as it is in Todo.rb. I also didn't like that
|
102
|
+
todolist uses JSON to store the todo items because I wanted to view the file on
|
103
|
+
my phone and I needed something more readable.
|
104
|
+
|
105
|
+
## Contribution
|
106
|
+
|
107
|
+
Contributions are welcome. If you find a bug or want to add a new feature,
|
108
|
+
please open an issue or send a pull request.
|
109
|
+
|
110
|
+
## License
|
111
|
+
|
112
|
+
See [LICENSE](https://github.com/aonemd/nin/blob/master/LICENSE).
|
data/Rakefile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
require "rake/testtask"
|
3
|
+
|
4
|
+
Rake::TestTask.new(:test) do |t|
|
5
|
+
t.libs << "test"
|
6
|
+
t.test_files = FileList['test/**/*_test.rb']
|
7
|
+
end
|
8
|
+
|
9
|
+
task :default => :test
|
10
|
+
|
11
|
+
desc "Make a new build and publish it to RubyGems"
|
12
|
+
task :publish do
|
13
|
+
build_name_and_version = "nin-#{Nin::VERSION}.gem"
|
14
|
+
|
15
|
+
system "gem build nin.gemspec --silent --output #{build_name_and_version}"
|
16
|
+
system "gem push #{build_name_and_version}"
|
17
|
+
end
|
data/bin/nin
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require_relative '../lib/nin'
|
4
|
+
|
5
|
+
config = {
|
6
|
+
integration_client: ENV['NIN_INTEGRATION_CLIENT'],
|
7
|
+
integration_client_token: ENV['NIN_INTEGRATION_CLIENT_TOKEN'],
|
8
|
+
integration_timeout_interval: ENV['NIN_INTEGRATION_TIMEOUT']
|
9
|
+
}
|
10
|
+
|
11
|
+
begin
|
12
|
+
Nin::Command.new(ARGV[0], ARGV[1..-1], config).call
|
13
|
+
rescue Nin::ItemNotFoundError
|
14
|
+
puts "Todo item does not exist"
|
15
|
+
rescue Nin::EmptyCommandArgumentError
|
16
|
+
puts "Command argument cannot be empty\nRun nin to view the USAGE message"
|
17
|
+
end
|
data/demo.gif
ADDED
Binary file
|
data/lib/nin.rb
CHANGED
@@ -1,59 +1,40 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
class Todo
|
15
|
-
attr_accessor :items
|
16
|
-
|
17
|
-
def initialize()
|
18
|
-
@items = load_items
|
19
|
-
end
|
20
|
-
|
21
|
-
def list
|
22
|
-
end
|
23
|
-
|
24
|
-
def add(todo)
|
25
|
-
end
|
26
|
-
|
27
|
-
def edit(id)
|
28
|
-
end
|
29
|
-
|
30
|
-
def delete(id)
|
31
|
-
end
|
32
|
-
|
33
|
-
private
|
34
|
-
|
35
|
-
def load_items
|
36
|
-
[]
|
37
|
-
end
|
38
|
-
end
|
1
|
+
require 'psych'
|
2
|
+
require 'date'
|
3
|
+
require 'delegate'
|
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'
|
39
13
|
end
|
40
14
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
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'
|
19
|
+
require_relative 'nin/extensions/array_extensions'
|
20
|
+
require_relative 'nin/extensions/string_extensions'
|
21
|
+
require_relative 'nin/extensions/integer_extensions'
|
22
|
+
require_relative 'nin/extensions/date_extensions'
|
23
|
+
require_relative 'nin/error'
|
24
|
+
require_relative 'nin/yaml_store'
|
25
|
+
require_relative 'nin/parser'
|
26
|
+
require_relative 'nin/item'
|
27
|
+
require_relative 'nin/presenters/item_presenter'
|
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'
|
40
|
+
require_relative 'nin/command'
|
data/lib/nin/command.rb
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
module Nin
|
2
|
+
class Command
|
3
|
+
COMMANDS_WITH_ARGS = %w(a e c ac d)
|
4
|
+
|
5
|
+
def initialize(command, args, config = {})
|
6
|
+
@command = command
|
7
|
+
@args = args
|
8
|
+
@config = config
|
9
|
+
@todo = Todo.new(collect_config, collect_options)
|
10
|
+
|
11
|
+
validate_args
|
12
|
+
end
|
13
|
+
|
14
|
+
def call
|
15
|
+
case @command
|
16
|
+
when 'l', 'list'
|
17
|
+
@todo.list
|
18
|
+
when 'a', 'add'
|
19
|
+
desc, date, tags = parse_item_desc(@args.join(' '))
|
20
|
+
@todo.add(desc, date, tags)
|
21
|
+
when 'e', 'edit'
|
22
|
+
desc, date, tags = parse_item_desc(@args[1..-1].join(' '))
|
23
|
+
@todo.edit(@args[0].to_i, desc, date, tags)
|
24
|
+
when 'p', 'prioritize'
|
25
|
+
@todo.prioritize(@args[0].to_i, @args[1].to_i)
|
26
|
+
when 'c', 'complete'
|
27
|
+
@todo.complete(*@args)
|
28
|
+
when 'ac', 'archive'
|
29
|
+
@todo.archive(*@args)
|
30
|
+
when 'd', 'delete'
|
31
|
+
@todo.delete(*@args)
|
32
|
+
when 'gc', 'garbage'
|
33
|
+
@todo.delete_archived
|
34
|
+
when 's', 'analyze'
|
35
|
+
@todo.analyze
|
36
|
+
when 'i', 'repl'
|
37
|
+
run_interactive_mode
|
38
|
+
when 'o', 'open'
|
39
|
+
system("`echo $EDITOR` #{@todo.store.file}")
|
40
|
+
when 'v', 'version'
|
41
|
+
puts "nin #{Nin::VERSION}"
|
42
|
+
else
|
43
|
+
puts "NAME:\n\tnin - a simple, full-featured command line todo app"
|
44
|
+
puts "\nUSAGE:\n\tnin COMMAND [arguments...]"
|
45
|
+
puts "\nCOMMANDS:"
|
46
|
+
puts "\tl | list [a|l] List all unarchived todos. Pass optional argument `a` to list all todos or `l` to list local todos only"
|
47
|
+
puts "\ta | add desc Add a todo. Prepend due date by a @. Prepend each tag by a \\#"
|
48
|
+
puts "\te | edit id desc Edit a todo. Prepend due date by a @. Prepend each tag by a \\#"
|
49
|
+
puts "\tp | prioritize id step Prioritize a todo by either a positive or negative step within its date group"
|
50
|
+
puts "\tc | complete id(s) Un/complete todo(s)"
|
51
|
+
puts "\tac | archive id(s)|c Un/archive todo(s) or pass `c` to archive all completed items"
|
52
|
+
puts "\td | delete id(s) Delete todo(s)"
|
53
|
+
puts "\tgc | garbage Delete all archived todos. Resets item ids as a side effect"
|
54
|
+
puts "\ts | analyze Analyze tasks and print statistics"
|
55
|
+
puts "\ti | repl Open nin in REPL mode"
|
56
|
+
puts "\to | open Open todo file in $EDITOR"
|
57
|
+
puts "\tv | version Print current version of nin"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def collect_config
|
64
|
+
config = { store: YamlStore.new }
|
65
|
+
|
66
|
+
_client_name = @config.fetch(:integration_client, nil)
|
67
|
+
_client_credentials = @config.fetch(:integration_client_token, nil)
|
68
|
+
_timeout_interval = @config.fetch(:integration_timeout_interval, 60)
|
69
|
+
if _client_name && _client_credentials
|
70
|
+
_client_klass = Object.const_get("Nin::Integration::#{_client_name.capitalize}::Client")
|
71
|
+
_synchronizer_klass = Object.const_get("Nin::Integration::Synchronizer::#{_client_name.capitalize}")
|
72
|
+
|
73
|
+
config[:integration_syncrhonizer] = _synchronizer_klass.new(_client_klass.new(_client_credentials), _timeout_interval)
|
74
|
+
end
|
75
|
+
|
76
|
+
config
|
77
|
+
end
|
78
|
+
|
79
|
+
def collect_options
|
80
|
+
options = { archived: false, local: false, completed_only: false }
|
81
|
+
|
82
|
+
if @command == 'l' && @args[0] == 'a'
|
83
|
+
options[:archived] = true
|
84
|
+
elsif @command == 'l' && @args[0] == 'l'
|
85
|
+
options[:local] = true
|
86
|
+
elsif @command == 'ac' && @args[0] == 'c'
|
87
|
+
options[:completed_only] = true
|
88
|
+
end
|
89
|
+
|
90
|
+
options
|
91
|
+
end
|
92
|
+
|
93
|
+
def parse_item_desc(desc)
|
94
|
+
begin
|
95
|
+
Parser.new(desc).call
|
96
|
+
rescue InvalidDateFormatError
|
97
|
+
puts "Invalid date format."
|
98
|
+
exit
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def validate_args
|
103
|
+
COMMANDS_WITH_ARGS.each do |command|
|
104
|
+
raise EmptyCommandArgumentError if @command == command && @args.empty?
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def run_interactive_mode
|
109
|
+
while line = Readline.readline("nin> ", true)
|
110
|
+
line = line.split(' ')
|
111
|
+
|
112
|
+
Command.new(line[0], line[1..-1]).call
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
data/lib/nin/error.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
class Array
|
2
|
+
def sorted_by?
|
3
|
+
each_cons(2).all? { |a, b| ((yield a) <=> (yield b)) <= 0 }
|
4
|
+
end
|
5
|
+
|
6
|
+
def limit(offset)
|
7
|
+
offset > 0 ? self.last(offset) : self.first(offset.abs)
|
8
|
+
end
|
9
|
+
|
10
|
+
def round_shift(element, step)
|
11
|
+
new_element = [self.min, (element - step), self.max].median
|
12
|
+
actual_step = element - new_element
|
13
|
+
|
14
|
+
[new_element, actual_step]
|
15
|
+
end
|
16
|
+
|
17
|
+
def median
|
18
|
+
return nil if self.empty?
|
19
|
+
|
20
|
+
sorted = self.sort
|
21
|
+
len = self.length
|
22
|
+
|
23
|
+
(sorted[(len - 1) / 2] + sorted[len / 2]) / 2
|
24
|
+
end
|
25
|
+
|
26
|
+
def find_by(key, val)
|
27
|
+
self.where(key, val).first
|
28
|
+
end
|
29
|
+
|
30
|
+
def where(key, val = nil, &block)
|
31
|
+
self.select do |element|
|
32
|
+
key_eval = element.send(key)
|
33
|
+
|
34
|
+
block_given? ? block.call(key_eval) : (key_eval == val)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def ensure_array
|
39
|
+
self.to_a
|
40
|
+
end
|
41
|
+
end
|