Class: Licensed::Sources::Cabal
- Defined in:
- lib/licensed/sources/cabal.rb
Constant Summary collapse
- DEPENDENCY_REGEX =
/\s*.+?\s*/.freeze
- DEFAULT_TARGETS =
%w{executable library}.freeze
Instance Attribute Summary
Attributes inherited from Source
Instance Method Summary collapse
-
#cabal_file_dependencies ⇒ Object
Returns a set of the top-level dependencies found in cabal files.
-
#cabal_file_regex ⇒ Object
Find ‘build-depends` lists from specified targets in a cabal file.
-
#cabal_file_targets ⇒ Object
Returns the targets to search for ‘build-depends` in a cabal file.
-
#cabal_files ⇒ Object
Returns an array of the local directory cabal package files.
-
#cabal_package_id(package_name) ⇒ Object
Returns an installed package id for the package.
- #enabled? ⇒ Boolean
- #enumerate_dependencies ⇒ Object
-
#ghc? ⇒ Boolean
Returns whether the ghc cli tool is available.
-
#ghc_pkg_field_command(id, fields, *args) ⇒ Object
Runs a ‘ghc-pkg field` command for a given set of fields and arguments Automatically includes ghc package DB locations in the command.
-
#ghc_version ⇒ Object
Returns the ghc cli tool version.
-
#package_db_args ⇒ Object
Returns an array of ghc package DB locations as specified in the app configuration.
-
#package_dependencies(id) ⇒ Object
Returns an array of dependency package names for the cabal package given by ‘id`.
-
#package_dependencies_command(id) ⇒ Object
Returns the output of running ‘ghc-pkg field depends` for a package id Optionally allows for interpreting the given id as an installed package id (`–ipid`).
-
#package_docs_dirs(package) ⇒ Object
Returns the packages document directory and search root directory as an array.
-
#package_info(id) ⇒ Object
Returns package information as a hash for the given id.
-
#package_info_command(id) ⇒ Object
Returns the output of running ‘ghc-pkg field` to obtain package information.
-
#packages ⇒ Object
Returns a list of all detected packages.
-
#realized_ghc_package_path(path) ⇒ Object
Returns a ghc package path with template markers replaced by live data.
-
#recursive_dependencies(package_names, results = Set.new) ⇒ Object
Recursively finds the dependencies for each cabal package.
-
#safe_homepage(homepage) ⇒ Object
Returns a homepage url that enforces https and removes url fragments.
Methods inherited from Source
#dependencies, #ignored?, inherited, #initialize, type
Constructor Details
This class inherits a constructor from Licensed::Sources::Source
Instance Method Details
#cabal_file_dependencies ⇒ Object
Returns a set of the top-level dependencies found in cabal files
151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 |
# File 'lib/licensed/sources/cabal.rb', line 151 def cabal_file_dependencies @cabal_file_dependencies ||= cabal_files.each_with_object(Set.new) do |cabal_file, targets| content = File.read(cabal_file) next if content.nil? || content.empty? # add any dependencies for matched targets from the cabal file. # by default this will find executable and library dependencies content.scan(cabal_file_regex).each do |match| # match[1] is a string of "," separated dependencies. # dependency packages might have a version specifier, remove them # to get the full id specifier for each package dependencies = match[1].split(",").map(&:strip) targets.merge(dependencies) end end end |
#cabal_file_regex ⇒ Object
Find ‘build-depends` lists from specified targets in a cabal file
181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 |
# File 'lib/licensed/sources/cabal.rb', line 181 def cabal_file_regex # this will match 0 or more occurences of # match[0] - specifier, e.g. executable, library, etc # match[1] - full list of matched dependencies # match[2] - first matched dependency (required) # match[3] - remainder of matched dependencies (not required) @cabal_file_regex ||= / # match a specifier, e.g. library or executable ^(#{cabal_file_targets.join("|")}) .*? # stuff # match a list of 1 or more dependencies build-depends:(#{DEPENDENCY_REGEX}(,#{DEPENDENCY_REGEX})*)\n /xmi end |
#cabal_file_targets ⇒ Object
Returns the targets to search for ‘build-depends` in a cabal file
198 199 200 201 202 |
# File 'lib/licensed/sources/cabal.rb', line 198 def cabal_file_targets targets = Array(config.dig("cabal", "cabal_file_targets")) targets.push(*DEFAULT_TARGETS) if targets.empty? targets end |
#cabal_files ⇒ Object
Returns an array of the local directory cabal package files
205 206 207 |
# File 'lib/licensed/sources/cabal.rb', line 205 def cabal_files @cabal_files ||= Dir.glob(File.join(config.pwd, "*.cabal")) end |
#cabal_package_id(package_name) ⇒ Object
Returns an installed package id for the package.
169 170 171 172 173 174 175 176 177 178 |
# File 'lib/licensed/sources/cabal.rb', line 169 def cabal_package_id(package_name) # using the first returned id assumes that package resolvers # order returned package information in the same order that it would # be used during build field = ghc_pkg_field_command(package_name, ["id"]).lines.first return unless field id = field.split(":", 2)[1] id.strip if id end |
#enabled? ⇒ Boolean
10 11 12 |
# File 'lib/licensed/sources/cabal.rb', line 10 def enabled? cabal_file_dependencies.any? && ghc? end |
#enumerate_dependencies ⇒ Object
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
# File 'lib/licensed/sources/cabal.rb', line 14 def enumerate_dependencies packages.map do |package| path, search_root = package_docs_dirs(package) Dependency.new( name: package["name"], version: package["version"], path: path, search_root: search_root, errors: Array(package["error"]), metadata: { "type" => Cabal.type, "summary" => package["synopsis"], "homepage" => safe_homepage(package["homepage"]) } ) end end |
#ghc? ⇒ Boolean
Returns whether the ghc cli tool is available
216 217 218 |
# File 'lib/licensed/sources/cabal.rb', line 216 def ghc? @ghc ||= Licensed::Shell.tool_available?("ghc") end |
#ghc_pkg_field_command(id, fields, *args) ⇒ Object
Runs a ‘ghc-pkg field` command for a given set of fields and arguments Automatically includes ghc package DB locations in the command
127 128 129 |
# File 'lib/licensed/sources/cabal.rb', line 127 def ghc_pkg_field_command(id, fields, *args) Licensed::Shell.execute("ghc-pkg", "field", id, fields.join(","), *args, *package_db_args, allow_failure: true) end |
#ghc_version ⇒ Object
Returns the ghc cli tool version
210 211 212 213 |
# File 'lib/licensed/sources/cabal.rb', line 210 def ghc_version return unless ghc? @version ||= Licensed::Shell.execute("ghc", "--numeric-version") end |
#package_db_args ⇒ Object
Returns an array of ghc package DB locations as specified in the app configuration
133 134 135 136 137 138 139 140 141 142 |
# File 'lib/licensed/sources/cabal.rb', line 133 def package_db_args @package_db_args ||= Array(config.dig("cabal", "ghc_package_db")).map do |path| next "--#{path}" if %w(global user).include?(path) path = realized_ghc_package_path(path) path = File.(path, config.root) next unless File.exist?(path) "--package-db=#{path}" end.compact end |
#package_dependencies(id) ⇒ Object
Returns an array of dependency package names for the cabal package given by ‘id`
97 98 99 |
# File 'lib/licensed/sources/cabal.rb', line 97 def package_dependencies(id) package_dependencies_command(id).gsub("depends:", "").split.map(&:strip) end |
#package_dependencies_command(id) ⇒ Object
Returns the output of running ‘ghc-pkg field depends` for a package id Optionally allows for interpreting the given id as an installed package id (`–ipid`)
104 105 106 107 |
# File 'lib/licensed/sources/cabal.rb', line 104 def package_dependencies_command(id) fields = %w(depends) ghc_pkg_field_command(id, fields, "--ipid") end |
#package_docs_dirs(package) ⇒ Object
Returns the packages document directory and search root directory as an array
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
# File 'lib/licensed/sources/cabal.rb', line 51 def package_docs_dirs(package) unless package["haddock-html"] # default to a local vendor directory if haddock-html property # isn't available return [File.join(config.pwd, "vendor", package["name"]), nil] end html_dir = package["haddock-html"] data_dir = package["data-dir"] return [html_dir, nil] unless data_dir # only allow data directories that are ancestors of the html directory unless Pathname.new(html_dir).fnmatch?(File.join(data_dir, "**")) data_dir = nil end [html_dir, data_dir] end |
#package_info(id) ⇒ Object
Returns package information as a hash for the given id
110 111 112 113 114 115 116 117 |
# File 'lib/licensed/sources/cabal.rb', line 110 def package_info(id) package_info_command(id).lines.each_with_object({}) do |line, info| key, value = line.split(":", 2).map(&:strip) next unless key && value info[key] = value end end |
#package_info_command(id) ⇒ Object
Returns the output of running ‘ghc-pkg field` to obtain package information
120 121 122 123 |
# File 'lib/licensed/sources/cabal.rb', line 120 def package_info_command(id) fields = %w(name version synopsis homepage haddock-html data-dir) ghc_pkg_field_command(id, fields, "--ipid") end |
#packages ⇒ Object
Returns a list of all detected packages
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
# File 'lib/licensed/sources/cabal.rb', line 33 def packages missing = [] package_ids = Set.new cabal_file_dependencies.each do |target| name, version = target.split(/\s/, 2) package_id = cabal_package_id(name) if package_id.nil? missing << { "name" => name, "version" => version, "error" => "package not found" } else recursive_dependencies([package_id], package_ids) end end Parallel.map(package_ids) { |id| package_info(id) }.concat(missing) end |
#realized_ghc_package_path(path) ⇒ Object
Returns a ghc package path with template markers replaced by live data
146 147 148 |
# File 'lib/licensed/sources/cabal.rb', line 146 def realized_ghc_package_path(path) path.gsub("<ghc_version>", ghc_version) end |
#recursive_dependencies(package_names, results = Set.new) ⇒ Object
Recursively finds the dependencies for each cabal package. Returns a ‘Set` containing the package names for all dependencies
80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
# File 'lib/licensed/sources/cabal.rb', line 80 def recursive_dependencies(package_names, results = Set.new) return [] if package_names.nil? || package_names.empty? new_packages = Set.new(package_names) - results return [] if new_packages.empty? results.merge new_packages dependencies = Parallel.map(new_packages, &method(:package_dependencies)).flatten return results if dependencies.empty? results.merge recursive_dependencies(dependencies, results) end |
#safe_homepage(homepage) ⇒ Object
Returns a homepage url that enforces https and removes url fragments
71 72 73 74 75 76 |
# File 'lib/licensed/sources/cabal.rb', line 71 def safe_homepage(homepage) return unless homepage # use https and remove url fragment homepage.gsub(/http:/, "https:") .gsub(/#[^?]*\z/, "") end |