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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fc5037bc8929225d80c4ffda217726c83a054c9a0c2d7c40d18b95b749248044
4
- data.tar.gz: 1a85efacd7a65c1df2d3da69faa058a430d898d6c30e7b0c401473a1d6a0cba8
3
+ metadata.gz: 39f9e7a10bb0b9d4433ec8e048f0f98a86b69c446df00b87f60011ed51656be2
4
+ data.tar.gz: f531e76fab7c0dee8883f8f05bf4f5b4f2ce8ecd8322203dac43bb4ecb6dd72b
5
5
  SHA512:
6
- metadata.gz: 8cd7d778fd4a06352be726ad2b2dae3b032d76d4c33db113d23f5d8e5f7527aca9e4e552d9f3200655839739064bade40dcfbc7fb0325abae69ebf00e6b846ce
7
- data.tar.gz: 60196625b942e4034c304aae5ee6856f36dcad94aff28ccb6153baf4beba30095f393fcf1800eb78dfd42da3545c1a62ae7a12fe85787666d25b9af1db3e1cc4
6
+ metadata.gz: 36dd67aa3390451a0c127c3fcfa8c053ba8976ce7c24da2c4a822c243dfdb6b5b70a2639e222e0b6378ea4f4bd3e1696a0ebb98025808ec7ff0bf95274852c27
7
+ data.tar.gz: 56953c2a4da2d7eab114f35fc195a0e9415958737b1dc8133edf658cf1cbe9c4f26ba1f903223ca861457b9da7e3230a2f715c1faace7a4e7a4e2de23e988995
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .byebug_history
3
+ .bundle/
4
+ Gemfile.lock
@@ -0,0 +1,5 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2
4
+ - 2.5
5
+ - jruby
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ gemspec
6
+
7
+ group :development, :test do
8
+ gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
9
+ end
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.
@@ -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
+ ![nin demo GIF](/demo.gif)
9
+ </div>
10
+
11
+ [![Build Status](https://travis-ci.com/aonemd/nin.svg?branch=master)](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).
@@ -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
Binary file
data/lib/nin.rb CHANGED
@@ -1,59 +1,40 @@
1
- module Nin
2
- class Item
3
- attr_accessor :body
4
-
5
- def initialize(desc)
6
- @desc = desc
7
- end
8
-
9
- def to_s
10
- "#{@desc}"
11
- end
12
- end
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
- if __FILE__ == $0
42
- case ARGV[0]
43
- when 'l'
44
- Nin::Todo.new.list
45
- when 'a'
46
- Nin::Todo.new.add(ARGV[1])
47
- when 'e'
48
- Nin::Todo.new.edit(ARGV[1])
49
- when 'd'
50
- Nin::Todo.new.delete(ARGV[1])
51
- else
52
- puts "\nUSAGE: nin COMMAND [arguments...]\n\n"
53
- puts "COMMANDS:"
54
- puts " l List all todos"
55
- puts " a desc Add a todo"
56
- puts " e id Edit a todo"
57
- puts " d id Remove a todo"
58
- end
59
- end
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'
@@ -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
@@ -0,0 +1,5 @@
1
+ module Nin
2
+ class ItemNotFoundError < StandardError; end
3
+ class EmptyCommandArgumentError < StandardError; end
4
+ class InvalidDateFormatError < StandardError; end
5
+ end
@@ -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