cardigan 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -16,7 +16,7 @@ A simple command line project task tracking tool.
16
16
 
17
17
  cardigan
18
18
 
19
- This will prompt for your name and email address (for no good reason at the moment), create a folder called .cards in the current directory and enter a shell.
19
+ This will prompt for your name and email address (and store them in ~/.cardigan), create a folder called .cards in the current directory and enter a shell. The idea is that you will run this at the root of a git/hg/svn/whatever repository and manage the cards in the same way you manage your source code.
20
20
 
21
21
  cardigan >
22
22
 
@@ -29,21 +29,36 @@ You start in listing mode.
29
29
  The commands available are (with the awesome power of tab completion):
30
30
 
31
31
  * quit or exit or ctrl-d - exit
32
- * list - shows all cards
32
+ * list - shows all cards with an index
33
33
  * filter <filter> - sets the filter for cards - this is ruby code such as card[:name].start_with?('a')
34
- * claim <numbers> - sets the owner of the specified cards (by index number from the list view)
35
- * unclaim <numbers> - removes the owner from the specified cards (by index number from the list view)
34
+ * claim <numbers> - sets the owner of the specified cards (by index from the list)
35
+ * unclaim <numbers> - removes the owner from the specified cards (by index from the list)
36
36
  * create <name> - creates a card with the specified name
37
37
  * open <name> - creates or opens the card and enters edit mode
38
38
  * columns - lists or changes the columns to be displayed by the list command
39
- * destroy <numbers> - deletes the specified cards (by index number from the list view)
39
+ * destroy <numbers> - deletes the specified cards (by index from the list)
40
+ * set <key> <numbers> - prompts for a field value and sets it on all specified cards (by index from the list)
41
+ * workflow - enters workflow editing mode
40
42
 
41
43
  == Editing mode
42
44
 
43
45
  * quit or exit or ctrl-d - exit
44
46
  * show - dumps the values of all card field values
45
47
  * set <key> - creates a new field and prompts for the new value
48
+ * to <status> - changes the status of the card to the given status (the tab completion will be populated with the valid subsequent statuses from the current status)
49
+
50
+ == Workflow mode
51
+
52
+ Workflow is pretty simple - it is just for convenience in specifiying the set of valid statuses for cards with a specific status.
53
+
54
+ The only purpose is to populate tab completion in editing mode for a card.
55
+
56
+ * quit or exit or ctrl-d - exit
57
+ * show - dumps the current workflow (statuses with their valid subsequent statuses)
58
+ * create <status> - creates a new status
59
+ * add <status> <statuses> - add the specified statuses as valid transitions from status
60
+ * remove <status> <statuses> - remove the specified statuses as valid transitions from status
46
61
 
47
62
  == Future plans
48
63
 
49
- Refer to the .cards for detailed story breakdown but mostly simple worflow, importing, exporting and generating pretty html reports/charts.
64
+ Refer to the .cards for detailed story breakdown but importing, exporting and generating pretty html reports/charts.
data/lib/cardigan/cli.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  require 'cardigan/io'
2
2
  require 'cardigan/root_context'
3
3
  require 'cardigan/repository'
4
+ require 'cardigan/workflow_repository'
4
5
 
5
6
  module Cardigan
6
7
  class Cli
@@ -21,7 +22,7 @@ module Cardigan
21
22
  @home.store CONFIG_FILE, config
22
23
  end
23
24
  name = "\"#{config[:name]}\" <#{config[:email]}>"
24
- RootContext.new(@io, Repository.new('.cards'), name).push
25
+ RootContext.new(@io, Repository.new('.cards'), name, WorkflowRepository.new('.')).push
25
26
  end
26
27
  end
27
28
  end
@@ -0,0 +1,24 @@
1
+ module Cardigan
2
+ module Command
3
+ class BatchUpdateCards
4
+ def initialize repository, io
5
+ @repository, @io = repository, io
6
+ end
7
+
8
+ def execute text
9
+ key, *rest = text.scan(/\w+/)
10
+ value = @io.ask("Enter the new value for #{key}")
11
+ @repository.each_card_from_indices(rest.join(' ')) do |card|
12
+ if value.empty?
13
+ @io.say "removing #{key} from '#{card['name']}'"
14
+ card.delete key
15
+ else
16
+ @io.say "setting #{key} to '#{value}' for '#{card['name']}'"
17
+ card[key] = value
18
+ end
19
+ @repository.save card
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,17 @@
1
+ module Cardigan
2
+ module Command
3
+ class ClaimCards
4
+ def initialize repository, io
5
+ @repository, @io = repository, io
6
+ end
7
+
8
+ def execute numbers
9
+ @repository.each_card_from_indices(numbers) do |card|
10
+ @io.say "claiming \"#{card['name']}\""
11
+ card['owner'] = @name
12
+ @repository.save card
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,13 @@
1
+ module Cardigan
2
+ module Command
3
+ class CreateCard
4
+ def initialize repository
5
+ @repository = repository
6
+ end
7
+
8
+ def execute name
9
+ @repository.save @repository.find_or_create(name)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,16 @@
1
+ module Cardigan
2
+ module Command
3
+ class DestroyCards
4
+ def initialize repository, io
5
+ @repository, @io = repository, io
6
+ end
7
+
8
+ def execute numbers
9
+ @repository.each_card_from_indices(numbers) do |card|
10
+ @io.say "destroying \"#{card['name']}\""
11
+ @repository.destroy card
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,19 @@
1
+ module Cardigan
2
+ module Command
3
+ class FilterCards
4
+ def initialize repository, io
5
+ @repository, @io = repository, io
6
+ end
7
+
8
+ def execute filter
9
+ @repository.filter = filter
10
+ begin
11
+ @io.say "#{@repository.cards.length} cards match filter"
12
+ rescue Exception => e
13
+ @io.say "Invalid expression:\n#{e.message}"
14
+ @repository.filter = nil
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,21 @@
1
+ require 'cardigan/text_report_formatter'
2
+
3
+ module Cardigan
4
+ module Command
5
+ class ListCards
6
+ def initialize repository, io
7
+ @repository, @io = repository, io
8
+ end
9
+
10
+ def execute name
11
+ cards = @repository.cards
12
+ formatter = TextReportFormatter.new @io
13
+ a = 0
14
+ @repository.columns.each do |column|
15
+ formatter.add_column(column, @repository.max_field_length(column))
16
+ end
17
+ formatter.output cards
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,17 @@
1
+ require 'cardigan/entry_context'
2
+
3
+ module Cardigan
4
+ module Command
5
+ class OpenCard
6
+ def initialize repository, workflow_repository, io
7
+ @repository, @workflow_repository, @io = repository, workflow_repository, io
8
+ end
9
+
10
+ def execute name
11
+ card = @repository.find_or_create(name)
12
+ EntryContext.new(@io, @workflow_repository, card).push
13
+ @repository.save card
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ require 'cardigan/workflow_context'
2
+
3
+ module Cardigan
4
+ module Command
5
+ class OpenWorkflow
6
+ def initialize workflow_repository, io
7
+ @workflow_repository, @io = workflow_repository, io
8
+ end
9
+
10
+ def execute name
11
+ workflow = @workflow_repository.load
12
+ WorkflowContext.new(@io, workflow).push
13
+ @workflow_repository.save workflow
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,20 @@
1
+ module Cardigan
2
+ module Command
3
+ class SelectColumns
4
+ def initialize repository, io
5
+ @repository, @io = repository, io
6
+ end
7
+
8
+ def execute text
9
+ if text
10
+ @repository.columns = text.scan(/\w+/)
11
+ else
12
+ @io.say "current columns: #{@repository.columns.join(',')}"
13
+ columns = Set.new
14
+ @repository.cards.each {|card| columns += card.keys }
15
+ @io.say "available columns: #{columns.sort.join(',')}"
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,17 @@
1
+ module Cardigan
2
+ module Command
3
+ class UnclaimCards
4
+ def initialize repository, io
5
+ @repository, @io = repository, io
6
+ end
7
+
8
+ def execute numbers
9
+ @repository.each_card_from_indices(numbers) do |card|
10
+ @io.say "unclaiming \"#{card['name']}\""
11
+ card.delete('owner')
12
+ @repository.save card
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,13 @@
1
+ module Cardigan
2
+ module Command
3
+ class UnfilterCards
4
+ def initialize repository
5
+ @repository = repository
6
+ end
7
+
8
+ def execute ignored
9
+ @repository.filter = nil
10
+ end
11
+ end
12
+ end
13
+ end
@@ -3,9 +3,9 @@ require 'readline'
3
3
  module Cardigan
4
4
  module Context
5
5
  def refresh
6
- refresh_commands if respond_to?(:refresh_commands)
6
+ commands = respond_to?(:refresh_commands) ? refresh_commands : []
7
7
  Readline.completion_proc = lambda do |text|
8
- (@commands + ['quit', 'exit']).grep( /^#{Regexp.escape(text)}/ ).sort
8
+ (commands + @commands.keys + ['quit', 'exit']).grep( /^#{Regexp.escape(text)}/ ).sort
9
9
  end
10
10
  Readline.completer_word_break_characters = ''
11
11
  end
@@ -33,11 +33,15 @@ module Cardigan
33
33
  end
34
34
  puts
35
35
  end
36
-
36
+
37
37
  def process_command name, parameter=nil
38
38
  m = "#{name}_command".to_sym
39
39
  if respond_to?(m)
40
- send(m, parameter)
40
+ send(m, parameter)
41
+ return
42
+ end
43
+ if @commands[name]
44
+ @commands[name].execute parameter
41
45
  else
42
46
  @io.say 'unknown command'
43
47
  end
@@ -3,17 +3,30 @@ require 'cardigan/context'
3
3
  module Cardigan
4
4
  class EntryContext
5
5
  include Context
6
-
7
- def initialize io, entry
8
- @io, @entry = io, entry
6
+
7
+ def initialize io, workflow_repository, entry
8
+ @io, @workflow_repository, @entry = io, workflow_repository, entry
9
9
  @prompt_text = "#{File.expand_path('.').split('/').last.slice(0..0)}/#{entry['name']} > "
10
- @commands = ['set', 'show']
10
+ @commands = {}
11
+ end
12
+
13
+ def refresh_commands
14
+ commands = ['set', 'show']
15
+ status = @entry['status'] || 'none'
16
+ @workflow_repository.load[status].each do |s|
17
+ commands << "now #{s}"
18
+ end
19
+ commands
20
+ end
21
+
22
+ def now_command status
23
+ @entry['status'] = status
11
24
  end
12
25
 
13
26
  def set_command key
14
27
  @entry[key] = @io.ask("Enter the new value for #{key}")
15
28
  end
16
-
29
+
17
30
  def show_command ignored=nil
18
31
  @entry.keys.sort.each do |key|
19
32
  @io.say "#{key}: #{@entry[key]}"
@@ -0,0 +1,31 @@
1
+ require 'forwardable'
2
+
3
+ module Cardigan
4
+ class FilteredRepository
5
+ attr_accessor :filter, :columns, :sort
6
+
7
+ extend Forwardable
8
+
9
+ def_delegators :@repository, :refresh, :save, :destroy, :find_or_create
10
+
11
+ def initialize repository, sort, *columns
12
+ @repository, @sort, @columns = repository, sort, columns
13
+ end
14
+
15
+ def cards
16
+ cards = @filter ? @repository.cards.select {|card| eval @filter } : @repository.cards
17
+ cards.sort {|a,b| a[sort] <=> b[sort] }
18
+ end
19
+
20
+ def max_field_length name
21
+ cards.map {|card| card[name] ? card[name].length : 0 }.max
22
+ end
23
+
24
+ def each_card_from_indices numbers
25
+ c = cards
26
+ numbers.scan(/\d+/).each do |n|
27
+ yield c[n.to_i - 1]
28
+ end
29
+ end
30
+ end
31
+ end
@@ -12,11 +12,11 @@ module Cardigan
12
12
  def refresh
13
13
  @cards = @directory.find('*.card')
14
14
  end
15
-
15
+
16
16
  def find_card name
17
17
  @cards.find {|card| card['name'] == name}
18
18
  end
19
-
19
+
20
20
  def save card
21
21
  @directory.store "#{card['id']}.card", card
22
22
  end
@@ -2,112 +2,44 @@ require 'rubygems'
2
2
  require 'uuidtools'
3
3
  require 'set'
4
4
  require 'cardigan/context'
5
- require 'cardigan/entry_context'
6
- require 'cardigan/text_report_formatter'
5
+ require 'cardigan/filtered_repository'
6
+ require 'cardigan/command/batch_update_cards'
7
+ require 'cardigan/command/claim_cards'
8
+ require 'cardigan/command/create_card'
9
+ require 'cardigan/command/destroy_cards'
10
+ require 'cardigan/command/filter_cards'
11
+ require 'cardigan/command/list_cards'
12
+ require 'cardigan/command/open_card'
13
+ require 'cardigan/command/open_workflow'
14
+ require 'cardigan/command/select_columns'
15
+ require 'cardigan/command/unclaim_cards'
16
+ require 'cardigan/command/unfilter_cards'
7
17
 
8
18
  module Cardigan
9
19
  class RootContext
10
20
  include Context
11
21
 
12
- def initialize io, repository, name
13
- @io, @repository, @name = io, repository, name
22
+ def initialize io, repository, name, workflow_repository
23
+ @io, @repository, @name, @workflow_repository = io, FilteredRepository.new(repository, 'name', 'name'), name, workflow_repository
14
24
  @prompt_text = "#{File.expand_path('.').split('/').last} > "
15
- @columns = ['name']
25
+ @commands = {
26
+ 'claim' => Command::ClaimCards.new(@repository, @io),
27
+ 'columns' => Command::SelectColumns.new(@repository, @io),
28
+ 'create' => Command::CreateCard.new(@repository),
29
+ 'destroy' => Command::DestroyCards.new(@repository, @io),
30
+ 'filter' => Command::FilterCards.new(@repository, @io),
31
+ 'list' => Command::ListCards.new(@repository, @io),
32
+ 'open' => Command::OpenCard.new(@repository, @workflow_repository, @io),
33
+ 'set' => Command::BatchUpdateCards.new(@repository, @io),
34
+ 'unclaim' => Command::UnclaimCards.new(@repository, @io),
35
+ 'unfilter' => Command::UnfilterCards.new(@repository),
36
+ 'workflow' => Command::OpenWorkflow.new(@workflow_repository, @io)
37
+ }
16
38
  end
17
39
 
18
40
  def refresh_commands
19
41
  @repository.refresh
20
- @commands = ['create', 'list', 'filter', 'unfilter', 'columns', 'claim', 'unclaim', 'destroy']
21
- @repository.cards.each do |card|
22
- @commands << "open #{card['name']}"
23
- end
24
- end
25
-
26
- def create_command name
27
- @repository.save @repository.find_or_create(name)
28
- end
29
-
30
- def open_command name
31
- card = @repository.find_or_create(name)
32
- EntryContext.new(@io, card).push
33
- @repository.save card
34
- end
35
-
36
- def destroy_command numbers
37
- each_card_from_indices(numbers) do |card|
38
- @io.say "destroying \"#{card['name']}\""
39
- @repository.destroy card
40
- end
41
- end
42
-
43
- def list_command ignored
44
- cards = sorted_selection
45
- formatter = TextReportFormatter.new @io
46
- a = 0
47
- @columns.each do |column|
48
- formatter.add_column(column, max_field_length(cards, column))
49
- end
50
- formatter.output cards
51
- end
52
-
53
- def unfilter_command ignored
54
- @filter = nil
55
- end
56
-
57
- def columns_command text
58
- if text
59
- @columns = text.scan(/\w+/)
60
- else
61
- @io.say "current columns: #{@columns.join(',')}"
62
- columns = Set.new
63
- sorted_selection.each do |card|
64
- columns += card.keys
65
- end
66
- @io.say "available columns: #{columns.sort.join(',')}"
67
- end
68
- end
69
-
70
- def filter_command code
71
- @filter = code
72
- begin
73
- cards = @repository.cards.select {|card| eval @filter }
74
- @io.say "#{cards.count} cards match filter"
75
- rescue Exception => e
76
- @io.say "Invalid expression:\n#{e.message}"
77
- @filter = nil
78
- end
79
- end
80
-
81
- def claim_command numbers
82
- each_card_from_indices(numbers) do |card|
83
- @io.say "claiming \"#{card['name']}\""
84
- card['owner'] = @name
85
- @repository.save card
86
- end
87
- end
88
-
89
- def unclaim_command numbers
90
- each_card_from_indices(numbers) do |card|
91
- @io.say "claiming \"#{card['name']}\""
92
- card.delete('owner')
93
- @repository.save card
94
- end
95
- end
96
- private
97
- def each_card_from_indices numbers
98
- cards = sorted_selection
99
- numbers.scan(/\d+/).each do |n|
100
- yield cards[n.to_i - 1]
101
- end
102
- end
103
-
104
- def max_field_length cards, name
105
- cards.map {|card| card[name] ? card[name].length : 0 }.max
106
- end
107
-
108
- def sorted_selection
109
- cards = @filter ? @repository.cards.select {|card| eval @filter } : @repository.cards
110
- cards.sort {|a,b| a['name'] <=> b['name'] }
42
+ @repository.cards.map {|card| "open #{card['name']}" }
111
43
  end
112
44
  end
113
- end
45
+ end
@@ -18,7 +18,9 @@ module Cardigan
18
18
  row 'index', @columns.map {|tuple| tuple.first }
19
19
  hline
20
20
  hashes.each_with_index do |h,i|
21
- row (i+1).to_s, @columns.map {|tuple| h[tuple.first]}
21
+ first = (i+1).to_s
22
+ values = @columns.map {|tuple| h[tuple.first]}
23
+ row first, values
22
24
  end
23
25
  hline
24
26
  end
@@ -26,7 +28,7 @@ private
26
28
  def hline
27
29
  @io.say ' ' + '-' * @width
28
30
  end
29
-
31
+
30
32
  def row first, values
31
33
  a = "| #{first.ljust(@heading.length)} | "
32
34
  @columns.each_with_index do |tuple, index|
@@ -0,0 +1,37 @@
1
+ require 'cardigan/context'
2
+
3
+ module Cardigan
4
+ class WorkflowContext
5
+ include Context
6
+
7
+ def initialize io, entry
8
+ @io, @entry = io, entry
9
+ @prompt_text = "#{File.expand_path('.').split('/').last.slice(0..0)}/workflow > "
10
+ @commands = {}
11
+ end
12
+
13
+ def refresh_commands
14
+ ['create', 'add', 'remove', 'show']
15
+ end
16
+
17
+ def create_command key
18
+ @entry[key] = []
19
+ end
20
+
21
+ def add_command text
22
+ name, *states = text.scan(/\w+/)
23
+ @entry[name] += states
24
+ end
25
+
26
+ def remove_command text
27
+ name, *states = text.scan(/\w+/)
28
+ @entry[name] -= states
29
+ end
30
+
31
+ def show_command ignored=nil
32
+ @entry.keys.sort.each do |key|
33
+ @io.say "#{key}: #{@entry[key].join(',')}"
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,16 @@
1
+ module Cardigan
2
+ class WorkflowRepository
3
+ def initialize path
4
+ @directory = Directory.new(path)
5
+ @path = '.card_workflow'
6
+ end
7
+
8
+ def save workflow
9
+ @directory.store @path, workflow
10
+ end
11
+
12
+ def load
13
+ @directory.has_file?(@path) ? @directory.load(@path) : {}
14
+ end
15
+ end
16
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cardigan
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mark Ryall
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2010-03-13 00:00:00 +11:00
12
+ date: 2010-03-14 00:00:00 +11:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -34,13 +34,27 @@ extra_rdoc_files: []
34
34
 
35
35
  files:
36
36
  - lib/cardigan/cli.rb
37
+ - lib/cardigan/command/batch_update_cards.rb
38
+ - lib/cardigan/command/claim_cards.rb
39
+ - lib/cardigan/command/create_card.rb
40
+ - lib/cardigan/command/destroy_cards.rb
41
+ - lib/cardigan/command/filter_cards.rb
42
+ - lib/cardigan/command/list_cards.rb
43
+ - lib/cardigan/command/open_card.rb
44
+ - lib/cardigan/command/open_workflow.rb
45
+ - lib/cardigan/command/select_columns.rb
46
+ - lib/cardigan/command/unclaim_cards.rb
47
+ - lib/cardigan/command/unfilter_cards.rb
37
48
  - lib/cardigan/context.rb
38
49
  - lib/cardigan/directory.rb
39
50
  - lib/cardigan/entry_context.rb
51
+ - lib/cardigan/filtered_repository.rb
40
52
  - lib/cardigan/io.rb
41
53
  - lib/cardigan/repository.rb
42
54
  - lib/cardigan/root_context.rb
43
55
  - lib/cardigan/text_report_formatter.rb
56
+ - lib/cardigan/workflow_context.rb
57
+ - lib/cardigan/workflow_repository.rb
44
58
  - bin/cardigan
45
59
  - README.rdoc
46
60
  - MIT-LICENSE