cardigan 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/HISTORY.md +12 -0
- data/README.md +126 -0
- data/Rakefile +14 -1
- data/features/commands/cd.feature +24 -0
- data/features/commands/claim.feature +20 -0
- data/features/commands/columns.feature +24 -0
- data/features/commands/count.feature +66 -0
- data/features/commands/export.feature +20 -0
- data/features/commands/filter.feature +42 -0
- data/features/commands/import.feature +32 -0
- data/features/commands/ls.feature +29 -0
- data/features/commands/rm.feature +27 -0
- data/features/commands/set.feature +31 -0
- data/features/commands/total.feature +70 -0
- data/features/commands/touch.feature +33 -0
- data/features/commands/unclaim.feature +20 -0
- data/features/commands/unfilter.feature +26 -0
- data/features/step_definitions/cardigan_steps.rb +10 -0
- data/features/support/env.rb +32 -0
- data/lib/cardigan/cli.rb +19 -11
- data/lib/cardigan/command/count_cards.rb +1 -1
- data/lib/cardigan/command/import_cards.rb +21 -19
- data/lib/cardigan/command/open_card.rb +1 -1
- data/lib/cardigan/command/total_cards.rb +4 -0
- data/lib/cardigan/configuration.rb +19 -0
- data/lib/cardigan/filtered_repository.rb +26 -3
- data/lib/cardigan/root_context.rb +7 -3
- data/lib/cardigan/workflow_repository.rb +5 -3
- data/spec/command/total_cards_spec.rb +2 -0
- data/spec/workflow_repository_spec.rb +25 -0
- data/tools/zsh/_cardigan +35 -0
- metadata +124 -87
- data/README.rdoc +0 -73
@@ -0,0 +1,31 @@
|
|
1
|
+
Feature: set
|
2
|
+
|
3
|
+
In order to manage my ideas
|
4
|
+
As a command line junkie
|
5
|
+
I want to be able to bulk edit attributes of cards
|
6
|
+
|
7
|
+
Scenario: editing an attribute on multiple cards
|
8
|
+
When I run `cardigan` interactively
|
9
|
+
And I create the following cards:
|
10
|
+
| name |
|
11
|
+
| card 1 |
|
12
|
+
| card 2 |
|
13
|
+
| card 3 |
|
14
|
+
| card 4 |
|
15
|
+
And I type "set estimate 1 4 "
|
16
|
+
And I type "10"
|
17
|
+
And I type "columns name estimate"
|
18
|
+
And I type "ls"
|
19
|
+
And I type "exit"
|
20
|
+
Then the exit status should be 0
|
21
|
+
And the stdout should contain:
|
22
|
+
"""
|
23
|
+
---------------------------
|
24
|
+
| index | name | estimate |
|
25
|
+
---------------------------
|
26
|
+
| 1 | card 1 | 10 |
|
27
|
+
| 2 | card 2 | |
|
28
|
+
| 3 | card 3 | |
|
29
|
+
| 4 | card 4 | 10 |
|
30
|
+
---------------------------
|
31
|
+
"""
|
@@ -0,0 +1,70 @@
|
|
1
|
+
Feature: total
|
2
|
+
|
3
|
+
In order to see relevant information
|
4
|
+
As a command line junkie
|
5
|
+
I want to calculate the total of a numeric attribute of cards
|
6
|
+
|
7
|
+
Scenario: incorrect usage of total command
|
8
|
+
When I run `cardigan` interactively
|
9
|
+
And I type "total"
|
10
|
+
And I type "exit"
|
11
|
+
Then the exit status should be 0
|
12
|
+
And the stdout should contain "missing required numeric total field"
|
13
|
+
|
14
|
+
Scenario: calculate total without any grouping
|
15
|
+
When I run `cardigan` interactively
|
16
|
+
And I create the following cards:
|
17
|
+
| name | estimate |
|
18
|
+
| card 1 | 3 |
|
19
|
+
| card 2 | 2 |
|
20
|
+
And I type "total estimate"
|
21
|
+
And I type "exit"
|
22
|
+
Then the exit status should be 0
|
23
|
+
And the stdout should contain:
|
24
|
+
"""
|
25
|
+
----------
|
26
|
+
| estimate |
|
27
|
+
----------
|
28
|
+
| 5 |
|
29
|
+
----------
|
30
|
+
"""
|
31
|
+
|
32
|
+
Scenario: calculate total with some missing values
|
33
|
+
When I run `cardigan` interactively
|
34
|
+
And I create the following cards:
|
35
|
+
| name | estimate |
|
36
|
+
| card 1 | 3 |
|
37
|
+
| card 2 | 2 |
|
38
|
+
| card 3 | |
|
39
|
+
And I type "total estimate"
|
40
|
+
And I type "exit"
|
41
|
+
Then the exit status should be 0
|
42
|
+
And the stdout should contain:
|
43
|
+
"""
|
44
|
+
----------
|
45
|
+
| estimate |
|
46
|
+
----------
|
47
|
+
| 5 |
|
48
|
+
----------
|
49
|
+
"""
|
50
|
+
|
51
|
+
Scenario: calculate total without grouping by a field
|
52
|
+
When I run `cardigan` interactively
|
53
|
+
And I create the following cards:
|
54
|
+
| name | estimate | release |
|
55
|
+
| card 1 | 3 | ocelot |
|
56
|
+
| card 2 | 2 | margay |
|
57
|
+
| card 3 | 5 | margay |
|
58
|
+
And I type "total estimate release"
|
59
|
+
And I type "exit"
|
60
|
+
Then the exit status should be 0
|
61
|
+
And the stdout should contain:
|
62
|
+
"""
|
63
|
+
--------------------
|
64
|
+
| release | estimate |
|
65
|
+
--------------------
|
66
|
+
| margay | 7 |
|
67
|
+
| ocelot | 3 |
|
68
|
+
| | 10 |
|
69
|
+
--------------------
|
70
|
+
"""
|
@@ -0,0 +1,33 @@
|
|
1
|
+
Feature: touch
|
2
|
+
|
3
|
+
In order to manage my ideas
|
4
|
+
As a command line junkie
|
5
|
+
I want to create store these ideas on virtual cards
|
6
|
+
|
7
|
+
Scenario: a single card is added in shell
|
8
|
+
When I run `cardigan` interactively
|
9
|
+
And I type "touch today is the first day of the rest of your life"
|
10
|
+
And I type "ls"
|
11
|
+
And I type "exit"
|
12
|
+
Then the exit status should be 0
|
13
|
+
And the stdout should contain:
|
14
|
+
"""
|
15
|
+
---------------------------------------------------------
|
16
|
+
| index | name |
|
17
|
+
---------------------------------------------------------
|
18
|
+
| 1 | today is the first day of the rest of your life |
|
19
|
+
---------------------------------------------------------
|
20
|
+
"""
|
21
|
+
|
22
|
+
Scenario: a single card is added without shell
|
23
|
+
When I run `cardigan touch do some amazing stuff with my life`
|
24
|
+
And I run `cardigan ls`
|
25
|
+
Then the exit status should be 0
|
26
|
+
And the stdout should contain:
|
27
|
+
"""
|
28
|
+
--------------------------------------------
|
29
|
+
| index | name |
|
30
|
+
--------------------------------------------
|
31
|
+
| 1 | do some amazing stuff with my life |
|
32
|
+
--------------------------------------------
|
33
|
+
"""
|
@@ -0,0 +1,20 @@
|
|
1
|
+
Feature: unclaim
|
2
|
+
|
3
|
+
In order to indicate to others I am no longer allocated to a card
|
4
|
+
As a command line junkie
|
5
|
+
I want to unclaim cards
|
6
|
+
|
7
|
+
Scenario: claim cards
|
8
|
+
When I run `cardigan touch a very interesting card`
|
9
|
+
And I run `cardigan unclaim 1`
|
10
|
+
And I run `cardigan columns name owner`
|
11
|
+
And I run `cardigan ls`
|
12
|
+
Then the exit status should be 0
|
13
|
+
And the stdout should contain:
|
14
|
+
"""
|
15
|
+
-----------------------------------------
|
16
|
+
| index | name | owner |
|
17
|
+
-----------------------------------------
|
18
|
+
| 1 | a very interesting card | |
|
19
|
+
-----------------------------------------
|
20
|
+
"""
|
@@ -0,0 +1,26 @@
|
|
1
|
+
Feature: unfilter
|
2
|
+
|
3
|
+
In order to see relevant information
|
4
|
+
As a command line junkie
|
5
|
+
I want to filter the cards that are displayed
|
6
|
+
|
7
|
+
Scenario: remove filter from cards
|
8
|
+
When I run `cardigan` interactively
|
9
|
+
And I create the following cards:
|
10
|
+
| name | release |
|
11
|
+
| another interesting card | margay |
|
12
|
+
| a seriously interesting card | ocelot |
|
13
|
+
And I type "filter card['release'] == 'ocelot'"
|
14
|
+
And I type "unfilter"
|
15
|
+
And I type "ls"
|
16
|
+
And I type "exit"
|
17
|
+
Then the exit status should be 0
|
18
|
+
And the stdout should contain:
|
19
|
+
"""
|
20
|
+
--------------------------------------
|
21
|
+
| index | name |
|
22
|
+
--------------------------------------
|
23
|
+
| 1 | a seriously interesting card |
|
24
|
+
| 2 | another interesting card |
|
25
|
+
--------------------------------------
|
26
|
+
"""
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'aruba/cucumber'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
ENV['PATH'] = "#{File.expand_path(File.dirname(__FILE__) + '/../../bin')}#{File::PATH_SEPARATOR}#{ENV['PATH']}"
|
5
|
+
|
6
|
+
HOME = File.expand_path '~'
|
7
|
+
CARDIGAN_CONFIG = HOME+'/.cardigan'
|
8
|
+
CARDIGAN_CONFIG_BKP = CARDIGAN_CONFIG+'.bkp'
|
9
|
+
CARDIGAN_NAME = 'Ms Crazy Person'
|
10
|
+
CARDIGAN_EMAIL = 'you@there.com'
|
11
|
+
|
12
|
+
Before do
|
13
|
+
if File.exist? CARDIGAN_CONFIG
|
14
|
+
FileUtils.mv CARDIGAN_CONFIG, CARDIGAN_CONFIG_BKP
|
15
|
+
File.open CARDIGAN_CONFIG, 'w' do |file|
|
16
|
+
file.puts <<EOF
|
17
|
+
email
|
18
|
+
#{CARDIGAN_EMAIL}
|
19
|
+
<----->
|
20
|
+
name
|
21
|
+
#{CARDIGAN_NAME}
|
22
|
+
EOF
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
After do
|
28
|
+
if File.exist? CARDIGAN_CONFIG_BKP
|
29
|
+
FileUtils.rm CARDIGAN_CONFIG
|
30
|
+
FileUtils.mv CARDIGAN_CONFIG_BKP, CARDIGAN_CONFIG
|
31
|
+
end
|
32
|
+
end
|
data/lib/cardigan/cli.rb
CHANGED
@@ -4,23 +4,31 @@ require 'flat_hash/directory'
|
|
4
4
|
require 'flat_hash/serialiser'
|
5
5
|
require 'flat_hash/repository'
|
6
6
|
require 'cardigan/workflow_repository'
|
7
|
+
require 'cardigan/configuration'
|
8
|
+
require 'cardigan/filtered_repository'
|
7
9
|
|
8
10
|
class Cardigan::Cli
|
9
|
-
CONFIG_FILE = '.cardigan'
|
10
|
-
|
11
11
|
def initialize io=Cardigan::Io.new
|
12
12
|
@io = io
|
13
|
-
|
13
|
+
home_path = File.expand_path '~'
|
14
|
+
home = FlatHash::Directory.new FlatHash::Serialiser.new, home_path
|
15
|
+
@local = FlatHash::Directory.new FlatHash::Serialiser.new, '.'
|
16
|
+
@configuration = Cardigan::Configuration.new home, '.cardigan'
|
14
17
|
end
|
15
18
|
|
16
19
|
def execute *args
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
@
|
21
|
-
|
22
|
-
|
23
|
-
workflow_repository = Cardigan::WorkflowRepository.new
|
24
|
-
Cardigan::RootContext.new
|
20
|
+
@configuration['name'] = @io.ask('Enter your full name') unless @configuration['name']
|
21
|
+
@configuration['email'] = @io.ask('Enter your email address') unless @configuration['email']
|
22
|
+
repository = FlatHash::Repository.new FlatHash::Serialiser.new, '.cards'
|
23
|
+
name = "\"#{@configuration['name']}\" <#{@configuration['email']}>"
|
24
|
+
local_configuration = Cardigan::Configuration.new @local, '.cardigan'
|
25
|
+
filtered_repository = Cardigan::FilteredRepository.new repository, name, local_configuration
|
26
|
+
workflow_repository = Cardigan::WorkflowRepository.new @local
|
27
|
+
root = Cardigan::RootContext.new @io, filtered_repository, name, workflow_repository
|
28
|
+
if args.size == 0
|
29
|
+
root.push
|
30
|
+
else
|
31
|
+
root.execute args
|
32
|
+
end
|
25
33
|
end
|
26
34
|
end
|
@@ -18,27 +18,29 @@ class Cardigan::Command::ImportCards
|
|
18
18
|
return
|
19
19
|
end
|
20
20
|
header, id_index = nil, nil
|
21
|
-
CSV.open(filename, 'r') do |
|
22
|
-
|
23
|
-
if
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
21
|
+
CSV.open(filename, 'r') do |csv|
|
22
|
+
csv.to_a.each do |row|
|
23
|
+
if header
|
24
|
+
if id_index and row[id_index]
|
25
|
+
id = row[id_index]
|
26
|
+
card = @repository[id] if @repository.exist?(id)
|
27
|
+
end
|
28
|
+
if card
|
29
|
+
puts "updating #{id}"
|
30
|
+
else
|
31
|
+
id ||= UUIDTools::UUID.random_create.to_s
|
32
|
+
card = {}
|
33
|
+
puts "creating new card with id #{id}"
|
34
|
+
end
|
35
|
+
editor = Cardigan::CardEditor.new(card, @io)
|
36
|
+
header.each_with_index do |field, index|
|
37
|
+
editor.set field, row[index]
|
38
|
+
end
|
39
|
+
@repository[id] = card
|
29
40
|
else
|
30
|
-
|
31
|
-
|
32
|
-
puts "creating card #{id}"
|
33
|
-
end
|
34
|
-
editor = Cardigan::CardEditor.new(card, @io)
|
35
|
-
header.each_with_index do |field, index|
|
36
|
-
editor.set field, row[index]
|
41
|
+
header = row
|
42
|
+
id_index = header.find_index('id')
|
37
43
|
end
|
38
|
-
@repository[id] = card
|
39
|
-
else
|
40
|
-
header = row
|
41
|
-
id_index = header.find_index('id')
|
42
44
|
end
|
43
45
|
end
|
44
46
|
end
|
@@ -12,9 +12,9 @@ class Cardigan::Command::OpenCard
|
|
12
12
|
|
13
13
|
def execute name
|
14
14
|
entry = @repository.cards.find { |entry| entry['name'] == name }
|
15
|
+
original = entry.dup if entry
|
15
16
|
id = entry ? entry.id : UUIDTools::UUID.random_create.to_s
|
16
17
|
entry ||= {'name' => name}
|
17
|
-
original = entry.dup
|
18
18
|
Cardigan::EntryContext.new(@io, @workflow_repository, entry).push
|
19
19
|
@repository[id] = entry unless original == entry
|
20
20
|
end
|
@@ -17,10 +17,12 @@ class Cardigan::Command::TotalCards
|
|
17
17
|
return
|
18
18
|
end
|
19
19
|
counts = {}
|
20
|
+
total = 0
|
20
21
|
@repository.cards.each do |card|
|
21
22
|
key = grouping_fields.map {|grouping_field| card[grouping_field] ? card[grouping_field] : ''}
|
22
23
|
value = card[count_field].to_i
|
23
24
|
counts[key] = counts[key] ? counts[key] + value : value
|
25
|
+
total += value
|
24
26
|
end
|
25
27
|
|
26
28
|
values = counts.keys.sort.map do |key|
|
@@ -29,6 +31,8 @@ class Cardigan::Command::TotalCards
|
|
29
31
|
hash
|
30
32
|
end
|
31
33
|
|
34
|
+
values << {count_field => total} unless counts.size == 1
|
35
|
+
|
32
36
|
formatter = Cardigan::TextReportFormatter.new @io
|
33
37
|
grouping_fields.each {|grouping_field| formatter.add_column(grouping_field, 0)}
|
34
38
|
formatter.add_column(count_field, 0)
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Cardigan
|
2
|
+
class Configuration
|
3
|
+
def initialize directory, name
|
4
|
+
@directory = directory
|
5
|
+
@name = name
|
6
|
+
@config = @directory.exist?(name) ? @directory[name] : {}
|
7
|
+
end
|
8
|
+
|
9
|
+
def [] key
|
10
|
+
@config[key]
|
11
|
+
end
|
12
|
+
|
13
|
+
def []= key, value
|
14
|
+
@config[key] = value
|
15
|
+
@config.delete key unless value
|
16
|
+
@directory[@name] = @config
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -5,13 +5,36 @@ module Cardigan
|
|
5
5
|
class FilteredRepository
|
6
6
|
include Enumerable
|
7
7
|
|
8
|
-
|
8
|
+
attr_reader :filter, :sort_columns, :display_columns
|
9
9
|
|
10
10
|
extend Forwardable
|
11
11
|
def_delegators :@repository, :[], :[]=, :exist?, :destroy, :addremovecommit
|
12
12
|
|
13
|
-
def initialize repository, user,
|
14
|
-
@repository, @
|
13
|
+
def initialize repository, user, configuration
|
14
|
+
@repository, @user, @configuration = repository, user, configuration
|
15
|
+
@sort_columns = multi('sort') || ['name']
|
16
|
+
@display_columns = multi('display') || ['name']
|
17
|
+
@filter = @configuration['filter']
|
18
|
+
end
|
19
|
+
|
20
|
+
def multi key
|
21
|
+
return nil unless @configuration[key]
|
22
|
+
@configuration[key].split "\n"
|
23
|
+
end
|
24
|
+
|
25
|
+
def filter= filter
|
26
|
+
@filter = filter
|
27
|
+
@configuration['filter'] = filter
|
28
|
+
end
|
29
|
+
|
30
|
+
def sort_columns= columns
|
31
|
+
@sort_columns = columns
|
32
|
+
@configuration['sort'] = columns.join "\n"
|
33
|
+
end
|
34
|
+
|
35
|
+
def display_columns= columns
|
36
|
+
@display_columns = columns
|
37
|
+
@configuration['display'] = columns.join "\n"
|
15
38
|
end
|
16
39
|
|
17
40
|
def cards
|
@@ -1,15 +1,14 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'shell_shock/context'
|
3
3
|
require 'cardigan/commands'
|
4
|
-
require 'cardigan/filtered_repository'
|
5
4
|
|
6
5
|
module Cardigan
|
7
6
|
class RootContext
|
8
7
|
include ShellShock::Context
|
9
8
|
|
10
9
|
def initialize io, repository, name, workflow_repository
|
11
|
-
@io, @repository, @name, @workflow_repository = io,
|
12
|
-
@
|
10
|
+
@io, @repository, @name, @workflow_repository = io, repository, name, workflow_repository
|
11
|
+
@prompt = "#{File.expand_path('.').split('/').last} > "
|
13
12
|
@commands = {
|
14
13
|
'claim' => Command.load(:claim_cards, @repository, @io, @name),
|
15
14
|
'touch' => Command.load(:create_card, @repository),
|
@@ -30,5 +29,10 @@ module Cardigan
|
|
30
29
|
'commit' => Command.load(:commit_changes, @repository, @io)
|
31
30
|
}
|
32
31
|
end
|
32
|
+
|
33
|
+
def execute args
|
34
|
+
command = args.shift
|
35
|
+
@commands[command].execute args.join(' ')
|
36
|
+
end
|
33
37
|
end
|
34
38
|
end
|