thicket 0.1.0

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: ed32e78f45dbc2f887839f2210edb89e4c0a7385c457c1b6f4a13a19ab1c384e
4
+ data.tar.gz: cede744f06f5ec921aad06af06f3ea7efd55e68a749b0290da4ac16752431bcd
5
+ SHA512:
6
+ metadata.gz: 1275727f176525c11c885ee985d1df0009a9005708f509d0107a5b3f6c4a8378a854dc0e748961dc8ef7cf3544cb9c002dac04646e8bdc7ac9e8b78fbb3804ac
7
+ data.tar.gz: 5191534f823312b3a1f30d9392aaf92980e964cd0787a5aded3940b7073fc72c1fed5b47b239b3b17918eaf4b8feef2c4f48f358f1e333f7139bcf8b7002c5ca
data/bin/thicket ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # frozen_string_literal: true
4
+
5
+ require "thicket"
6
+
7
+ parser = Thicket::OptionParser.new
8
+ Thicket::Log.new(parser.options).print
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+ require "time"
5
+
6
+ module Thicket
7
+ class Log
8
+ LOG_PARSE_REGEX = /[a-f0-9]{7}.+?m(.+?) .+?m\{([\w ]+)\}.+?m (?:\((.+?)\))?.+?m(.+$)/.freeze
9
+
10
+ def initialize(options)
11
+ @options = options
12
+ end
13
+
14
+ # Gets a printable version of the log for purposes of printing to a
15
+ # terminal. This effectively builds the final printable log to display to
16
+ # the user.
17
+ def print
18
+ FileUtils.cd(git_working_directory)
19
+ `#{git_log_command}`.split("\n").each { |l| puts process_git_log_line(l) }
20
+ rescue Errno::EPIPE, SystemExit, Interrupt
21
+ exit
22
+ end
23
+
24
+ private
25
+
26
+ # Takes a single line of raw, colored git log output and manipulates it
27
+ # into the desired format.
28
+ def process_git_log_line(line)
29
+ @padding_char = @padding_char == " " ? "-" : " "
30
+
31
+ line.match(LOG_PARSE_REGEX) do |matcher|
32
+ process_date_time(matcher[1], line, matcher[3])
33
+ process_message_prefix(matcher[4], line) if @options[:color_prefixes]
34
+ process_author_name(matcher[2], line)
35
+ end
36
+
37
+ line
38
+ end
39
+
40
+ # Takes an input log string and a commit date/time and replaces it in the
41
+ # log string with a formatted version.
42
+ def process_date_time(time_string, line, have_refs)
43
+ seconds_ago = Time.now - Time.iso8601(time_string)
44
+ measure = TimeMeasure.measures.find { |m| m.threshold < seconds_ago }
45
+ quantity = (seconds_ago / measure.length).floor
46
+ to_sub = +"#{quantity}#{measure.abbreviation}".rjust(3)
47
+ to_sub << "\e[31m" if have_refs # add color if we have refs in this line
48
+ line.sub!(time_string, to_sub)
49
+ end
50
+
51
+ # Takes an input log string and commit message, finds commit messages
52
+ # prefixes, and darkens them.
53
+ def process_message_prefix(message, line)
54
+ prefix_regex = %r{^(?=.*[0-9])([A-Z\d-]+?[: \/])}
55
+ message.match(prefix_regex) do |matcher|
56
+ prefix = matcher[1]
57
+ line.sub!(/([^\/])#{prefix}/, "\\1\e[1;30m#{prefix}\e[m")
58
+ end
59
+ end
60
+
61
+ # Takes an input log string and commit author, and moves it from the normal
62
+ # position in the string to a right-justified location.
63
+ def process_author_name(author, line)
64
+ line.sub!("\e[34m{#{author}}\e[31m ", "")
65
+ total_length = strip_color(line).length
66
+ over = (total_length + author.length + 1) - terminal_width
67
+ line = line[0...-over] if over > 0
68
+
69
+ total_length = strip_color(line).length
70
+ spaces_needed = terminal_width - total_length - author.length - 2
71
+ if spaces_needed < 0
72
+ line = +"#{line[0...-spaces_needed - 5]}... "
73
+ else
74
+ line << " \e[30m"
75
+ line << @padding_char * spaces_needed
76
+ line << " \e[m"
77
+ end
78
+
79
+ line << "\e[34m#{author}\e[m"
80
+ end
81
+
82
+ # Strips ANSI color escape codes from a string. Colorize's
83
+ # String#uncolorize would be used, but it seems to only remove escape codes
84
+ # which match a strict pattern, which git log's colored output doesn't
85
+ # follow.
86
+ def strip_color(string)
87
+ color_escape_regex = /\e\[([;\d]+)?m/
88
+ string.gsub(color_escape_regex, "").chomp
89
+ end
90
+
91
+ # Gets the width of the terminal window in columns. Memoizes the result to
92
+ # avoid more shell calls, and because the terminal size won't be changing
93
+ # during the execution of this script.
94
+ def terminal_width
95
+ @terminal_width ||= `tput cols`.to_i
96
+ rescue Errno::ENOENT
97
+ puts "Failed to determine terminal column width."
98
+ exit
99
+ end
100
+
101
+ # The command string which gets the raw git log input straight from git.
102
+ # Includes all formatting and color escape codes.
103
+ def git_log_command
104
+ format = "%C(yellow)%h %Cgreen%aI %Cblue{%an}%Cred%d %Creset%s"
105
+ cmd = "#{git_executable} log --oneline --decorate --color " \
106
+ "--graph --pretty=format:'#{format}'"
107
+ cmd << " --all" if @options[:all]
108
+
109
+ cmd
110
+ end
111
+
112
+ # The path to the git executable. Honors the passed in command line option,
113
+ # and falls back to just "git".
114
+ def git_executable
115
+ @options[:git_binary] || "git"
116
+ end
117
+
118
+ # The directory which represents the git project that we want to retreive
119
+ # logs for. Uses the command line option if it was provided, and falls back
120
+ # to the current working directory otherwise.
121
+ def git_working_directory
122
+ @options[:project_directory] || Dir.pwd
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "optparse"
4
+
5
+ module Thicket
6
+ Options = Struct.new(:name)
7
+
8
+ class OptionParser
9
+ attr_reader :options
10
+
11
+ def initialize
12
+ @options = {}
13
+ parse ARGV
14
+ end
15
+
16
+ private
17
+
18
+ def parse(options)
19
+ args = Options.new("world")
20
+
21
+ opt_parser = ::OptionParser.new do |opts|
22
+ opts.banner = "Usage: thicket [options] <command>"
23
+
24
+ opts.on("-v", "--version", "Print the version number") do |v|
25
+ args.name = v
26
+ puts Thicket::VERSION
27
+ exit
28
+ end
29
+
30
+ opts.on("-h", "--help", "Prints this help") do
31
+ puts opts
32
+ exit
33
+ end
34
+
35
+ opts.on("-d", "--directory DIRECTORY", String, "Path to the project directory") do |project_directory|
36
+ args.name = project_directory
37
+ @options[:project_directory] = File.expand_path(project_directory)
38
+ end
39
+
40
+ opts.on("-a", "--all", TrueClass, "Displays all branches on all remotes.") do |all|
41
+ args.name = all
42
+ @options[:all] = all
43
+ end
44
+
45
+ opts.on("-p", "--color-prefixes", TrueClass, "Adds coloring to commit message prefixes.") do |prefixes|
46
+ args.name = prefixes
47
+ @options[:color_prefixes] = prefixes
48
+ end
49
+
50
+ opts.on("--git-binary BINARY", String, "Path to a git executable") do |git_binary|
51
+ args.name = git_binary
52
+ @options[:git_binary] = File.expand_path(git_binary)
53
+ end
54
+ end
55
+
56
+ opt_parser.parse!(options)
57
+ rescue ::OptionParser::ParseError => e
58
+ puts e.message
59
+ exit
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Thicket
4
+ module TimeMeasure
5
+ class Measure
6
+ attr_reader :length, :abbreviation, :threshold
7
+
8
+ def initialize(length_in_seconds, abbreviation, threshold_in_seconds)
9
+ @length = length_in_seconds
10
+ @abbreviation = abbreviation
11
+ @threshold = threshold_in_seconds
12
+ end
13
+ end
14
+
15
+ YEAR = Measure.new(31_104_000, "Y", 155_520_000)
16
+ MONTH = Measure.new(2_592_000, "M", 15_552_000)
17
+ WEEK = Measure.new(604_800, "w", 2_419_200)
18
+ DAY = Measure.new(86_400, "d", 172_800)
19
+ HOUR = Measure.new(3_600, "h", 3_600)
20
+ MINUTE = Measure.new(60, "m", 60)
21
+ SECOND = Measure.new(1, "s", 0)
22
+
23
+ def self.measures
24
+ [YEAR, MONTH, WEEK, DAY, HOUR, MINUTE, SECOND]
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Thicket
4
+ #:nodoc:
5
+ VERSION ||= "0.1.0"
6
+ end
data/lib/thicket.rb ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "thicket/version"
4
+ require "thicket/log"
5
+ require "thicket/option_parser"
6
+ require "thicket/time_measure"
7
+
8
+ module Thicket
9
+ def self.root
10
+ File.expand_path("..", __dir__)
11
+ end
12
+ end
metadata ADDED
@@ -0,0 +1,150 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: thicket
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Taylor Thurlow
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-05-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: guard
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: guard-rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: pry-byebug
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rufo
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 0.5.1
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 0.5.1
83
+ - !ruby/object:Gem::Dependency
84
+ name: simplecov
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: solargraph
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ description: |
112
+ Git's default log command gets the job done, but its formatting capabilities
113
+ sometimes leave something to be desired. Thicket is an opinionated replacement for
114
+ "git log".
115
+ email: taylorthurlow8@gmail.com
116
+ executables:
117
+ - thicket
118
+ extensions: []
119
+ extra_rdoc_files: []
120
+ files:
121
+ - bin/thicket
122
+ - lib/thicket.rb
123
+ - lib/thicket/log.rb
124
+ - lib/thicket/option_parser.rb
125
+ - lib/thicket/time_measure.rb
126
+ - lib/thicket/version.rb
127
+ homepage: https://github.com/taylorthurlow/thicket
128
+ licenses:
129
+ - MIT
130
+ metadata: {}
131
+ post_install_message:
132
+ rdoc_options: []
133
+ require_paths:
134
+ - lib
135
+ required_ruby_version: !ruby/object:Gem::Requirement
136
+ requirements:
137
+ - - ">="
138
+ - !ruby/object:Gem::Version
139
+ version: '2.3'
140
+ required_rubygems_version: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - ">="
143
+ - !ruby/object:Gem::Version
144
+ version: '0'
145
+ requirements: []
146
+ rubygems_version: 3.0.3
147
+ signing_key:
148
+ specification_version: 4
149
+ summary: An opinionated replacement for git's log command.
150
+ test_files: []