cardigan 0.0.3 → 0.0.4

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/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