cardigan 0.1.0 → 0.1.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/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
|