ppl 0.0.5 → 0.2.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 (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
+