homedir 3.0.alpha2 → 3.0.alpha4

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 (47) hide show
  1. data/.travis.yml +0 -1
  2. data/Guardfile +8 -4
  3. data/README.mkd +11 -1
  4. data/homedir.gemspec +1 -0
  5. data/lib/homedir.rb +3 -2
  6. data/lib/homedir/catalog.rb +4 -1
  7. data/lib/homedir/cli.rb +34 -3
  8. data/lib/homedir/errors.rb +4 -0
  9. data/lib/homedir/hacks.rb +9 -0
  10. data/lib/homedir/package.rb +47 -18
  11. data/lib/homedir/package_discovery_loader.rb +29 -0
  12. data/lib/homedir/package_loader.rb +48 -0
  13. data/lib/homedir/package_version1_loader.rb +70 -0
  14. data/lib/homedir/package_version2_loader.rb +48 -0
  15. data/lib/homedir/package_version3_loader.rb +45 -0
  16. data/lib/homedir/version.rb +1 -1
  17. data/spec/acceptance/load_packages_spec.rb +17 -0
  18. data/spec/examples/version1-example/.homedir.control +11 -0
  19. data/spec/examples/version1-example/combined/version1-file +3 -0
  20. data/spec/examples/version1-example/version1/separate-file +2 -0
  21. data/spec/examples/version2-example/.homedir/control +13 -0
  22. data/spec/examples/version2-example/.homedir/post-install +2 -0
  23. data/spec/examples/version2-example/.homedir/post-remove +2 -0
  24. data/spec/examples/version2-example/.homedir/pre-install +2 -0
  25. data/spec/examples/version2-example/.homedir/pre-remove +2 -0
  26. data/spec/examples/version2-example/combined/version2-file +3 -0
  27. data/spec/examples/version2-example/version2/separate-file +2 -0
  28. data/spec/examples/version3-example/.gitignore +1 -0
  29. data/spec/examples/version3-example/description.txt +3 -0
  30. data/spec/examples/version3-example/homedir.yml +4 -0
  31. data/spec/examples/version3-example/homedir/combined/version3-file +3 -0
  32. data/spec/examples/version3-example/homedir/version3/separate-file +2 -0
  33. data/spec/examples/version3-example/post-install.sh +2 -0
  34. data/spec/examples/version3-example/post-remove.sh +2 -0
  35. data/spec/examples/version3-example/post-update.sh +2 -0
  36. data/spec/examples/version3-example/pre-install.sh +2 -0
  37. data/spec/examples/version3-example/pre-remove.sh +2 -0
  38. data/spec/examples/version3-example/pre-update.sh +2 -0
  39. data/spec/integration/catalog_packages_spec.rb +27 -0
  40. data/spec/lib/homedir/package_loader_spec.rb +30 -0
  41. data/spec/lib/homedir/package_spec.rb +49 -4
  42. data/spec/lib/homedir/package_version1_loader_spec.rb +66 -0
  43. data/spec/lib/homedir/package_version2_loader_spec.rb +41 -0
  44. data/spec/lib/homedir/package_version3_loader_spec.rb +41 -0
  45. data/spec/spec_helper.rb +15 -8
  46. data/spec/support/package_shared_examples.rb +59 -0
  47. metadata +93 -20
data/.travis.yml CHANGED
@@ -1,5 +1,4 @@
1
1
  rvm:
2
- - jruby
3
2
  - 1.8.7
4
3
  - 1.9.2
5
4
  - 1.9.3
data/Guardfile CHANGED
@@ -7,11 +7,15 @@ guard 'bundler' do
7
7
  # watch(/^.+\.gemspec/)
8
8
  end
9
9
 
10
- guard 'rspec', :version => 2, :cli => "--color --format doc --fail-fast" do
10
+ guard 'rspec', :version => 2, :cli => "--color --format doc" do
11
11
  watch(%r{^spec/.+_spec\.rb$})
12
- watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
13
- watch('spec/spec_helper.rb') { "spec" }
14
- watch('spec/factories.rb') { "spec" }
12
+ watch(%r{^lib/}) { "spec/integration" }
13
+ #watch(%r{^(lib|bin)/}) { "spec/acceptance" }
14
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
15
+ watch('spec/spec_helper.rb') { "spec" }
16
+ watch('spec/factories.rb') { "spec" }
17
+ watch(%r{^spec/support/.+\.rb}) { "spec" }
18
+ watch(%r{^spec/examples/}) { "spec" }
15
19
  end
16
20
 
17
21
 
data/README.mkd CHANGED
@@ -22,7 +22,17 @@ That's it!
22
22
 
23
23
  ## Support
24
24
 
25
- You can find me on freenode's IRC servers as the user 'docwhat'. I'm usually on the #homedir channel.
25
+ * File an [issue](https://github.com/docwhat/homedir/issues).
26
+ * Freenode IRC in [`#homedir`](http://webchat.freenode.net/?channels=rvm).
27
+ * Contact me [directly](http://docwhat.org/email).
28
+
29
+ ## Contributing
30
+
31
+ I love having people help me. All changes are welcome!
32
+
33
+ * Submit a [pull request](https://github.com/docwhat/homedir/pulls)
34
+ * Add to the [wiki](https://github.com/docwhat/homedir/wiki).
35
+ * Use the github instant editing feature.
26
36
 
27
37
  ## The Story So Far…
28
38
 
data/homedir.gemspec CHANGED
@@ -24,6 +24,7 @@ Gem::Specification.new do |gem|
24
24
  gem.add_development_dependency('factory_girl', ["~> 2.5.2"])
25
25
 
26
26
  gem.add_development_dependency('yard')
27
+ gem.add_development_dependency('redcarpet')
27
28
 
28
29
  gem.add_development_dependency('guard')
29
30
  gem.add_development_dependency('guard-rspec')
data/lib/homedir.rb CHANGED
@@ -2,8 +2,9 @@
2
2
  # COPYRIGHT:: Copyright (c) 2012 Christian Höltje
3
3
  # LICENSE:: The MIT License. See the file LICENSE distributed with this source.
4
4
  require 'homedir/package'
5
+ require 'homedir/catalog'
6
+ require 'homedir/package_discovery_loader'
7
+ require 'homedir/errors'
5
8
 
6
9
  module Homedir
7
- class Error < StandardError
8
- end
9
10
  end
@@ -1,8 +1,11 @@
1
1
  require 'homedir/errors'
2
+ require 'set'
2
3
 
3
4
  module Homedir
4
- class Catalog < Set
5
+ class Catalog < ::Set
5
6
 
7
+ # Find the package with the name `name`
8
+ #
6
9
  # @param {String} name The name of the {Homedir::Package package} to find.
7
10
  # @return {Homedir::Package} The package that matches the `name`
8
11
  def find_by_name name
data/lib/homedir/cli.rb CHANGED
@@ -1,4 +1,6 @@
1
1
  require 'thor'
2
+ require 'homedir'
3
+ require 'pathname'
2
4
 
3
5
  module Homedir
4
6
  # The command line interface for {Homedir}.
@@ -7,7 +9,28 @@ module Homedir
7
9
  # information
8
10
  class CLI < Thor
9
11
 
10
- desc "list", "List all known packages."
12
+ no_tasks do
13
+ # The directories to scan for repositories
14
+ #
15
+ # @return {Enumerable} A list of Pathname objects
16
+ def repositories
17
+ [ Pathname.new(ENV['HOME']) + '.homedir' + 'packages']
18
+ end
19
+
20
+ # Helper method to initialize the catalog, if needed.
21
+ #
22
+ # @return {Homedir::Catalog} A populated catalog
23
+ def catalog
24
+ @catalog ||= Catalog.new.tap do |cat|
25
+ pdl = PackageDiscoveryLoader.new(cat)
26
+ repositories.each do |repo|
27
+ pdl.load_from_directory(repo)
28
+ end
29
+ end
30
+ end
31
+ end
32
+
33
+ desc "list", "Lists available packages."
11
34
  method_option(
12
35
  :remote,
13
36
  :type => :boolean,
@@ -15,8 +38,11 @@ module Homedir
15
38
  :aliases => '-r',
16
39
  :description => "Queries the remote server")
17
40
  def list
18
- # FIXME: list needs to do something
19
- puts "Not implemented yet"
41
+ packages = catalog.to_a # TODO sort
42
+ puts "#{packages.length} local packages found:"
43
+ packages.each do |package|
44
+ puts " * #{package}"
45
+ end
20
46
  end
21
47
 
22
48
  desc "info PACKAGE", "Describe a specific PACKAGE in detail."
@@ -50,6 +76,11 @@ module Homedir
50
76
  puts "Not implemented yet #{package_name.inspect}"
51
77
  end
52
78
 
79
+ desc "repair", "Scans through your home directory and repairs all links. Warning: This can take a long time."
80
+ def repair
81
+ # FIXME: needs to do something
82
+ puts "Not implemented yet"
83
+ end
53
84
  end
54
85
  end
55
86
 
@@ -14,4 +14,8 @@ module Homedir
14
14
  # message for the list of acceptable characters in a name.
15
15
  class InvalidNameError < Error
16
16
  end
17
+
18
+ # The directory does not contain a package
19
+ class InvalidPackageDirectoryError < Error
20
+ end
17
21
  end
@@ -0,0 +1,9 @@
1
+ require 'fileutils'
2
+ require 'pathname'
3
+
4
+ # This is adds a couple of methods to make my Homedir work better.
5
+ class Pathname
6
+ def mkdir_p *args
7
+ FileUtils.mkdir_p(self.to_s, *args)
8
+ end
9
+ end
@@ -20,12 +20,16 @@ module Homedir
20
20
  :directory,
21
21
  ].freeze
22
22
  DEFAULT_VALUES = {
23
- :name => nil,
24
- :description => nil,
25
- :dependencies => [],
26
- :post_install => nil,
27
- :pre_uninstall => nil,
28
- :directory => nil,
23
+ :name => nil,
24
+ :description => nil,
25
+ :dependencies => [],
26
+ :pre_install => nil,
27
+ :post_install => nil,
28
+ :pre_remove => nil,
29
+ :post_remove => nil,
30
+ :pre_update => nil,
31
+ :post_update => nil,
32
+ :directory => nil,
29
33
  }.freeze
30
34
 
31
35
  # The name of the package.
@@ -44,16 +48,40 @@ module Homedir
44
48
  # A list of package names this package depends on.
45
49
  #
46
50
  # When assigning a list, they can either be {Homedir::Package Package} objects or strings.
47
- # @return [Enumerable] A list of package {#name names}.
51
+ # @return [Set] The set of package {#name names}.
48
52
  attr_reader :dependencies
49
53
 
50
- # A script that should run after install.
54
+ # A script to run before a package is enabled.
55
+ #
56
+ # @return {String}
57
+ attr_accessor :pre_install
58
+
59
+ # A script to run after a package is enabled.
51
60
  #
52
61
  # @return {String}
53
62
  attr_accessor :post_install
54
63
 
55
- # A script to run before install.
56
- attr_accessor :pre_uninstall
64
+ # A script to run before the package is disabled.
65
+ # @return {String}
66
+ attr_accessor :pre_remove
67
+
68
+ # A script to run after the package is disabled
69
+ # @return {String}
70
+ attr_accessor :post_remove
71
+
72
+ # A script to run before upgrading the package's source
73
+ #
74
+ # This is run before any upgrade steps run for the package source.
75
+ # @return {String}
76
+ attr_accessor :pre_update
77
+
78
+ # A script that runs after upgrading the package's source
79
+ #
80
+ # This is run after a package is upgraded and after the
81
+ # the package has been re-enabled but before the pre_install
82
+ # script.
83
+ # @return {String}
84
+ attr_accessor :post_update
57
85
 
58
86
  # The directory where the package is stored.
59
87
  #
@@ -62,15 +90,15 @@ module Homedir
62
90
  # @return {Pathname}
63
91
  attr_reader :directory
64
92
 
65
- # Create a new {Homedir::Package Package} instance.
93
+ # Create a new {Package} instance.
66
94
  #
67
95
  # @param {Hash} options
68
- # @option options [String] :name The name of the package.
69
- # @option options [String] :description The description of the package.
70
- # @option options [Enumerable] :dependencies A list of {Homedir::Package packages} or {String strings}.
71
- # @option options [Pathname] :directory The directory the packages is located at.
72
- # @option options [String] :post_install Commands to run after installing the package.
73
- # @option options [String] :pre_uninstall Commands to run before uninstalling the package.
96
+ # @option options {String} :name The name of the package.
97
+ # @option options {String} :description The description of the package.
98
+ # @option options {Enumerable} :dependencies A list of {Homedir::Package packages} or {String strings}.
99
+ # @option options {Pathname} :directory The directory the packages is located at.
100
+ # @option options {String} :post_install Commands to run after installing the package.
101
+ # @option options {String} :pre_uninstall Commands to run before uninstalling the package.
74
102
  def initialize(options = {})
75
103
  options = DEFAULT_VALUES.merge(options)
76
104
 
@@ -100,7 +128,7 @@ module Homedir
100
128
 
101
129
  # {include:#dependencies}
102
130
  def dependencies= value
103
- @dependencies = value.map { |p| p.to_s }
131
+ @dependencies = Set.new( value.map { |p| p.to_s } )
104
132
  end
105
133
 
106
134
  # {include:#directory}
@@ -159,5 +187,6 @@ module Homedir
159
187
  def to_s
160
188
  @name
161
189
  end
190
+
162
191
  end
163
192
  end
@@ -0,0 +1,29 @@
1
+ require 'homedir/package_loader'
2
+
3
+ module Homedir
4
+ class PackageDiscoveryLoader
5
+ attr_reader :catalog
6
+ def initialize(catalog=nil)
7
+ @catalog = catalog
8
+ end
9
+
10
+ def loader
11
+ @loader ||= PackageLoader.new
12
+ end
13
+
14
+ # Recursively loads packages from the directory into {#catalog}
15
+ #
16
+ # @params {Pathname} path The directory to start scanning from
17
+ # @return {Homedir::PackageDiscoveryLoader} self
18
+ def load_from_directory path
19
+ if loader.path_is_valid?(path)
20
+ catalog << loader.load_from_path(path)
21
+ else
22
+ path.children.each do |child|
23
+ load_from_directory(child) if child.directory?
24
+ end
25
+ end
26
+ return self
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,48 @@
1
+ require 'homedir/errors'
2
+ require 'homedir/package_version1_loader'
3
+ require 'homedir/package_version2_loader'
4
+ require 'homedir/package_version3_loader'
5
+
6
+ module Homedir
7
+ class PackageLoader
8
+
9
+ # The list of loaders, in the order they should be tried
10
+ #
11
+ # @return {Enumerable} A list of package loaders
12
+ def loaders
13
+ # The order is important
14
+ @loaders ||= [
15
+ PackageVersion3Loader.new,
16
+ PackageVersion2Loader.new,
17
+ PackageVersion1Loader.new,
18
+ ].freeze
19
+ end
20
+
21
+ # Loads a package from a path
22
+ #
23
+ # @param {Pathname} path The directory of the package
24
+ # @return {Homedir::Package} The package object
25
+ def load_from_path path
26
+ loaders.each do |loader|
27
+ begin
28
+ return loader.load_from_path(path) if loader.path_is_valid?(path)
29
+ rescue InvalidPackageDirectoryError
30
+ next
31
+ end
32
+ end
33
+
34
+ raise InvalidPackageDirectoryError.new("The directory '#{path}' doesn't contain a valid package")
35
+ end
36
+
37
+ # Is the path a valid version1 package directory?
38
+ #
39
+ # @return {Boolean} Returns true if the path is a valid package directory.
40
+ def path_is_valid? path
41
+ loaders.each do |loader|
42
+ return true if loader.path_is_valid?(path)
43
+ end
44
+ return false
45
+ end
46
+
47
+ end
48
+ end
@@ -0,0 +1,70 @@
1
+ require 'homedir/package'
2
+ require 'homedir/errors'
3
+
4
+ module Homedir
5
+ class PackageVersion1Loader
6
+
7
+ # Loads a package from a path
8
+ #
9
+ # @param {Pathname} path The directory of the package
10
+ # @return {Homedir::Package} The package object
11
+ def load_from_path(path)
12
+ raise InvalidPackageDirectoryError.new("The directory '#{path}' is not a version 1 package.") unless path_is_valid?(path)
13
+ control_path = path + '.homedir.control'
14
+
15
+ control = parse_control_file control_path
16
+
17
+ pkg = Package.new( :directory => path )
18
+
19
+ pkg.name = control[:package]
20
+ pkg.description = control[:description] || ''
21
+ pkg.dependencies = (control[:depends] || '').split("\n")
22
+
23
+ return pkg
24
+ end
25
+
26
+ # Is the path a valid version1 package directory?
27
+ #
28
+ # @return {Boolean} Returns true if the path is a valid package directory.
29
+ def path_is_valid? path
30
+ return false unless (path).directory?
31
+ return false unless (path + '.homedir.control').file?
32
+ return true
33
+ end
34
+
35
+ # Parse a control file
36
+ #
37
+ # @param {Pathname} path The path to control file
38
+ # @return {Hash} A hash containing the keys/values.
39
+ # @see {#parse_control_stream}
40
+ def parse_control_file path
41
+ path.open('r') { |f| parse_control_stream f }
42
+ end
43
+
44
+ # Parse a version 1 control from a stream
45
+ #
46
+ # This is *not* a robust parser.
47
+ # @param {IO} stream An IO stream to read from
48
+ # @return {Hash} A hash containing the keys/values.
49
+ def parse_control_stream stream
50
+ contents = stream.read()
51
+
52
+ hash = {}
53
+ last_key = nil
54
+ last_value = nil
55
+ contents.split(/[\r\n]+/).each do |line|
56
+ next if line =~ /^\s*#/
57
+ if line =~ /^\s/
58
+ last_value << line.strip unless last_key.nil?
59
+ elsif line.include? ":"
60
+ hash[last_key.to_sym] = last_value.join("\n") unless last_key.nil?
61
+ last_key, value = line.split(/\s*:\s*/, 2)
62
+ last_value = value.strip.empty? ? [] : [ value.strip ]
63
+ end
64
+ end
65
+ hash[last_key.to_sym] = last_value.join("\n") unless last_key.nil?
66
+
67
+ return hash
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,48 @@
1
+ require 'homedir/package'
2
+ require 'homedir/package_version1_loader'
3
+ require 'homedir/errors'
4
+
5
+ module Homedir
6
+ class PackageVersion2Loader < PackageVersion1Loader
7
+
8
+ # Loads a package from a path
9
+ #
10
+ # @param {Pathname} path The directory of the package
11
+ # @return {Homedir::Package} The package object
12
+ def load_from_path(path)
13
+ raise InvalidPackageDirectoryError.new("The directory '#{path}' is not a version 2 package.") unless path_is_valid?(path)
14
+ control_path = path + '.homedir' + 'control'
15
+
16
+ control = parse_control_file control_path
17
+
18
+ pkg = Package.new( :directory => path )
19
+
20
+ pkg.name = control[:package]
21
+ pkg.description = control[:description] || ''
22
+ pkg.dependencies = (control[:depends] || '').split("\n")
23
+
24
+ ['pre', 'post'].each do |prefix|
25
+ ['install', 'remove'].each do |action|
26
+ filepath = (path + ".homedir" + "#{prefix}-#{action}")
27
+ if filepath.file? and filepath.executable?
28
+ filepath.open('r') do |f|
29
+ pkg.send("#{prefix}_#{action}=", f.read())
30
+ end
31
+ end
32
+ end
33
+ end
34
+
35
+ return pkg
36
+ end
37
+
38
+ # Is the path a valid version2 package directory?
39
+ #
40
+ # @return {Boolean} Returns true if the path is a valid package directory.
41
+ def path_is_valid? path
42
+ return false unless (path).directory?
43
+ return false unless (path + '.homedir').directory?
44
+ return false unless (path + '.homedir' + 'control').file?
45
+ return true
46
+ end
47
+ end
48
+ end