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.
@@ -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