ppl 0.0.5 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. data/.gitignore +2 -0
  2. data/.rspec +3 -0
  3. data/.travis.yml +5 -0
  4. data/Gemfile +7 -3
  5. data/Gemfile.lock +15 -7
  6. data/README.md +47 -34
  7. data/bin/ppl +7 -6
  8. data/lib/ppl/adapter/storage/disk.rb +40 -0
  9. data/lib/ppl/adapter/storage.rb +28 -0
  10. data/lib/ppl/adapter/vcard/vpim.rb +45 -0
  11. data/lib/ppl/adapter/vcard.rb +13 -0
  12. data/lib/ppl/application/bootstrap.rb +62 -0
  13. data/lib/ppl/application/command.rb +13 -0
  14. data/lib/ppl/application/command_suite.rb +25 -0
  15. data/lib/ppl/application/input.rb +13 -0
  16. data/lib/ppl/application/output.rb +21 -0
  17. data/lib/ppl/application/router.rb +19 -0
  18. data/lib/ppl/application/shell.rb +34 -0
  19. data/lib/ppl/command/command_list.rb +21 -0
  20. data/lib/ppl/command/contact_add.rb +26 -0
  21. data/lib/ppl/command/contact_delete.rb +16 -0
  22. data/lib/ppl/command/contact_list.rb +24 -0
  23. data/lib/ppl/command/contact_rename.rb +23 -0
  24. data/lib/ppl/command/contact_show.rb +20 -0
  25. data/lib/ppl/entity/address_book.rb +21 -0
  26. data/lib/ppl/entity/contact.rb +10 -0
  27. data/lib/ppl/error/contact_not_found.rb +4 -0
  28. data/lib/ppl/error/incorrect_usage.rb +4 -0
  29. data/lib/ppl.rb +51 -11
  30. data/ppl.gemspec +3 -3
  31. data/spec/ppl/adapter/output_spec.rb +43 -0
  32. data/spec/ppl/adapter/storage/disk_spec.rb +83 -0
  33. data/spec/ppl/adapter/storage_spec.rb +46 -0
  34. data/spec/ppl/adapter/vcard/vpim_spec.rb +77 -0
  35. data/spec/ppl/adapter/vcard_spec.rb +24 -0
  36. data/spec/ppl/application/bootstrap_spec.rb +88 -0
  37. data/spec/ppl/application/command_spec.rb +19 -0
  38. data/spec/ppl/application/command_suite_spec.rb +44 -0
  39. data/spec/ppl/application/input_spec.rb +21 -0
  40. data/spec/ppl/application/router_spec.rb +43 -0
  41. data/spec/ppl/application/shell_spec.rb +79 -0
  42. data/spec/ppl/command/command_list_spec.rb +37 -0
  43. data/spec/ppl/command/contact_add_spec.rb +41 -0
  44. data/spec/ppl/command/contact_delete_spec.rb +31 -0
  45. data/spec/ppl/command/contact_list_spec.rb +15 -0
  46. data/spec/ppl/command/contact_rename_spec.rb +42 -0
  47. data/spec/ppl/command/contact_show_spec.rb +35 -0
  48. data/spec/ppl/entity/address_book_spec.rb +26 -0
  49. data/spec/ppl/entity/contact_spec.rb +34 -0
  50. data/spec/spec_helper.rb +9 -0
  51. metadata +47 -14
  52. data/lib/ppl/address_book.rb +0 -74
  53. data/lib/ppl/cli.rb +0 -49
  54. data/lib/ppl/command/add.rb +0 -40
  55. data/lib/ppl/command/birthdays.rb +0 -34
  56. data/lib/ppl/command/help.rb +0 -32
  57. data/lib/ppl/command/list.rb +0 -31
  58. data/lib/ppl/command/rm.rb +0 -38
  59. data/lib/ppl/command/set.rb +0 -49
  60. data/lib/ppl/command/show.rb +0 -43
  61. data/lib/ppl/command.rb +0 -55
  62. data/lib/ppl/contact.rb +0 -43
data/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ tmp
2
+
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --require spec_helper
2
+ -c
3
+
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+
2
+ language: ruby
3
+ rvm: "1.9.3"
4
+ script: bundle exec rspec spec
5
+
data/Gemfile CHANGED
@@ -1,7 +1,11 @@
1
1
 
2
- gem 'active_support'
3
-
4
- gem "colored", "~> 1.2"
2
+ source :rubygems
5
3
 
6
4
  gem "vpim", "12.1.12", :git => "git://github.com/sam-github/vpim.git"
7
5
 
6
+ group :test do
7
+ gem "fakefs"
8
+ gem "rake"
9
+ gem "rspec"
10
+ end
11
+
data/Gemfile.lock CHANGED
@@ -1,9 +1,3 @@
1
- GIT
2
- remote: git://github.com/defunkt/colored.git
3
- revision: 829bde0f8832406be1cacc5c99c49d976e05ccfc
4
- specs:
5
- colored (1.2)
6
-
7
1
  GIT
8
2
  remote: git://github.com/sam-github/vpim.git
9
3
  revision: 6753a367cda64e53eab745414cedc130d2f149c8
@@ -11,11 +5,25 @@ GIT
11
5
  vpim (12.1.12)
12
6
 
13
7
  GEM
8
+ remote: http://rubygems.org/
14
9
  specs:
10
+ diff-lcs (1.1.3)
11
+ fakefs (0.4.1)
12
+ rake (10.0.1)
13
+ rspec (2.12.0)
14
+ rspec-core (~> 2.12.0)
15
+ rspec-expectations (~> 2.12.0)
16
+ rspec-mocks (~> 2.12.0)
17
+ rspec-core (2.12.0)
18
+ rspec-expectations (2.12.0)
19
+ diff-lcs (~> 1.1.3)
20
+ rspec-mocks (2.12.0)
15
21
 
16
22
  PLATFORMS
17
23
  ruby
18
24
 
19
25
  DEPENDENCIES
20
- colored (~> 1.2)!
26
+ fakefs
27
+ rake
28
+ rspec
21
29
  vpim (= 12.1.12)!
data/README.md CHANGED
@@ -11,49 +11,50 @@ like "people". You might be interested in ppl if:
11
11
  * You want ownership of your address book data back from the cloud
12
12
  * You prefer to keep your data stored in an open format
13
13
 
14
+ ppl is in an incomplete state right now, and is in active development. Stay
15
+ tuned.
14
16
 
15
- Usage
16
- -----
17
+ [![Build Status](https://secure.travis-ci.org/h2s/ppl.png)](http://travis-ci.org/h2s/ppl)
17
18
 
18
- ### Add a contact
19
+ Installation
20
+ ------------
19
21
 
20
22
  ```bash
21
- $ ppl add adam "Adam Brown"
23
+ $ sudo gem install ppl
22
24
  ```
23
25
 
26
+ Usage
27
+ -----
28
+
24
29
  ### List all contacts
25
30
 
26
31
  ```bash
27
- $ ppl list
28
- fred Fred Smith fred.smith@example.org
29
- dave Dave Jones dave.jones@example.org
30
- joe Joe Bloggs joe.bloggs@example.org
32
+ $ ppl ls
33
+ dave: dave@example.org
34
+ fred: fred@example.org
35
+ john: john@example.org
31
36
  ```
32
37
 
33
- ### Show a single contact
38
+ ### Add a contact
39
+ ```bash
40
+ $ ppl add paul "Paul Baker"
41
+ ```
34
42
 
43
+ ### Show a contact
35
44
  ```bash
36
45
  $ ppl show fred
37
46
  Fred Smith
38
- fred.simth@example.org
39
-
40
- Birthday 1980-01-01
41
- Cellphone 01189998819991197253
42
- ````
43
-
44
- ### List upcoming birthdays
45
-
46
- ```bash
47
- $ ppl birthdays
48
- fred Fred Smith 1980-01-01 99 days
49
- dave Dave Jones 1980-01-02 100 days
50
- joe Joe Bloggs 1980-01-03 101 days
47
+ fred@example.org
51
48
  ```
52
49
 
53
50
  ### Delete a contact
51
+ ```bash
52
+ $ ppl rm dave
53
+ ```
54
54
 
55
+ ### Rename a contact
55
56
  ```bash
56
- $ ppl rm workadam
57
+ $ ppl mv dave david
57
58
  ```
58
59
 
59
60
  Roadmap
@@ -61,22 +62,34 @@ Roadmap
61
62
 
62
63
  Support for the following commands is planned.
63
64
 
64
- ### Update a contact
65
+ ### Change a contact's email address
65
66
  ```bash
66
- $ ppl set adam --email=adam.brown@example.com
67
+ $ ppl email dave david@example.org
67
68
  ```
68
69
 
69
- ### Rename a contact
70
+ ### Change a contact's name
70
71
  ```bash
71
- $ ppl mv adam workadam
72
+ $ ppl name john "John Smith"
72
73
  ```
73
74
 
74
- In a highly optimistic version of the future, I plan to integrate Git.
75
- Any commands that modify the address book will also commit their changes.
76
- Then this will be posisble:
75
+ ### Change a contact's birthday
77
76
  ```bash
78
- $ ppl sync
79
- Pulling latest changes from all remotes...
80
- Pushing local changes to all remotes...
81
- Done
77
+ $ ppl birthday john 1980-01-01
82
78
  ```
79
+
80
+ Contributing
81
+ ------------
82
+
83
+ Bug reports, fixes, and additional features are encouraged. The project uses
84
+ [Github issues](https://github.com/h2s/ppl/issues) to track bug reports.
85
+
86
+ If you'd like to contribute code, please just bear in mind that ppl development
87
+ is test-driven and attempts to follow the "Object Mentor school of clean code"
88
+ as described in the book [Clean
89
+ Code](http://books.google.co.uk/books?id=_i6bDeoCQzsC).
90
+
91
+ License
92
+ -------
93
+
94
+ ppl is licensed under the GNU GENERAL PUBLIC LICENSE Version 2.
95
+
data/bin/ppl CHANGED
@@ -5,12 +5,13 @@ require "pathname"
5
5
  bin_file = Pathname.new(__FILE__).realpath
6
6
  $:.unshift File.expand_path("../../lib", bin_file)
7
7
 
8
- require "ppl/cli"
8
+ require "ppl"
9
9
 
10
- class String
11
- alias_method :each, :each_line
12
- end
10
+ bootstrap = Ppl::Application::Bootstrap.new
11
+ input = bootstrap.input
12
+ output = bootstrap.output
13
+ shell = bootstrap.shell
14
+
15
+ exit(shell.run(input, output))
13
16
 
14
- cli = Ppl::CLI.new
15
- cli.run ARGV
16
17
 
@@ -0,0 +1,40 @@
1
+
2
+ class Ppl::Adapter::Storage::Disk < Ppl::Adapter::Storage
3
+
4
+ attr_accessor :path
5
+ attr_accessor :vcard_adapter
6
+
7
+ def initialize(path)
8
+ @path = path
9
+ end
10
+
11
+ def load_address_book
12
+ address_book = Ppl::Entity::AddressBook.new
13
+
14
+ pattern = File.join @path, "*.vcf"
15
+ filenames = Dir.glob pattern
16
+
17
+ filenames.each do |filename|
18
+ contact_id = File.basename(filename).slice(0..-5)
19
+ contact = load_contact(contact_id)
20
+ address_book.add_contact(contact)
21
+ end
22
+
23
+ return address_book
24
+ end
25
+
26
+ def load_contact(id)
27
+ filename = File.join @path, id + ".vcf"
28
+ contact = nil
29
+ if File.exists?(filename)
30
+ vcard = File.read filename
31
+ contact = @vcard_adapter.decode(vcard)
32
+ if !contact.nil? && contact.is_a?(Ppl::Entity::Contact)
33
+ contact.id = id
34
+ end
35
+ end
36
+ return contact
37
+ end
38
+
39
+ end
40
+
@@ -0,0 +1,28 @@
1
+
2
+ class Ppl::Adapter::Storage
3
+
4
+ def delete_contact(contact)
5
+ raise NotImplementedError
6
+ end
7
+
8
+ def load_address_book
9
+ raise NotImplementedError
10
+ end
11
+
12
+ def load_contact(id)
13
+ raise NotImplementedError
14
+ end
15
+
16
+ def require_contact(id)
17
+ contact = load_contact(id)
18
+ if contact.nil?
19
+ raise Ppl::Error::ContactNotFound, id
20
+ end
21
+ end
22
+
23
+ def save_contact(contact)
24
+ raise NotImplementedError
25
+ end
26
+
27
+ end
28
+
@@ -0,0 +1,45 @@
1
+
2
+ require "vpim/vcard"
3
+
4
+ class Ppl::Adapter::Vcard::Vpim
5
+
6
+ def encode(contact)
7
+ vcard = Vpim::Vcard::Maker.make2 do |maker|
8
+
9
+ if !contact.birthday.nil?
10
+ maker.birthday = contact.birthday
11
+ end
12
+
13
+ maker.add_name do |name|
14
+ name.given = contact.id unless contact.id.nil?
15
+ name.fullname = contact.name unless contact.name.nil?
16
+ end
17
+ end
18
+
19
+ return vcard.to_s
20
+ end
21
+
22
+ def decode(string)
23
+ vcard = Vpim::Vcard.decode(string).first
24
+ contact = Ppl::Entity::Contact.new
25
+
26
+ if !vcard.birthday.nil?
27
+ contact.birthday = vcard.birthday
28
+ end
29
+
30
+ vcard.emails.each do |email|
31
+ contact.email_address = email.to_s
32
+ end
33
+
34
+ name = nil
35
+ name = vcard.name
36
+
37
+ if !name.nil?
38
+ contact.name = name.fullname
39
+ end
40
+
41
+ return contact
42
+ end
43
+
44
+ end
45
+
@@ -0,0 +1,13 @@
1
+
2
+ class Ppl::Adapter::Vcard
3
+
4
+ def encode(contact)
5
+ raise NotImplementedError
6
+ end
7
+
8
+ def decode(string)
9
+ raise NotImplementedError
10
+ end
11
+
12
+ end
13
+
@@ -0,0 +1,62 @@
1
+
2
+ class Ppl::Application::Bootstrap
3
+
4
+ def commands
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
+ ]
13
+ commands.each do |command|
14
+ command.storage = storage_adapter
15
+ end
16
+ return commands
17
+ end
18
+
19
+ def command_suite
20
+ suite = Ppl::Application::CommandSuite.new
21
+ commands.each do |command|
22
+ suite.add_command(command)
23
+ end
24
+ suite.find_command("help").command_suite = suite
25
+ return suite
26
+ end
27
+
28
+ def input
29
+ input = Ppl::Application::Input.new(ARGV.dup)
30
+ return input
31
+ end
32
+
33
+ def output
34
+ output = Ppl::Application::Output.new($stdout, $stderr)
35
+ return output
36
+ end
37
+
38
+ def router
39
+ router = Ppl::Application::Router.new(command_suite)
40
+ router.default = "help"
41
+ return router
42
+ end
43
+
44
+ def shell
45
+ shell = Ppl::Application::Shell.new
46
+ shell.router = router
47
+ return shell
48
+ end
49
+
50
+ def storage_adapter
51
+ storage = Ppl::Adapter::Storage::Disk.new(Dir.pwd)
52
+ storage.vcard_adapter = vcard_adapter
53
+ return storage
54
+ end
55
+
56
+ def vcard_adapter
57
+ vcard = Ppl::Adapter::Vcard::Vpim.new
58
+ return vcard
59
+ end
60
+
61
+ end
62
+
@@ -0,0 +1,13 @@
1
+
2
+ class Ppl::Application::Command
3
+
4
+ attr_accessor :name
5
+ attr_accessor :description
6
+ attr_accessor :storage
7
+
8
+ def execute(input, output)
9
+ raise NotImplementedError
10
+ end
11
+
12
+ end
13
+
@@ -0,0 +1,25 @@
1
+
2
+ require "enumerator"
3
+
4
+ class Ppl::Application::CommandSuite
5
+
6
+ include Enumerable
7
+
8
+ def initialize
9
+ @commands = []
10
+ end
11
+
12
+ def add_command(command)
13
+ @commands.push command
14
+ end
15
+
16
+ def each
17
+ @commands.each { |command| yield command }
18
+ end
19
+
20
+ def find_command(name)
21
+ @commands.select { |command| command.name == name }.first
22
+ end
23
+
24
+ end
25
+
@@ -0,0 +1,13 @@
1
+
2
+ class Ppl::Application::Input
3
+
4
+ attr_accessor :arguments
5
+ attr_accessor :options
6
+
7
+ def initialize(arguments=[])
8
+ @arguments = arguments
9
+ @options = {}
10
+ end
11
+
12
+ end
13
+
@@ -0,0 +1,21 @@
1
+
2
+ class Ppl::Application::Output
3
+
4
+ attr_accessor :stdout
5
+ attr_accessor :stderr
6
+
7
+ def initialize(stdout, stderr)
8
+ @stdout = stdout
9
+ @stderr = stderr
10
+ end
11
+
12
+ def error(string)
13
+ @stderr.puts string
14
+ end
15
+
16
+ def line(string)
17
+ @stdout.puts string
18
+ end
19
+
20
+ end
21
+
@@ -0,0 +1,19 @@
1
+
2
+ class Ppl::Application::Router
3
+
4
+ attr_accessor :default
5
+
6
+ def initialize(command_suite)
7
+ @command_suite = command_suite
8
+ end
9
+
10
+ def route(argument)
11
+ command = @command_suite.find_command(argument)
12
+ if command.nil? && !@default.nil?
13
+ command = @command_suite.find_command(@default)
14
+ end
15
+ return command
16
+ end
17
+
18
+ end
19
+
@@ -0,0 +1,34 @@
1
+
2
+ class Ppl::Application::Shell
3
+
4
+ attr_writer :router
5
+
6
+ def run(input, output)
7
+ outcome = false
8
+ begin
9
+ command = select_command(input)
10
+ outcome = execute_command(command, input, output)
11
+ rescue
12
+ output.error("ppl: " + $!.message)
13
+ outcome = false
14
+ end
15
+ return outcome
16
+ end
17
+
18
+
19
+ private
20
+
21
+ def select_command(input)
22
+ @router.route(input.arguments.shift)
23
+ end
24
+
25
+ def execute_command(command, input, output)
26
+ outcome = false
27
+ if !command.nil?
28
+ outcome = command.execute(input, output)
29
+ end
30
+ return outcome
31
+ end
32
+
33
+ end
34
+
@@ -0,0 +1,21 @@
1
+
2
+ class Ppl::Command::CommandList < 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.each do |command|
13
+ name = command.name
14
+ description = command.description
15
+ output.line("#{name}: #{description}")
16
+ end
17
+ return true
18
+ end
19
+
20
+ end
21
+
@@ -0,0 +1,26 @@
1
+
2
+ class Ppl::Command::ContactAdd < Ppl::Application::Command
3
+
4
+ def initialize
5
+ @name = "add"
6
+ @description = "Add a new contact"
7
+ end
8
+
9
+ def execute(input, output)
10
+ contact_id = input.arguments.shift
11
+ contact_name = input.arguments.shift
12
+
13
+ if contact_id.nil? || contact_name.nil?
14
+ raise Ppl::Error::IncorrectUsage
15
+ end
16
+
17
+ contact = Ppl::Entity::Contact.new
18
+ contact.id = contact_id
19
+ contact.name = contact_name
20
+
21
+ @storage.save_contact(contact)
22
+ return true
23
+ end
24
+
25
+ end
26
+
@@ -0,0 +1,16 @@
1
+
2
+ class Ppl::Command::ContactDelete < Ppl::Application::Command
3
+
4
+ def initialize
5
+ @name = "rm"
6
+ @description = "Delete a contact"
7
+ end
8
+
9
+ def execute(input, output)
10
+ contact_id = input.arguments.first
11
+ contact = @storage.require_contact(contact_id)
12
+ @storage.delete_contact(contact)
13
+ end
14
+
15
+ end
16
+
@@ -0,0 +1,24 @@
1
+
2
+ class Ppl::Command::ContactList < Ppl::Application::Command
3
+
4
+ def initialize
5
+ @name = "ls"
6
+ @description = "List all contacts"
7
+ end
8
+
9
+ def execute(input, output)
10
+ address_book = @storage.load_address_book
11
+
12
+ address_book.each do |contact|
13
+ line = sprintf("%s: %s",
14
+ contact.id,
15
+ contact.email_address
16
+ )
17
+ output.line(line)
18
+ end
19
+
20
+ return true
21
+ end
22
+
23
+ end
24
+
@@ -0,0 +1,23 @@
1
+
2
+ class Ppl::Command::ContactRename < Ppl::Application::Command
3
+
4
+ def initialize
5
+ @name = "mv"
6
+ @description = "Rename a contact"
7
+ end
8
+
9
+ def execute(input, output)
10
+ old_id = input.arguments.shift
11
+ new_id = input.arguments.shift
12
+ contact = @storage.require_contact(old_id)
13
+
14
+ @storage.delete_contact(contact)
15
+
16
+ contact.id = new_id
17
+ @storage.save_contact(contact)
18
+
19
+ return true
20
+ end
21
+
22
+ end
23
+
@@ -0,0 +1,20 @@
1
+
2
+ class Ppl::Command::ContactShow < Ppl::Application::Command
3
+
4
+ def initialize
5
+ @name = "show"
6
+ @description = "Display the full details of a contact"
7
+ end
8
+
9
+ def execute(input, output)
10
+ contact_id = input.arguments.shift
11
+ contact = @storage.require_contact(contact_id)
12
+
13
+ output.line(contact.name)
14
+ output.line(contact.email_address)
15
+
16
+ return true
17
+ end
18
+
19
+ end
20
+
@@ -0,0 +1,21 @@
1
+
2
+ require "enumerator"
3
+
4
+ class Ppl::Entity::AddressBook
5
+
6
+ include Enumerable
7
+
8
+ def initialize
9
+ @contacts = []
10
+ end
11
+
12
+ def add_contact(contact)
13
+ @contacts.push contact
14
+ end
15
+
16
+ def each
17
+ @contacts.each { |contact| yield contact }
18
+ end
19
+
20
+ end
21
+