skippy 0.2.0.a → 0.3.0.a
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.
- checksums.yaml +5 -5
- data/.gitignore +1 -0
- data/.gitmodules +3 -0
- data/.idea/.rakeTasks +6 -6
- data/.idea/codeStyleSettings.xml +8 -8
- data/.idea/encodings.xml +5 -5
- data/.idea/inspectionProfiles/Project_Default.xml +7 -7
- data/.idea/misc.xml +3 -3
- data/.idea/modules.xml +7 -7
- data/.idea/skippy.iml +89 -82
- data/.idea/vcs.xml +5 -5
- data/.rubocop.yml +93 -0
- data/.rubocop_todo.yml +24 -0
- data/.vscode/launch.json +51 -61
- data/.vscode/settings.json +41 -2
- data/.vscode/tasks.json +16 -16
- data/Gemfile +11 -2
- data/README.md +194 -15
- data/Rakefile +1 -1
- data/app/boot.rb +1 -1
- data/app/commands/install.rb +41 -0
- data/app/commands/lib.rb +42 -2
- data/app/commands/new.rb +22 -8
- data/app/commands/template.rb +2 -2
- data/bin/rubocop +17 -0
- data/bin/ruby-parse +17 -0
- data/bin/ruby-rewrite +17 -0
- data/debug/skippy.bat +2 -0
- data/fixtures/my_lib/{src → modules}/command.rb +0 -0
- data/fixtures/my_lib/{src → modules}/geometry.rb +0 -0
- data/fixtures/my_lib/modules/gl.rb +4 -0
- data/fixtures/my_lib/modules/gl/container.rb +8 -0
- data/fixtures/my_lib/modules/gl/control.rb +6 -0
- data/fixtures/my_lib/modules/gl/nested/nested.rb +8 -0
- data/fixtures/my_lib/{src → modules}/tool.rb +0 -0
- data/fixtures/my_lib/skippy.json +1 -1
- data/fixtures/my_project/skippy.json +2 -1
- data/fixtures/my_project/src/hello_world.rb +2 -2
- data/fixtures/my_project/src/hello_world/extension.json +9 -9
- data/fixtures/project_with_lib/.skippy/libs/my-lib/modules/command.rb +4 -0
- data/fixtures/project_with_lib/.skippy/libs/my-lib/modules/gl.rb +4 -0
- data/fixtures/project_with_lib/.skippy/libs/my-lib/modules/gl/container.rb +8 -0
- data/fixtures/project_with_lib/.skippy/libs/my-lib/modules/gl/control.rb +6 -0
- data/fixtures/project_with_lib/.skippy/libs/my-lib/skippy.json +5 -0
- data/fixtures/project_with_lib/.skippy/libs/my-other-lib/modules/something.rb +4 -0
- data/fixtures/project_with_lib/.skippy/libs/my-other-lib/skippy.json +5 -0
- data/fixtures/project_with_lib/skippy.json +25 -0
- data/fixtures/project_with_lib/skippy/commands/example.rb +14 -0
- data/fixtures/project_with_lib/src/hello_world.rb +47 -0
- data/fixtures/project_with_lib/src/hello_world/extension.json +10 -0
- data/fixtures/project_with_lib/src/hello_world/main.rb +21 -0
- data/fixtures/project_with_lib/src/hello_world/vendor/my-lib/command.rb +4 -0
- data/fixtures/project_with_lib/src/hello_world/vendor/my-other-lib/something.rb +4 -0
- data/lib/skippy.rb +2 -0
- data/lib/skippy/app.rb +2 -2
- data/lib/skippy/cli.rb +41 -20
- data/lib/skippy/command.rb +2 -4
- data/lib/skippy/config.rb +27 -22
- data/lib/skippy/config_accessors.rb +12 -12
- data/lib/skippy/group.rb +1 -3
- data/lib/skippy/helpers/file.rb +3 -3
- data/lib/skippy/installer.rb +49 -0
- data/lib/skippy/installer/git.rb +115 -0
- data/lib/skippy/installer/local.rb +19 -0
- data/lib/skippy/lib_module.rb +16 -16
- data/lib/skippy/lib_source.rb +139 -0
- data/lib/skippy/library.rb +50 -10
- data/lib/skippy/library_manager.rb +116 -18
- data/lib/skippy/module_manager.rb +104 -26
- data/lib/skippy/namespace.rb +17 -1
- data/lib/skippy/project.rb +34 -4
- data/lib/skippy/version.rb +3 -1
- data/skippy.gemspec +10 -5
- metadata +85 -29
- data/cSpell.json +0 -18
| @@ -0,0 +1,19 @@ | |
| 1 | 
            +
            require 'pathname'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'skippy/installer'
         | 
| 4 | 
            +
            require 'skippy/library'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            class Skippy::LocalLibraryInstaller < Skippy::LibraryInstaller
         | 
| 7 | 
            +
             | 
| 8 | 
            +
              # @return [Skippy::Library]
         | 
| 9 | 
            +
              def install
         | 
| 10 | 
            +
                info "Installing #{source.basename} from #{source.origin}..."
         | 
| 11 | 
            +
                library = Skippy::Library.new(source.origin)
         | 
| 12 | 
            +
                target = path.join(library.name)
         | 
| 13 | 
            +
                FileUtils.mkdir_p(path)
         | 
| 14 | 
            +
                # Must remove the destination in order to ensure update installations works.
         | 
| 15 | 
            +
                FileUtils.copy_entry(source.origin, target, false, false, true)
         | 
| 16 | 
            +
                Skippy::Library.new(target, source: source)
         | 
| 17 | 
            +
              end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
            end
         | 
    
        data/lib/skippy/lib_module.rb
    CHANGED
    
    | @@ -4,14 +4,25 @@ require 'skippy/library' | |
| 4 4 |  | 
| 5 5 | 
             
            class Skippy::LibModule
         | 
| 6 6 |  | 
| 7 | 
            -
              attr_reader :path
         | 
| 7 | 
            +
              attr_reader :path, :library
         | 
| 8 8 |  | 
| 9 9 | 
             
              class ModuleNotFoundError < Skippy::Error; end
         | 
| 10 10 |  | 
| 11 | 
            +
              # @param [Skippy::Library] library
         | 
| 11 12 | 
             
              # @param [String] path
         | 
| 12 | 
            -
              def initialize(path)
         | 
| 13 | 
            +
              def initialize(library, path)
         | 
| 13 14 | 
             
                @path = Pathname.new(path)
         | 
| 14 15 | 
             
                raise ModuleNotFoundError, @path.to_s unless @path.file?
         | 
| 16 | 
            +
                @library = library
         | 
| 17 | 
            +
              end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
              def <=>(other)
         | 
| 20 | 
            +
                other.is_a?(self.class) ? name <=> other.name : nil
         | 
| 21 | 
            +
              end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
              def eql?(other)
         | 
| 24 | 
            +
                # http://javieracero.com/blog/the-key-to-ruby-hashes-is-eql-hash
         | 
| 25 | 
            +
                other.is_a?(self.class) && name.casecmp(other.name).zero?
         | 
| 15 26 | 
             
              end
         | 
| 16 27 |  | 
| 17 28 | 
             
              # @param [String]
         | 
| @@ -19,14 +30,13 @@ class Skippy::LibModule | |
| 19 30 | 
             
                path.basename('.*').to_s
         | 
| 20 31 | 
             
              end
         | 
| 21 32 |  | 
| 22 | 
            -
               | 
| 23 | 
            -
             | 
| 24 | 
            -
                Skippy::Library.new(library_path)
         | 
| 33 | 
            +
              def hash
         | 
| 34 | 
            +
                name.hash
         | 
| 25 35 | 
             
              end
         | 
| 26 36 |  | 
| 27 37 | 
             
              # @param [String]
         | 
| 28 38 | 
             
              def name
         | 
| 29 | 
            -
                "#{ | 
| 39 | 
            +
                "#{library.name}/#{basename}"
         | 
| 30 40 | 
             
              end
         | 
| 31 41 |  | 
| 32 42 | 
             
              # @param [String]
         | 
| @@ -34,14 +44,4 @@ class Skippy::LibModule | |
| 34 44 | 
             
                name
         | 
| 35 45 | 
             
              end
         | 
| 36 46 |  | 
| 37 | 
            -
              private
         | 
| 38 | 
            -
             | 
| 39 | 
            -
              def library_name
         | 
| 40 | 
            -
                library_path.basename.to_s
         | 
| 41 | 
            -
              end
         | 
| 42 | 
            -
             | 
| 43 | 
            -
              def library_path
         | 
| 44 | 
            -
                path.parent.parent
         | 
| 45 | 
            -
              end
         | 
| 46 | 
            -
             | 
| 47 47 | 
             
            end
         | 
| @@ -0,0 +1,139 @@ | |
| 1 | 
            +
            require 'digest'
         | 
| 2 | 
            +
            require 'net/http'
         | 
| 3 | 
            +
            require 'pathname'
         | 
| 4 | 
            +
            require 'uri'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            require 'skippy/error'
         | 
| 7 | 
            +
            require 'skippy/library'
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            class Skippy::LibrarySource
         | 
| 10 | 
            +
             | 
| 11 | 
            +
              attr_reader :origin, :options
         | 
| 12 | 
            +
             | 
| 13 | 
            +
              class LibraryNotFoundError < Skippy::Error; end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
              # @param [Skippy::Project] project
         | 
| 16 | 
            +
              # @param [Pathname, String] source
         | 
| 17 | 
            +
              # @param [Hash] options
         | 
| 18 | 
            +
              def initialize(project, source, options = {})
         | 
| 19 | 
            +
                @project = project
         | 
| 20 | 
            +
                @origin = resolve(source.to_s)
         | 
| 21 | 
            +
                @options = options
         | 
| 22 | 
            +
              end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
              def git?
         | 
| 25 | 
            +
                git_source?(@origin)
         | 
| 26 | 
            +
              end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
              def local?
         | 
| 29 | 
            +
                local_source?(@origin)
         | 
| 30 | 
            +
              end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
              def relative?
         | 
| 33 | 
            +
                local? && Pathname.new(@origin).relative?
         | 
| 34 | 
            +
              end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
              def absolute?
         | 
| 37 | 
            +
                !relative?
         | 
| 38 | 
            +
              end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
              # @return [String, nil]
         | 
| 41 | 
            +
              def requirement
         | 
| 42 | 
            +
                return nil if @options[:requirement].nil?
         | 
| 43 | 
            +
                # Normalize the version requirement pattern.
         | 
| 44 | 
            +
                parts = Gem::Requirement.parse(@options[:requirement])
         | 
| 45 | 
            +
                # .parse will from '1.2.3' return ['=', '1.2.3']. Don't need that.
         | 
| 46 | 
            +
                parts.delete('=')
         | 
| 47 | 
            +
                parts.join(' ')
         | 
| 48 | 
            +
              rescue Gem::Requirement::BadRequirementError
         | 
| 49 | 
            +
                @options[:requirement]
         | 
| 50 | 
            +
              end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
              # @return [String]
         | 
| 53 | 
            +
              def branch
         | 
| 54 | 
            +
                @options[:branch]
         | 
| 55 | 
            +
              end
         | 
| 56 | 
            +
             | 
| 57 | 
            +
              # @param [String]
         | 
| 58 | 
            +
              def basename
         | 
| 59 | 
            +
                if local?
         | 
| 60 | 
            +
                  Pathname.new(@origin).basename
         | 
| 61 | 
            +
                else
         | 
| 62 | 
            +
                  uri = URI.parse(@origin)
         | 
| 63 | 
            +
                  Pathname.new(uri.path).basename('.git')
         | 
| 64 | 
            +
                end
         | 
| 65 | 
            +
              end
         | 
| 66 | 
            +
             | 
| 67 | 
            +
              # @param [String]
         | 
| 68 | 
            +
              def lib_path
         | 
| 69 | 
            +
                if local?
         | 
| 70 | 
            +
                  source = File.expand_path(@origin)
         | 
| 71 | 
            +
                  hash_signature = Digest::SHA1.hexdigest(source)
         | 
| 72 | 
            +
                  "#{basename}_local_#{hash_signature}"
         | 
| 73 | 
            +
                else
         | 
| 74 | 
            +
                  # https://github.com/thomthom/tt-lib.git
         | 
| 75 | 
            +
                  #         ^^^^^^^^^^ ^^^^^^^^ ^^^^^^
         | 
| 76 | 
            +
                  #        source_name  author  basename
         | 
| 77 | 
            +
                  uri = URI.parse(@origin)
         | 
| 78 | 
            +
                  source_name = uri.hostname.gsub(/[.]/, '-')
         | 
| 79 | 
            +
                  author = Pathname.new(uri.path).parent.basename
         | 
| 80 | 
            +
                  "#{basename}_#{author}_#{source_name}"
         | 
| 81 | 
            +
                end
         | 
| 82 | 
            +
              end
         | 
| 83 | 
            +
             | 
| 84 | 
            +
              # @param [String]
         | 
| 85 | 
            +
              def to_s
         | 
| 86 | 
            +
                @origin.to_s
         | 
| 87 | 
            +
              end
         | 
| 88 | 
            +
             | 
| 89 | 
            +
              private
         | 
| 90 | 
            +
             | 
| 91 | 
            +
              # @param [String] source
         | 
| 92 | 
            +
              def resolve(source)
         | 
| 93 | 
            +
                if git_source?(source)
         | 
| 94 | 
            +
                  resolve_from_git_uri(source)
         | 
| 95 | 
            +
                elsif lib_name?(source)
         | 
| 96 | 
            +
                  resolve_from_lib_name(source, @project.sources)
         | 
| 97 | 
            +
                else
         | 
| 98 | 
            +
                  source
         | 
| 99 | 
            +
                end
         | 
| 100 | 
            +
              end
         | 
| 101 | 
            +
             | 
| 102 | 
            +
              # @param [String] source
         | 
| 103 | 
            +
              def git_source?(source)
         | 
| 104 | 
            +
                source.end_with?('.git') || Pathname.new(source).join('.git').exist?
         | 
| 105 | 
            +
              end
         | 
| 106 | 
            +
             | 
| 107 | 
            +
              # @param [String] source
         | 
| 108 | 
            +
              def local_source?(source)
         | 
| 109 | 
            +
                File.exist?(source)
         | 
| 110 | 
            +
              end
         | 
| 111 | 
            +
             | 
| 112 | 
            +
              # @param [String] source
         | 
| 113 | 
            +
              def lib_name?(source)
         | 
| 114 | 
            +
                !local_source?(source) && source =~ %r{^[^/]+/[^/]+$}
         | 
| 115 | 
            +
              end
         | 
| 116 | 
            +
             | 
| 117 | 
            +
              # @param [String] source
         | 
| 118 | 
            +
              # @return [String]
         | 
| 119 | 
            +
              def resolve_from_git_uri(source)
         | 
| 120 | 
            +
                uri = URI.parse(source)
         | 
| 121 | 
            +
                # When logged in, BitBucket will display a URI with the user's username.
         | 
| 122 | 
            +
                uri.user = ''
         | 
| 123 | 
            +
                uri.to_s
         | 
| 124 | 
            +
              end
         | 
| 125 | 
            +
             | 
| 126 | 
            +
              # @param [String] source
         | 
| 127 | 
            +
              # @return [String]
         | 
| 128 | 
            +
              def resolve_from_lib_name(source, domains)
         | 
| 129 | 
            +
                domains.each { |domain|
         | 
| 130 | 
            +
                  uri_str = "https://#{domain}/#{source}.git"
         | 
| 131 | 
            +
                  uri = URI.parse(uri_str)
         | 
| 132 | 
            +
                  response = Net::HTTP.get_response(uri)
         | 
| 133 | 
            +
                  return uri_str if response.is_a?(Net::HTTPSuccess) ||
         | 
| 134 | 
            +
                                    response.is_a?(Net::HTTPRedirection)
         | 
| 135 | 
            +
                }
         | 
| 136 | 
            +
                raise LibraryNotFoundError, "Library '#{source}' not found"
         | 
| 137 | 
            +
              end
         | 
| 138 | 
            +
             | 
| 139 | 
            +
            end
         | 
    
        data/lib/skippy/library.rb
    CHANGED
    
    | @@ -14,44 +14,84 @@ class Skippy::Library | |
| 14 14 |  | 
| 15 15 | 
             
              CONFIG_FILENAME = 'skippy.json'.freeze
         | 
| 16 16 |  | 
| 17 | 
            -
              attr_reader :path
         | 
| 17 | 
            +
              attr_reader :path, :source, :requirement
         | 
| 18 18 |  | 
| 19 | 
            -
              config_attr_reader : | 
| 19 | 
            +
              config_attr_reader :name
         | 
| 20 20 | 
             
              config_attr_reader :version
         | 
| 21 21 |  | 
| 22 22 | 
             
              class LibraryNotFoundError < Skippy::Error; end
         | 
| 23 23 |  | 
| 24 | 
            -
               | 
| 24 | 
            +
              # @param [Pathname, String] path
         | 
| 25 | 
            +
              # @param [Skippy::LibrarySource] source
         | 
| 26 | 
            +
              def initialize(path, source: nil)
         | 
| 27 | 
            +
                # TODO: Rename LibrarySource - it also contain requirement.
         | 
| 25 28 | 
             
                @path = Pathname.new(path)
         | 
| 26 29 | 
             
                raise LibraryNotFoundError, @path.to_s unless @path.directory?
         | 
| 27 | 
            -
                 | 
| 30 | 
            +
                raise LibraryNotFoundError, config_file.to_s unless config_file.exist?
         | 
| 28 31 | 
             
                @config = Skippy::Config.load(config_file)
         | 
| 32 | 
            +
                raise LibraryNotFoundError, 'Not a Skippy Library' unless @config[:library]
         | 
| 33 | 
            +
                @source = source
         | 
| 29 34 | 
             
              end
         | 
| 30 35 |  | 
| 31 | 
            -
              def  | 
| 32 | 
            -
                 | 
| 36 | 
            +
              def <=>(other)
         | 
| 37 | 
            +
                # TODO: This isn't taking into account version. Maybe take that into account
         | 
| 38 | 
            +
                #       and implement Comparable.
         | 
| 39 | 
            +
                other.is_a?(self.class) ? name <=> other.name : nil
         | 
| 33 40 | 
             
              end
         | 
| 34 41 |  | 
| 42 | 
            +
              def eql?(other)
         | 
| 43 | 
            +
                # http://javieracero.com/blog/the-key-to-ruby-hashes-is-eql-hash
         | 
| 44 | 
            +
                # TODO: Compare using #hash.
         | 
| 45 | 
            +
                other.is_a?(self.class) && name.casecmp(other.name).zero?
         | 
| 46 | 
            +
              end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
              def hash
         | 
| 49 | 
            +
                # TODO: This doesn't take into account version. Right now LibraryManager
         | 
| 50 | 
            +
                #        relies on this to avoid listing the same lib multiple times.
         | 
| 51 | 
            +
                #        But maybe this hash should reflect version differences and the
         | 
| 52 | 
            +
                #        library manager enforce library uniqueness differently.
         | 
| 53 | 
            +
                name.hash
         | 
| 54 | 
            +
              end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
              # @return [Array<Skippy::LibModule>]
         | 
| 35 57 | 
             
              def modules
         | 
| 58 | 
            +
                # .rb files in the library's modules_path are considered modules.
         | 
| 36 59 | 
             
                libs = modules_path.children(false).select { |file|
         | 
| 37 | 
            -
                  file.extname. | 
| 60 | 
            +
                  file.extname.casecmp('.rb').zero?
         | 
| 38 61 | 
             
                }
         | 
| 39 62 | 
             
                libs.map! { |lib|
         | 
| 40 63 | 
             
                  path = modules_path.join(lib)
         | 
| 41 | 
            -
                  Skippy::LibModule.new(path)
         | 
| 64 | 
            +
                  Skippy::LibModule.new(self, path)
         | 
| 42 65 | 
             
                }
         | 
| 43 66 | 
             
                libs
         | 
| 44 67 | 
             
              end
         | 
| 45 68 |  | 
| 69 | 
            +
              # @return [Hash]
         | 
| 70 | 
            +
              def to_h
         | 
| 71 | 
            +
                hash = {
         | 
| 72 | 
            +
                  name: name, # TODO: Could be issue as UUID if name changes...
         | 
| 73 | 
            +
                  version: version,
         | 
| 74 | 
            +
                  source: source.origin,
         | 
| 75 | 
            +
                }
         | 
| 76 | 
            +
                hash[:requirement] = source.requirement unless source.requirement.nil?
         | 
| 77 | 
            +
                hash
         | 
| 78 | 
            +
              end
         | 
| 79 | 
            +
             | 
| 80 | 
            +
              # @return [String]
         | 
| 81 | 
            +
              def to_s
         | 
| 82 | 
            +
                name
         | 
| 83 | 
            +
              end
         | 
| 84 | 
            +
             | 
| 46 85 | 
             
              private
         | 
| 47 86 |  | 
| 87 | 
            +
              # @return [Pathname]
         | 
| 48 88 | 
             
              def config_file
         | 
| 49 89 | 
             
                path.join(CONFIG_FILENAME)
         | 
| 50 90 | 
             
              end
         | 
| 51 91 |  | 
| 92 | 
            +
              # @return [Pathname]
         | 
| 52 93 | 
             
              def modules_path
         | 
| 53 | 
            -
                 | 
| 54 | 
            -
                path.join('src')
         | 
| 94 | 
            +
                path.join('modules')
         | 
| 55 95 | 
             
              end
         | 
| 56 96 |  | 
| 57 97 | 
             
            end
         | 
| @@ -1,10 +1,24 @@ | |
| 1 | 
            +
            require 'git'
         | 
| 1 2 | 
             
            require 'json'
         | 
| 3 | 
            +
            require 'naturally'
         | 
| 2 4 | 
             
            require 'pathname'
         | 
| 5 | 
            +
            require 'set'
         | 
| 3 6 |  | 
| 4 7 | 
             
            require 'skippy/helpers/file'
         | 
| 8 | 
            +
            require 'skippy/installer/git'
         | 
| 9 | 
            +
            require 'skippy/installer/local'
         | 
| 10 | 
            +
            require 'skippy/error'
         | 
| 11 | 
            +
            require 'skippy/lib_source'
         | 
| 5 12 | 
             
            require 'skippy/library'
         | 
| 6 13 | 
             
            require 'skippy/project'
         | 
| 7 14 |  | 
| 15 | 
            +
            module Skippy
         | 
| 16 | 
            +
             | 
| 17 | 
            +
              class LibraryNotFound < Skippy::Error; end
         | 
| 18 | 
            +
              class UnknownSourceType < Skippy::Error; end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
            end
         | 
| 21 | 
            +
             | 
| 8 22 | 
             
            class Skippy::LibraryManager
         | 
| 9 23 |  | 
| 10 24 | 
             
              include Enumerable
         | 
| @@ -16,13 +30,12 @@ class Skippy::LibraryManager | |
| 16 30 | 
             
              def initialize(project)
         | 
| 17 31 | 
             
                raise TypeError, 'expected a Project' unless project.is_a?(Skippy::Project)
         | 
| 18 32 | 
             
                @project = project
         | 
| 33 | 
            +
                @libraries = SortedSet.new(discover_libraries)
         | 
| 19 34 | 
             
              end
         | 
| 20 35 |  | 
| 21 36 | 
             
              # @yield [Skippy::Library]
         | 
| 22 37 | 
             
              def each
         | 
| 23 | 
            -
                 | 
| 24 | 
            -
                  yield Skippy::Library.new(lib_path)
         | 
| 25 | 
            -
                }
         | 
| 38 | 
            +
                @libraries.each { |library| yield library }
         | 
| 26 39 | 
             
                self
         | 
| 27 40 | 
             
              end
         | 
| 28 41 |  | 
| @@ -30,34 +43,80 @@ class Skippy::LibraryManager | |
| 30 43 | 
             
                to_a.empty?
         | 
| 31 44 | 
             
              end
         | 
| 32 45 |  | 
| 46 | 
            +
              # @param [String] library_name
         | 
| 47 | 
            +
              # @return [Skippy::LibModule, nil]
         | 
| 48 | 
            +
              def find_library(library_name)
         | 
| 49 | 
            +
                find { |lib| lib.name.casecmp(library_name).zero? }
         | 
| 50 | 
            +
              end
         | 
| 51 | 
            +
             | 
| 33 52 | 
             
              # @param [String] module_name
         | 
| 34 53 | 
             
              # @return [Skippy::LibModule, nil]
         | 
| 35 54 | 
             
              def find_module(module_name)
         | 
| 36 55 | 
             
                library_name, module_name = module_name.split('/')
         | 
| 37 | 
            -
                 | 
| 38 | 
            -
             | 
| 56 | 
            +
                if library_name.nil? || module_name.nil?
         | 
| 57 | 
            +
                  raise ArgumentError, 'expected a module path'
         | 
| 58 | 
            +
                end
         | 
| 59 | 
            +
                library = find_library(library_name)
         | 
| 39 60 | 
             
                return nil if library.nil?
         | 
| 40 | 
            -
                library.modules.find { |mod| mod.basename | 
| 61 | 
            +
                library.modules.find { |mod| mod.basename.casecmp(module_name).zero? }
         | 
| 62 | 
            +
              end
         | 
| 63 | 
            +
             | 
| 64 | 
            +
              # @raise [Skippy::LibModule::ModuleNotFoundError]
         | 
| 65 | 
            +
              # @param [String] module_name
         | 
| 66 | 
            +
              # @return [Skippy::LibModule]
         | 
| 67 | 
            +
              def find_module_or_fail(module_name)
         | 
| 68 | 
            +
                lib_module = find_module(module_name)
         | 
| 69 | 
            +
                if lib_module.nil?
         | 
| 70 | 
            +
                  raise Skippy::LibModule::ModuleNotFoundError,
         | 
| 71 | 
            +
                    "module '#{module_name}' not found"
         | 
| 72 | 
            +
                end
         | 
| 73 | 
            +
                lib_module
         | 
| 41 74 | 
             
              end
         | 
| 42 75 |  | 
| 43 76 | 
             
              # @param [Pathname, String] source
         | 
| 44 | 
            -
               | 
| 77 | 
            +
              # @return [Skippy::Library]
         | 
| 78 | 
            +
              def install(source, options = {})
         | 
| 45 79 | 
             
                raise Skippy::Project::ProjectNotSavedError unless project.exist?
         | 
| 46 | 
            -
                 | 
| 80 | 
            +
                lib_source = Skippy::LibrarySource.new(project, source, options)
         | 
| 47 81 |  | 
| 48 | 
            -
                 | 
| 82 | 
            +
                installer = get_installer(lib_source)
         | 
| 83 | 
            +
                if block_given?
         | 
| 84 | 
            +
                  installer.on_status { |type, message|
         | 
| 85 | 
            +
                    yield type, message
         | 
| 86 | 
            +
                  }
         | 
| 87 | 
            +
                end
         | 
| 88 | 
            +
                library = installer.install
         | 
| 49 89 |  | 
| 50 | 
            -
                 | 
| 51 | 
            -
                 | 
| 90 | 
            +
                @libraries.delete(library)
         | 
| 91 | 
            +
                @libraries << library
         | 
| 52 92 |  | 
| 53 | 
            -
                project. | 
| 54 | 
            -
                  name: library.name,
         | 
| 55 | 
            -
                  version: library.version,
         | 
| 56 | 
            -
                  source: source
         | 
| 57 | 
            -
                })
         | 
| 93 | 
            +
                project.modules.update(library)
         | 
| 58 94 |  | 
| 59 | 
            -
                 | 
| 95 | 
            +
                library
         | 
| 96 | 
            +
              end
         | 
| 60 97 |  | 
| 98 | 
            +
              # @param [Skippy::Library, String] source
         | 
| 99 | 
            +
              # @return [Skippy::Library]
         | 
| 100 | 
            +
              def uninstall(lib)
         | 
| 101 | 
            +
                raise Skippy::Project::ProjectNotSavedError unless project.exist?
         | 
| 102 | 
            +
                library = lib.is_a?(Skippy::Library) ? lib : find_library(lib)
         | 
| 103 | 
            +
                raise Skippy::LibraryNotFound, 'Library not found' if library.nil?
         | 
| 104 | 
            +
                # Uninstall modules first - using the module manager.
         | 
| 105 | 
            +
                vendor_path = project.modules.vendor_path
         | 
| 106 | 
            +
                vendor_module_path = vendor_path.join(library.name)
         | 
| 107 | 
            +
                library.modules.each { |mod|
         | 
| 108 | 
            +
                  project.modules.remove(mod.name)
         | 
| 109 | 
            +
                }
         | 
| 110 | 
            +
                vendor_module_path.rmtree if vendor_module_path.exist?
         | 
| 111 | 
            +
                # Remove the vendor path - no need to package unused directories.
         | 
| 112 | 
            +
                if vendor_path.exist? && vendor_path.children.empty?
         | 
| 113 | 
            +
                  vendor_path.rmdir
         | 
| 114 | 
            +
                end
         | 
| 115 | 
            +
                raise 'Unable to remove vendor modules' if vendor_module_path.exist?
         | 
| 116 | 
            +
                # Now the library itself is safe to remove.
         | 
| 117 | 
            +
                library.path.rmtree if library.path.exist?
         | 
| 118 | 
            +
                raise 'Unable to remove library' if library.path.exist?
         | 
| 119 | 
            +
                @libraries.delete(library)
         | 
| 61 120 | 
             
                library
         | 
| 62 121 | 
             
              end
         | 
| 63 122 |  | 
| @@ -65,11 +124,50 @@ class Skippy::LibraryManager | |
| 65 124 | 
             
              def length
         | 
| 66 125 | 
             
                to_a.length
         | 
| 67 126 | 
             
              end
         | 
| 68 | 
            -
              alias  | 
| 127 | 
            +
              alias size length
         | 
| 69 128 |  | 
| 70 129 | 
             
              # @return [Pathname]
         | 
| 71 130 | 
             
              def path
         | 
| 72 131 | 
             
                project.path('.skippy/libs')
         | 
| 73 132 | 
             
              end
         | 
| 74 133 |  | 
| 134 | 
            +
              private
         | 
| 135 | 
            +
             | 
| 136 | 
            +
              # @return [Array<Skippy::Library>]
         | 
| 137 | 
            +
              def discover_libraries
         | 
| 138 | 
            +
                project.config.get(:libraries, []).map { |lib_config|
         | 
| 139 | 
            +
                  begin
         | 
| 140 | 
            +
                    library_from_config(lib_config)
         | 
| 141 | 
            +
                  rescue Skippy::Library::LibraryNotFoundError => error
         | 
| 142 | 
            +
                    # TODO: Revisit how to handle this.
         | 
| 143 | 
            +
                    warn "Unable to load library: #{error.message}"
         | 
| 144 | 
            +
                    nil
         | 
| 145 | 
            +
                  end
         | 
| 146 | 
            +
                }.compact
         | 
| 147 | 
            +
              end
         | 
| 148 | 
            +
             | 
| 149 | 
            +
              # @param [Hash] config
         | 
| 150 | 
            +
              def library_from_config(config)
         | 
| 151 | 
            +
                options = {}
         | 
| 152 | 
            +
                options[:requirement] = config[:requirement] if config[:requirement]
         | 
| 153 | 
            +
                source = Skippy::LibrarySource.new(project, config[:source], options)
         | 
| 154 | 
            +
                directories(path).each { |directory|
         | 
| 155 | 
            +
                  library = Skippy::Library.new(directory, source: source)
         | 
| 156 | 
            +
                  return library if library.name.casecmp(config[:name]).zero?
         | 
| 157 | 
            +
                }
         | 
| 158 | 
            +
                nil
         | 
| 159 | 
            +
              end
         | 
| 160 | 
            +
             | 
| 161 | 
            +
              # @param [Skippy::LibrarySource] lib_source
         | 
| 162 | 
            +
              # @return [Skippy::Installer]
         | 
| 163 | 
            +
              def get_installer(lib_source)
         | 
| 164 | 
            +
                if lib_source.git?
         | 
| 165 | 
            +
                  Skippy::GitLibraryInstaller.new(project, lib_source)
         | 
| 166 | 
            +
                elsif lib_source.local?
         | 
| 167 | 
            +
                  Skippy::LocalLibraryInstaller.new(project, lib_source)
         | 
| 168 | 
            +
                else
         | 
| 169 | 
            +
                  raise Skippy::UnknownSourceType, "Unable to handle source: #{lib_source}"
         | 
| 170 | 
            +
                end
         | 
| 171 | 
            +
              end
         | 
| 172 | 
            +
             | 
| 75 173 | 
             
            end
         |