cardigan 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,10 @@
1
+ When /^I create the following cards:$/ do |table|
2
+ table.hashes.each do |hash|
3
+ type "cd #{hash['name']}"
4
+ hash.keys.each do |key|
5
+ type "set #{key}"
6
+ type hash[key]
7
+ end
8
+ type "exit"
9
+ end
10
+ end
@@ -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
@@ -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
- @home = FlatHash::Directory.new(FlatHash::Serialiser.new,File.expand_path('~'))
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
- config = @home.exist?(CONFIG_FILE) ? @home[CONFIG_FILE] : {}
18
- config['name'] = @io.ask('Enter your full name') unless config['name']
19
- config['email'] = @io.ask('Enter your email address') unless config['email']
20
- @home[CONFIG_FILE] = config
21
- repository = FlatHash::Repository.new(FlatHash::Serialiser.new,'.cards')
22
- name = "\"#{config['name']}\" <#{config['email']}>"
23
- workflow_repository = Cardigan::WorkflowRepository.new('.')
24
- Cardigan::RootContext.new(@io, repository, name, workflow_repository).push
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
@@ -22,7 +22,7 @@ class Cardigan::Command::CountCards
22
22
  end
23
23
  key = grouping_fields.map {|key| card[key] ? card[key] : ''}
24
24
  counts[key] = counts[key] ? counts[key] + 1 : 1
25
- total += counts[key]
25
+ total += 1
26
26
  end
27
27
 
28
28
  values = counts.keys.sort.map do |key|
@@ -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 |row|
22
- if header
23
- if id_index and row[id_index]
24
- id = row[id_index]
25
- card = @repository[id] if @repository.exist?(id)
26
- end
27
- if card
28
- puts "updating #{id}"
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
- id = UUIDTools::UUID.random_create.to_s
31
- card = {}
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
- attr_accessor :filter, :sort_columns, :display_columns
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, *columns
14
- @repository, @sort_columns, @display_columns, @user = repository, columns, columns, user
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, FilteredRepository.new(repository, name, 'name'), name, workflow_repository
12
- @prompt_text = "#{File.expand_path('.').split('/').last} > "
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