Compare commits
10 commits
15079007e7
...
066a39aa8c
| Author | SHA1 | Date | |
|---|---|---|---|
| 066a39aa8c | |||
| baf855666d | |||
| cdc3429882 | |||
| 28b13a85fa | |||
| edcf9bb0aa | |||
| 827a5bdc60 | |||
| a96308ff2c | |||
| 9583952806 | |||
| 3ec2009f54 | |||
| d422ad36b8 |
67 changed files with 446 additions and 76 deletions
75
.gitea/workflows/release.yml
Normal file
75
.gitea/workflows/release.yml
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
name: release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*-coa.*' # Asc-1.1.6-coa.2, 9.1.40-coa.3, etc.
|
||||
- 'v*' # v0.3.0 for repos without an upstream version
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: linux-amd64
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0 # build_zip uses git archive HEAD; full history is fine
|
||||
|
||||
- name: Build per-addon zip(s)
|
||||
run: bash tools/build_zip.sh
|
||||
|
||||
- name: Publish release (Gitea API direct; no action dependency)
|
||||
env:
|
||||
GITEA_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
REPO: ${{ github.repository }}
|
||||
TAG: ${{ github.ref_name }}
|
||||
API: ${{ github.server_url }}/api/v1
|
||||
# Gitea attachment ceiling is 200 MiB (see roles/gitea config).
|
||||
# Skip anything larger so one oversized asset doesn't fail the job.
|
||||
MAX_BYTES: 209715200
|
||||
run: |
|
||||
set -euo pipefail
|
||||
# Create the release (or reuse if it already exists for this tag).
|
||||
RID=$(curl -s -H "Authorization: token $GITEA_TOKEN" \
|
||||
"$API/repos/$REPO/releases/tags/$TAG" 2>/dev/null \
|
||||
| jq -r '.id // empty')
|
||||
if [ -z "$RID" ]; then
|
||||
RID=$(curl -sf -X POST -H "Authorization: token $GITEA_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
"$API/repos/$REPO/releases" \
|
||||
-d "$(jq -nc --arg t "$TAG" '{tag_name:$t,name:$t,draft:false,prerelease:false,hide_archive_links:true}')" \
|
||||
| jq -r '.id')
|
||||
fi
|
||||
echo "release id: $RID"
|
||||
# Gitea honors hide_archive_links only on edit, not create — PATCH it
|
||||
# so the auto-generated Source Code (zip/tar.gz) links stay hidden.
|
||||
curl -sf -X PATCH -H "Authorization: token $GITEA_TOKEN" -H "Content-Type: application/json" \
|
||||
"$API/repos/$REPO/releases/$RID" -d '{"hide_archive_links":true}' >/dev/null || true
|
||||
# Upload every dist/*.zip. Per-asset failures don't fail the job —
|
||||
# we want partial releases to still publish rather than block the
|
||||
# whole pipeline on one big file.
|
||||
failed=0
|
||||
uploaded=0
|
||||
for f in dist/*.zip; do
|
||||
name=$(basename "$f")
|
||||
size=$(stat -c '%s' "$f")
|
||||
if [ "$size" -gt "$MAX_BYTES" ]; then
|
||||
echo "::warning::skip $name (${size} B > ${MAX_BYTES} B Gitea limit; host on CDN instead)"
|
||||
failed=$((failed+1))
|
||||
continue
|
||||
fi
|
||||
echo "uploading $name ($(numfmt --to=iec "$size"))"
|
||||
if curl -sf -X POST -H "Authorization: token $GITEA_TOKEN" \
|
||||
-F "attachment=@$f" \
|
||||
"$API/repos/$REPO/releases/$RID/assets?name=$name" \
|
||||
| jq -r '" -> " + .browser_download_url'; then
|
||||
uploaded=$((uploaded+1))
|
||||
else
|
||||
echo "::warning::upload failed for $name"
|
||||
failed=$((failed+1))
|
||||
fi
|
||||
done
|
||||
echo "release published: $uploaded uploaded, $failed skipped/failed"
|
||||
# Only fail the job if NO assets uploaded — a release with zero
|
||||
# attachments isn't useful to anyone.
|
||||
[ "$uploaded" -gt 0 ]
|
||||
9
.gitignore
vendored
Normal file
9
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
.env
|
||||
.DS_Store
|
||||
.release
|
||||
.install
|
||||
.lua/*
|
||||
.vscode
|
||||
.idea
|
||||
|
||||
dist/
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
## Interface: 11508, 11507, 20505, 30405, 38001, 40402, 50503, 50504, 120001, 120005, 120007
|
||||
## Interface: 30300
|
||||
|
||||
## Title: Lib: Ace3
|
||||
## Notes: AddOn development framework
|
||||
|
|
@ -6,7 +6,7 @@
|
|||
## X-Website: http://www.wowace.com
|
||||
## X-Category: Library
|
||||
## X-License: Limited BSD
|
||||
## Version: @project-version@
|
||||
## Version: master-52e5f2c
|
||||
|
||||
LibStub\LibStub.lua
|
||||
CallbackHandler-1.0\CallbackHandler-1.0.xml
|
||||
|
|
@ -585,11 +585,11 @@ do
|
|||
button:SetSize(128, 21)
|
||||
button:SetNormalFontObject(GameFontNormal)
|
||||
button:SetHighlightFontObject(GameFontHighlight)
|
||||
button:SetNormalTexture(130763) -- "Interface\\Buttons\\UI-DialogBox-Button-Up"
|
||||
button:SetNormalTexture("Interface\\Buttons\\UI-DialogBox-Button-Up")
|
||||
button:GetNormalTexture():SetTexCoord(0.0, 1.0, 0.0, 0.71875)
|
||||
button:SetPushedTexture(130761) -- "Interface\\Buttons\\UI-DialogBox-Button-Down"
|
||||
button:SetPushedTexture("Interface\\Buttons\\UI-DialogBox-Button-Down")
|
||||
button:GetPushedTexture():SetTexCoord(0.0, 1.0, 0.0, 0.71875)
|
||||
button:SetHighlightTexture(130762) -- "Interface\\Buttons\\UI-DialogBox-Button-Highlight"
|
||||
button:SetHighlightTexture("Interface\\Buttons\\UI-DialogBox-Button-Highlight")
|
||||
button:GetHighlightTexture():SetTexCoord(0.0, 1.0, 0.0, 0.71875)
|
||||
button:SetText(newText)
|
||||
return button
|
||||
|
|
@ -2014,28 +2014,37 @@ function AceConfigDialog:AddToBlizOptions(appName, name, parent, ...)
|
|||
group:SetCallback("OnHide", ClearBlizPanel)
|
||||
|
||||
local categoryName = name or appName
|
||||
if parent then
|
||||
local parentID = BlizOptionsIDMap[parent] or parent
|
||||
local category = Settings.GetCategory(parentID)
|
||||
if not category then
|
||||
error(("The parent category '%s' was not found"):format(parent), 2)
|
||||
end
|
||||
local subcategory = Settings.RegisterCanvasLayoutSubcategory(category, group.frame, categoryName)
|
||||
group:SetName(subcategory.ID, parentID)
|
||||
else
|
||||
if BlizOptionsIDMap[categoryName] then
|
||||
error(("%s has already been added to the Blizzard Options Window with the given name: %s"):format(appName, categoryName), 2)
|
||||
end
|
||||
-- CoA-compat: the Settings.* API (GetCategory / RegisterCanvasLayoutCategory /
|
||||
-- RegisterCanvasLayoutSubcategory / RegisterAddOnCategory) is a retail-only
|
||||
-- (Dragonflight+) replacement for the WotLK-era InterfaceOptions_AddCategory.
|
||||
-- On the 3.3.5-based CoA client Settings is nil, so fall back to the legacy API.
|
||||
if Settings and Settings.GetCategory then
|
||||
if parent then
|
||||
local parentID = BlizOptionsIDMap[parent] or parent
|
||||
local category = Settings.GetCategory(parentID)
|
||||
if not category then
|
||||
error(("The parent category '%s' was not found"):format(parent), 2)
|
||||
end
|
||||
local subcategory = Settings.RegisterCanvasLayoutSubcategory(category, group.frame, categoryName)
|
||||
group:SetName(subcategory.ID, parentID)
|
||||
else
|
||||
if BlizOptionsIDMap[categoryName] then
|
||||
error(("%s has already been added to the Blizzard Options Window with the given name: %s"):format(appName, categoryName), 2)
|
||||
end
|
||||
|
||||
local category = Settings.RegisterCanvasLayoutCategory(group.frame, categoryName)
|
||||
if not (C_SettingsUtil and C_SettingsUtil.OpenSettingsPanel) then
|
||||
-- override the ID so the name can be used in Settings.OpenToCategory
|
||||
-- unfortunately with incoming API changes in 12.0 (and likely classic at some point) this override is no longer possible
|
||||
category.ID = categoryName
|
||||
local category = Settings.RegisterCanvasLayoutCategory(group.frame, categoryName)
|
||||
if not (C_SettingsUtil and C_SettingsUtil.OpenSettingsPanel) then
|
||||
-- override the ID so the name can be used in Settings.OpenToCategory
|
||||
-- unfortunately with incoming API changes in 12.0 (and likely classic at some point) this override is no longer possible
|
||||
category.ID = categoryName
|
||||
end
|
||||
group:SetName(category.ID)
|
||||
BlizOptionsIDMap[categoryName] = category.ID
|
||||
Settings.RegisterAddOnCategory(category)
|
||||
end
|
||||
group:SetName(category.ID)
|
||||
BlizOptionsIDMap[categoryName] = category.ID
|
||||
Settings.RegisterAddOnCategory(category)
|
||||
else
|
||||
group:SetName(name or appName, parent)
|
||||
InterfaceOptions_AddCategory(group.frame)
|
||||
end
|
||||
|
||||
return group.frame, group.frame.name
|
||||
|
|
@ -111,7 +111,15 @@ local function copyDefaults(dest, src)
|
|||
end
|
||||
else
|
||||
-- Values are not tables, so this is just a simple return
|
||||
local mt = {__index = function(t,k2) return k2~=nil and v or nil end}
|
||||
-- (PR #10 backport: the old `k2~=nil and v or nil` short-circuits to
|
||||
-- nil whenever the default `v` itself is falsy — so `["*"] = false`
|
||||
-- defaults silently became nil. Make the read explicit instead.)
|
||||
local mt = {
|
||||
__index = function(t,k2)
|
||||
if k2 == nil then return nil end
|
||||
return v
|
||||
end,
|
||||
}
|
||||
setmetatable(dest, mt)
|
||||
end
|
||||
elseif type(v) == "table" then
|
||||
|
|
@ -99,7 +99,11 @@ local methods = {
|
|||
Constructor
|
||||
-------------------------------------------------------------------------------]]
|
||||
local function Constructor()
|
||||
local frame = CreateFrame("Frame", nil, InterfaceOptionsFramePanelContainer)
|
||||
-- CoA-compat: InterfaceOptionsFramePanelContainer is a global from the stock 3.3.5
|
||||
-- Interface Options frame; on the CoA reworked FrameXML it can be nil at the time
|
||||
-- AceGUI widgets are constructed. Fall back to UIParent so CreateFrame doesn't blow up.
|
||||
local _parent = InterfaceOptionsFramePanelContainer or UIParent
|
||||
local frame = CreateFrame("Frame", nil, _parent)
|
||||
frame:Hide()
|
||||
|
||||
-- support functions for the Blizzard Interface Options
|
||||
|
|
@ -221,7 +221,7 @@ local function Constructor()
|
|||
statustext:SetText("")
|
||||
|
||||
local titlebg = frame:CreateTexture(nil, "OVERLAY")
|
||||
titlebg:SetTexture(131080) -- Interface\\DialogFrame\\UI-DialogBox-Header
|
||||
titlebg:SetTexture("Interface\\DialogFrame\\UI-DialogBox-Header")
|
||||
titlebg:SetTexCoord(0.31, 0.67, 0, 0.63)
|
||||
titlebg:SetPoint("TOP", 0, 12)
|
||||
titlebg:SetWidth(100)
|
||||
|
|
@ -237,14 +237,14 @@ local function Constructor()
|
|||
titletext:SetPoint("TOP", titlebg, "TOP", 0, -14)
|
||||
|
||||
local titlebg_l = frame:CreateTexture(nil, "OVERLAY")
|
||||
titlebg_l:SetTexture(131080) -- Interface\\DialogFrame\\UI-DialogBox-Header
|
||||
titlebg_l:SetTexture("Interface\\DialogFrame\\UI-DialogBox-Header")
|
||||
titlebg_l:SetTexCoord(0.21, 0.31, 0, 0.63)
|
||||
titlebg_l:SetPoint("RIGHT", titlebg, "LEFT")
|
||||
titlebg_l:SetWidth(30)
|
||||
titlebg_l:SetHeight(40)
|
||||
|
||||
local titlebg_r = frame:CreateTexture(nil, "OVERLAY")
|
||||
titlebg_r:SetTexture(131080) -- Interface\\DialogFrame\\UI-DialogBox-Header
|
||||
titlebg_r:SetTexture("Interface\\DialogFrame\\UI-DialogBox-Header")
|
||||
titlebg_r:SetTexCoord(0.67, 0.77, 0, 0.63)
|
||||
titlebg_r:SetPoint("LEFT", titlebg, "RIGHT")
|
||||
titlebg_r:SetWidth(30)
|
||||
|
|
@ -262,7 +262,7 @@ local function Constructor()
|
|||
line1:SetWidth(14)
|
||||
line1:SetHeight(14)
|
||||
line1:SetPoint("BOTTOMRIGHT", -8, 8)
|
||||
line1:SetTexture(137057) -- Interface\\Tooltips\\UI-Tooltip-Border
|
||||
line1:SetTexture("Interface\\Tooltips\\UI-Tooltip-Border")
|
||||
local x = 0.1 * 14/17
|
||||
line1:SetTexCoord(0.05 - x, 0.5, 0.05, 0.5 + x, 0.05, 0.5 - x, 0.5 + x, 0.5)
|
||||
|
||||
|
|
@ -270,7 +270,7 @@ local function Constructor()
|
|||
line2:SetWidth(8)
|
||||
line2:SetHeight(8)
|
||||
line2:SetPoint("BOTTOMRIGHT", -8, 8)
|
||||
line2:SetTexture(137057) -- Interface\\Tooltips\\UI-Tooltip-Border
|
||||
line2:SetTexture("Interface\\Tooltips\\UI-Tooltip-Border")
|
||||
x = 0.1 * 8/17
|
||||
line2:SetTexCoord(0.05 - x, 0.5, 0.05, 0.5 + x, 0.05, 0.5 - x, 0.5 + x, 0.5)
|
||||
|
||||
|
|
@ -105,11 +105,11 @@ local function UpdateButton(button, treeline, selected, canExpand, isExpanded)
|
|||
|
||||
if canExpand then
|
||||
if not isExpanded then
|
||||
toggle:SetNormalTexture(130838) -- Interface\\Buttons\\UI-PlusButton-UP
|
||||
toggle:SetPushedTexture(130836) -- Interface\\Buttons\\UI-PlusButton-DOWN
|
||||
toggle:SetNormalTexture("Interface\\Buttons\\UI-PlusButton-UP")
|
||||
toggle:SetPushedTexture("Interface\\Buttons\\UI-PlusButton-DOWN")
|
||||
else
|
||||
toggle:SetNormalTexture(130821) -- Interface\\Buttons\\UI-MinusButton-UP
|
||||
toggle:SetPushedTexture(130820) -- Interface\\Buttons\\UI-MinusButton-DOWN
|
||||
toggle:SetNormalTexture("Interface\\Buttons\\UI-MinusButton-UP")
|
||||
toggle:SetPushedTexture("Interface\\Buttons\\UI-MinusButton-DOWN")
|
||||
end
|
||||
toggle:Show()
|
||||
else
|
||||
|
|
@ -190,67 +190,67 @@ do
|
|||
frame:SetToplevel(true)
|
||||
|
||||
local titlebg = frame:CreateTexture(nil, "BACKGROUND")
|
||||
titlebg:SetTexture(251966) -- Interface\\PaperDollInfoFrame\\UI-GearManager-Title-Background
|
||||
titlebg:SetTexture("Interface\\PaperDollInfoFrame\\UI-GearManager-Title-Background")
|
||||
titlebg:SetPoint("TOPLEFT", 9, -6)
|
||||
titlebg:SetPoint("BOTTOMRIGHT", frame, "TOPRIGHT", -28, -24)
|
||||
|
||||
local dialogbg = frame:CreateTexture(nil, "BACKGROUND")
|
||||
dialogbg:SetTexture(137056) -- Interface\\Tooltips\\UI-Tooltip-Background
|
||||
dialogbg:SetTexture("Interface\\Tooltips\\UI-Tooltip-Background")
|
||||
dialogbg:SetPoint("TOPLEFT", 8, -24)
|
||||
dialogbg:SetPoint("BOTTOMRIGHT", -6, 8)
|
||||
dialogbg:SetVertexColor(0, 0, 0, .75)
|
||||
|
||||
local topleft = frame:CreateTexture(nil, "BORDER")
|
||||
topleft:SetTexture(251963) -- Interface\\PaperDollInfoFrame\\UI-GearManager-Border
|
||||
topleft:SetTexture("Interface\\PaperDollInfoFrame\\UI-GearManager-Border")
|
||||
topleft:SetWidth(64)
|
||||
topleft:SetHeight(64)
|
||||
topleft:SetPoint("TOPLEFT")
|
||||
topleft:SetTexCoord(0.501953125, 0.625, 0, 1)
|
||||
|
||||
local topright = frame:CreateTexture(nil, "BORDER")
|
||||
topright:SetTexture(251963) -- Interface\\PaperDollInfoFrame\\UI-GearManager-Border
|
||||
topright:SetTexture("Interface\\PaperDollInfoFrame\\UI-GearManager-Border")
|
||||
topright:SetWidth(64)
|
||||
topright:SetHeight(64)
|
||||
topright:SetPoint("TOPRIGHT")
|
||||
topright:SetTexCoord(0.625, 0.75, 0, 1)
|
||||
|
||||
local top = frame:CreateTexture(nil, "BORDER")
|
||||
top:SetTexture(251963) -- Interface\\PaperDollInfoFrame\\UI-GearManager-Border
|
||||
top:SetTexture("Interface\\PaperDollInfoFrame\\UI-GearManager-Border")
|
||||
top:SetHeight(64)
|
||||
top:SetPoint("TOPLEFT", topleft, "TOPRIGHT")
|
||||
top:SetPoint("TOPRIGHT", topright, "TOPLEFT")
|
||||
top:SetTexCoord(0.25, 0.369140625, 0, 1)
|
||||
|
||||
local bottomleft = frame:CreateTexture(nil, "BORDER")
|
||||
bottomleft:SetTexture(251963) -- Interface\\PaperDollInfoFrame\\UI-GearManager-Border
|
||||
bottomleft:SetTexture("Interface\\PaperDollInfoFrame\\UI-GearManager-Border")
|
||||
bottomleft:SetWidth(64)
|
||||
bottomleft:SetHeight(64)
|
||||
bottomleft:SetPoint("BOTTOMLEFT")
|
||||
bottomleft:SetTexCoord(0.751953125, 0.875, 0, 1)
|
||||
|
||||
local bottomright = frame:CreateTexture(nil, "BORDER")
|
||||
bottomright:SetTexture(251963) -- Interface\\PaperDollInfoFrame\\UI-GearManager-Border
|
||||
bottomright:SetTexture("Interface\\PaperDollInfoFrame\\UI-GearManager-Border")
|
||||
bottomright:SetWidth(64)
|
||||
bottomright:SetHeight(64)
|
||||
bottomright:SetPoint("BOTTOMRIGHT")
|
||||
bottomright:SetTexCoord(0.875, 1, 0, 1)
|
||||
|
||||
local bottom = frame:CreateTexture(nil, "BORDER")
|
||||
bottom:SetTexture(251963) -- Interface\\PaperDollInfoFrame\\UI-GearManager-Border
|
||||
bottom:SetTexture("Interface\\PaperDollInfoFrame\\UI-GearManager-Border")
|
||||
bottom:SetHeight(64)
|
||||
bottom:SetPoint("BOTTOMLEFT", bottomleft, "BOTTOMRIGHT")
|
||||
bottom:SetPoint("BOTTOMRIGHT", bottomright, "BOTTOMLEFT")
|
||||
bottom:SetTexCoord(0.376953125, 0.498046875, 0, 1)
|
||||
|
||||
local left = frame:CreateTexture(nil, "BORDER")
|
||||
left:SetTexture(251963) -- Interface\\PaperDollInfoFrame\\UI-GearManager-Border
|
||||
left:SetTexture("Interface\\PaperDollInfoFrame\\UI-GearManager-Border")
|
||||
left:SetWidth(64)
|
||||
left:SetPoint("TOPLEFT", topleft, "BOTTOMLEFT")
|
||||
left:SetPoint("BOTTOMLEFT", bottomleft, "TOPLEFT")
|
||||
left:SetTexCoord(0.001953125, 0.125, 0, 1)
|
||||
|
||||
local right = frame:CreateTexture(nil, "BORDER")
|
||||
right:SetTexture(251963) -- Interface\\PaperDollInfoFrame\\UI-GearManager-Border
|
||||
right:SetTexture("Interface\\PaperDollInfoFrame\\UI-GearManager-Border")
|
||||
right:SetWidth(64)
|
||||
right:SetPoint("TOPRIGHT", topright, "BOTTOMRIGHT")
|
||||
right:SetPoint("BOTTOMRIGHT", bottomright, "TOPRIGHT")
|
||||
|
|
@ -290,7 +290,7 @@ do
|
|||
line1:SetWidth(14)
|
||||
line1:SetHeight(14)
|
||||
line1:SetPoint("BOTTOMRIGHT", -8, 8)
|
||||
line1:SetTexture(137057) -- Interface\\Tooltips\\UI-Tooltip-Border
|
||||
line1:SetTexture("Interface\\Tooltips\\UI-Tooltip-Border")
|
||||
local x = 0.1 * 14/17
|
||||
line1:SetTexCoord(0.05 - x, 0.5, 0.05, 0.5 + x, 0.05, 0.5 - x, 0.5 + x, 0.5)
|
||||
|
||||
|
|
@ -299,7 +299,7 @@ do
|
|||
line2:SetWidth(8)
|
||||
line2:SetHeight(8)
|
||||
line2:SetPoint("BOTTOMRIGHT", -8, 8)
|
||||
line2:SetTexture(137057) -- Interface\\Tooltips\\UI-Tooltip-Border
|
||||
line2:SetTexture("Interface\\Tooltips\\UI-Tooltip-Border")
|
||||
x = 0.1 * 8/17
|
||||
line2:SetTexCoord(0.05 - x, 0.5, 0.05, 0.5 + x, 0.05, 0.5 - x, 0.5 + x, 0.5)
|
||||
|
||||
|
|
@ -151,21 +151,21 @@ local methods = {
|
|||
local size
|
||||
if type == "radio" then
|
||||
size = 16
|
||||
checkbg:SetTexture(130843) -- Interface\\Buttons\\UI-RadioButton
|
||||
checkbg:SetTexture("Interface\\Buttons\\UI-RadioButton")
|
||||
checkbg:SetTexCoord(0, 0.25, 0, 1)
|
||||
check:SetTexture(130843) -- Interface\\Buttons\\UI-RadioButton
|
||||
check:SetTexture("Interface\\Buttons\\UI-RadioButton")
|
||||
check:SetTexCoord(0.25, 0.5, 0, 1)
|
||||
check:SetBlendMode("ADD")
|
||||
highlight:SetTexture(130843) -- Interface\\Buttons\\UI-RadioButton
|
||||
highlight:SetTexture("Interface\\Buttons\\UI-RadioButton")
|
||||
highlight:SetTexCoord(0.5, 0.75, 0, 1)
|
||||
else
|
||||
size = 24
|
||||
checkbg:SetTexture(130755) -- Interface\\Buttons\\UI-CheckBox-Up
|
||||
checkbg:SetTexture("Interface\\Buttons\\UI-CheckBox-Up")
|
||||
checkbg:SetTexCoord(0, 1, 0, 1)
|
||||
check:SetTexture(130751) -- Interface\\Buttons\\UI-CheckBox-Check
|
||||
check:SetTexture("Interface\\Buttons\\UI-CheckBox-Check")
|
||||
check:SetTexCoord(0, 1, 0, 1)
|
||||
check:SetBlendMode("BLEND")
|
||||
highlight:SetTexture(130753) -- Interface\\Buttons\\UI-CheckBox-Highlight
|
||||
highlight:SetTexture("Interface\\Buttons\\UI-CheckBox-Highlight")
|
||||
highlight:SetTexCoord(0, 1, 0, 1)
|
||||
end
|
||||
checkbg:SetHeight(size)
|
||||
|
|
@ -251,11 +251,11 @@ local function Constructor()
|
|||
checkbg:SetWidth(24)
|
||||
checkbg:SetHeight(24)
|
||||
checkbg:SetPoint("TOPLEFT")
|
||||
checkbg:SetTexture(130755) -- Interface\\Buttons\\UI-CheckBox-Up
|
||||
checkbg:SetTexture("Interface\\Buttons\\UI-CheckBox-Up")
|
||||
|
||||
local check = frame:CreateTexture(nil, "OVERLAY")
|
||||
check:SetAllPoints(checkbg)
|
||||
check:SetTexture(130751) -- Interface\\Buttons\\UI-CheckBox-Check
|
||||
check:SetTexture("Interface\\Buttons\\UI-CheckBox-Check")
|
||||
|
||||
local text = frame:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
|
||||
text:SetJustifyH("LEFT")
|
||||
|
|
@ -264,7 +264,7 @@ local function Constructor()
|
|||
text:SetPoint("RIGHT")
|
||||
|
||||
local highlight = frame:CreateTexture(nil, "HIGHLIGHT")
|
||||
highlight:SetTexture(130753) -- Interface\\Buttons\\UI-CheckBox-Highlight
|
||||
highlight:SetTexture("Interface\\Buttons\\UI-CheckBox-Highlight")
|
||||
highlight:SetBlendMode("ADD")
|
||||
highlight:SetAllPoints(checkbg)
|
||||
|
||||
|
|
@ -180,7 +180,7 @@ local function Constructor()
|
|||
local colorSwatch = frame:CreateTexture(nil, "OVERLAY")
|
||||
colorSwatch:SetWidth(19)
|
||||
colorSwatch:SetHeight(19)
|
||||
colorSwatch:SetTexture(130939) -- Interface\\ChatFrame\\ChatFrameColorSwatch
|
||||
colorSwatch:SetTexture("Interface\\ChatFrame\\ChatFrameColorSwatch")
|
||||
colorSwatch:SetPoint("LEFT")
|
||||
|
||||
local texture = frame:CreateTexture(nil, "BACKGROUND")
|
||||
|
|
@ -195,7 +195,7 @@ local function Constructor()
|
|||
colorSwatch.checkers = checkers
|
||||
checkers:SetWidth(14)
|
||||
checkers:SetHeight(14)
|
||||
checkers:SetTexture(188523) -- Tileset\\Generic\\Checkers
|
||||
checkers:SetTexture("Tileset\\Generic\\Checkers")
|
||||
checkers:SetTexCoord(.25, 0, 0.5, .25)
|
||||
checkers:SetDesaturated(true)
|
||||
checkers:SetVertexColor(1, 1, 1, 0.75)
|
||||
|
|
@ -210,7 +210,7 @@ local function Constructor()
|
|||
text:SetPoint("RIGHT")
|
||||
|
||||
--local highlight = frame:CreateTexture(nil, "HIGHLIGHT")
|
||||
--highlight:SetTexture(136810) -- Interface\\QuestFrame\\UI-QuestTitleHighlight
|
||||
--highlight:SetTexture("Interface\\QuestFrame\\UI-QuestTitleHighlight")
|
||||
--highlight:SetBlendMode("ADD")
|
||||
--highlight:SetAllPoints(frame)
|
||||
|
||||
|
|
@ -169,7 +169,7 @@ function ItemBase.Create(type)
|
|||
self.text = text
|
||||
|
||||
local highlight = frame:CreateTexture(nil, "OVERLAY")
|
||||
highlight:SetTexture(136810) -- Interface\\QuestFrame\\UI-QuestTitleHighlight
|
||||
highlight:SetTexture("Interface\\QuestFrame\\UI-QuestTitleHighlight")
|
||||
highlight:SetBlendMode("ADD")
|
||||
highlight:SetHeight(14)
|
||||
highlight:ClearAllPoints()
|
||||
|
|
@ -182,7 +182,7 @@ function ItemBase.Create(type)
|
|||
check:SetWidth(16)
|
||||
check:SetHeight(16)
|
||||
check:SetPoint("LEFT",frame,"LEFT",3,-1)
|
||||
check:SetTexture(130751) -- Interface\\Buttons\\UI-CheckBox-Check
|
||||
check:SetTexture("Interface\\Buttons\\UI-CheckBox-Check")
|
||||
check:Hide()
|
||||
self.check = check
|
||||
|
||||
|
|
@ -190,7 +190,7 @@ function ItemBase.Create(type)
|
|||
sub:SetWidth(16)
|
||||
sub:SetHeight(16)
|
||||
sub:SetPoint("RIGHT",frame,"RIGHT",-3,-1)
|
||||
sub:SetTexture(130940) -- Interface\\ChatFrame\\ChatFrameExpandArrow
|
||||
sub:SetTexture("Interface\\ChatFrame\\ChatFrameExpandArrow")
|
||||
sub:Hide()
|
||||
self.sub = sub
|
||||
|
||||
|
|
@ -51,14 +51,14 @@ local function Constructor()
|
|||
left:SetHeight(8)
|
||||
left:SetPoint("LEFT", 3, 0)
|
||||
left:SetPoint("RIGHT", label, "LEFT", -5, 0)
|
||||
left:SetTexture(137057) -- Interface\\Tooltips\\UI-Tooltip-Border
|
||||
left:SetTexture("Interface\\Tooltips\\UI-Tooltip-Border")
|
||||
left:SetTexCoord(0.81, 0.94, 0.5, 1)
|
||||
|
||||
local right = frame:CreateTexture(nil, "BACKGROUND")
|
||||
right:SetHeight(8)
|
||||
right:SetPoint("RIGHT", -3, 0)
|
||||
right:SetPoint("LEFT", label, "RIGHT", 5, 0)
|
||||
right:SetTexture(137057) -- Interface\\Tooltips\\UI-Tooltip-Border
|
||||
right:SetTexture("Interface\\Tooltips\\UI-Tooltip-Border")
|
||||
right:SetTexCoord(0.81, 0.94, 0.5, 1)
|
||||
|
||||
local widget = {
|
||||
|
|
@ -118,7 +118,7 @@ local function Constructor()
|
|||
|
||||
local highlight = frame:CreateTexture(nil, "HIGHLIGHT")
|
||||
highlight:SetAllPoints(image)
|
||||
highlight:SetTexture(136580) -- Interface\\PaperDollInfoFrame\\UI-Character-Tab-Highlight
|
||||
highlight:SetTexture("Interface\\PaperDollInfoFrame\\UI-Character-Tab-Highlight")
|
||||
highlight:SetTexCoord(0, 1, 0.23, 0.77)
|
||||
highlight:SetBlendMode("ADD")
|
||||
|
||||
|
|
@ -199,7 +199,7 @@ local function Constructor()
|
|||
button:SetScript("OnKeyDown", Keybinding_OnKeyDown)
|
||||
button:SetScript("OnMouseDown", Keybinding_OnMouseDown)
|
||||
button:SetScript("OnMouseWheel", Keybinding_OnMouseWheel)
|
||||
button:SetScript("OnGamePadButtonDown", Keybinding_OnKeyDown)
|
||||
pcall(button.SetScript, button, "OnGamePadButtonDown", Keybinding_OnKeyDown)
|
||||
button:SetPoint("BOTTOMLEFT")
|
||||
button:SetPoint("BOTTOMRIGHT")
|
||||
button:SetHeight(24)
|
||||
49
README.md
49
README.md
|
|
@ -2,10 +2,20 @@
|
|||
|
||||
Canonical [Ace3](https://www.wowace.com/projects/ace3) bundle for the CoA Guild 'Exiles' addon forks.
|
||||
|
||||
Lifted verbatim from upstream [WoWUIDev/Ace3](https://github.com/WoWUIDev/Ace3) at commit
|
||||
Lifted from upstream [WoWUIDev/Ace3](https://github.com/WoWUIDev/Ace3) at commit
|
||||
[`52e5f2c`](https://github.com/WoWUIDev/Ace3/commit/52e5f2c7101b6edb02b48ea232bdda2df09d2960)
|
||||
(2026-05-17). Every fork in the `Exiles` org should converge on this bundle so the runtime
|
||||
LibStub resolution is predictable and addons can't quietly regress when one of them is disabled.
|
||||
(2026-05-17), with a small stack of CoA-compat patches on top (see below). Every fork in the `Exiles`
|
||||
org should converge on this bundle so the runtime LibStub resolution is predictable and addons
|
||||
can't quietly regress when one of them is disabled.
|
||||
|
||||
## CoA-compat patches on top of upstream
|
||||
|
||||
| # | Issue | Fix |
|
||||
|---|-------|-----|
|
||||
| 1 | Upstream Ace3 calls `Texture:Set*Texture(<FileDataID>)` with numeric FileDataIDs in 42 places across `AceGUI-3.0/widgets/*` and `AceConfigDialog-3.0`. FileDataIDs are a retail-only API (post WoD/Legion). On the WoW 3.3.5-based CoA client, `SetTexture` only accepts string paths — passing a number silently fails and the engine renders a red placeholder. Symptom: solid-red squares where color swatches / checkboxes / window chrome should be. | Each FDID call was substituted with the string path that already lived in the trailing comment, e.g. `colorSwatch:SetTexture(130939)` → `colorSwatch:SetTexture("Interface\\ChatFrame\\ChatFrameColorSwatch")`. |
|
||||
| 2 | `AceGUI-3.0/widgets/AceGUIContainer-BlizOptionsGroup.lua` Constructor parents its frame to the global `InterfaceOptionsFramePanelContainer`. On the CoA reworked FrameXML that global is nil at AceGUI widget-construction time, and `CreateFrame("Frame", nil, nil)` is fine, but downstream code that calls `:SetPoint` against the parent / `:Show` it via the options tree relies on a real parent. Symptom: every addon that registers a Blizzard Interface Options panel via AceConfigDialog errors during load. | Guard at line 102: `local _parent = InterfaceOptionsFramePanelContainer or UIParent` and pass `_parent` to `CreateFrame`. Widget behaviour is unchanged on retail; on CoA the panel parents to `UIParent` so the rest of the widget works. |
|
||||
| 3 | `AceConfig-3.0/AceConfigDialog-3.0/AceConfigDialog-3.0.lua` `:AddToBlizOptions` uses the Dragonflight+ `Settings.*` API (`Settings.GetCategory`, `Settings.RegisterCanvasLayoutCategory`, `Settings.RegisterCanvasLayoutSubcategory`, `Settings.RegisterAddOnCategory`). The `Settings` table doesn't exist on the 3.3.5-based CoA client, so every AceConfig-driven options panel errors out the moment it's registered. | Wrap the whole `Settings.*` block in `if Settings and Settings.GetCategory then … else … end`. The `else` branch falls back to the WotLK-era `InterfaceOptions_AddCategory(group.frame)` after stamping the category name via `group:SetName(name or appName, parent)`. |
|
||||
| 4 | `AceDB-3.0/AceDB-3.0.lua:114` — the simple-value `__index` metatable for defaults is `function(t,k2) return k2~=nil and v or nil end`. Whenever the default value `v` is itself falsy (`false`, `0`, `""`), the `and` short-circuits and the `or nil` resolves to `nil`, so `["*"] = false` and similar falsy defaults are silently lost when read. | Replaces the one-liner with an explicit `if k2 == nil then return nil end; return v` so falsy defaults round-trip correctly. Backport of [WoWUIDev/Ace3 PR #10](https://github.com/WoWUIDev/Ace3/pull/10) (open since 2023-11-04, not merged upstream). Drop this patch if/when upstream merges. |
|
||||
|
||||
## Versions
|
||||
|
||||
|
|
@ -47,14 +57,35 @@ Or replace the entire `Libs/Ace3/` tree in one go.
|
|||
|
||||
### Option 2: standalone addon
|
||||
|
||||
Drop the contents (except `README.md` / `.gitattributes`) into
|
||||
`Interface/AddOns/Ace3/` and the loadable `Ace3.toc` will register every library at top
|
||||
Drop the **`Ace3/`** directory from this repo straight into
|
||||
`Interface/AddOns/` and the loadable `Ace3.toc` will register every library at top
|
||||
priority via LibStub. Useful for non-bundling forks (`chatter`, `sexymap`, `clique`, …) to
|
||||
get Ace without each one carrying its own copy.
|
||||
|
||||
(The canonical bundle lives under `Ace3/` at the repo root so this repo follows the same
|
||||
"each addon in its own folder" layout as every other `Exiles/coa-*` fork.)
|
||||
|
||||
## Sync policy
|
||||
|
||||
Bumping upstream means a single PR here, then a sweep across every fork that embeds these
|
||||
libs. Note the new upstream commit in the README's commit-pin line above. Never edit a
|
||||
library file in place; if a CoA-specific patch is genuinely needed, ship it as a separate
|
||||
loadable addon that registers a higher MINOR via LibStub, leaving this bundle pristine.
|
||||
Bumping upstream means a single commit here, then a sweep across every fork that embeds these
|
||||
libs. Note the new upstream commit in the README's commit-pin line above, and re-apply
|
||||
the CoA-compat patches listed above against the new revision (the FDID one is mechanical —
|
||||
see `/tmp/fix_fdid.py` history). Keep patches **minimal and documented in this README**;
|
||||
prefer fixing them upstream where reasonable.
|
||||
|
||||
Run the sweep via `tools/sweep.py` from this repo — it walks every sibling `coa-*` fork
|
||||
under `/home/sub/repos/coa`, finds each fork's bundled `LibStub` / `CallbackHandler-1.0` /
|
||||
`Ace*-3.0` dirs, and rsyncs them from this bundle. Use `--dry-run` first.
|
||||
|
||||
### Forks excluded from sweep
|
||||
|
||||
`coa-elvui` is excluded from the sweep — see `EXCLUDED_FORKS` in `tools/sweep.py`. ElvUI
|
||||
ships its own bundled Ace3 stack with ElvUI-specific patches inside otherwise-stock-named
|
||||
files (`AceLocale-3.0.lua`, `AceConfigDialog-3.0.lua`, every `AceGUI-3.0/widgets/*.lua`)
|
||||
**plus** `-ElvUI`-suffixed widgets that don't exist in canonical at all (e.g.
|
||||
`AceGUIWidget-Button-ElvUI.lua`). `rsync --delete` obliterates the latter; an in-place
|
||||
sync overwrites the former. Either failure breaks `/ec` and floods locale errors. ElvUI
|
||||
maintains its own bundle on its own cadence and must never be touched by this tool.
|
||||
|
||||
The sweep also passes `--exclude='*-ElvUI*'` to every rsync as a belt-and-braces guard
|
||||
against future forks that happen to carry an ElvUI-namespaced file we didn't anticipate.
|
||||
|
|
|
|||
71
tools/build_zip.sh
Executable file
71
tools/build_zip.sh
Executable file
|
|
@ -0,0 +1,71 @@
|
|||
#!/usr/bin/env bash
|
||||
# Build per-addon zip artefacts from HEAD via git-archive.
|
||||
#
|
||||
# - Discovers top-level addon folders (Foo/Foo.toc).
|
||||
# - Re-creates dist/ each run.
|
||||
# - Always archives HEAD, so the working tree state is irrelevant.
|
||||
# - If more than one addon folder is present, also emits <repo>-all.zip
|
||||
# with every addon folder side-by-side at the zip root.
|
||||
# - When run inside Gitea Actions the working tree lives under a
|
||||
# per-job dir like /var/lib/act_runner/work/.../hostexecutor, so the
|
||||
# repo name comes from $GITHUB_REPOSITORY (set by the runner) and
|
||||
# only falls back to the toplevel basename for local invocations.
|
||||
set -euo pipefail
|
||||
|
||||
root=$(git rev-parse --show-toplevel)
|
||||
cd "$root"
|
||||
|
||||
# Gitea Actions sets GITHUB_REPOSITORY=owner/repo. The basename of
|
||||
# `git rev-parse --show-toplevel` inside the runner is the worker dir
|
||||
# (e.g. `hostexecutor`), which would name the bundle wrong.
|
||||
if [ -n "${GITHUB_REPOSITORY:-}" ]; then
|
||||
repo_name="${GITHUB_REPOSITORY##*/}"
|
||||
else
|
||||
repo_name=$(basename "$root")
|
||||
fi
|
||||
dist="$root/dist"
|
||||
|
||||
# Find Foo/Foo.toc pairs at depth 1; ignore libs nested deeper.
|
||||
addons=()
|
||||
while IFS= read -r toc; do
|
||||
dir=$(dirname "$toc")
|
||||
folder=$(basename "$dir")
|
||||
base=$(basename "$toc" .toc)
|
||||
# Accept Foo.toc and Foo_Wrath.toc style flavour variants; folder must match
|
||||
# at least one toc basename prefix (Foo).
|
||||
case "$base" in
|
||||
"$folder"|"$folder"_*) addons+=("$folder") ;;
|
||||
esac
|
||||
done < <(command find . -mindepth 2 -maxdepth 2 -type f -name '*.toc' | sed 's|^\./||' | sort)
|
||||
|
||||
# Dedupe (a folder with Foo.toc + Foo_Wrath.toc shows up twice).
|
||||
if [ ${#addons[@]} -gt 0 ]; then
|
||||
mapfile -t addons < <(printf '%s\n' "${addons[@]}" | awk '!seen[$0]++')
|
||||
fi
|
||||
|
||||
if [ ${#addons[@]} -eq 0 ]; then
|
||||
echo "no addon folders found (looking for */Foo.toc with matching folder name)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
rm -rf "$dist"
|
||||
mkdir -p "$dist"
|
||||
|
||||
for folder in "${addons[@]}"; do
|
||||
out="$dist/$folder.zip"
|
||||
# No --prefix: the folder already sits at the repo root, so git-archive
|
||||
# emits entries as <folder>/... which is exactly what
|
||||
# Interface/AddOns/ expects after extraction.
|
||||
git archive HEAD --format=zip -o "$out" -- "$folder"
|
||||
echo "built dist/$folder.zip"
|
||||
done
|
||||
|
||||
# Combined bundle only makes sense when there are multiple addons.
|
||||
if [ ${#addons[@]} -gt 1 ]; then
|
||||
tmp=$(mktemp -d)
|
||||
trap 'rm -rf "$tmp"' EXIT
|
||||
git archive HEAD --format=tar -- "${addons[@]}" | tar -x -C "$tmp"
|
||||
out="$dist/$repo_name-all.zip"
|
||||
( cd "$tmp" && zip -qr "$out" "${addons[@]}" )
|
||||
echo "built dist/$repo_name-all.zip"
|
||||
fi
|
||||
163
tools/sweep.py
Executable file
163
tools/sweep.py
Executable file
|
|
@ -0,0 +1,163 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Sweep this coa-ace3 bundle into every Exiles fork that embeds Ace3 libs.
|
||||
|
||||
Run from anywhere; the script locates itself and walks sibling repos at
|
||||
$REPOS_ROOT (default /home/sub/repos/coa). For each fork that bundles
|
||||
any AceXxx-3.0 library, the matching directory is rsynced from this
|
||||
bundle with --delete so the fork stays byte-identical to canonical.
|
||||
|
||||
USAGE
|
||||
tools/sweep.py # apply sweep
|
||||
tools/sweep.py --dry-run # show what would change without writing
|
||||
|
||||
WHY THIS SCRIPT EXISTS
|
||||
Previous sweeps were ad-hoc scripts living in /tmp during a session
|
||||
and re-derived by hand each time. That made it easy to forget the
|
||||
ElvUI exclusion (see EXCLUDED_FORKS below) and re-clobber ElvUI's
|
||||
customized lib stack on every sync. Committing the canonical script
|
||||
here keeps the exclusion list visible and reviewable.
|
||||
|
||||
EXCLUDED_FORKS
|
||||
coa-elvui ships its OWN bundled Ace3 with ElvUI-specific patches
|
||||
inside otherwise-stock-named files (AceLocale, AceConfigDialog, the
|
||||
AceGUI widgets) PLUS files that don't exist in canonical at all
|
||||
(AceGUIWidget-Button-ElvUI.lua). rsync --delete obliterates the
|
||||
latter; an in-place sync overwrites the former. Either failure
|
||||
breaks /ec and floods locale errors. ElvUI maintains its own bundle
|
||||
on its own cadence and must never be swept by this tool.
|
||||
"""
|
||||
import argparse
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# --- configuration -----------------------------------------------------------
|
||||
|
||||
REPOS_ROOT = Path(os.environ.get("REPOS_ROOT", "/home/sub/repos/coa"))
|
||||
BUNDLE = Path(__file__).resolve().parent.parent / "Ace3" # canonical Ace3 lives under <repo>/Ace3/
|
||||
|
||||
# Forks that this sweep MUST NOT touch. Document the reason inline.
|
||||
EXCLUDED_FORKS = {
|
||||
"coa-elvui", # ships its own ElvUI-patched Ace3 stack; sweep would clobber it
|
||||
}
|
||||
|
||||
# Lib names whose directories we sync from the bundle.
|
||||
LIB_NAMES = re.compile(r"^(LibStub|CallbackHandler-1\.0|Ace\w*-3\.0)$")
|
||||
|
||||
# rsync excludes — repo metadata never deployed, plus a belt-and-braces
|
||||
# guard against deleting any -ElvUI file that might exist in destinations we
|
||||
# didn't intend to touch (e.g. a future fork that pulls in some ElvUI widget).
|
||||
RSYNC_EXCLUDES = [
|
||||
".git", ".gitattributes", ".gitignore", ".github", ".idea",
|
||||
".editorconfig", ".luacheckrc", ".pkgmeta",
|
||||
"*-ElvUI*", # never delete or overwrite ElvUI-namespaced files
|
||||
]
|
||||
|
||||
|
||||
# --- discovery ---------------------------------------------------------------
|
||||
|
||||
def bundle_lib_sources():
|
||||
"""Return {libname: absolute path inside bundle} for every shippable lib.
|
||||
|
||||
Handles the nested AceConfig children (AceConfigCmd/Dialog/Registry).
|
||||
"""
|
||||
out = {}
|
||||
for path in BUNDLE.rglob("*"):
|
||||
if not path.is_dir():
|
||||
continue
|
||||
if path.name in {".git", "tools"}:
|
||||
continue
|
||||
if LIB_NAMES.match(path.name):
|
||||
out.setdefault(path.name, path)
|
||||
return out
|
||||
|
||||
|
||||
def fork_lib_targets(fork: Path, src_names):
|
||||
"""Return {libname: absolute path inside fork} for each lib the fork bundles.
|
||||
|
||||
A lib is "bundled" when there's a top-level directory containing
|
||||
<libname>/<libname>.lua. We don't add new libs to forks that didn't
|
||||
already ship them.
|
||||
"""
|
||||
targets = {}
|
||||
for path in fork.rglob("*.lua"):
|
||||
if "/.git/" in str(path):
|
||||
continue
|
||||
if "-ElvUI" in path.name:
|
||||
continue
|
||||
name = path.stem
|
||||
if name not in src_names:
|
||||
continue
|
||||
parent = path.parent
|
||||
if parent.name != name:
|
||||
continue
|
||||
targets.setdefault(name, parent)
|
||||
return targets
|
||||
|
||||
|
||||
# --- sweep -------------------------------------------------------------------
|
||||
|
||||
def sweep(dry_run: bool) -> int:
|
||||
src_map = bundle_lib_sources()
|
||||
if not src_map:
|
||||
print(f"no libs found inside {BUNDLE}; nothing to sweep", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
forks = sorted(p for p in REPOS_ROOT.iterdir()
|
||||
if p.is_dir() and p.name.startswith("coa-")
|
||||
and p.name != "coa-ace3"
|
||||
and (p / ".git").exists())
|
||||
|
||||
print(f"bundle: {BUNDLE}")
|
||||
print(f"forks scan: {REPOS_ROOT}")
|
||||
print(f"excluded: {sorted(EXCLUDED_FORKS)}")
|
||||
print(f"libs: {sorted(src_map)}")
|
||||
print()
|
||||
|
||||
total_synced = total_skipped = 0
|
||||
for fork in forks:
|
||||
if fork.name in EXCLUDED_FORKS:
|
||||
print(f" skip {fork.name} (excluded — ships its own customized Ace3)")
|
||||
total_skipped += 1
|
||||
continue
|
||||
|
||||
targets = fork_lib_targets(fork, src_map.keys())
|
||||
if not targets:
|
||||
print(f" no-libs {fork.name}")
|
||||
continue
|
||||
|
||||
for libname in sorted(targets):
|
||||
src = src_map[libname]
|
||||
dst = targets[libname]
|
||||
cmd = ["rsync", "-a", "--delete"]
|
||||
if dry_run:
|
||||
cmd += ["--dry-run", "--itemize-changes"]
|
||||
for exc in RSYNC_EXCLUDES:
|
||||
cmd += ["--exclude", exc]
|
||||
cmd += [f"{src}/", f"{dst}/"]
|
||||
res = subprocess.run(cmd, capture_output=True, text=True)
|
||||
if res.returncode != 0:
|
||||
print(f" ! rsync failed for {fork.name}/{libname}: {res.stderr.strip()}")
|
||||
continue
|
||||
tag = "would-sync" if dry_run else "synced"
|
||||
print(f" {tag:<9} {fork.name}/{dst.relative_to(fork)}")
|
||||
total_synced += 1
|
||||
|
||||
print()
|
||||
print(f"summary: {total_synced} lib dirs {'would be synced' if dry_run else 'synced'}, "
|
||||
f"{total_skipped} forks excluded")
|
||||
return 0
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser(description=__doc__)
|
||||
ap.add_argument("--dry-run", action="store_true",
|
||||
help="show what would change without writing")
|
||||
args = ap.parse_args()
|
||||
sys.exit(sweep(args.dry_run))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Loading…
Add table
Add a link
Reference in a new issue