Initial commit: Tonalmine, a Material Design 3 theme for Redmine 7
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
# Builds tonalmine/images/icons.svg from tools/icon_map.yml.
|
||||
# Usage (from repo root): ruby tools/build_icons.rb
|
||||
# Ruby stdlib only. Downloads are cached in tools/.cache/.
|
||||
require 'yaml'
|
||||
require 'net/http'
|
||||
require 'uri'
|
||||
require 'fileutils'
|
||||
|
||||
CORE_ICONS = 'https://raw.githubusercontent.com/redmine/redmine/7.0-stable/config/icon_source.yml'
|
||||
SYMBOLS = 'https://raw.githubusercontent.com/google/material-design-icons/master/symbols/web'
|
||||
ROOT = File.expand_path('..', __dir__)
|
||||
CACHE = File.join(ROOT, 'tools/.cache')
|
||||
OUT = File.join(ROOT, 'tonalmine/images/icons.svg')
|
||||
|
||||
def fetch(url)
|
||||
res = Net::HTTP.get_response(URI(url))
|
||||
res.is_a?(Net::HTTPSuccess) ? res.body : nil
|
||||
end
|
||||
|
||||
def fail_with(message, items)
|
||||
warn "#{message}:\n #{items.join("\n ")}"
|
||||
exit 1
|
||||
end
|
||||
|
||||
# 1. Map must cover exactly the core icon names.
|
||||
core_yml = fetch(CORE_ICONS) or fail_with('Could not fetch', [CORE_ICONS])
|
||||
core = YAML.safe_load(core_yml).map { |e| e['name'] }.uniq
|
||||
map = YAML.load_file(File.join(ROOT, 'tools/icon_map.yml'))
|
||||
names = map.map { |e| e['name'] }
|
||||
dupes = names.select { |n| names.count(n) > 1 }.uniq
|
||||
fail_with('Duplicate names in icon_map.yml', dupes) if dupes.any?
|
||||
missing = core - names
|
||||
extra = names - core
|
||||
fail_with('Missing from icon_map.yml', missing) if missing.any?
|
||||
fail_with('Not in Redmine icon_source.yml (extra)', extra) if extra.any?
|
||||
|
||||
# 2. Download (or read cached) SVGs.
|
||||
FileUtils.mkdir_p(CACHE)
|
||||
not_found = []
|
||||
symbols = map.sort_by { |e| e['name'] }.map do |e|
|
||||
file = "#{e['symbol']}#{e['fill'] ? '_fill1' : ''}_24px.svg"
|
||||
cached = File.join(CACHE, file)
|
||||
unless File.exist?(cached)
|
||||
svg = fetch("#{SYMBOLS}/#{e['symbol']}/materialsymbolsoutlined/#{file}")
|
||||
next not_found << "#{e['name']} -> #{file}" unless svg
|
||||
File.write(cached, svg)
|
||||
end
|
||||
paths = File.read(cached).scan(/<path\b[^>]*\bd="([^"]+)"/).flatten
|
||||
fail_with('No <path> found in', [cached]) if paths.empty?
|
||||
body = paths.map { |d| %(<path d="#{d}" fill="currentColor" stroke="none"/>) }.join
|
||||
%(<symbol id="icon--#{e['name']}" viewBox="0 -960 960 960">#{body}</symbol>)
|
||||
end
|
||||
fail_with('Material Symbol not found', not_found) if not_found.any?
|
||||
|
||||
# 3. Write the sprite.
|
||||
File.write(OUT, <<~SVG)
|
||||
<svg xmlns="http://www.w3.org/2000/svg">
|
||||
<!-- Generated by tools/build_icons.rb from tools/icon_map.yml. Do not edit by hand.
|
||||
Icons: Material Symbols Outlined by Google (github.com/google/material-design-icons),
|
||||
Apache License 2.0, see LICENSE-material-symbols.txt. -->
|
||||
#{symbols.join("\n")}
|
||||
</svg>
|
||||
SVG
|
||||
puts "Wrote #{symbols.size} icons to #{OUT.sub("#{ROOT}/", '')}"
|
||||
Reference in New Issue
Block a user