Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 21 additions & 18 deletions lua/wrapped/config.lua
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
---@class Wrapped.Config
local M = {}

---@type Wrapped.Config
M.defaults = {
path = vim.fn.stdpath "config",
border = false,
size = {
width = 120,
height = 40,
},
exclude_filetype = {
".gitmodules",
},
cap = {
commits = 1000,
plugins = 100,
plugins_ever = 200,
lines = 10000,
},
}
---@return WrappedConfig defaults
function M.defaults()
return { ---@type WrappedConfig
path = vim.fn.stdpath "config",
border = false,
size = {
width = 120,
height = 40,
},
exclude_filetype = {
".gitmodules",
},
cap = {
commits = 1000,
plugins = 100,
plugins_ever = 200,
lines = 10000,
},
}
end

return M
15 changes: 8 additions & 7 deletions lua/wrapped/core/files.lua
Original file line number Diff line number Diff line change
@@ -1,28 +1,29 @@
---@class Wrapped.Core.Files
local M = {}
local scandir = require "plenary.scandir"

---@return string path
local function get_path() return require("wrapped").config.path end

---@return Wrapped.FileStats
---@return Wrapped.FileStats stats
function M.get_stats()
local config_path = get_path()
local stats = {
local stats = { ---@type Wrapped.FileStats
total_lines = 0,
biggest = { name = "", lines = 0 },
smallest = { name = "", lines = math.huge },
lines_by_type = {},
}
local files = scandir.scan_dir(
local files = require("plenary.scandir").scan_dir( ---@type string[]
config_path,
{ hidden = true, add_dirs = false, respect_gitignore = false, depth = 20 }
)
local excluded = {}
local excluded = {} ---@type table<string, boolean>
for _, ext in ipairs(require("wrapped").config.exclude_filetype or {}) do
excluded[ext] = true
end

for _, path in ipairs(files) do
if not path:match "%.git[/\\]" and not path:match "%.git$" then
if not (path:match "%.git[/\\]" or path:match "%.git$") then
local filename = vim.fn.fnamemodify(path, ":t")
local ext = filename:match "^%." and filename
or vim.fn.fnamemodify(filename, ":e")
Expand Down Expand Up @@ -55,7 +56,7 @@ function M.get_stats()
stats.smallest = { name = "None", lines = 0 }
end

local sorted = {}
local sorted = {} ---@type Wrapped.FileStat[]
for k, v in pairs(stats.lines_by_type) do
table.insert(sorted, { name = k, lines = v })
end
Expand Down
103 changes: 53 additions & 50 deletions lua/wrapped/core/git.lua
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
local Job = require "plenary.job"
---@class Wrapped.Core.Git
local M = {}
local Job = require "plenary.job"

---@return string path
local function get_path() return require("wrapped").config.path end

---@param args string[]
---@return string[] result
local function exec_git(args)
local job = Job:new {
command = "git",
Expand All @@ -13,72 +17,69 @@ local function exec_git(args)
return job:result()
end

---@return string[]
---@return string[] commits
function M.get_commits()
local all = exec_git { "log", "--format=%s" }
if #all <= 5 then return all end

local random_commits = {}
local indices = {}
local random_commits = {} ---@type string[]
local indices = {} ---@type integer[]
while #indices < 5 do
local idx = math.random(1, #all)
if not vim.tbl_contains(indices, idx) then
if not vim.list_contains(indices, idx) then
table.insert(indices, idx)
table.insert(random_commits, all[idx])
end
end
return random_commits
end

---@return string
---@return string total
function M.get_total_count()
return exec_git({ "rev-list", "--count", "HEAD" })[1] or "0"
end

---@return string
---@return string first_date
function M.get_first_commit_date()
return exec_git({ "log", "--reverse", "--format=%ad", "--date=short" })[1]
or "Unknown"
end

-- converts seconds to human-readable
---@param secs integer
---@return string ago
local function format_ago(secs)
local days = math.floor(secs / 86400)
if days >= 365 then return string.format("%.1f years ago", days / 365) end
if days >= 365 then return ("%.1f years ago"):format(days / 365) end
if days >= 30 then return math.floor(days / 30) .. " months ago" end
if days >= 1 then return days .. " days ago" end
local hours = math.floor(secs / 3600)
if hours >= 1 then return hours .. " hours ago" end
if secs >= 3600 then return math.floor(secs / 3600) .. " hours ago" end
return math.floor(secs / 60) .. " minutes ago"
end

---@return Wrapped.ConfigStats
---@return Wrapped.ConfigStats stats
function M.get_config_stats()
local dates = exec_git { "log", "--format=%ad", "--date=short" }
local unique = {}
local unique = {} ---@type table<string, boolean>
for _, d in ipairs(dates) do
unique[d] = true
end

-- sort unique dates
local sorted = {}
local sorted = {} ---@type string[]
for d in pairs(unique) do
table.insert(sorted, d)
end
table.sort(sorted)

-- longest consecutive day streak
local max_streak, cur_streak = 1, 1
local max_streak, cur_streak = 1, 1 ---@type integer, integer
for i = 2, #sorted do
local y, m, d = sorted[i]:match "(%d+)-(%d+)-(%d+)"
local py, pm, pd = sorted[i - 1]:match "(%d+)-(%d+)-(%d+)"
local y, m, d = sorted[i]:match "(%d+)-(%d+)-(%d+)" ---@type string, string, string
local py, pm, pd = sorted[i - 1]:match "(%d+)-(%d+)-(%d+)" ---@type string, string, string
local t1 = os.time { year = y, month = m, day = d }
local t0 = os.time { year = py, month = pm, day = pd }
if math.abs(t1 - t0) <= 86400 then
cur_streak = cur_streak + 1
else
cur_streak = 1
end
cur_streak = math.abs(t1 - t0) <= 86400 and (cur_streak + 1) or 1
if cur_streak > max_streak then max_streak = cur_streak end
end

Expand All @@ -88,27 +89,32 @@ function M.get_config_stats()
local last = dates[1]
local last_change = "Unknown"
if last then
local y, m, d = last:match "(%d+)-(%d+)-(%d+)"
local y, m, d = last:match "(%d+)-(%d+)-(%d+)" ---@type string, string, string
if y and m and d then
last_change = format_ago(
now
- os.time { year = tonumber(y), month = tonumber(m), day = tonumber(d) }
)
last_change = format_ago(now - os.time {
year = tonumber(y, 10),
month = tonumber(m, 10),
day = tonumber(d, 10),
})
end
end

-- config lifetime from first commit
local first = sorted[1]
local lifetime = "Unknown"
if first then
local y, m, d = first:match "(%d+)-(%d+)-(%d+)"
local y, m, d = first:match "(%d+)-(%d+)-(%d+)" ---@type string, string, string
if y and m and d then
local age_days = (
now
- os.time { year = tonumber(y), month = tonumber(m), day = tonumber(d) }
- os.time {
year = tonumber(y, 10),
month = tonumber(m, 10),
day = tonumber(d, 10),
}
) / 86400
if age_days >= 365 then
lifetime = string.format("%.1f years old", age_days / 365)
lifetime = ("%.1f years old"):format(age_days / 365)
elseif age_days >= 30 then
lifetime = math.floor(age_days / 30) .. " months old"
else
Expand All @@ -120,11 +126,11 @@ function M.get_config_stats()
local subjects = exec_git { "log", "--format=%s" }
local shortest, longest = subjects[1] or "", subjects[1] or ""
for _, s in ipairs(subjects) do
if #s < #shortest and #s > 0 then shortest = s end
if #s > #longest then longest = s end
if s:len() < shortest:len() and s:len() > 0 then shortest = s end
if s:len() > longest:len() then longest = s end
end

return {
return { ---@type Wrapped.ConfigStats
longest_streak = #sorted > 0 and max_streak or 0,
last_change = last_change,
lifetime = lifetime,
Expand All @@ -134,34 +140,31 @@ function M.get_config_stats()
end

-- commit count per day for a given year, keyed as ddmmyyyy
---@param year number
---@return table<string, number>
---@param year integer
---@return table<string, integer> counts
function M.get_commit_activity(year)
local dates = exec_git { "log", "--format=%ad", "--date=short" }
local yr = tostring(year)
local counts = {}
local counts = {} ---@type table<string, integer>
for _, d in ipairs(dates) do
local y, m, day = d:match "(%d+)-(%d+)-(%d+)"
if y == yr then
local y, m, day = d:match "(%d+)-(%d+)-(%d+)" ---@type string, string, string
if y == tostring(year) then
local key = day .. m .. y
counts[key] = (counts[key] or 0) + 1
end
end
return counts
end

---@return number
---@return integer year
function M.get_first_commit_year()
local d = M.get_first_commit_date()
local y = d:match "(%d+)"
return tonumber(y) or tonumber(os.date "%Y")
return tonumber(M.get_first_commit_date():match "(%d+)" or os.date "%Y", 10)
end

-- sample ~12 commits and get total line count at each point
---@return { values: number[], labels: string[] }
---@return { values: integer[], labels: string[] } history
function M.get_size_history()
local log = exec_git { "log", "--reverse", "--format=%H %ad", "--date=short" }
if #log == 0 then return { values = {}, labels = {} } end
if vim.tbl_isempty(log) then return { values = {}, labels = {} } end

-- get the empty tree hash for this repo
local empty = Job:new {
Expand All @@ -170,26 +173,26 @@ function M.get_size_history()
cwd = get_path(),
}
empty:sync()
local empty_tree = (empty:result()[1] or ""):match "%S+"
local empty_tree = (empty:result()[1] or ""):match "%S+" ---@type string

-- sample ~20 evenly spaced commits
local samples = {}
local samples = {} ---@type string[]
local step = math.max(1, math.floor(#log / 49))
for i = 1, #log, step do
table.insert(samples, log[i])
end
-- always include latest
if #samples > 0 and samples[#samples] ~= log[#log] then
if not vim.tbl_isempty(samples) and samples[#samples] ~= log[#log] then
table.insert(samples, log[#log])
end

local values, labels = {}, {}
local values, labels = {}, {} ---@type integer[], string[]
for _, entry in ipairs(samples) do
local hash, date = entry:match "(%S+)%s+(%S+)"
local hash, date = entry:match "(%S+)%s+(%S+)" ---@type string, string
if hash and empty_tree then
local stat = exec_git { "diff", "--shortstat", empty_tree, hash }
local ins = (stat[1] or ""):match "(%d+) insertion"
table.insert(values, tonumber(ins) or 0)
table.insert(values, ins and tonumber(ins, 10) or 0)
table.insert(labels, date)
end
end
Expand Down
Loading