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.
-
#missing_package(id) ⇒ Object
Returns a package info structure with an error set.
-
#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
154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 |
# File 'lib/licensed/sources/cabal.rb', line 154 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
184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 |
# File 'lib/licensed/sources/cabal.rb', line 184 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
201 202 203 204 205 |
# File 'lib/licensed/sources/cabal.rb', line 201 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
208 209 210 |
# File 'lib/licensed/sources/cabal.rb', line 208 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.
172 173 174 175 176 177 178 179 180 181 |
# File 'lib/licensed/sources/cabal.rb', line 172 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
219 220 221 |
# File 'lib/licensed/sources/cabal.rb', line 219 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
130 131 132 |
# File 'lib/licensed/sources/cabal.rb', line 130 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
213 214 215 216 |
# File 'lib/licensed/sources/cabal.rb', line 213 def ghc_version return unless ghc? @version ||= Licensed::Shell.execute("ghc", "--numeric-version") end |
#missing_package(id) ⇒ Object
Returns a package info structure with an error set
224 225 226 227 228 229 230 231 232 |
# File 'lib/licensed/sources/cabal.rb', line 224 def missing_package(id) name, _, version = if id.index(/\s/).nil? id.rpartition("-") # e.g. to match the right-most dash from ipid fused-effects-1.0.0.0 else id.partition(/\s/) # e.g. to match the left-most space from constraint fused-effects > 1.0.0.0 end { "name" => name, "version" => version, "error" => "package not found" } end |
#package_db_args ⇒ Object
Returns an array of ghc package DB locations as specified in the app configuration
136 137 138 139 140 141 142 143 144 145 |
# File 'lib/licensed/sources/cabal.rb', line 136 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
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
# File 'lib/licensed/sources/cabal.rb', line 50 def package_docs_dirs(package) return [nil, nil] if package.nil? || package.empty? 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 118 119 120 |
# File 'lib/licensed/sources/cabal.rb', line 110 def package_info(id) info = package_info_command(id).strip return missing_package(id) if info.empty? info.lines.each_with_object({}) do |line, hsh| key, value = line.split(":", 2).map(&:strip) next unless key && value hsh[key] = value end end |
#package_info_command(id) ⇒ Object
Returns the output of running ‘ghc-pkg field` to obtain package information
123 124 125 126 |
# File 'lib/licensed/sources/cabal.rb', line 123 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 |
# File 'lib/licensed/sources/cabal.rb', line 33 def packages package_ids = Set.new cabal_file_dependencies.each do |target| name = target.split(/\s/)[0] package_id = cabal_package_id(name) if package_id.nil? package_ids << target else recursive_dependencies([package_id], package_ids) end end Parallel.map(package_ids) { |id| package_info(id) } end |
#realized_ghc_package_path(path) ⇒ Object
Returns a ghc package path with template markers replaced by live data
149 150 151 |
# File 'lib/licensed/sources/cabal.rb', line 149 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
81 82 83 84 85 86 87 88 89 90 91 92 93 |
# File 'lib/licensed/sources/cabal.rb', line 81 def recursive_dependencies(package_names, results = Set.new) return results if package_names.nil? || package_names.empty? new_packages = Set.new(package_names) - results return results if new_packages.empty? results.merge new_packages dependencies = Parallel.map(new_packages, &method(:package_dependencies)).flatten recursive_dependencies(dependencies, results) results end |
#safe_homepage(homepage) ⇒ Object
Returns a homepage url that enforces https and removes url fragments
72 73 74 75 76 77 |
# File 'lib/licensed/sources/cabal.rb', line 72 def safe_homepage(homepage) return unless homepage # use https and remove url fragment homepage.gsub(/http:/, "https:") .gsub(/#[^?]*\z/, "") end |