nin 0.0.0 → 1.3.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: 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