ppl 0.3.0 → 0.9.0

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.
Files changed (97) hide show
  1. data/.gitignore +1 -0
  2. data/Gemfile +2 -0
  3. data/Gemfile.lock +4 -0
  4. data/README.md +1 -3
  5. data/lib/ppl/adapter/storage/disk.rb +27 -7
  6. data/lib/ppl/adapter/storage/factory.rb +26 -0
  7. data/lib/ppl/adapter/storage/git.rb +101 -0
  8. data/lib/ppl/adapter/storage.rb +4 -0
  9. data/lib/ppl/adapter/vcard/vpim.rb +16 -0
  10. data/lib/ppl/application/bootstrap.rb +23 -10
  11. data/lib/ppl/application/command.rb +3 -0
  12. data/lib/ppl/application/command_suite.rb +8 -0
  13. data/lib/ppl/application/configuration.rb +50 -0
  14. data/lib/ppl/application/output.rb +1 -0
  15. data/lib/ppl/application/shell.rb +21 -0
  16. data/lib/ppl/command/add.rb +34 -0
  17. data/lib/ppl/command/bday.rb +72 -0
  18. data/lib/ppl/command/email.rb +55 -0
  19. data/lib/ppl/command/help.rb +43 -0
  20. data/lib/ppl/command/init.rb +26 -0
  21. data/lib/ppl/command/ls.rb +26 -0
  22. data/lib/ppl/command/mv.rb +42 -0
  23. data/lib/ppl/command/name.rb +54 -0
  24. data/lib/ppl/command/org.rb +55 -0
  25. data/lib/ppl/command/phone.rb +55 -0
  26. data/lib/ppl/command/rm.rb +26 -0
  27. data/lib/ppl/command/show.rb +31 -0
  28. data/lib/ppl/entity/contact.rb +2 -0
  29. data/lib/ppl/format/address_book/birthdays.rb +34 -0
  30. data/lib/ppl/format/address_book/email_addresses.rb +34 -0
  31. data/lib/ppl/format/address_book/names.rb +34 -0
  32. data/lib/ppl/format/address_book/one_line.rb +34 -0
  33. data/lib/ppl/format/address_book/organizations.rb +34 -0
  34. data/lib/ppl/format/address_book/phone_numbers.rb +34 -0
  35. data/lib/ppl/format/address_book.rb +9 -0
  36. data/lib/ppl/format/contact/birthday.rb +13 -0
  37. data/lib/ppl/format/contact/email_address.rb +13 -0
  38. data/lib/ppl/format/contact/full.rb +55 -0
  39. data/lib/ppl/format/contact/name.rb +15 -0
  40. data/lib/ppl/format/contact/organization.rb +13 -0
  41. data/lib/ppl/format/contact/phone_number.rb +13 -0
  42. data/lib/ppl/format/contact.rb +9 -0
  43. data/lib/ppl/format/table.rb +48 -0
  44. data/lib/ppl.rb +33 -11
  45. data/ppl.gemspec +1 -1
  46. data/spec/ppl/adapter/storage/disk_spec.rb +57 -10
  47. data/spec/ppl/adapter/storage/factory_spec.rb +34 -0
  48. data/spec/ppl/adapter/storage/git_spec.rb +114 -0
  49. data/spec/ppl/adapter/storage_spec.rb +6 -0
  50. data/spec/ppl/adapter/vcard/vpim_spec.rb +34 -0
  51. data/spec/ppl/application/bootstrap_spec.rb +42 -4
  52. data/spec/ppl/application/command_suite_spec.rb +8 -0
  53. data/spec/ppl/application/configuration_spec.rb +32 -0
  54. data/spec/ppl/application/shell_spec.rb +4 -0
  55. data/spec/ppl/command/{contact_add_spec.rb → add_spec.rb} +2 -2
  56. data/spec/ppl/command/bday_spec.rb +56 -0
  57. data/spec/ppl/command/email_spec.rb +56 -0
  58. data/spec/ppl/command/help_spec.rb +79 -0
  59. data/spec/ppl/command/init_spec.rb +40 -0
  60. data/spec/ppl/command/ls_spec.rb +32 -0
  61. data/spec/ppl/command/mv_spec.rb +62 -0
  62. data/spec/ppl/command/name_spec.rb +59 -0
  63. data/spec/ppl/command/org_spec.rb +57 -0
  64. data/spec/ppl/command/phone_spec.rb +55 -0
  65. data/spec/ppl/command/{contact_delete_spec.rb → rm_spec.rb} +8 -2
  66. data/spec/ppl/command/{contact_show_spec.rb → show_spec.rb} +12 -6
  67. data/spec/ppl/format/address_book/birthdays_spec.rb +38 -0
  68. data/spec/ppl/format/address_book/email_addresses_spec.rb +38 -0
  69. data/spec/ppl/format/address_book/names_spec.rb +38 -0
  70. data/spec/ppl/format/address_book/one_line_spec.rb +41 -0
  71. data/spec/ppl/format/address_book/organizations_spec.rb +38 -0
  72. data/spec/ppl/format/address_book/phone_numbers_spec.rb +38 -0
  73. data/spec/ppl/format/address_book_spec.rb +18 -0
  74. data/spec/ppl/format/contact/birthday_spec.rb +23 -0
  75. data/spec/ppl/format/contact/email_address_spec.rb +23 -0
  76. data/spec/ppl/format/contact/full_spec.rb +44 -0
  77. data/spec/ppl/format/contact/name_spec.rb +23 -0
  78. data/spec/ppl/format/contact/organization_spec.rb +23 -0
  79. data/spec/ppl/format/contact/phone_number_spec.rb +23 -0
  80. data/spec/ppl/format/contact_spec.rb +17 -0
  81. data/spec/ppl/format/table_spec.rb +72 -0
  82. metadata +61 -19
  83. data/lib/ppl/command/command_list.rb +0 -21
  84. data/lib/ppl/command/contact_add.rb +0 -26
  85. data/lib/ppl/command/contact_delete.rb +0 -16
  86. data/lib/ppl/command/contact_list.rb +0 -24
  87. data/lib/ppl/command/contact_rename.rb +0 -23
  88. data/lib/ppl/command/contact_show.rb +0 -20
  89. data/lib/ppl/command/set_birthday.rb +0 -30
  90. data/lib/ppl/command/set_email.rb +0 -23
  91. data/lib/ppl/command/set_name.rb +0 -28
  92. data/spec/ppl/command/command_list_spec.rb +0 -37
  93. data/spec/ppl/command/contact_list_spec.rb +0 -15
  94. data/spec/ppl/command/contact_rename_spec.rb +0 -42
  95. data/spec/ppl/command/set_birthday_spec.rb +0 -50
  96. data/spec/ppl/command/set_email_spec.rb +0 -38
  97. data/spec/ppl/command/set_name_spec.rb +0 -45
data/.gitignore CHANGED
@@ -1,2 +1,3 @@
1
1
  tmp
2
+ _site
2
3
 
data/Gemfile CHANGED
@@ -1,6 +1,8 @@
1
1
 
2
2
  source :rubygems
3
3
 
4
+ gem "inifile", "2.0.2"
5
+ gem "rugged", "0.17.0.b6"
4
6
  gem "vpim", "12.1.12", :git => "git://github.com/sam-github/vpim.git"
5
7
 
6
8
  group :test do
data/Gemfile.lock CHANGED
@@ -9,6 +9,7 @@ GEM
9
9
  specs:
10
10
  diff-lcs (1.1.3)
11
11
  fakefs (0.4.1)
12
+ inifile (2.0.2)
12
13
  rake (10.0.1)
13
14
  rspec (2.12.0)
14
15
  rspec-core (~> 2.12.0)
@@ -18,12 +19,15 @@ GEM
18
19
  rspec-expectations (2.12.0)
19
20
  diff-lcs (~> 1.1.3)
20
21
  rspec-mocks (2.12.0)
22
+ rugged (0.17.0.b6)
21
23
 
22
24
  PLATFORMS
23
25
  ruby
24
26
 
25
27
  DEPENDENCIES
26
28
  fakefs
29
+ inifile (= 2.0.2)
27
30
  rake
28
31
  rspec
32
+ rugged (= 0.17.0.b6)
29
33
  vpim (= 12.1.12)!
data/README.md CHANGED
@@ -127,9 +127,7 @@ Bug reports, fixes, and additional features are encouraged. The project uses
127
127
  [Github issues](https://github.com/h2s/ppl/issues) to track bug reports.
128
128
 
129
129
  If you'd like to contribute code, please just bear in mind that ppl development
130
- is test-driven and attempts to follow the "Object Mentor school of clean code"
131
- as described in the book [Clean
132
- Code](http://books.google.co.uk/books?id=_i6bDeoCQzsC).
130
+ is test-driven.
133
131
 
134
132
  License
135
133
  -------
@@ -1,17 +1,26 @@
1
1
 
2
+ require "fileutils"
3
+
2
4
  class Ppl::Adapter::Storage::Disk < Ppl::Adapter::Storage
3
5
 
4
- attr_accessor :path
6
+ attr_accessor :directory
5
7
  attr_accessor :vcard_adapter
6
8
 
7
- def initialize(path)
8
- @path = path
9
+ def self.create_address_book(path)
10
+ if !Dir.exists? path
11
+ FileUtils.mkdir_p(path)
12
+ end
13
+ storage = self.new(path)
14
+ end
15
+
16
+ def initialize(directory)
17
+ @directory = directory
9
18
  end
10
19
 
11
20
  def load_address_book
12
21
  address_book = Ppl::Entity::AddressBook.new
13
22
 
14
- pattern = File.join @path, "*.vcf"
23
+ pattern = File.join @directory.path, "*.vcf"
15
24
  filenames = Dir.glob pattern
16
25
 
17
26
  filenames.each do |filename|
@@ -24,7 +33,7 @@ class Ppl::Adapter::Storage::Disk < Ppl::Adapter::Storage
24
33
  end
25
34
 
26
35
  def load_contact(id)
27
- filename = File.join @path, id + ".vcf"
36
+ filename = filename_for_contact_id(id)
28
37
  contact = nil
29
38
  if File.exists?(filename)
30
39
  vcard = File.read filename
@@ -39,12 +48,23 @@ class Ppl::Adapter::Storage::Disk < Ppl::Adapter::Storage
39
48
  def save_contact(contact)
40
49
  vcard = @vcard_adapter.encode(contact)
41
50
 
42
- basename = contact.id + ".vcf"
43
- filename = File.join @path, basename
51
+ filename = filename_for_contact(contact)
44
52
  File.open(filename, "w") do |file|
45
53
  file.write(vcard)
46
54
  end
47
55
  end
48
56
 
57
+ def delete_contact(contact)
58
+ File.unlink filename_for_contact(contact)
59
+ end
60
+
61
+ def filename_for_contact(contact)
62
+ filename_for_contact_id(contact.id)
63
+ end
64
+
65
+ def filename_for_contact_id(id)
66
+ File.join(@directory.path, id + ".vcf")
67
+ end
68
+
49
69
  end
50
70
 
@@ -0,0 +1,26 @@
1
+
2
+ class Ppl::Adapter::Storage::Factory
3
+
4
+ def initialize(vcard_adapter)
5
+ @vcard_adapter = vcard_adapter
6
+ end
7
+
8
+ def load_adapter(directory)
9
+ git_dir = File.join(directory.path, ".git")
10
+
11
+ disk_adapter = Ppl::Adapter::Storage::Disk.new(directory)
12
+ disk_adapter.vcard_adapter = @vcard_adapter
13
+
14
+ adapter = disk_adapter
15
+
16
+ if File.exists?(git_dir)
17
+ git_adapter = Ppl::Adapter::Storage::Git.new(disk_adapter)
18
+ git_adapter.vcard_adapter = @vcard_adapter
19
+ adapter = git_adapter
20
+ end
21
+
22
+ return adapter
23
+ end
24
+
25
+ end
26
+
@@ -0,0 +1,101 @@
1
+
2
+ require "rugged"
3
+ require "socket"
4
+
5
+ class Ppl::Adapter::Storage::Git < Ppl::Adapter::Storage
6
+
7
+ attr_accessor :disk
8
+ attr_accessor :repository
9
+ attr_accessor :vcard_adapter
10
+
11
+ def initialize(disk)
12
+ @disk = disk
13
+ @repository = Rugged::Repository.new(@disk.directory.path)
14
+ end
15
+
16
+ def self.create_address_book(path)
17
+ disk = Ppl::Adapter::Storage::Disk.create_address_book(path)
18
+ repo = Rugged::Repository.init_at(path, false)
19
+ end
20
+
21
+ def load_address_book
22
+ address_book = Ppl::Entity::AddressBook.new
23
+
24
+ head = @repository.lookup(@repository.head.target)
25
+ head.tree.each do |file|
26
+ extension = file[:name].slice(-4..-1)
27
+ if extension != ".vcf"
28
+ next
29
+ end
30
+ contact_id = file[:name].slice(0..-5)
31
+ contact = load_contact(contact_id)
32
+ address_book.add_contact(contact)
33
+ end
34
+
35
+ return address_book
36
+ end
37
+
38
+ def load_contact(id)
39
+ filename = id + ".vcf"
40
+ target = @repository.head.target
41
+ vcard = @repository.file_at(target, filename)
42
+ contact = nil
43
+
44
+ if !vcard.nil?
45
+ contact = @vcard_adapter.decode(vcard)
46
+ contact.id = id
47
+ end
48
+
49
+ return contact
50
+ end
51
+
52
+ def save_contact(contact)
53
+ @disk.save_contact(contact)
54
+
55
+ add("#{contact.id}.vcf")
56
+ commit("save_contact(#{contact.id})")
57
+ end
58
+
59
+ def delete_contact(contact)
60
+ @repository.index.remove("#{contact.id}.vcf")
61
+ @repository.index.write
62
+ commit("remove_contact(#{contact.id})")
63
+ @disk.delete_contact(contact)
64
+ end
65
+
66
+ private
67
+
68
+ def add(file)
69
+ @repository.index.add(file)
70
+ @repository.index.write
71
+ end
72
+
73
+ def commit(message)
74
+ hash = @repository.index.write_tree
75
+ tree = @repository.lookup hash
76
+ parents = [ @repository.lookup( @repository.head.target ).oid ]
77
+
78
+ name = ENV['USER']
79
+ host = Socket.gethostname
80
+
81
+ author = {
82
+ :email => name + "@" + host,
83
+ :time => Time.now,
84
+ :name => name,
85
+ }
86
+
87
+ data = {
88
+ :author => author,
89
+ :message => message,
90
+ :committer => author,
91
+ :tree => tree,
92
+ :parents => parents,
93
+ :update_ref => "HEAD",
94
+ }
95
+
96
+ Rugged::Commit.create(@repository, data)
97
+ true
98
+ end
99
+
100
+ end
101
+
@@ -1,6 +1,10 @@
1
1
 
2
2
  class Ppl::Adapter::Storage
3
3
 
4
+ def self.create_address_book(path)
5
+ raise NotImplementedError
6
+ end
7
+
4
8
  def delete_contact(contact)
5
9
  raise NotImplementedError
6
10
  end
@@ -19,6 +19,14 @@ class Ppl::Adapter::Vcard::Vpim
19
19
  maker.add_email(contact.email_address)
20
20
  end
21
21
 
22
+ if !contact.phone_number.nil?
23
+ maker.add_tel(contact.phone_number)
24
+ end
25
+
26
+ if !contact.organization.nil?
27
+ maker.org=(contact.organization)
28
+ end
29
+
22
30
  end
23
31
 
24
32
  return vcard.to_s
@@ -36,6 +44,14 @@ class Ppl::Adapter::Vcard::Vpim
36
44
  contact.email_address = email.to_s
37
45
  end
38
46
 
47
+ if !vcard.telephones.empty?
48
+ contact.phone_number = vcard.telephones.first
49
+ end
50
+
51
+ if !vcard.org.nil?
52
+ contact.organization = vcard.org.first
53
+ end
54
+
39
55
  name = nil
40
56
  name = vcard.name
41
57
 
@@ -3,15 +3,18 @@ class Ppl::Application::Bootstrap
3
3
 
4
4
  def commands
5
5
  commands = [
6
- Ppl::Command::CommandList.new,
7
- Ppl::Command::ContactAdd.new,
8
- Ppl::Command::ContactDelete.new,
9
- Ppl::Command::ContactList.new,
10
- Ppl::Command::ContactRename.new,
11
- Ppl::Command::ContactShow.new,
12
- Ppl::Command::SetBirthday.new,
13
- Ppl::Command::SetEmail.new,
14
- Ppl::Command::SetName.new,
6
+ Ppl::Command::Init.new,
7
+ Ppl::Command::Bday.new,
8
+ Ppl::Command::Help.new,
9
+ Ppl::Command::Add.new,
10
+ Ppl::Command::Rm.new,
11
+ Ppl::Command::Ls.new,
12
+ Ppl::Command::Mv.new,
13
+ Ppl::Command::Show.new,
14
+ Ppl::Command::Name.new,
15
+ Ppl::Command::Email.new,
16
+ Ppl::Command::Org.new,
17
+ Ppl::Command::Phone.new,
15
18
  ]
16
19
  commands.each do |command|
17
20
  command.storage = storage_adapter
@@ -28,6 +31,11 @@ class Ppl::Application::Bootstrap
28
31
  return suite
29
32
  end
30
33
 
34
+ def configuration
35
+ config = Ppl::Application::Configuration.new
36
+ return config
37
+ end
38
+
31
39
  def input
32
40
  input = Ppl::Application::Input.new(ARGV.dup)
33
41
  return input
@@ -51,7 +59,12 @@ class Ppl::Application::Bootstrap
51
59
  end
52
60
 
53
61
  def storage_adapter
54
- storage = Ppl::Adapter::Storage::Disk.new(Dir.pwd)
62
+ config = configuration
63
+
64
+ directory = Dir.new(config.address_book_path)
65
+ factory = Ppl::Adapter::Storage::Factory.new(vcard_adapter)
66
+ storage = factory.load_adapter(directory)
67
+
55
68
  storage.vcard_adapter = vcard_adapter
56
69
  return storage
57
70
  end
@@ -9,5 +9,8 @@ class Ppl::Application::Command
9
9
  raise NotImplementedError
10
10
  end
11
11
 
12
+ def options(parser, options)
13
+ end
14
+
12
15
  end
13
16
 
@@ -20,6 +20,14 @@ class Ppl::Application::CommandSuite
20
20
  def find_command(name)
21
21
  @commands.select { |command| command.name == name }.first
22
22
  end
23
+
24
+ def sort_by_name
25
+ @commands.sort! { |a, b| a.name <=> b.name }
26
+ end
27
+
28
+ def [](index)
29
+ @commands[index]
30
+ end
23
31
 
24
32
  end
25
33
 
@@ -0,0 +1,50 @@
1
+
2
+ require "inifile"
3
+
4
+ class Ppl::Application::Configuration
5
+
6
+ USER_CONFIG = "~/.pplconfig"
7
+ REPO_CONFIG = "./.ppl/config"
8
+
9
+ def address_book_path
10
+ default_config = default_configuration
11
+ user_config = user_configuration
12
+
13
+ path = default_config["address book"]["path"]
14
+ if !user_config["address book"].nil? && !user_config["address book"]["path"].nil?
15
+ path = File.expand_path(user_config["address book"]["path"])
16
+ end
17
+
18
+ return path
19
+ end
20
+
21
+ def aliases
22
+ aliases = {}
23
+ return aliases
24
+ end
25
+
26
+ private
27
+
28
+ def default_configuration
29
+ {
30
+ "address book" => {
31
+ "path" => Dir.pwd
32
+ },
33
+ }
34
+ end
35
+
36
+ def user_configuration
37
+ filename = File.expand_path(USER_CONFIG)
38
+ config = {}
39
+ if File.exists?(filename)
40
+ config = IniFile::load(filename).to_h
41
+ end
42
+ return config
43
+ end
44
+
45
+ def repository_configuration
46
+ {}
47
+ end
48
+
49
+ end
50
+
@@ -15,6 +15,7 @@ class Ppl::Application::Output
15
15
 
16
16
  def line(string)
17
17
  @stdout.puts string
18
+ true
18
19
  end
19
20
 
20
21
  end
@@ -1,4 +1,6 @@
1
1
 
2
+ require "optparse"
3
+
2
4
  class Ppl::Application::Shell
3
5
 
4
6
  attr_writer :router
@@ -7,7 +9,17 @@ class Ppl::Application::Shell
7
9
  outcome = false
8
10
  begin
9
11
  command = select_command(input)
12
+ prepare_command(command, input)
10
13
  outcome = execute_command(command, input, output)
14
+ rescue OptionParser::InvalidOption
15
+ output.error($!)
16
+ output.error(@optparse.to_s)
17
+ rescue OptionParser::MissingArgument
18
+ output.error($!)
19
+ output.error(@optparse.to_s)
20
+ rescue Ppl::Error::IncorrectUsage
21
+ output.error($!)
22
+ output.error(@optparse.to_s)
11
23
  rescue
12
24
  output.error("ppl: " + $!.message)
13
25
  outcome = false
@@ -22,6 +34,15 @@ class Ppl::Application::Shell
22
34
  @router.route(input.arguments.shift)
23
35
  end
24
36
 
37
+ def prepare_command(command, input)
38
+ if !command.nil?
39
+ @optparse = OptionParser.new do |parser|
40
+ command.options(parser, input.options)
41
+ end
42
+ @optparse.parse!(input.arguments)
43
+ end
44
+ end
45
+
25
46
  def execute_command(command, input, output)
26
47
  outcome = false
27
48
  if !command.nil?
@@ -0,0 +1,34 @@
1
+
2
+ class Ppl::Command::Add < Ppl::Application::Command
3
+
4
+ def initialize
5
+ @name = "add"
6
+ @description = "Add a new contact"
7
+ end
8
+
9
+ def options(parser, options)
10
+ parser.banner = "usage: ppl add <contact> <name>"
11
+ end
12
+
13
+ def execute(input, output)
14
+ contact_id = input.arguments.shift
15
+ contact_name = input.arguments.shift
16
+
17
+ if contact_id.nil?
18
+ raise Ppl::Error::IncorrectUsage, "No contact specified"
19
+ end
20
+
21
+ if contact_name.nil?
22
+ raise Ppl::Error::IncorrectUsage, "No name specified"
23
+ end
24
+
25
+ contact = Ppl::Entity::Contact.new
26
+ contact.id = contact_id.dup
27
+ contact.name = contact_name.dup
28
+
29
+ @storage.save_contact(contact)
30
+ return true
31
+ end
32
+
33
+ end
34
+
@@ -0,0 +1,72 @@
1
+
2
+ class Ppl::Command::Bday < Ppl::Application::Command
3
+
4
+ attr_writer :list_format
5
+ attr_writer :show_format
6
+
7
+ def initialize
8
+ @name = "bday"
9
+ @description = "List, show or change birthdays"
10
+ @list_format = Ppl::Format::AddressBook::Birthdays.new
11
+ @show_format = Ppl::Format::Contact::Birthday.new
12
+ end
13
+
14
+ def options(parser, options)
15
+ parser.banner = "usage: ppl birthday <contact> [<date>]"
16
+ end
17
+
18
+ def execute(input, output)
19
+ action = determine_action(input)
20
+ send(action, input, output)
21
+ end
22
+
23
+
24
+ private
25
+
26
+ def determine_action(input)
27
+ if input.arguments[0].nil?
28
+ :list_birthdays
29
+ elsif input.arguments[1].nil?
30
+ :show_birthday
31
+ else
32
+ :set_birthday
33
+ end
34
+ end
35
+
36
+ def list_birthdays(input, output)
37
+ address_book = @storage.load_address_book
38
+ birthday_list = @list_format.process(address_book)
39
+ output.line(birthday_list)
40
+ end
41
+
42
+ def show_birthday(input, output)
43
+ contact_id = input.arguments.shift
44
+ if contact_id.nil?
45
+ raise Ppl::Error::IncorrectUsage, "No contact specified"
46
+ end
47
+ contact = @storage.require_contact(contact_id)
48
+
49
+ line = @show_format.process(contact)
50
+ output.line(line)
51
+ end
52
+
53
+ def set_birthday(input, output)
54
+ contact_id = input.arguments.shift
55
+ birthday = input.arguments.shift
56
+
57
+ if contact_id.nil?
58
+ raise Ppl::Error::IncorrectUsage, "No contact specified"
59
+ end
60
+ contact = @storage.require_contact(contact_id)
61
+
62
+ begin
63
+ date = Date.parse birthday
64
+ rescue ArgumentError
65
+ raise Ppl::Error::IncorrectUsage, "Invalid date '#{birthday}'"
66
+ end
67
+ contact.birthday = date
68
+ @storage.save_contact(contact)
69
+ end
70
+
71
+ end
72
+
@@ -0,0 +1,55 @@
1
+
2
+ class Ppl::Command::Email < Ppl::Application::Command
3
+
4
+ attr_writer :show_format
5
+ attr_writer :list_format
6
+
7
+ def initialize
8
+ @name = "email"
9
+ @description = "Show or change a contact's email address"
10
+ @show_format = Ppl::Format::Contact::EmailAddress.new
11
+ @list_format = Ppl::Format::AddressBook::EmailAddresses.new
12
+ end
13
+
14
+ def options(parser, options)
15
+ parser.banner = "usage: ppl email <contact> [<email-address>]"
16
+ end
17
+
18
+ def execute(input, output)
19
+ action = determine_action(input)
20
+ send(action, input, output)
21
+ end
22
+
23
+
24
+ private
25
+
26
+ def determine_action(input)
27
+ if input.arguments[0].nil?
28
+ :list_email_addresses
29
+ elsif input.arguments[1].nil?
30
+ :show_email_address
31
+ else
32
+ :set_email_address
33
+ end
34
+ end
35
+
36
+ def list_email_addresses(input, output)
37
+ address_book = @storage.load_address_book
38
+ email_list = @list_format.process(address_book)
39
+ output.line(email_list)
40
+ end
41
+
42
+ def show_email_address(input, output)
43
+ contact = @storage.require_contact(input.arguments[0])
44
+ email_address = @show_format.process(contact)
45
+ output.line(email_address)
46
+ end
47
+
48
+ def set_email_address(input, output)
49
+ contact = @storage.require_contact(input.arguments[0])
50
+ contact.email_address = input.arguments[1].dup
51
+ @storage.save_contact(contact)
52
+ end
53
+
54
+ end
55
+
@@ -0,0 +1,43 @@
1
+
2
+ class Ppl::Command::Help < Ppl::Application::Command
3
+
4
+ attr_accessor :command_suite
5
+
6
+ def initialize
7
+ @name = "help"
8
+ @description = "Show a list of commands"
9
+ end
10
+
11
+ def execute(input, output)
12
+ @command_suite.sort_by_name
13
+ max_name_length = 0
14
+
15
+ @command_suite.each do |command|
16
+ name_length = command.name.length
17
+ if name_length > max_name_length
18
+ max_name_length = name_length
19
+ end
20
+ end
21
+
22
+ output.line("usage: ppl <command>")
23
+ output.line(nil)
24
+
25
+ @command_suite.each do |command|
26
+ name = command.name
27
+ description = command.description
28
+
29
+ if @name == name
30
+ next
31
+ end
32
+
33
+ line = sprintf(" %-#{max_name_length}s %s", name, description)
34
+
35
+ output.line(line)
36
+ end
37
+
38
+ output.line(nil)
39
+ return true
40
+ end
41
+
42
+ end
43
+
@@ -0,0 +1,26 @@
1
+
2
+ class Ppl::Command::Init < Ppl::Application::Command
3
+
4
+ def initialize
5
+ @name = "init"
6
+ @description = "Create an empty address book"
7
+ end
8
+
9
+ def options(parser, options)
10
+ parser.banner = "usage: ppl init [directory]"
11
+ end
12
+
13
+ def execute(input, output)
14
+
15
+ path = input.arguments.shift
16
+ if path.nil?
17
+ path = Dir.pwd
18
+ end
19
+
20
+ storage = Ppl::Adapter::Storage::Git.create_address_book(path)
21
+
22
+ return true
23
+ end
24
+
25
+ end
26
+