Created
February 15, 2022 12:46
-
-
Save NightFeather/908e57e3b6a22b5f8e73488d57a4f59b to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env ruby | |
require "time" | |
require "optparse" | |
require "optparse/time" | |
class Package | |
@@list = {} | |
attr_reader :name, :events, :versions, :current_version | |
def self.list | |
@@list | |
end | |
def self.get name | |
@@list[name] ||= Package.new name: name | |
end | |
module PackageListAppend | |
def initialize name: | |
super(name: name) | |
self.class.list[name] ||= self | |
end | |
end | |
prepend PackageListAppend | |
class Event < Struct.new :name, :timestamp, :data | |
def method_missing meth, *args, &block | |
if data.key? meth | |
data[meth] | |
else | |
super | |
end | |
end | |
class Upgraded < Event | |
def initialize timestamp:, from:, to:, version:, **args | |
super("upgraded", timestamp, { from: from, to: to, version: version }) | |
end | |
def to_s | |
"\e[1;32m+\e[m (#{timestamp}) #{from} -> #{to}" | |
end | |
end | |
class Downgraded < Event | |
def initialize timestamp:, from:, to:, version:, **args | |
super("downgraded", timestamp, { from: from, to: to, version: version }) | |
end | |
def to_s | |
"\e[1;33m-\e[m (#{timestamp}) #{from} -> #{to}" | |
end | |
end | |
class Installed < Event | |
def initialize timestamp:, version:, **args | |
super("installed", timestamp, { version: version }) | |
end | |
def to_s | |
"\e[1;37m*\e[m (#{timestamp}) #{version}" | |
end | |
end | |
class Removed < Event | |
attr_reader :from, :to | |
def initialize timestamp:, **args | |
super("removed", timestamp, { version: nil }) | |
end | |
def to_s | |
"\e[1;30mx\e[m (#{timestamp}) #{version}" | |
end | |
end | |
end | |
def initialize name: | |
@name = name | |
@events = [] | |
@versions = [] | |
@current_version = nil | |
end | |
def << ev | |
@events << ev | |
@current_version = ev.version | |
@versions << ev.version if ev.version | |
end | |
def display show_events = false | |
parts = [ | |
"Package: #{@name}", | |
"Current: #{@current_version}" | |
] | |
unless show_events | |
parts += [ | |
"History:", | |
*(@versions.map { |v| " #{v}" }) | |
] | |
else | |
parts += [ | |
"Events:", | |
*(@events), | |
] | |
end | |
parts.join("\n") + "\n\n" | |
end | |
end | |
options = {} | |
parser = OptionParser.new do |_parser| | |
_parser.on "-p", "--package REGEXP", Regexp, "Show packages match the regexp" do |opt| | |
options[:package] = opt | |
end | |
_parser.on "-l", "--list", "List all packages" do | |
options[:list] = true | |
end | |
_parser.on "-b", "--before TIME", Time, "Show events before given date" do |opt| | |
options[:before] = opt | |
end | |
_parser.on "-a", "--after TIME", Time, "Show events after given date" do |opt| | |
options[:after] = opt | |
end | |
_parser.on "-e", "--events", "Show detailed events" do | |
options[:events] = true | |
end | |
_parser.on_tail "-h", "--help", "Show this help" do | |
options[:help] = true | |
end | |
end | |
parser.parse! | |
events = [] | |
File.open("/var/log/pacman.log") do |f| | |
events = f.select { |l| | |
l =~ /^\[\d{4}-\d{2}-\d{2} \d{2}:\d{2}\] \[ALPM\] (?:upgraded|downgraded|installed|removed).+$/ || | |
l =~ /^\[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\+\d{4}\] \[ALPM\] (?:upgraded|downgraded|installed|removed).+$/ | |
}.map(&:chomp) | |
.map { |l| | |
{ | |
segs: ( | |
l.scan(/^\[(\d{4}-\d{2}-\d{2} \d{2}:\d{2})\] \[ALPM\] (installed|removed|upgraded|downgraded) (.+?) \((.+?)(?: -> (.+?))?\)/).flatten+ | |
l.scan(/^\[(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\+\d{4})\] \[ALPM\] (upgraded|downgraded|installed|removed) (.+?) \((.+?)(?: -> (.+?))?\)$/).flatten | |
), raw: l | |
} | |
}.map { |o| | |
ts, act, pkgname, version, version1 = o[:segs] | |
{ | |
segs: { | |
timestamp: Time.parse(ts), | |
act: act, | |
pkgname: pkgname, | |
from: version, | |
to: version1, | |
version: version1 || version | |
}, | |
raw: o[:raw] | |
} | |
} | |
if options.key? :after | |
events.select! { |ev| ev[:segs][:timestamp] > options[:after] } | |
end | |
if options.key? :before | |
events.select! { |ev| ev[:segs][:timestamp] <= options[:before] } | |
end | |
events.each do |stat| | |
pkg = Package.get stat[:segs][:pkgname] | |
pkg << Package::Event.const_get(stat[:segs][:act].capitalize).new(**stat[:segs]) | |
end | |
end | |
if options.key? :package | |
Package.list.select { |k,v| k =~ options[:package] }.each { |k, v| puts v.display(options[:events]) } | |
elsif options[:list] | |
Package.list.map { |_, v| puts v.display(options[:events]) } | |
elsif options.empty? or options[:help] | |
puts parser | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment