Skip to content

Commit

Permalink
ci(primitives): transition to new css primitives
Browse files Browse the repository at this point in the history
GitHub no longer distributes primitives in JSON format. Also, the names
of the values (i.e. CSS variables) have changed. Most of the new names
correspond 1-to-1 with one of the old names. Some colors have also
changed slightly (e.g. `fg-default`), but otherwise remain mostly the
same. See https://primer.style/foundations/primitives/migrating.

Source color primitives from `@primer/primitives/dist/internalCss`
instead of `@primer/primitives/dist/css/functional/themes` as only the
former directory contains the base colors (scales).

Convert new primer css primitives/variables directly to lua in
.github/workflows/csstolua.lua (runs in CI). This script generates some
debugging info in case an error occurs (which can be found in CI logs).
Convert to a nested table structure for idiomatic usage in lua. The
primitives table now provides type-hints via lsp, and accessing invalid
names at runtime will throw an error. Append `.default` to
names/keypaths which are too short and would otherwise collide with
existing tables.

- `scale` no longer exists, but is still provided (by us) for
  backwards-compatibility and ergonomics. The new names are in the
  format of `base.color.red[4]` (for example to access `scale.red[5]`).
  The values in `scale` are 1-indexed for lua, but the original upstream
  names (in `base.color.*`) are 0-indexed and left untouched.

- `scale.gray` no longer exists, use `scale.neutral` in its place

- `*.subtle` variants no longer exist, see the link above for the
  corresponding replacements
  • Loading branch information
tmillr committed Jul 10, 2024
1 parent 83527b3 commit 04440ef
Show file tree
Hide file tree
Showing 14 changed files with 489 additions and 357 deletions.
145 changes: 145 additions & 0 deletions .github/workflows/csstolua.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
local res = {}

local function is_valid_ident(ident)
local keyword = {
['do'] = true,
['end'] = true,
['if'] = true,
['then'] = true,
['local'] = true,
['function'] = true,
['return'] = true,
['while'] = true,
['repeat'] = true,
['until'] = true,
['for'] = true,
['in'] = true,
['true'] = true,
['false'] = true,
['nil'] = true,
}

if type(ident) ~= 'string' or keyword[ident] then
return false
end

return ident:find('^[_%a][_%w]*$') ~= nil
end

local function set(cssvar, v)
local before, last = cssvar:match('^(.+)%-+(.+)$')
print(('\ncss variable: %s'):format(cssvar))
print(('css variable prefix: %s'):format(before))
print(('css variable suffix: %s'):format(last))
print(('css variable value: %s'):format(v))

-- Top-level key
if not last then
res[tonumber(cssvar) or cssvar] = v
return
end

last = tonumber(last) or last
local cur = res
for k in before:gmatch('[^%-_]+') do
k = tonumber(k) or k
cur[k] = cur[k] or {}
cur = cur[k]
end

-- Path is too short: append `default`
if type(cur[last]) == 'table' then
local suffix = 'default'
print(('keypath is too short, appending suffix "%s"'):format(suffix))
print(('new name: %s-%s'):format(cssvar, suffix))
cur, last = cur[last], suffix
end

-- Check that duplicates have the same value
if cur[last] ~= nil and cur[last] ~= v then
error(([[
variable appears multiple times with different values:
- %s
- %s
]]):format(cur[last], v))
end

cur[last] = v
end

local function print_recur(value, _ind)
_ind = _ind or 0

if type(value) == 'table' then
io.write('m {')
_ind = _ind + 2

for k, v in pairs(value) do
local fmt = '[%q] = '
if type(k) == 'number' then
fmt = '[%s] = '
elseif is_valid_ident(k) then
fmt = '%s = '
end
io.write(('\n%s' .. fmt):format((' '):rep(_ind), k))
print_recur(v, _ind)
io.write(',')
end

_ind = _ind - 2
io.write(('\n%s}'):format((' '):rep(_ind)))
else
io.write(('%q'):format(value))
end
end

local defs = {}
for ln in io.lines() do
local k, v = ln:match('^%s*%-%-(%w.-)%s*:%s*(.-)%s*;%s*$')
if k then
table.insert(defs, { k, v })
end
end

-- Since we are un-flattening, ensure that longer keys (whose prefix could
-- match another key) are visited first.
table.sort(defs, function(a, b)
return a[1] > b[1]
end)

for _, kv in ipairs(defs) do
set(unpack(kv))
end

-- Add `scale` key for convenience and backwards-compatibility
assert(res.scale == nil)
res.scale = {}
for color, scale in pairs(res.base.color) do
if type(scale) == 'table' then
res.scale[color] = {}
for i, v in pairs(scale) do
res.scale[color][i + 1] = v
end
else
res.scale[color] = scale
end
end

-- NOTE: the metatable `mt` helps to catch errors (e.g. during CI tests)
io.write([=[
local mt = {
__index = function(_, k)
error('invalid index: ' .. k)
end,
}
---@generic T
---@param tbl T
---@return T
local function m(tbl)
return setmetatable(tbl, mt)
end
local M = ]=])

print_recur(res)

io.write('\n')
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
name: Get/Update Primer Color Primitives
name: Get/Update Primer Primitives

env:
_DEST_DIR: "${{ github.workspace }}/lua/github-theme/palette/primitives"
_JSON_DIR: "${{ github.workspace }}/node_modules/@primer/primitives/dist/json/colors"
_SRC_DIR: "${{ github.workspace }}/node_modules/@primer/primitives/dist/internalCss"
_LICENSE_GLOB: "${{ github.workspace }}/node_modules/@primer/primitives/[Ll][Ii][Cc][Ee][Nn][Ss][Ee]*"
_PRIMITIVES_PKGJSON: "${{ github.workspace }}/node_modules/@primer/primitives/package.json"
_CSSTOLUA: "${{ github.workspace }}/.github/workflows/csstolua.lua"

on:
workflow_dispatch:
schedule:
# 3x per week (every other day) at 12:40pm Pacific Time
- cron: "40 19 * * 1,3,5"
# once a week, every Monday at 12:40pm Pacific Time
- cron: "40 19 * * 1"

jobs:
get-colors:
install-primitives:
runs-on: ubuntu-latest
permissions:
checks: write
Expand All @@ -25,6 +26,9 @@ jobs:

steps:
- uses: actions/checkout@v4
- uses: rhysd/action-setup-vim@v1
with:
neovim: true

- uses: actions/setup-node@v4
with:
Expand All @@ -34,21 +38,22 @@ jobs:
- run: npm i @primer/primitives@latest

- run: |
set -u +f
set -eu +f
shopt -s nocaseglob failglob
license="$(<$_LICENSE_GLOB)"
rm -r "$_DEST_DIR" || :
mkdir -p "$_DEST_DIR"
cd "$_JSON_DIR"
cd "$_SRC_DIR"
if jq -e .version "$_PRIMITIVES_PKGJSON"; then
version="M._VERSION = vim.json.decode([=[$(jq -e .version "$_PRIMITIVES_PKGJSON")]=], { luanil = { object = false, array = false } })"
fi
for file in *.json; do
cat >|"${_DEST_DIR}/${file%.json}.lua" <<EOF
for file in *.css; do
values="$(nvim -l "$_CSSTOLUA" < "$file")"
cat >| "${_DEST_DIR}/$(tr '-' '_' <<< "${file%.css}.lua")" <<EOF
-- NOTE: THIS IS AN AUTO-GENERATED FILE. DO NOT EDIT BY-HAND.
local M = vim.json.decode([=[$(<"$file")]=], { luanil = { object = false, array = false } })
${values}
${version-}
M._LICENSE = [=[
$license]=]
Expand All @@ -64,7 +69,7 @@ jobs:

- uses: peter-evans/create-pull-request@v6
with:
commit-message: Update color primitives
branch: update-color-primitives
commit-message: 'deps(primer): update primitives'
branch: update-primer-primitives
delete-branch: true
title: Update color primitives
title: 'deps(primer): update primitives'
2 changes: 1 addition & 1 deletion lua/github-theme/group/modules/treesitter.lua
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ function M.get(spec, config, opts)
:gsub('^github_(.-)$', '%1')
)

local pl = primitives.prettylights
local pl = primitives.color.prettylights
local syn = spec.syntax
local stl = config.styles
local P = spec.palette
Expand Down
60 changes: 30 additions & 30 deletions lua/github-theme/palette/github_dark.lua
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ local meta = {
local primitives =
require('github-theme.palette.primitives.' .. meta.name:gsub('^github%W*', '', 1))

local pl = primitives.prettylights
local pl = primitives.color.prettylights
local scale = primitives.scale

C.WHITE = C(scale.white)
C.BLACK = C(scale.black)
C.BG = C(scale.gray[7])
C.BG = C(scale.neutral[7])

local function alpha(color, a)
return color:alpha_blend(a):to_css()
Expand All @@ -24,40 +24,40 @@ local palette = {

orange = scale.orange[4],

black = { base = scale.gray[10], bright = scale.gray[9] },
gray = { base = scale.gray[5], bright = scale.gray[5] },
black = { base = scale.neutral[10], bright = scale.neutral[9] },
gray = { base = scale.neutral[5], bright = scale.neutral[5] },
blue = { base = scale.blue[4], bright = scale.blue[3] },
green = { base = scale.green[4], bright = scale.green[3] },
magenta = { base = scale.purple[4], bright = scale.purple[3] },
pink = { base = scale.pink[4], bright = scale.pink[3] },
red = { base = scale.red[4], bright = scale.red[3] },
white = { base = scale.gray[3], bright = scale.gray[3] },
white = { base = scale.neutral[3], bright = scale.neutral[3] },
yellow = { base = scale.yellow[4], bright = scale.yellow[3] },
cyan = { base = '#76e3ea', bright = '#b3f0ff' },

fg = {
default = '#e6edf3',
muted = '#7d8590',
subtle = scale.gray[5],
subtle = scale.neutral[5],
on_emphasis = scale.white,
},

canvas = {
default = scale.gray[7],
overlay = scale.gray[9],
inset = scale.gray[8],
subtle = scale.gray[9],
default = scale.neutral[7],
overlay = scale.neutral[9],
inset = scale.neutral[8],
subtle = scale.neutral[9],
},

border = {
default = scale.gray[9],
muted = scale.gray[8],
default = scale.neutral[9],
muted = scale.neutral[8],
subtle = alpha(C.from_rgba(240, 246, 252, 1), 0.1),
},

neutral = {
emphasis_plus = scale.gray[5],
emphasis = scale.gray[5],
emphasis_plus = scale.neutral[5],
emphasis = scale.neutral[5],
muted = alpha(C.from_rgba(110, 118, 129, 1), 0.4),
subtle = alpha(C.from_rgba(110, 118, 129, 1), 0.1),
},
Expand Down Expand Up @@ -129,16 +129,16 @@ local palette = {
local function generate_spec(pal)
-- stylua: ignore start
local spec = {
bg0 = alpha(C(pal.canvas.inset), 0.75), -- Dark bg (popup and float)
bg1 = pal.canvas.default, -- Default bg
bg0 = alpha(C(primitives.bgColor.inset), 0.75), -- Dark bg (popup and float)
bg1 = primitives.bgColor.default, -- Default bg
bg2 = alpha(C(pal.neutral.emphasis), 0.1), -- Lighter bg (colorcolumn Folds)
bg3 = pal.scale.gray[6], -- Lighter bg (cursor line)
bg4 = pal.scale.gray[4], -- Conceal
bg3 = pal.scale.neutral[6], -- Lighter bg (cursor line)
bg4 = pal.scale.neutral[4], -- Conceal

fg0 = pal.fg.subtle, -- Lighter fg
fg1 = pal.fg.default, -- Default fg
fg1 = primitives.fgColor.default, -- Default fg
fg2 = pal.fg.muted, -- Darker fg (status line)
fg3 = pal.scale.gray[5], -- Darker fg (line numbers, fold columns)
fg3 = pal.scale.neutral[5], -- Darker fg (line numbers, fold columns)

sel0 = alpha(C(pal.accent.fg), 0.30), -- Visual selection bg
sel1 = alpha(C(pal.accent.muted), 0.90), -- Popup sel bg
Expand All @@ -147,26 +147,26 @@ local function generate_spec(pal)

spec.syntax = {
bracket = spec.fg1, -- Brackets and Punctuation
builtin0 = pl.syntax.constant, -- Builtin variable
builtin0 = pl.syntax.constant.default, -- Builtin variable
builtin1 = pl.syntax.keyword, -- Builtin type
builtin2 = pl.syntax.constant, -- Builtin const
builtin2 = pl.syntax.constant.default, -- Builtin const
comment = pl.syntax.comment, -- Comment
conditional = pl.syntax.keyword, -- Conditional and loop
const = pl.syntax.constant, -- Constants, imports and booleans
const = pl.syntax.constant.default, -- Constants, imports and booleans
dep = pal.scale.red[3], -- Deprecated
field = pl.syntax.constant, -- Field
func = pl.syntax.entity, -- Functions and Titles
field = pl.syntax.constant.default, -- Field
func = pl.syntax.entity.default, -- Functions and Titles
ident = spec.fg1, -- Identifiers
keyword = pl.syntax.keyword, -- Keywords
number = pl.syntax.constant, -- Numbers
operator = pl.syntax.constant, -- Operators
number = pl.syntax.constant.default, -- Numbers
operator = pl.syntax.constant.default, -- Operators
param = spec.fg1, -- Parameters
preproc = pl.syntax.keyword, -- PreProc
regex = pl.syntax.string, -- Regex
regex = pl.syntax.string.default, -- Regex
statement = pl.syntax.keyword, -- Statements
string = pl.syntax.string, -- Strings
string = pl.syntax.string.default, -- Strings
type = pl.syntax.variable, -- Types
tag = pl.syntax.entityTag, -- Tags
tag = pl.syntax.entity.tag, -- Tags
variable = spec.fg1, -- Variables
}

Expand Down
Loading

0 comments on commit 04440ef

Please sign in to comment.