#!/usr/bin/env ruby # WANT_JSON # init bundler in dev env if ENV['QB_DEV_ENV'] ENV.each {|k, v| if k.start_with? 'QB_DEV_ENV_' ENV[k.sub('QB_DEV_ENV_', '')] = v end } require 'bundler/setup' end require 'ostruct' require 'qb' require 'qb/package/version' require 'cmds' require 'pathname' require 'uri' require 'net/http' class Result < OpenStruct def to_json *args to_h.to_json *args end end GITHUB_SSH_URL_RE = /^git@github\.com\:(?.*)\/(?.*)\.git$/ GITHUB_HTTPS_URL_RE = /^https:\/\/github\.com\/(?.*)\/(?.*)\.git$/ class PathFacts < QB::Ansible::Module # Add a bunch of useful things to know about the path def add_path_facts @result.expanded = @path.expand_path @result.exists = @path.exist? @result.is_expanded = @result.expanded == @path @result.is_absolute = @path.absolute? @result.is_relative = @path.relative? @result.is_dir = @path.directory? @result.is_file = @path.file? @result.is_cwd = @path == Pathname.getwd # Will raise if there is no relative path between them, in which case # 'relative' will be null. @result.relative = begin @path.relative_path_from Pathname.getwd rescue ArgumentError => error end # Pathname#realpath will raise if the path doesn't exist @result.realpath = begin @path.realpath rescue Exception => error end @result.is_realpath = @result.realpath == @path end def add_git_facts @result.git = QB::Repo::Git.from_path @path, use_github_api: !!@args['github_api'] end # If the path is part of a Git repo, as well as useful general # Git environment facts. def add_git_facts_old # see if we're in a git repo. first, we need a directory that exists dir = @path.expand_path.ascend.find {|p| p.directory? } Dir.chdir(dir) do root_result = Cmds.capture "git rev-parse --show-toplevel" unless root_result.ok? @result.in_git_repo = false @result.is_git_root = false return end @result.in_git_repo = true git = @result.git = Result.new git.root = Pathname.new root_result.out.chomp @result.is_git_root = @path == git.root user = git.user = Result.new ['name', 'email'].each {|key| user[key] = begin Cmds.chomp! "git config user.#{ key }" rescue end } git.is_clean = Cmds.chomp!('git status --porcelain 2>/dev/null').empty? rev_parse = Cmds.capture 'git rev-parse HEAD' if rev_parse.ok? git.head = rev_parse.out.chomp git.head_short = git.head[0...7] else git.head = git.head_short = nil end branch = Cmds.capture 'git branch --no-color 2> /dev/null' git.branch = if branch.ok? if line = branch.out.lines.find {|line| line.start_with? '*'} if m = line.match(/\*\s+(\S+)/) m[1] end end end git.origin = begin Cmds.chomp! "git remote get-url origin" rescue end match = GITHUB_SSH_URL_RE.match(git.origin) || GITHUB_HTTPS_URL_RE.match(git.origin) git.is_github = !! match return unless match git.owner = match['owner'] git.name = match['name'] git.full_name = "#{ git.owner }/#{ git.name }" if true == @args['github_api'] github = git.github = Result.new github.api_url = "https://api.github.com/repos/#{ git.owner }/#{ git.name }" response = Net::HTTP.get_response URI(github.api_url) if response.is_a? Net::HTTPSuccess # parse response body and add everything to github result parsed = JSON.parse response.body parsed.each {|k, v| github[k] = v} else # assume it's private if we failed to find it github.private = true end end end end # Find the only *.gemspec path in the `@path` directory. Warns and returns # `nil` if there is more than one match. def gemspec_path paths = Pathname.glob(@path.join('*.gemspec')) case paths.length when 0 nil when 1 paths[0] else warn "found multiple gemspecs: #{ paths }, unable to pick one." nil end end # If `path` is a directory containing the source for a Ruby Gem, add # useful information about it. def add_gem_facts unless @path.directory? @result.is_gem = false return end path = gemspec_path if path.nil? @result.is_gem = false return end @result.is_gem = true @result.package.types << 'gem' gem = @result.gem = Result.new gem.gemspec_path = gemspec_path.to_s spec = Gem::Specification::load(gemspec_path.to_s) gem.name = spec.name gem.version = QB::Package::Version.from_gem_version spec.version end # Add facts about an NPM package based in `@path`, if any. def add_npm_facts package_json_path = @path.join('package.json') unless @path.directory? && package_json_path.file? @result.is_npm = false return end @result.is_npm = true @result.package.types << 'npm' npm = @result.npm = Result.new npm.package_json = JSON.load package_json_path.read # To stay consistent with Gem npm.name = npm.package_json['name'] if npm.package_json['version'] npm.version = QB::Package::Version.from_npm_version( npm.package_json['version'] ) end end # Add version/package facts derives from @path/VERSION file if one exists. # # Added values: # # - Always: # - `has_version_file` - Boolean # - True if `@path/VERSION` exists and was successfully parsed. # # - If `has_version_file` is `true`: # - # def add_version_file_facts unless @path.directory? @result.has_version_file = false return end version_file_path = @path.join 'VERSION' unless version_file_path.file? @result.has_version_file = false return end version = begin QB::Package::Version.from_string version_file_path.read rescue Exception => e warn "Unable to parse version from #{ version_file_path.to_s }; #{ e }" @result.has_version_file = false return end @result.has_version_file = true version_file = @result.version_file = Result.new @result.package.types << 'version_file' # get the name from git if we have it if @result.is_git && @result.git.name version_file.name = @result.git.name else # otherwise use the directory name version_file.name = version_file_path.dirname.basename.to_s end version_file.version = version version_file.path = version_file_path end # Run the module. def main # check the 'path' arg unless @args['path'].is_a? String raise ArgumentError, "'path' arg must be a string, found #{ @args['path'].inspect }." end # We'll return the value of @result @result = Result.new # Default to signaling no change (we're not gonna change anything in this # module either) @result.changed = @changed # String warnings that will be shown to the user @result.warnings = @warnings # Set this up here so `add_*_facts` can append to `package.types` package = @result.package = Result.new package.types = [] # Return the input as 'raw' @result.raw = @args['path'] @path = Pathname.new @result.raw add_path_facts # Add git facts if @path is in a git repo add_git_facts # Add Ruby Gem version/package facts if @path is the root of a Ruby Gem add_gem_facts # Add version/package facts from a @path/package.json file if present add_npm_facts # Add version/package facts from a @path/VERSION file if present add_version_file_facts # If we only have one type of package present, we set it's type and # version as `package.type` and `package.version`, which makes it easy for # code to 'auto-detect' the info. # # If there's more than one then we obviously can't guess what to do; the # user needs to specify. # if package.types.length == 1 package.type = package.types[0] package.name = @result[package.type].name package.version = @result[package.type].version else # Otherwise, if all the present info matches, we can use that. # # We won't have a `type` though, because that doesn't make any sense. # # See if we have a unique common version that we can use for the overall # version of the package. # versions = package.types. # Map the types to their version object map { |type| @result[type].version }. # Omit any that don't have a version reject(&:nil?). # Reduce to unique versions uniq # Do we have a single unique version? if versions.length == 1 # Yes! package.version = versions.first end # See if we have a unique common name that we can use use for the overall # name of the package # # TODO since version_file kinda fudges this using the git repo name or # directory name, this might be problematic since those may not # stay consistent... we'll have to see how it goes. # names = package.types. map { |type| @result[type].name }. reject(&:nil?). uniq if names.length == 1 package.name = names.first end end nil end def done exit_json @result.to_h end end PathFacts.new.run