-
# frozen_string_literal: true
-
-
1
require "zeitwerk"
-
-
1
Zeitwerk::Loader.new.then do |loader|
-
1
loader.inflector.inflect "container" => "CONTAINER"
-
1
loader.ignore "#{__dir__}/gitt/rspec/shared_contexts"
-
1
loader.tag = File.basename __FILE__, ".rb"
-
1
loader.push_dir __dir__
-
1
loader.setup
-
end
-
-
# Main namespace.
-
1
module Gitt
-
1
SHELL = Shell.new.freeze
-
-
1
def self.loader registry = Zeitwerk::Registry
-
6
@loader ||= registry.loaders.each.find { |loader| loader.tag == File.basename(__FILE__, ".rb") }
-
end
-
-
1
def self.new(...) = Repository.new(...)
-
end
-
# frozen_string_literal: true
-
-
1
require "dry/monads"
-
-
1
module Gitt
-
1
module Commands
-
# A Git branch command wrapper.
-
1
class Branch
-
1
include Dry::Monads[:result]
-
-
1
def initialize shell: SHELL
-
37
@shell = shell
-
end
-
-
1
def default(fallback = "main", *, **)
-
6
shell.call("config", "init.defaultBranch", *, **)
-
.fmap(&:chomp)
-
5
then: 3
else: 2
.fmap { |name| name.empty? ? fallback : name }
-
.or(Success(fallback))
-
end
-
-
1
def call(*, **) = shell.call("branch", *, **)
-
-
1
def name(*, **) = shell.call("rev-parse", "--abbrev-ref", "HEAD", *, **).fmap(&:chomp)
-
-
1
private
-
-
1
attr_reader :shell
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
require "core"
-
1
require "dry/monads"
-
-
1
module Gitt
-
1
module Commands
-
# A Git config command wrapper.
-
1
class Config
-
1
include Dry::Monads[:result]
-
-
1
def initialize shell: SHELL
-
41
@shell = shell
-
end
-
-
1
def call(*, **) = shell.call("config", *, **)
-
-
1
def get(key, fallback = Core::EMPTY_STRING, *, **)
-
11
shell.call("config", "--get", key, *, **)
-
.fmap(&:chomp)
-
4
then: 1
else: 3
.or { |error| block_given? ? yield(error) : Success(fallback) }
-
end
-
-
1
def origin? = !get("remote.origin.url").value_or(Core::EMPTY_STRING).empty?
-
-
6
def set(key, value, *, **) = shell.call("config", "--add", key, value, *, **).fmap { value }
-
-
1
private
-
-
1
attr_reader :shell
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
require "dry/monads"
-
-
1
module Gitt
-
1
module Commands
-
# A Git log command wrapper.
-
1
class Log
-
1
include Dry::Monads[:result]
-
-
1
KEY_MAP = {
-
author_email: "%ae",
-
author_name: "%an",
-
authored_at: "%at",
-
authored_relative_at: "%ar",
-
body: "%b",
-
committed_at: "%ct",
-
committed_relative_at: "%cr",
-
committer_email: "%ce",
-
committer_name: "%cn",
-
encoding: "%e",
-
notes: "%N",
-
raw: "%B",
-
sha: "%H",
-
signature: "%G?",
-
fingerprint: "%GK",
-
fingerprint_key: "%GF",
-
subject: "%s",
-
trailers: "%(trailers)"
-
}.freeze
-
-
1
def initialize shell: SHELL, key_map: KEY_MAP, parser: Parsers::Commit.new
-
48
@shell = shell
-
48
@key_map = key_map
-
48
@parser = parser
-
end
-
-
1
def call(*, **) = shell.call("log", *, **)
-
-
1
def index(*arguments, **)
-
18
arguments.prepend("--shortstat", pretty_format)
-
18
.then { |pretty_format| call(*pretty_format, **) }
-
17
.fmap { |content| String(content).scrub("?") }
-
17
.fmap { |entries| build_records entries }
-
end
-
-
1
def uncommitted path
-
5
else: 4
then: 1
return Failure %(Invalid commit message path: "#{path}".) unless path.exist?
-
-
4
shell.call("mktree")
-
4
.bind { |raw_sha| shell.call "commit-tree", "-F", path.to_s, raw_sha.chomp }
-
4
.bind { |sha| index "-1", sha.chomp }
-
.fmap(&:first)
-
end
-
-
1
private
-
-
1
attr_reader :shell, :key_map, :parser
-
-
1
def pretty_format
-
342
key_map.reduce(+"") { |format, (key, value)| format << "<#{key}>#{value}</#{key}>%n" }
-
18
.then { |format| %(--pretty=format:"#{format}") }
-
end
-
-
1
def build_records entries
-
17
wrap_statistics entries
-
17
add_empty_statistics entries
-
37
entries.split("<break/>").map { |entry| parser.call entry }
-
end
-
-
# :reek:UtilityFunction
-
1
def wrap_statistics entries
-
17
entries.gsub!(/\n"\n\s\d+\sfile.+\d+\s(insertion|deletion).+\n/) do |match|
-
14
match.delete_prefix!("\n\"\n").strip!
-
14
"\n<statistics>#{match}</statistics>\n<break/>"
-
end
-
end
-
-
# :reek:UtilityFunction
-
1
def add_empty_statistics entries
-
17
entries.gsub! %(</trailers>\n"\n"<author_email>),
-
"</trailers>\n<statistics></statistics><break/>\n<author_email>"
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
require "core"
-
1
require "dry/monads"
-
1
require "tempfile"
-
-
1
module Gitt
-
1
module Commands
-
# A Git tag command wrapper.
-
1
class Tag
-
1
include Dry::Monads[:result]
-
-
1
KEY_MAP = {
-
author_email: "%(*authoremail)",
-
author_name: "%(*authorname)",
-
authored_at: "%(*authordate:raw)",
-
authored_relative_at: "%(*authordate:relative)",
-
body: "%(body)",
-
committed_at: "%(*committerdate:raw)",
-
committed_relative_at: "%(*committerdate:relative)",
-
committer_email: "%(*committeremail)",
-
committer_name: "%(*committername)",
-
sha: "%(objectname)",
-
signature: "%(contents:signature)",
-
subject: "%(subject)",
-
trailers: "%(trailers)",
-
version: "%(refname)"
-
}.freeze
-
-
1
def initialize shell: SHELL, key_map: KEY_MAP, parser: Parsers::Tag.new
-
55
@shell = shell
-
55
@key_map = key_map
-
55
@parser = parser
-
end
-
-
1
def call(*, **) = shell.call("tag", *, **)
-
-
1
def create version, body = Core::EMPTY_STRING, *flags
-
7
else: 6
then: 1
return Failure "Unable to create Git tag without version." unless version
-
6
then: 1
else: 5
return Failure "Tag exists: #{version}." if exist? version
-
-
5
Tempfile.open "gitt" do |file|
-
5
file.write body
-
5
write version, file.tap(&:rewind), *flags
-
end
-
end
-
-
1
def delete_local(version, *, **)
-
7
call("--delete", version, *, **).fmap { |text| text[/\d+\.\d+\.\d+/] }
-
.alt_map do |error|
-
1
error.delete_prefix("error: tag ").chomp
-
end
-
end
-
-
1
def delete_remote(version, *, **)
-
3
shell.call("push", "--delete", "origin", version, *, **)
-
2
.fmap { version }
-
1
.alt_map { |error| error.gsub("error: ", "").chomp }
-
end
-
-
1
def exist?(version) = local?(version) || remote?(version)
-
-
1
def index(*arguments, **)
-
2
arguments.prepend(pretty_format, "--list")
-
2
.then { |flags| call(*flags, **) }
-
2
.fmap { |content| String(content).scrub("?").split %("\n") }
-
2
.fmap { |entries| build_records entries }
-
end
-
-
1
def last(*, **)
-
5
shell.call("describe", "--abbrev=0", "--tags", *, **)
-
.fmap(&:strip)
-
.or do |error|
-
2
then: 1
if error.match?(/no names found/i)
-
1
Failure "No tags found."
-
else: 1
else
-
1
Failure error.delete_prefix("fatal: ").chomp
-
end
-
end
-
end
-
-
1
def local? version
-
14
call("--list", version).value_or(Core::EMPTY_STRING).match?(/\A#{version}\Z/)
-
end
-
-
1
def push(*, **) = shell.call("push", "--tags", *, **)
-
-
1
def remote? version
-
11
shell.call("ls-remote", "--tags", "origin", version)
-
.value_or(Core::EMPTY_STRING)
-
.match?(%r(.+tags/#{version}\Z))
-
end
-
-
1
def show(version, *, **)
-
4
call(pretty_format, "--list", version, *, **).fmap { |content| parser.call content }
-
end
-
-
1
def tagged? = !call.value_or(Core::EMPTY_STRING).empty?
-
-
1
private
-
-
1
attr_reader :shell, :key_map, :parser
-
-
1
def pretty_format
-
60
key_map.reduce(+"") { |format, (key, value)| format << "<#{key}>#{value}</#{key}>%n" }
-
4
.then { |format| %(--format="#{format}") }
-
end
-
-
3
def build_records(entries) = entries.map { |entry| parser.call entry }
-
-
1
def write version, file, *flags
-
5
arguments = ["--annotate", version, "--cleanup", "verbatim", *flags, "--file", file.path]
-
9
call(*arguments).fmap { version }
-
1
.or { Failure "Unable to create tag: #{version}." }
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Gitt
-
# Provides shared behavior for objects that can act like a commit.
-
1
module Directable
-
1
def directive? = amend? || fixup? || squash?
-
-
1
def amend? = subject.match?(/\Aamend!\s/)
-
-
1
def fixup? = subject.match?(/\Afixup!\s/)
-
-
1
def squash? = subject.match?(/\Asquash!\s/)
-
-
1
def prefix = subject[/\A[\w!]+/]
-
end
-
end
-
# frozen_string_literal: true
-
-
1
require "dry/monads"
-
-
1
module Gitt
-
1
module Models
-
# Represents commit details.
-
1
Commit = Struct.new(
-
:author_email,
-
:author_name,
-
:authored_at,
-
:authored_relative_at,
-
:body,
-
:body_lines,
-
:body_paragraphs,
-
:committed_at,
-
:committed_relative_at,
-
:committer_email,
-
:committer_name,
-
:deletions,
-
:encoding,
-
:files_changed,
-
:fingerprint,
-
:fingerprint_key,
-
:insertions,
-
:lines,
-
:notes,
-
:raw,
-
:sha,
-
:signature,
-
:subject,
-
:trailers
-
) do
-
1
include Directable
-
1
include Trailable
-
1
include Dry::Monads[:result]
-
-
1
def initialize(**)
-
65
super
-
65
freeze
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
require "core"
-
-
1
module Gitt
-
1
module Models
-
# Represents a person within a repository.
-
1
Person = Data.define :name, :delimiter, :email do
-
1
def self.for(string, parser: Parsers::Person.new) = parser.call string
-
-
1
def initialize name: nil, delimiter: " ", email: nil
-
21
super
-
end
-
-
1
def to_s
-
6
in: 2
case self
-
2
in: 1
in String, String, String then "#{name}#{delimiter}<#{email}>"
-
1
in: 1
in String, String, nil then name
-
1
else: 2
in nil, String, String then "<#{email}>"
-
2
else Core::EMPTY_STRING
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Gitt
-
1
module Models
-
# Represents tag details.
-
1
Tag = Struct.new(
-
:author_email,
-
:author_name,
-
:authored_at,
-
:authored_relative_at,
-
:body,
-
:committed_at,
-
:committed_relative_at,
-
:committer_email,
-
:committer_name,
-
:sha,
-
:signature,
-
:subject,
-
:trailers,
-
:version
-
) do
-
1
include Trailable
-
-
1
def initialize(**)
-
20
super
-
20
freeze
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Gitt
-
1
module Models
-
# Represents commit trailer details.
-
1
Trailer = Data.define :key, :delimiter, :space, :value do
-
1
def self.for(string, parser: Parsers::Trailer.new) = parser.call string
-
-
1
def initialize key:, value:, delimiter: ":", space: " "
-
107
super
-
end
-
-
1
def empty? = String(key).empty? || String(value).empty?
-
-
1
def to_s = to_h.values.join
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
require "core"
-
-
1
module Gitt
-
1
module Parsers
-
# Extracts attributes from XML formatted content.
-
1
class Attributer
-
1
def initialize keys = Core::EMPTY_ARRAY
-
130
@keys = keys
-
end
-
-
1
def call content
-
51
build String(content)
-
rescue ArgumentError => error
-
2
then: 1
else: 1
error.message.include?("invalid byte") ? build(content.scrub("?")) : raise
-
end
-
-
1
private
-
-
1
attr_reader :keys
-
-
1
def build content
-
51
keys.each.with_object({}) do |key, attributes|
-
797
attributes[key] = content[%r(<#{key}>(?<value>.*?)</#{key}>)m, :value]
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
require "refinements/hash"
-
-
1
module Gitt
-
1
module Parsers
-
# Parses raw commit to produce a commit record.
-
1
class Commit
-
1
using Refinements::Hash
-
-
1
def initialize attributer: Attributer.new(Commands::Log::KEY_MAP.keys.append(:statistics)),
-
sanitizers: Sanitizers::CONTAINER,
-
model: Models::Commit
-
61
@attributer = attributer
-
61
@sanitizers = sanitizers
-
61
@model = model
-
end
-
-
1
def call content
-
33
attributer.call(content)
-
33
.then { |attributes| mutate attributes }
-
33
.then { |attributes| model[**attributes] }
-
end
-
-
1
private
-
-
1
attr_reader :attributer, :sanitizers, :model
-
-
# :reek:TooManyStatements
-
1
def mutate attributes
-
33
body, trailers = attributes.values_at :body, :trailers
-
33
body = scissors_sanitizer.call body
-
-
33
attributes.transform_with! signature: signature_sanitizer, trailers: trailers_sanitizer
-
-
33
attributes[:body] =
-
33
then: 25
else: 8
(trailers ? body.sub(/\n??#{Regexp.escape trailers}\n??/, "") : body).chomp
-
-
165
private_methods.grep(/\Aprocess_/).sort.each { |method| __send__ method, attributes }
-
33
attributes
-
end
-
-
# :reek:FeatureEnvy
-
1
def process_body_lines attributes
-
33
attributes[:body_lines] = lines_sanitizer.call attributes[:body]
-
end
-
-
# :reek:FeatureEnvy
-
1
def process_body_paragraphs attributes
-
33
attributes[:body_paragraphs] = paragraphs_sanitizer.call attributes[:body]
-
end
-
-
# :reek:FeatureEnvy
-
1
def process_lines attributes
-
33
attributes[:lines] = lines_sanitizer.call attributes[:raw]
-
end
-
-
# :reek:FeatureEnvy
-
1
def process_statistics attributes
-
33
attributes.merge! statistics_sanitizer.call(attributes.delete(:statistics))
-
end
-
-
1
def lines_sanitizer = sanitizers.fetch :lines
-
-
1
def paragraphs_sanitizer = sanitizers.fetch :paragraphs
-
-
1
def scissors_sanitizer = sanitizers.fetch :scissors
-
-
1
def statistics_sanitizer = sanitizers.fetch :statistics
-
-
1
def signature_sanitizer = sanitizers.fetch :signature
-
-
1
def trailers_sanitizer = sanitizers.fetch :trailers
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Gitt
-
1
module Parsers
-
# Parses trailer to produce a person.
-
1
class Person
-
1
def initialize email_start: "<", email_end: ">", model: Models::Person
-
6
@email_start = email_start
-
6
@email_end = email_end
-
6
@model = model
-
end
-
-
1
def call content
-
6
then: 1
if content.start_with? email_start
-
1
model[email: content.delete_prefix(email_start).delete_suffix(email_end)]
-
else: 5
else
-
5
name, email = content.split " #{email_start}"
-
5
then: 2
else: 3
email.delete_suffix! email_end if email
-
-
5
model[name:, email:]
-
end
-
end
-
-
1
private
-
-
1
attr_reader :email_start, :email_end, :model
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
require "refinements/hash"
-
-
1
module Gitt
-
1
module Parsers
-
# Parses raw tag information to produce a tag record.
-
1
class Tag
-
1
using Refinements::Hash
-
-
1
def initialize attributer: Attributer.new(Commands::Tag::KEY_MAP.keys),
-
sanitizers: Sanitizers::CONTAINER,
-
model: Models::Tag
-
62
@attributer = attributer
-
62
@sanitizers = sanitizers
-
62
@model = model
-
end
-
-
# :reek:TooManyStatements
-
1
def call content
-
11
attributes = attributer.call content
-
11
body, trailers = attributes.values_at :body, :trailers
-
11
sanitize attributes
-
-
11
attributes[:body] = (
-
11
then: 6
else: 5
trailers ? body.sub(/\n??#{Regexp.escape trailers}\n??/, "") : body
-
).to_s.chomp
-
-
11
model[**attributes]
-
end
-
-
1
private
-
-
1
attr_reader :attributer, :sanitizers, :model
-
-
1
def sanitize attributes
-
11
attributes.transform_with! author_email: email_sanitizer,
-
authored_at: date_sanitizer,
-
committed_at: date_sanitizer,
-
committer_email: email_sanitizer,
-
trailers: trailers_sanitizer,
-
version: version_serializer
-
end
-
-
1
def date_sanitizer = sanitizers.fetch :date
-
-
1
def email_sanitizer = sanitizers.fetch :email
-
-
1
def trailers_sanitizer = sanitizers.fetch :trailers
-
-
1
def version_serializer = sanitizers.fetch :version
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Gitt
-
1
module Parsers
-
# Parses raw trailer data to produce a trailer record.
-
1
class Trailer
-
1
PATTERN = /
-
\A # Start of line.
-
(?<key>[a-zA-Z-]+) # Key.
-
(?<delimiter>:) # Delimiter (colon).
-
(?<space>\s?) # Space (optional).
-
(?<value>.*?) # Value.
-
\Z # End of line.
-
/x
-
-
1
EMPTY = Models::Trailer[key: nil, value: nil]
-
-
1
def initialize pattern: PATTERN, model: Models::Trailer, empty: EMPTY
-
12
@pattern = pattern
-
12
@model = model
-
12
@empty = empty
-
end
-
-
1
def call content
-
25
then: 2
else: 23
return empty if content.start_with? "#"
-
-
23
content.match(pattern)
-
23
then: 21
else: 2
.then { |data| data ? model[**data.named_captures(symbolize_names: true)] : empty }
-
end
-
-
1
private
-
-
1
attr_reader :pattern, :model, :empty
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
require "core"
-
-
1
module Gitt
-
# Primary object/wrapper for processing all Git related commands.
-
1
class Repository
-
COMMANDS = {
-
1
branch: Commands::Branch,
-
config: Commands::Config,
-
log: Commands::Log,
-
tag: Commands::Tag
-
}.freeze
-
-
1
def initialize shell: SHELL, commands: COMMANDS
-
28
@shell = shell
-
140
@commands = commands.transform_values { |command| command.new shell: }
-
end
-
-
1
def branch(...) = commands.fetch(__method__).call(...)
-
-
1
def branch_default(...) = commands.fetch(:branch).default(...)
-
-
1
def branch_name(...) = commands.fetch(:branch).name(...)
-
-
1
def call(...) = shell.call(...)
-
-
1
def commits(...) = commands.fetch(:log).index(...)
-
-
1
def config(...) = commands.fetch(__method__).call(...)
-
-
1
def exist? = shell.call("rev-parse", "--git-dir").value_or(Core::EMPTY_STRING).chomp == ".git"
-
-
1
def get(...) = commands.fetch(:config).get(...)
-
-
1
def inspect
-
1
"#<#{self.class}:#{object_id} @shell=#{shell.inspect} " \
-
"@commands=#{commands.values.map(&:class).inspect}>"
-
end
-
-
1
def log(...) = commands.fetch(__method__).call(...)
-
-
1
def origin? = commands.fetch(:config).origin?
-
-
1
def set(...) = commands.fetch(:config).set(...)
-
-
1
def tag(...) = commands.fetch(__method__).call(...)
-
-
1
def tags(...) = commands.fetch(:tag).index(...)
-
-
1
def tag?(...) = commands.fetch(:tag).exist?(...)
-
-
1
def tag_create(...) = commands.fetch(:tag).create(...)
-
-
1
def tag_delete_local(...) = commands.fetch(:tag).delete_local(...)
-
-
1
def tag_delete_remote(...) = commands.fetch(:tag).delete_remote(...)
-
-
1
def tag_last(...) = commands.fetch(:tag).last(...)
-
-
1
def tag_local?(...) = commands.fetch(:tag).local?(...)
-
-
1
def tag_remote?(...) = commands.fetch(:tag).remote?(...)
-
-
1
def tag_show(...) = commands.fetch(:tag).show(...)
-
-
1
def tagged? = commands.fetch(:tag).tagged?
-
-
1
def tags_push(...) = commands.fetch(:tag).push(...)
-
-
1
def uncommitted(...) = commands.fetch(:log).uncommitted(...)
-
-
1
private
-
-
1
attr_reader :shell, :commands
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Gitt
-
1
module Sanitizers
-
CONTAINER = {
-
1
date: Date,
-
email: Email,
-
lines: Lines,
-
paragraphs: Paragraphs.new,
-
scissors: Scissors,
-
signature: Signature,
-
statistics: Statistics.new,
-
trailers: Trailers.new,
-
version: Version
-
}.freeze
-
end
-
end
-
# frozen_string_literal: true
-
-
1
require "core"
-
-
1
module Gitt
-
1
module Sanitizers
-
25
then: 13
else: 11
Date = -> text { text.sub(/\s.+\Z/, Core::EMPTY_STRING) if text }
-
end
-
end
-
# frozen_string_literal: true
-
-
1
require "core"
-
-
1
module Gitt
-
1
module Sanitizers
-
25
then: 13
else: 11
Email = -> text { text.tr "<>", Core::EMPTY_STRING if text }
-
end
-
end
-
# frozen_string_literal: true
-
-
1
require "core"
-
-
1
module Gitt
-
1
module Sanitizers
-
69
then: 57
else: 11
Lines = -> text { text ? text.split("\n") : Core::EMPTY_ARRAY }
-
end
-
end
-
# frozen_string_literal: true
-
-
1
require "core"
-
1
require "refinements/array"
-
1
require "strscan"
-
-
1
module Gitt
-
1
module Sanitizers
-
# Detects and parses paragraphs (including code blocks).
-
1
class Paragraphs
-
1
using Refinements::Array
-
-
1
PATTERN = /
-
( # Condition start.
-
(?:\..*?\n)? # Optional ASCII Doc label.
-
(?:\[.*\]\n)? # Optional ASCII Doc directive.
-
[-_=+.*]{4} # ASCII Doc block start.
-
[\s\S]*? # Lazy block content of any character.
-
[-_=+.*]{4} # ASCII Doc block end.
-
| # Or.
-
``` # Markdown start.
-
[\s\S]*? # Lazy block content of any character.
-
``` # Markdown end.
-
) # Condition end.
-
/mx
-
-
1
def initialize pattern: PATTERN, client: StringScanner
-
28
@pattern = pattern
-
28
@client = client
-
end
-
-
1
def call(text) = scan(client.new(text.to_s))
-
-
1
private
-
-
1
attr_reader :pattern, :client
-
-
# :reek:FeatureEnvy
-
1
def scan scanner, collection = []
-
60
body: 67
until scanner.eos?
-
67
match = scanner.scan_until pattern
-
-
67
else: 24
then: 43
break collection << scanner.string[scanner.rest].tap(&:strip!).split("\n\n") unless match
-
-
24
collection << scanner.pre_match.strip
-
24
collection << scanner.captures
-
end
-
-
60
collection.tap(&:flatten!).tap(&:compress!)
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
require "core"
-
-
1
module Gitt
-
1
module Sanitizers
-
36
then: 34
else: 1
Scissors = -> text { text.sub(/^#\s-.+\s>8\s-.+/m, Core::EMPTY_STRING) if text }
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Gitt
-
1
module Sanitizers
-
1
Signature = lambda do |text|
-
42
when: 1
case text
-
1
when: 1
when "B" then "Bad"
-
1
when: 1
when "E" then "Error"
-
1
when: 23
when "G" then "Good"
-
23
when: 1
when "N" then "None"
-
1
when: 1
when "R" then "Revoked"
-
1
when: 1
when "U" then "Unknown"
-
1
when: 1
when "X" then "Expired"
-
1
else: 12
when "Y" then "Expired Key"
-
12
else "Invalid"
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Gitt
-
1
module Sanitizers
-
# Converts raw text into a statistics hash.
-
1
class Statistics
-
1
EMPTY = {files_changed: 0, insertions: 0, deletions: 0}.freeze
-
-
1
PATTERN = /
-
(?<total>\d+) # Total capture group.
-
\s # Space delimiter.
-
(?<kind>file|insertion|deletion) # Kind capture group.
-
/x
-
-
1
def self.update attributes, kind, total
-
56
when: 20
case kind
-
20
when: 18
when "file" then attributes[:files_changed] = total
-
18
when: 17
when "insertion" then attributes[:insertions] = total
-
17
else: 1
when "deletion" then attributes[:deletions] = total
-
1
else fail StandardError, "Invalid kind: #{kind.inspect}."
-
end
-
end
-
-
1
def initialize attributes = EMPTY, pattern: PATTERN
-
7
@attributes = attributes
-
7
@pattern = pattern
-
end
-
-
1
def call text
-
39
else: 22
then: 17
return attributes unless text
-
-
22
text.scan(pattern).each.with_object(attributes.dup) do |(number, kind), aggregate|
-
52
self.class.update aggregate, kind, number.to_i
-
end
-
end
-
-
1
private
-
-
1
attr_reader :attributes, :pattern
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Gitt
-
1
module Sanitizers
-
# Sanitizes content by turning it into an array of trailer records.
-
1
class Trailers
-
1
def initialize parser: Parsers::Trailer.new
-
5
@parser = parser
-
end
-
-
1
def call text
-
48
String(text).split("\n")
-
18
.map { |line| parser.call line }
-
.reject(&:empty?)
-
end
-
-
1
private
-
-
1
attr_reader :parser
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
require "core"
-
-
1
module Gitt
-
1
module Sanitizers
-
14
then: 7
else: 6
Version = -> text { text.delete_prefix "refs/tags/" if text }
-
end
-
end
-
# frozen_string_literal: true
-
-
1
require "dry/monads"
-
1
require "open3"
-
-
1
module Gitt
-
# A low-level shell client.
-
1
class Shell
-
1
include Dry::Monads[:result]
-
-
1
def initialize client: Open3
-
7
@client = client
-
end
-
-
1
def call(*all, **)
-
489
environment, arguments = all.partition { it.is_a? Hash }
-
-
120
client.capture3(*environment, "git", *arguments, **).then do |stdout, stderr, status|
-
120
then: 104
else: 16
status.success? ? Success(stdout) : Failure(stderr)
-
end
-
end
-
-
1
private
-
-
1
attr_reader :client
-
end
-
end
-
# frozen_string_literal: true
-
-
1
require "dry/monads"
-
-
1
module Gitt
-
# Provides shared behavior for objects that have trailers.
-
1
module Trailable
-
1
include Dry::Monads[:result]
-
-
1
def find_trailer key
-
24
trailers.find { |trailer| trailer.key == key }
-
.then do |trailer|
-
12
then: 6
else: 6
return Success trailer if trailer
-
-
6
Failure "Unable to find trailer for key: #{key.inspect}."
-
end
-
end
-
-
1
def find_trailers key
-
48
trailers.select { |trailer| trailer.key == key }
-
12
.then { |trailers| Success trailers }
-
end
-
-
1
def trailer_value_for(key) = find_trailer(key).fmap(&:value)
-
-
7
def trailer_values_for(key) = find_trailers(key).fmap { |trailers| trailers.map(&:value) }
-
end
-
end