dst 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (9) hide show
  1. data/LICENCE +19 -0
  2. data/Manifest +7 -0
  3. data/README +41 -0
  4. data/bin/dst +7 -0
  5. data/dst.gemspec +81 -0
  6. data/lib/dst.rb +63 -0
  7. data/lib/dst/models.rb +78 -0
  8. data/spec/dst_spec.rb +181 -0
  9. metadata +68 -0
data/LICENCE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2008 Simon Rozet
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
@@ -0,0 +1,7 @@
1
+ README
2
+ spec/dst_spec.rb
3
+ LICENCE
4
+ lib/dst/models.rb
5
+ lib/dst.rb
6
+ bin/dst
7
+ Manifest
data/README ADDED
@@ -0,0 +1,41 @@
1
+ dst is a KISS GTD manager for the command line lovers.
2
+
3
+ == Licence
4
+
5
+ dst is copyright 2008 Simon Rozet. It is licensed under the MIT licence.
6
+ See the included LICENSE file for details.
7
+
8
+ == Install
9
+
10
+ sudo gem install ape
11
+
12
+ == Usage
13
+
14
+ % dst @mail john@doe.org about foo
15
+ 1 - `@mail john@doe.org about foo' created.
16
+ % dst @code :myproj fix bug 123
17
+ 2 - `@code :myproj fix bug 123' created.
18
+ % dst @code :myproj do foo and bar
19
+ 3 - `@code :myproj do foo and bar' created.
20
+ % dst
21
+ 1 - @mail john@doe.org about foo
22
+ 2 - @code :myproj fix bug
23
+ 3 - @code :myproj do foo and bar
24
+ % dst @mail
25
+ 1 - @mail john@doe.org about foo
26
+ % dst :myproj
27
+ 2 - @code :myproj fix bug
28
+ 3 - @code :myproj do foo and bar
29
+ % dst ^2
30
+ Ok, `2 - @code :myproj fix bug' marked as `completed'.
31
+ % dst
32
+ 1 - @mail john@doe.org about foo
33
+ 3 - @code :myproj do foo and bar
34
+
35
+ == Contact
36
+
37
+ Simon Rozet, simon@rozet.name
38
+
39
+ == Acknowledgements
40
+
41
+ I originally wrote dst back in July 2007 with the help of Gregory Brown to whom I am very grateful.
data/bin/dst ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ $:.unshift 'lib/', File.dirname(__FILE__) + '/../lib'
3
+ require 'dst'
4
+
5
+ Dst::Models.establish_connection(:database => File.expand_path('~/.dst.db'))
6
+ Dst::Models.create_tables_if_necessary
7
+ Dst.process_command(ARGV.join(' '))
@@ -0,0 +1,81 @@
1
+
2
+ # Gem::Specification for Dst-0.0.1
3
+ # Originally generated by Echoe
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = %q{dst}
7
+ s.version = "0.0.1"
8
+
9
+ s.specification_version = 2 if s.respond_to? :specification_version=
10
+
11
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
12
+ s.authors = ["Simon Rozet"]
13
+ s.date = %q{2008-02-11}
14
+ s.default_executable = %q{dst}
15
+ s.description = %q{a KISS GTD manager for the command line lovers.}
16
+ s.email = %q{simon@rozet.name}
17
+ s.executables = ["dst"]
18
+ s.files = ["README", "spec/dst_spec.rb", "LICENCE", "lib/dst/models.rb", "lib/dst.rb", "bin/dst", "Manifest", "dst.gemspec"]
19
+ s.has_rdoc = true
20
+ s.homepage = %q{http://atonie.org/2008/dst}
21
+ s.require_paths = ["lib"]
22
+ s.rubyforge_project = %q{dst}
23
+ s.rubygems_version = %q{1.0.1}
24
+ s.summary = %q{a KISS GTD manager for the command line lovers.}
25
+
26
+ s.add_dependency(%q<activerecord>, [">= 2.0.2"])
27
+ end
28
+
29
+
30
+ # # Original Rakefile source (requires the Echoe gem):
31
+ #
32
+ # require 'rubygems'
33
+ # require 'rake'
34
+ # require 'spec/rake/spectask'
35
+ #
36
+ # Version = '0.0.1'
37
+ #
38
+ # begin
39
+ # require 'echoe'
40
+ #
41
+ # Echoe.new('dst', Version) do |p|
42
+ # p.summary = 'a KISS GTD manager for the command line lovers.'
43
+ # p.description = 'a KISS GTD manager for the command line lovers.'
44
+ # p.url = 'http://atonie.org/2008/dst'
45
+ # p.author = 'Simon Rozet'
46
+ # p.email = 'simon@rozet.name'
47
+ # p.dependencies << 'activerecord >=2.0.2'
48
+ # p.clean_pattern << 'report.html'
49
+ # p.clean_pattern << 'coverage'
50
+ # end
51
+ # rescue LoadError => boom
52
+ # puts 'You are missing a dependency required for meta-operations on this gem.'
53
+ # puts boom.to_s.capitalize
54
+ # end
55
+ #
56
+ # desc 'Install the package as a gem, without generating documentation'
57
+ # task :install_gem_no_doc => [:clean, :package] do
58
+ # sh "#{'sudo ' unless Hoe::WINDOZE }gem install pkg/*.gem --no-rdoc --no-ri"
59
+ # end
60
+ #
61
+ # task :default => 'spec'
62
+ # desc 'Run specs'
63
+ # Spec::Rake::SpecTask.new do |t|
64
+ # t.spec_opts = ['--format', 'specdoc', '--colour', '--diff']
65
+ # end
66
+ #
67
+ # desc 'Generate coverage reports'
68
+ # Spec::Rake::SpecTask.new('spec:coverage') do |t|
69
+ # t.rcov = true
70
+ # end
71
+ #
72
+ # desc 'Generate a nice HTML report of spec results'
73
+ # Spec::Rake::SpecTask.new('spec:report') do |t|
74
+ # t.spec_opts = ['--format', 'html:report.html', '--diff']
75
+ # end
76
+ #
77
+ # task :publish_on_atonie => ['spec:coverage', 'spec:report'] do
78
+ # sh 'cp -R coverage ~/web/org.atonie./2008/dst/'
79
+ # sh 'cp report.html ~/web/org.atonie./2008/dst'
80
+ # sh '~/bin/publish_atonie'
81
+ # end
@@ -0,0 +1,63 @@
1
+ require 'rubygems'
2
+ require 'dst/models'
3
+
4
+ class Dst
5
+ include Models
6
+
7
+ def self.process_command(command)
8
+ new.process_command(command)
9
+ end
10
+
11
+ def process_command(command)
12
+ options = extract_options_from_command(command)
13
+ if command.blank? || !options.has_key?(:description)
14
+ list_tasks(options)
15
+ elsif command =~ /^\^(\d+)$/
16
+ toggle_task($1.to_i)
17
+ else
18
+ create_task(options)
19
+ end
20
+ end
21
+
22
+ def create_task(options={})
23
+ task = Task.new(:description => options[:description])
24
+ task.context = Context.find_or_create_by_name(options[:context]) if options.has_key?(:context)
25
+ task.project = Project.find_or_create_by_name(options[:project]) if options.has_key?(:project)
26
+ task.save
27
+ puts "`#{task}' created."
28
+ end
29
+
30
+ def toggle_task(task_id)
31
+ task = Task.toggle!(task_id)
32
+ puts "Ok, `#{task}' marked as `#{task.status}'."
33
+ rescue ActiveRecord::RecordNotFound
34
+ puts "Oops, task ##{task_id} not found."
35
+ end
36
+
37
+ def list_tasks(options={}, include_completed=false)
38
+ context, project = options.values_at(:context, :project)
39
+ tasks = if options.empty?
40
+ Task.unfinished
41
+ elsif context
42
+ Task.unfinished.select { |task|
43
+ task.context.name == context if task.context
44
+ }
45
+ elsif project
46
+ Task.unfinished.select { |task|
47
+ task.project.name == project if task.project
48
+ }
49
+ end || []
50
+ puts tasks.empty? ? 'No tasks found' : tasks.map(&:to_s).join("\n")
51
+ end
52
+
53
+ protected
54
+ def extract_options_from_command(command)
55
+ context = command =~ /^@(\w+)/ && $1
56
+ project = command =~ /(?!\w):(\w+)/ && $1
57
+ description = command.dup
58
+ description.gsub!("@#{context}", '') unless context.nil?
59
+ description.gsub!(":#{project}", '') unless project.nil?
60
+ options = {:context => context, :project => project, :description => description.strip}
61
+ options.reject { |k, v| v.nil? || v.blank? }
62
+ end
63
+ end
@@ -0,0 +1,78 @@
1
+ require 'rubygems'
2
+ require 'active_record'
3
+
4
+ class Dst
5
+ module Models
6
+ class << self
7
+ def schema(&block)
8
+ @@schema = block if block_given?
9
+ @@schema
10
+ end
11
+
12
+ def establish_connection(options={})
13
+ ActiveRecord::Base.establish_connection({:adapter => 'sqlite3', :database => 'dst.db'}.merge(options))
14
+ end
15
+
16
+ def create_tables_if_necessary(force=false)
17
+ ActiveRecord::Schema.define(&Dst::Models.schema) if force || !Task.table_exists?
18
+ end
19
+ end
20
+
21
+ class Task < ActiveRecord::Base
22
+ belongs_to :context
23
+ belongs_to :project
24
+
25
+ def self.unfinished(options={})
26
+ find(:all, :conditions => 'status = "f"', :include => [:context, :project], :order => 't1_r1')
27
+ end
28
+
29
+ def self.toggle!(task_id)
30
+ task = find(task_id)
31
+ task.toggle!(:status)
32
+ task
33
+ end
34
+
35
+ def status
36
+ read_attribute(:status) ? 'completed' : 'unfinished'
37
+ end
38
+
39
+ def to_s
40
+ "#{id} - #{context || ''}#{project || ''}#{description}"
41
+ end
42
+ end
43
+
44
+ class Context < ActiveRecord::Base
45
+ has_many :tasks
46
+
47
+ def to_s
48
+ name ? "@#{name} ": ""
49
+ end
50
+ end
51
+
52
+ class Project < ActiveRecord::Base
53
+ has_many :tasks
54
+
55
+ def to_s
56
+ name ? ":#{name} ": ""
57
+ end
58
+ end
59
+ end
60
+ end
61
+
62
+ Dst::Models.schema do
63
+ create_table :tasks do |t|
64
+ t.string :description
65
+ t.boolean :status, :default => false
66
+ t.integer :context_id
67
+ t.integer :project_id
68
+ t.timestamps
69
+ end
70
+
71
+ create_table :contexts do |t|
72
+ t.string :name
73
+ end
74
+
75
+ create_table :projects do |t|
76
+ t.string :name
77
+ end
78
+ end
@@ -0,0 +1,181 @@
1
+ $:.unshift 'lib/', File.dirname(__FILE__) + '/../lib'
2
+ require 'rubygems'
3
+ require 'spec'
4
+ require 'dst'
5
+
6
+ module Kernel; def puts(s); end; end
7
+
8
+ describe Dst do
9
+ before(:each) do
10
+ @dst = Dst.new
11
+ @task_model = Dst::Models::Task
12
+ @context_model = Dst::Models::Context
13
+ @project_model = Dst::Models::Project
14
+
15
+ @task = mock('Task', :context= => true, :project= => true, :save => true)
16
+ @task_model.stub!(:new).and_return(@task)
17
+ end
18
+
19
+ it 'should exists' do
20
+ @dst.should_not be_nil
21
+ @dst.should be_an_instance_of(Dst)
22
+ end
23
+
24
+ describe "when creating tasks: `<@context> <:project> task'" do
25
+ before(:each) do
26
+ @command = '@mail :proj john@doe.name about http://example.org'
27
+ end
28
+
29
+ describe '#process_command' do
30
+ it 'dispatchs command to create_task' do
31
+ @dst.should_receive(:create_task)
32
+ @dst.process_command(@command)
33
+ end
34
+
35
+ it 'parses description correctly' do
36
+ @dst.should_receive(:create_task) do |opts|
37
+ opts[:description].should == 'john@doe.name about http://example.org'
38
+ end
39
+ @dst.process_command(@command)
40
+ end
41
+
42
+ it 'parses context correctly' do
43
+ @dst.should_receive(:create_task) { |opts| opts[:context].should == 'mail' }
44
+ @dst.process_command(@command)
45
+ end
46
+
47
+ it 'parses project correctly' do
48
+ @dst.should_receive(:create_task) { |opts| opts[:project].should == 'proj' }
49
+ @dst.process_command(@command)
50
+ end
51
+ end
52
+
53
+ describe '#create_task' do
54
+ before(:each) do
55
+ @context = mock('Context')
56
+ @context_model.stub!(:find_or_create_by_name).and_return(@context)
57
+
58
+ @project = mock('Project')
59
+ @project_model.stub!(:find_or_create_by_name).and_return(@project)
60
+ end
61
+
62
+ it 'creates a new Task with given description' do
63
+ @task_model.should_receive(:new).with(
64
+ :description => 'john@doe.name about http://example.org'
65
+ ).and_return(@task)
66
+ @dst.process_command(@command)
67
+ end
68
+
69
+ it 'finds or creates context' do
70
+ @context_model.should_receive(:find_or_create_by_name).and_return(@context)
71
+ @dst.process_command(@command)
72
+ end
73
+
74
+ it "sets task's context if specified" do
75
+ @task.should_receive(:context=).with(@context)
76
+ @dst.process_command(@command)
77
+ end
78
+
79
+ it "doesn't set tasks's context if not specified" do
80
+ @task.should_not_receive(:context=)
81
+ @dst.process_command('task without context')
82
+ end
83
+
84
+ it "finds or creates project" do
85
+ @project_model.should_receive(:find_or_create_by_name).and_return(@project)
86
+ @dst.process_command(@command)
87
+ end
88
+
89
+ it "sets task's project if specified" do
90
+ @task.should_receive(:project=).with(@project)
91
+ @dst.process_command(@command)
92
+ end
93
+
94
+ it "doesn't set tasks's project if not specified" do
95
+ @task.should_not_receive(:project=)
96
+ @dst.process_command('task without project')
97
+ end
98
+
99
+ it 'saves new task' do
100
+ @task.should_receive(:save).and_return(true)
101
+ @dst.process_command(@command)
102
+ end
103
+
104
+ it 'notices that tasks have been successfuly created' do
105
+ @dst.should_receive(:puts).with(/ created.$/)
106
+ @dst.process_command(@command)
107
+ end
108
+ end
109
+ end
110
+
111
+ describe "when toggling a task: `^<task id>'" do
112
+ before(:each) do
113
+ @task.stub!(:status).and_return(true)
114
+ @task_model.stub!(:toggle!).and_return(@task)
115
+ end
116
+
117
+ describe '#proccess_command' do
118
+ it 'dispatchs to toggle_task' do
119
+ @dst.should_receive(:toggle_task).with(479)
120
+ @dst.process_command('^479')
121
+ end
122
+
123
+ it 'parses task id correctly' do
124
+ [1, 479, 0, 1087].each do |id|
125
+ @dst.should_receive(:toggle_task).with(id)
126
+ @dst.process_command("^#{id}")
127
+ end
128
+ end
129
+ end
130
+
131
+ describe '#toggle_task' do
132
+ it 'toggle task' do
133
+ @task_model.should_receive(:toggle!).with(3).and_return(@task)
134
+ @dst.process_command('^3')
135
+ end
136
+
137
+ it 'outputs an error message if task not found' do
138
+ @task_model.should_receive(:toggle!).and_raise(ActiveRecord::RecordNotFound)
139
+ @dst.should_receive(:puts).with("Oops, task #98 not found.")
140
+ @dst.process_command('^98')
141
+ end
142
+ end
143
+ end
144
+
145
+ describe "when listing tasks: `[<@context> <@project>] [-a]]'" do
146
+ before(:each) do
147
+ @tasks = mock('tasks', :select => [@task], :empty? => false, :map => [@task])
148
+ @task_model.stub!(:unfinished).and_return(@tasks)
149
+ end
150
+
151
+ it 'finds all unfinished tasks if no filter provided' do
152
+ @task_model.should_receive(:unfinished).and_return(@tasks)
153
+ @tasks.should_not_receive(:select)
154
+ @dst.process_command('')
155
+ end
156
+
157
+ it 'finds unfinished tasks filtered by context' do
158
+ @task_model.should_receive(:unfinished).and_return(@tasks)
159
+ @tasks.should_receive(:select)
160
+ @dst.process_command('@context')
161
+ end
162
+
163
+ it 'finds unfinished tasks filtered by project' do
164
+ @task_model.should_receive(:unfinished).and_return(@tasks)
165
+ @tasks.should_receive(:select)
166
+ @dst.process_command(':project')
167
+ end
168
+
169
+ it 'outputs tasks' do
170
+ @tasks.should_receive(:map).and_return(@tasks)
171
+ @tasks.should_receive(:join).and_return("")
172
+ @dst.process_command('')
173
+ end
174
+
175
+ it 'notices if no tasks found' do
176
+ @tasks.should_receive(:empty?).and_return(true)
177
+ @dst.should_receive(:puts).with('No tasks found')
178
+ @dst.process_command('')
179
+ end
180
+ end
181
+ end
metadata ADDED
@@ -0,0 +1,68 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dst
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Simon Rozet
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-02-11 00:00:00 +01:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: activerecord
17
+ version_requirement:
18
+ version_requirements: !ruby/object:Gem::Requirement
19
+ requirements:
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 2.0.2
23
+ version:
24
+ description: a KISS GTD manager for the command line lovers.
25
+ email: simon@rozet.name
26
+ executables:
27
+ - dst
28
+ extensions: []
29
+
30
+ extra_rdoc_files: []
31
+
32
+ files:
33
+ - README
34
+ - spec/dst_spec.rb
35
+ - LICENCE
36
+ - lib/dst/models.rb
37
+ - lib/dst.rb
38
+ - bin/dst
39
+ - Manifest
40
+ - dst.gemspec
41
+ has_rdoc: true
42
+ homepage: http://atonie.org/2008/dst
43
+ post_install_message:
44
+ rdoc_options: []
45
+
46
+ require_paths:
47
+ - lib
48
+ required_ruby_version: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: "0"
53
+ version:
54
+ required_rubygems_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: "0"
59
+ version:
60
+ requirements: []
61
+
62
+ rubyforge_project: dst
63
+ rubygems_version: 1.0.1
64
+ signing_key:
65
+ specification_version: 2
66
+ summary: a KISS GTD manager for the command line lovers.
67
+ test_files: []
68
+