koi 0.2.1

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.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
4
+ rdoc
5
+ pkg
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 cloudhead
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,56 @@
1
+ koi
2
+ ===
3
+
4
+ minimal task management for hackers.
5
+
6
+ $ sudo gem install koi -s http://gemcutter.org
7
+
8
+ _Note: this project is still under heavy development!_
9
+
10
+ Synopsis
11
+ --------
12
+
13
+ Start by creating a new project folder
14
+
15
+ $ mkdir project/
16
+ $ cd project/
17
+
18
+ **koi** uses folders as projects, in the same way as **git**. We start by initializing our project, and adding two tasks.
19
+
20
+ $ koi init
21
+ $ koi add "refactor spaghetti code"
22
+ $ koi add "find a better name"
23
+
24
+ Let's see what we've got now with `list`:
25
+
26
+ $ koi list
27
+
28
+ [0] refactor spaghetti code
29
+ [1] find a better name
30
+
31
+ Tasks can be refered to by index `1`, `2` or by name. You don't have to type in the full name though:
32
+
33
+ $ koi tag spaghetti #R
34
+ $ koi done 1
35
+
36
+ I just went ahead and tagged my first task with `#R`, and completed my 2nd one. Here's the new list:
37
+
38
+ $ koi list
39
+
40
+ [0] refactor spaghetti code #R
41
+
42
+ # recently completed
43
+ - find a better name
44
+
45
+ You can also specify tags when adding new tasks:
46
+
47
+ $ koi add "make pasta" #food #yum #kitchen
48
+
49
+ And remove tasks:
50
+
51
+ $ koi remove pasta
52
+
53
+ **koi** creates an _.koi_ folder in the directory you initialize your project in. Inside that folder is a _database.yml_ with all your tasks for that project.
54
+
55
+
56
+
data/Rakefile ADDED
@@ -0,0 +1,31 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "koi"
8
+ gem.summary = %Q{minimal task management for hackers}
9
+ gem.description = %Q{minimalist console-based task management for hackers}
10
+ gem.email = "self@cloudhead.net"
11
+ gem.homepage = "http://github.com/cloudhead/koi"
12
+ gem.authors = ["cloudhead"]
13
+ gem.add_development_dependency "rspec"
14
+ gem.add_dependency "mutter", ">= 0.4"
15
+ end
16
+ Jeweler::GemcutterTasks.new
17
+ rescue LoadError
18
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
19
+ end
20
+
21
+ require 'spec/rake/spectask'
22
+ Spec::Rake::SpecTask.new(:spec) do |spec|
23
+ spec.libs << 'lib' << 'spec'
24
+ spec.spec_files = FileList['spec/**/*_spec.rb']
25
+ spec.spec_opts = ['--color', '--format=specdoc']
26
+ end
27
+
28
+ task :spec => :check_dependencies
29
+
30
+ task :default => :spec
31
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.2.1
data/bin/koi ADDED
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env ruby
2
+ require 'optparse'
3
+
4
+ $:.unshift File.dirname(__FILE__) + "/../lib"
5
+
6
+ require 'koi'
7
+
8
+ args = ARGV.dup
9
+
10
+ options = {verbose: false, silent: false}
11
+
12
+ OptionParser.new do |o|
13
+ o.banner = "usage: it COMMAND [ARGS]"
14
+ o.separator ""
15
+
16
+ o.on_tail("-s", "--silent", "turn output off") do
17
+ options[:silent] = true
18
+ end
19
+
20
+ o.on_tail("-v", "--verbose", "enable verbose mode") do
21
+ options[:verbose] = true
22
+ end
23
+ end.parse!(args)
24
+
25
+ Koi::Command.new(args[0] || :status, args[1] || nil, args[2] || [], options).run
26
+
data/koi.gemspec ADDED
@@ -0,0 +1,59 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{koi}
8
+ s.version = "0.2.1"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["cloudhead"]
12
+ s.date = %q{2009-10-20}
13
+ s.default_executable = %q{koi}
14
+ s.description = %q{minimalist console-based task management for hackers}
15
+ s.email = %q{self@cloudhead.net}
16
+ s.executables = ["koi"]
17
+ s.extra_rdoc_files = [
18
+ "LICENSE",
19
+ "README.md"
20
+ ]
21
+ s.files = [
22
+ ".document",
23
+ ".gitignore",
24
+ "LICENSE",
25
+ "README.md",
26
+ "Rakefile",
27
+ "VERSION",
28
+ "bin/koi",
29
+ "koi.gemspec",
30
+ "lib/koi.rb",
31
+ "spec/koi_spec.rb",
32
+ "spec/spec_helper.rb"
33
+ ]
34
+ s.homepage = %q{http://github.com/cloudhead/koi}
35
+ s.rdoc_options = ["--charset=UTF-8"]
36
+ s.require_paths = ["lib"]
37
+ s.rubygems_version = %q{1.3.5}
38
+ s.summary = %q{minimal task management for hackers}
39
+ s.test_files = [
40
+ "spec/koi_spec.rb",
41
+ "spec/spec_helper.rb"
42
+ ]
43
+
44
+ if s.respond_to? :specification_version then
45
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
46
+ s.specification_version = 3
47
+
48
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
49
+ s.add_development_dependency(%q<rspec>, [">= 0"])
50
+ s.add_runtime_dependency(%q<mutter>, [">= 0.4"])
51
+ else
52
+ s.add_dependency(%q<rspec>, [">= 0"])
53
+ s.add_dependency(%q<mutter>, [">= 0.4"])
54
+ end
55
+ else
56
+ s.add_dependency(%q<rspec>, [">= 0"])
57
+ s.add_dependency(%q<mutter>, [">= 0.4"])
58
+ end
59
+ end
data/lib/koi.rb ADDED
@@ -0,0 +1,281 @@
1
+
2
+ require 'yaml'
3
+ require 'mutter'
4
+ require 'fileutils'
5
+
6
+ module Koi
7
+
8
+ Path = {root: ".koi", db: ".koi/database.yml"}
9
+
10
+ def self.init dir = Dir.pwd
11
+ unless init?
12
+ FileUtils.mkdir File.join(dir, Path[:root])
13
+ FileUtils.touch File.join(dir, Path[:db])
14
+ end
15
+ end
16
+
17
+ def self.init! dir = Dir.pwd
18
+ FileUtils.rm_rf File.join(dir, Path[:root])
19
+ init dir
20
+ end
21
+
22
+ def self.init? dir = root
23
+ File.exist? File.join(dir, Path[:root]) if dir
24
+ end
25
+
26
+ def self.run *args
27
+ cmd = Command.new(*args)
28
+ cmd[:silent] = true
29
+ cmd.run
30
+ end
31
+
32
+ def self.version
33
+ File.read(File.join(File.dirname(__FILE__), '..', 'VERSION')).strip
34
+ end
35
+
36
+ def self.root
37
+ path = Dir.pwd.split('/').reject {|d| d.empty? }
38
+ (path.size + 1).times do
39
+ if Koi.init? (sub = File.join('/', *path))
40
+ return sub
41
+ end
42
+ path.pop
43
+ end
44
+ return nil
45
+ end
46
+
47
+ class Command
48
+ Commands = [
49
+ :init, :add, :list, :tag,
50
+ :done, :did, :log, :status,
51
+ :remove, :float, :sink,
52
+ :ls, :rm
53
+ ]
54
+ Initializers = [:init, :add]
55
+ Special = {"!" => :done, "?" => :status}
56
+
57
+ def initialize *all
58
+ cmd, param, args, options = all
59
+ @command = Special[cmd] || cmd.to_sym
60
+ @args = [args || []].flatten
61
+ @param = param =~ /^\d+$/ ? param.to_i : param
62
+ @options = options || {}
63
+ @db = Koi.init?? Database.new(File.join(Koi.root, Path[:db])) : Database.new
64
+ @mut = Mutter.new(blue: '#', underline: "''", cyan: '@@', green: '!!', yellow: '^').clear(:default)
65
+ end
66
+
67
+ def run
68
+ if Commands.include? @command
69
+ if Koi.init? or Initializers.include? @command
70
+ if !@param or @command == :add or @param = @db.find(@param)
71
+ if send(@command, *[@param, *@args].compact.flatten)
72
+ save
73
+ else
74
+ err "error running #@command"
75
+ end
76
+ else
77
+ err "task wasn't found"
78
+ end
79
+ else
80
+ err "'it' is not initialized here, please run `it init`"
81
+ end
82
+ else
83
+ err "#{@command} is not a valid command."
84
+ end
85
+ end
86
+
87
+ def []= key, val
88
+ @options[key] = val
89
+ end
90
+
91
+ def [] key
92
+ @options[key]
93
+ end
94
+
95
+ def init
96
+ unless Koi.init
97
+ err "'it' has already been initialized here"
98
+ else
99
+ true
100
+ end
101
+ end
102
+
103
+ def status
104
+ out "in the water (#{@db.select {|e| e.new? }.size})"
105
+
106
+ self.list 5
107
+
108
+ out "recently fished (#{@db.select {|e| e.completed? }.size})"
109
+
110
+ @db.select {|e| e[:status] == :completed }.
111
+ sort_by {|e| e[:completed_at] }[0..3].reverse.each do |e|
112
+ out "- !!#{e[:title]}!!"
113
+ end
114
+
115
+ out
116
+ true
117
+ end
118
+
119
+ #
120
+ # List current tasks
121
+ #
122
+ def list count = 10, index = -1
123
+ out
124
+
125
+ @db.select {|e| e.new? }[0..count].each do |e|
126
+ out "#[#{index += 1}]# ''#{e[:title]}'' @@#{e[:tags].join(' ')}@@" unless e[:status] == :removed
127
+ end.tap do |list|
128
+ out " !!nothing left to do!!" if list.size.zero?
129
+ end
130
+
131
+ out
132
+ end
133
+ alias :ls list
134
+
135
+ def float
136
+ end
137
+
138
+ def sink
139
+ end
140
+
141
+ #
142
+ # Show task history
143
+ #
144
+ def log
145
+ @db.map do |entity|
146
+ Entity::Status.map do |status|
147
+ { title: entity[:title],
148
+ action: status,
149
+ time: entity[:"#{status}_at"]
150
+ } if entity[:"#{status}_at"]
151
+ end.compact
152
+ end.flatten.sort_by {|e| e[:time]}.reverse.each do |entry|
153
+ out "##{entry[:time]}# ^#{entry[:action]}^ ''#{entry[:title]}''"
154
+ end
155
+ end
156
+
157
+ def out obj = ""
158
+ if obj.is_a? Hash
159
+ puts "#{obj[:title]} : #{obj[:status]}"
160
+ else
161
+ @mut.say obj.to_s
162
+ end unless @options[:silent]
163
+ end
164
+
165
+ def err str
166
+ @options[:silent] ? abort : abort(str)
167
+ end
168
+
169
+ def add entry, *args
170
+ Koi.init
171
+ target = args.find {|a| a.start_with? '@' }[1..-1] rescue nil
172
+ tags = args.select {|a| a.start_with? '#' }
173
+ @db << Entity.new(title: entry, tags: tags, target: target)
174
+ end
175
+
176
+ def tag entry, tags
177
+ entry[:tags] << tags
178
+ end
179
+
180
+ def did entry = 0
181
+ entry.status = :completed
182
+ entry[:completed_by] = ENV['USER']
183
+ end
184
+ alias :done did
185
+ alias :fish did
186
+
187
+ def save
188
+ File.open(File.join(Koi.root, Path[:db]), 'w') {|f| f.write @db.to_yaml }
189
+ end
190
+
191
+ #
192
+ # Mark task as :removed (doesn't show up anywhere)
193
+ #
194
+ def remove entry
195
+ entry.status = :removed
196
+ end
197
+ alias :rm remove
198
+ alias :kill remove
199
+ end
200
+
201
+ class Database
202
+ include Enumerable
203
+
204
+ def initialize path = nil
205
+ if @path = path
206
+ self.load path
207
+ else
208
+ @data = []
209
+ end
210
+ end
211
+
212
+ def find key
213
+ if key.is_a? String
214
+ @data.find {|e| e[:title].include? key }
215
+ elsif key.is_a? Fixnum
216
+ @data.select {|e| e[:status] == :created}[key]
217
+ else
218
+ raise ArgumentError, "key must be a String or Fixnum, but is #{key.class}"
219
+ end
220
+ end
221
+ alias :[] find
222
+
223
+ def load path = @path || Path[:db]
224
+ @data = if db = YAML.load_file(path)
225
+ db.map {|e| Entity.new(e) }
226
+ else
227
+ []
228
+ end
229
+ self
230
+ end
231
+
232
+ def each &blk
233
+ @data.each &blk
234
+ end
235
+
236
+ def << entry
237
+ @data << (entry.is_a?(Entity) ? entry : Entity.new(entry))
238
+ end
239
+
240
+ def to_yaml *args, &blk
241
+ @data.to_yaml *args, &blk
242
+ end
243
+ end
244
+
245
+ class Entity < Hash
246
+ Status = [:created, :completed, :removed]
247
+
248
+ def initialize data = {}
249
+ self.replace status: :created,
250
+ created_at: Time.now,
251
+ owner: ENV['USER'],
252
+ velocity: 0,
253
+ freshness: 0,
254
+ depth: 0,
255
+ tags: []
256
+ merge!(data)
257
+ end
258
+
259
+ def new?
260
+ self[:status] == :created
261
+ end
262
+
263
+ def status= st
264
+ self[:status] = st
265
+ self[:"#{st}_at"] = Time.now
266
+ end
267
+
268
+ #
269
+ # Handle things like `self.removed?`
270
+ #
271
+ def method_missing meth, *args, &blk
272
+ if meth.to_s.end_with?('?') && Status.include?(s = meth.to_s.chop.to_sym)
273
+ self[:status] == s
274
+ else
275
+ super
276
+ end
277
+ end
278
+ end
279
+ end
280
+
281
+
data/spec/koi_spec.rb ADDED
@@ -0,0 +1,78 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe Koi do
4
+ TASK = "buy milk"
5
+
6
+ context "in a new project" do
7
+ before(:each) do
8
+ FileUtils.rm_rf(Koi::Path[:root])
9
+ end
10
+
11
+ it "should initialize the directory" do
12
+ Koi.run(:init)
13
+ File.exist?(Koi::Path[:root]).should be_true
14
+ File.exist?(Koi::Path[:db]).should be_true
15
+ end
16
+
17
+ it "should warn that the project isn't initialized" do
18
+ -> {Koi.run(:list)}.should raise_error(SystemExit)
19
+ end
20
+ end
21
+
22
+ context "in an existing project" do
23
+ before(:all) do
24
+ Koi.init!
25
+ end
26
+
27
+ context "with no tasks" do
28
+ it "shouldn't try to init" do
29
+ -> {Koi.run(:init)}.should raise_error(SystemExit)
30
+ end
31
+
32
+ it "should warn about invalid commands" do
33
+ -> {Koi.run(:choo)}.should raise_error(SystemExit)
34
+ end
35
+
36
+ it "should add tasks" do
37
+ if Koi.run(:add, TASK)
38
+ e = Koi::Database.new(Koi::Path[:db]).find(TASK)
39
+ e.should be_a(Koi::Entity)
40
+ e[:status].should == :created
41
+ e[:tags].should == []
42
+ else
43
+ fail
44
+ end
45
+ end
46
+ end
47
+
48
+ context "with a couple tasks" do
49
+ TASKS = ["milk", "eggs", "bananas"]
50
+ before(:each) do
51
+ Koi.init!
52
+ TASKS.each do |t|
53
+ Koi.run(:add, t)
54
+ end
55
+ @db = Koi::Database.new(Koi::Path[:db])
56
+ end
57
+
58
+ it "should remove tasks" do
59
+ Koi.run(:remove, TASKS.last)
60
+ @db.load.find(TASKS.last)[:status].should == :removed
61
+ end
62
+
63
+ it "should complete tasks" do
64
+ Koi.run(:did, TASKS.first)
65
+ @db.load.find(TASKS.first)[:status].should == :completed
66
+ end
67
+
68
+ it "should tag tasks" do
69
+ Koi.run(:tag, TASKS[1], ["food"])
70
+ @db.load.find(TASKS[1])[:tags].should include("food")
71
+ end
72
+
73
+ it "should warn when the task wasn't found" do
74
+ -> {Koi.run(:did, "celery")}.should raise_error(SystemExit)
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,9 @@
1
+ $:.unshift(File.dirname(__FILE__))
2
+ $:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+ require 'koi'
4
+ require 'spec'
5
+ require 'spec/autorun'
6
+
7
+ Spec::Runner.configure do |config|
8
+
9
+ end
metadata ADDED
@@ -0,0 +1,86 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: koi
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.1
5
+ platform: ruby
6
+ authors:
7
+ - cloudhead
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-10-20 00:00:00 -04:00
13
+ default_executable: koi
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: rspec
17
+ type: :development
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: mutter
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: "0.4"
34
+ version:
35
+ description: minimalist console-based task management for hackers
36
+ email: self@cloudhead.net
37
+ executables:
38
+ - koi
39
+ extensions: []
40
+
41
+ extra_rdoc_files:
42
+ - LICENSE
43
+ - README.md
44
+ files:
45
+ - .document
46
+ - .gitignore
47
+ - LICENSE
48
+ - README.md
49
+ - Rakefile
50
+ - VERSION
51
+ - bin/koi
52
+ - koi.gemspec
53
+ - lib/koi.rb
54
+ - spec/koi_spec.rb
55
+ - spec/spec_helper.rb
56
+ has_rdoc: true
57
+ homepage: http://github.com/cloudhead/koi
58
+ licenses: []
59
+
60
+ post_install_message:
61
+ rdoc_options:
62
+ - --charset=UTF-8
63
+ require_paths:
64
+ - lib
65
+ required_ruby_version: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: "0"
70
+ version:
71
+ required_rubygems_version: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: "0"
76
+ version:
77
+ requirements: []
78
+
79
+ rubyforge_project:
80
+ rubygems_version: 1.3.5
81
+ signing_key:
82
+ specification_version: 3
83
+ summary: minimal task management for hackers
84
+ test_files:
85
+ - spec/koi_spec.rb
86
+ - spec/spec_helper.rb