1
1
mirror of https://github.com/wez/wezterm.git synced 2024-12-23 05:12:40 +03:00

color schemes: add alias concept

Various color schemes have been duplicated as they have been added to
different scheme collections.  They don't always have identical names
(eg: some remove spaces) and sometimes they have very different names
(eg: _bash vs. nightfox, or Miu vs. Blazer).

We already detected duplicates from different collections but previously
we would omit those dupes.

This commit allows us to track those duplicates by recording their
aliases.

When we write out our data, we only include "interesting" alias names;
those where the name isn't trivially identical.

Some scheme collections (eg: iterm2 color schemes) have duplicates
(eg: zenbones and zenbones_light are identical) and we have previously
shipped with both of those names, so we special case to emit dupes
for which we have prior version information in order to avoid
breaking backwards compatibility for our users.

In the doc generation we can generate links to the aliases if we
included them, but also note about the other names and how we don't
include them.  That is so that someone searching the docs for say
"_bash" can discover that it is actually a duplicate of "nightfox" and
use nightfox instead.
This commit is contained in:
Wez Furlong 2022-07-20 07:32:46 -07:00
parent ffb6ea76cd
commit 50d5f94ab0
12 changed files with 1781 additions and 778 deletions

2
Cargo.lock generated
View File

@ -4272,9 +4272,11 @@ dependencies = [
"anyhow",
"color-funcs",
"config",
"env_logger",
"futures",
"lazy_static",
"libflate",
"log",
"reqwest",
"rusqlite",
"serde",

View File

@ -185,12 +185,23 @@ class GenColorScheme(object):
with open("colorschemes/data.json") as f:
scheme_data = json.load(f)
by_prefix = {}
by_name = {}
for scheme in scheme_data:
scheme = load_scheme(scheme)
prefix = scheme["prefix"]
if prefix not in by_prefix:
by_prefix[prefix] = []
by_prefix[prefix].append(scheme)
by_name[scheme["name"]] = scheme
def scheme_link(name):
if name in by_name:
scheme = by_name[name]
prefix = scheme["prefix"]
ident = scheme["ident"]
return f"../{prefix}/index.md#{ident}"
else:
return None
children = []
for scheme_prefix in sorted(by_prefix.keys()):
@ -239,6 +250,25 @@ window.addEventListener('load', function () {{
if version and version != "Always":
idx.write(f"Since: *{version}*<br/>\n")
aliases = scheme["metadata"]["aliases"]
if len(aliases) > 1:
canon_name = aliases[0]
am_canon = title == canon_name
if am_canon:
alias_list = []
for a in aliases[1:]:
alias_link = scheme_link(a)
if alias_link:
alias_list.append(f"[{a}]({alias_link})")
else:
alias_list.append(f"`{a}` (but that name isn't used here)")
aliases = ', '.join(alias_list)
idx.write(f"This scheme is also known as {aliases}.<br/>\n")
else:
canon_link = scheme_link(canon_name)
idx.write(f"This scheme is the same as [{canon_name}]({canon_link}).<br/>\n")
idx.write("\nTo use this scheme, add this to your config:\n")
idx.write(
f"""

View File

@ -438,6 +438,8 @@ pub struct ColorSchemeMetaData {
pub author: Option<String>,
pub origin_url: Option<String>,
pub wezterm_version: Option<String>,
#[dynamic(default)]
pub aliases: Vec<String>,
}
impl_lua_conversion_dynamic!(ColorSchemeMetaData);

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -74,6 +74,7 @@ impl Base16Scheme {
author: Some(scheme.author.clone()),
origin_url: None,
wezterm_version: None,
aliases: vec![],
},
})
}

View File

@ -73,6 +73,7 @@ impl GoghTheme {
author: None,
origin_url: None,
wezterm_version: None,
aliases: vec![],
},
})
}

View File

@ -137,6 +137,7 @@ impl ITerm2 {
author,
origin_url,
wezterm_version: None,
aliases: vec![],
},
})
}

View File

@ -51,6 +51,7 @@ impl Sexy {
author: Some(sexy.author),
origin_url: None,
wezterm_version: None,
aliases: vec![],
},
})
}

View File

@ -9,9 +9,11 @@ edition = "2021"
anyhow = "1.0"
color-funcs = { path = "../lua-api-crates/color-funcs" }
config = { path = "../config" }
env_logger = "0.9"
futures = "0.3"
lazy_static = "1.4"
libflate = "1"
log = "0.4"
reqwest = "0.11"
rusqlite = {version="0.27", features=["bundled", "blob"]}
serde = {version="1.0", features=["derive"]}

View File

@ -94,6 +94,26 @@ fn make_prefix(key: &str) -> (char, String) {
panic!("no good prefix");
}
const KNOWN_NAMESPACES: &[&str] = &[" (base16)", " (terminal.sexy)", " (Gogh)"];
fn known_namespace(name: &str) -> bool {
for ns in KNOWN_NAMESPACES {
if name.ends_with(ns) {
return true;
}
}
false
}
fn suffixed_alias_matches_name(alias: &str, name: &str) -> bool {
for ns in KNOWN_NAMESPACES {
if let Some(stripped) = alias.strip_suffix(ns) {
return stripped == name;
}
}
false
}
fn bake_for_config(mut schemeses: Vec<Scheme>) -> anyhow::Result<()> {
let json_file_name = "docs/colorschemes/data.json";
@ -112,7 +132,7 @@ fn bake_for_config(mut schemeses: Vec<Scheme>) -> anyhow::Result<()> {
let existing: Vec<MetaOnly> = serde_json::from_str(&data)?;
for item in &existing {
if let Some(version) = &item.metadata.wezterm_version {
version_by_name.insert(&item.metadata.name, version.as_str());
version_by_name.insert(item.metadata.name.to_string(), version.to_string());
}
}
@ -120,13 +140,10 @@ fn bake_for_config(mut schemeses: Vec<Scheme>) -> anyhow::Result<()> {
// We're bootstrapping the version info
for item in &existing {
let name = &item.metadata.name;
if name.ends_with("(base16)")
|| name.ends_with("(terminal.sexy)")
|| name.ends_with("(Gogh)")
{
if known_namespace(name) {
continue;
}
version_by_name.insert(name, "Always");
version_by_name.insert(name.to_string(), "Always".to_string());
}
}
@ -142,8 +159,38 @@ fn bake_for_config(mut schemeses: Vec<Scheme>) -> anyhow::Result<()> {
}
let mut all = vec![];
for s in &schemeses {
// Only interested in aliases with different-enough names
let mut aliases = s.data.metadata.aliases.clone();
aliases.retain(|name| !suffixed_alias_matches_name(&name, &s.name));
let count = schemeses.len();
// Normalize the list of aliases so that the canonical
// name is included in the list at the start.
let mut s = s.clone();
s.data.metadata.aliases = aliases.clone();
if !aliases.is_empty() {
s.data.metadata.aliases.insert(0, s.name.clone());
}
all.push(s.clone());
// Only instantiate aliases in our scheme data if
// we had previously shipped a version with that
// name in use
for name in aliases {
if version_by_name.get(&name).is_some() {
let mut alias = Scheme {
name: name.clone(),
..s.clone()
};
alias.data.metadata.name.replace(name.clone());
all.push(alias);
}
}
}
all.sort_by_key(|s| make_prefix(&s.name));
let count = all.len();
let mut code = String::new();
code.push_str(&format!(
"//! This file was generated by sync-color-schemes\n
@ -151,10 +198,6 @@ pub const SCHEMES: [(&'static str, &'static str); {count}] = [\n
// Start here
",
));
for s in &schemeses {
all.push(s);
}
all.sort_by_key(|s| make_prefix(&s.name));
for s in &all {
let name = s.name.escape_default();
@ -172,7 +215,7 @@ pub const SCHEMES: [(&'static str, &'static str); {count}] = [\n
};
if update {
eprintln!("Updating {file_name}");
println!("Updating {file_name}");
std::fs::write(file_name, code)?;
}
}
@ -192,34 +235,37 @@ pub const SCHEMES: [(&'static str, &'static str); {count}] = [\n
};
if update {
eprintln!("Updating {json_file_name}");
println!("Updating {json_file_name}");
std::fs::write(json_file_name, json)?;
}
Ok(())
}
fn scheme_exists<'a>(schemeses: &'a [Scheme], candidate: &Scheme) -> Option<&'a str> {
for existing in schemeses {
fn push_or_alias(schemeses: &mut Vec<Scheme>, candidate: Scheme) -> bool {
let mut aliased = false;
for existing in schemeses.iter_mut() {
if candidate.data.colors.ansi == existing.data.colors.ansi
&& candidate.data.colors.brights == existing.data.colors.brights
&& candidate.data.colors.foreground == existing.data.colors.foreground
&& candidate.data.colors.background == existing.data.colors.background
{
return Some(&existing.name);
log::info!("Adding {} as alias of {}", candidate.name, existing.name);
existing.data.metadata.aliases.push(candidate.name.clone());
aliased = true;
}
}
None
if !aliased {
log::info!("Adding {}", candidate.name);
schemeses.push(candidate);
}
aliased
}
fn accumulate(schemeses: &mut Vec<Scheme>, to_add: Vec<Scheme>) {
// Only accumulate if the scheme looks different enough
for candidate in to_add {
if let Some(existing) = scheme_exists(schemeses, &candidate) {
println!("{} is same as {}", candidate.name, existing);
continue;
}
schemeses.push(candidate);
push_or_alias(schemeses, candidate);
}
}
@ -248,37 +294,34 @@ async fn sync_toml(
let data = std::fs::read_to_string(dest_file.path())?;
if let Ok(mut scheme) = ColorSchemeFile::from_toml_str(&data) {
let name = match scheme.metadata.name {
Some(name) => name,
None => entry
.path()?
.file_stem()
.unwrap()
.to_str()
.unwrap()
.to_string(),
};
let name = format!("{name}{suffix}");
scheme.metadata.name = Some(name.clone());
scheme.metadata.origin_url = Some(repo_url.to_string());
apply_nightly_version(&mut scheme.metadata);
match ColorSchemeFile::from_toml_str(&data) {
Ok(mut scheme) => {
let name = match scheme.metadata.name {
Some(name) => name,
None => entry
.path()?
.file_stem()
.unwrap()
.to_str()
.unwrap()
.to_string(),
};
let name = format!("{name}{suffix}");
scheme.metadata.name = Some(name.clone());
scheme.metadata.origin_url = Some(repo_url.to_string());
apply_nightly_version(&mut scheme.metadata);
let scheme = Scheme {
name: name.clone(),
file_name: None,
data: scheme,
};
let scheme = Scheme {
name: name.clone(),
file_name: None,
data: scheme,
};
if !suffix.is_empty() {
// A pre-existing entry is considered canonical
if let Some(existing) = scheme_exists(schemeses, &scheme) {
println!("{} is same as {}", name, existing);
continue;
}
push_or_alias(schemeses, scheme);
}
Err(err) => {
log::error!("{tarball_url}/{}: {err:#}", entry.path().unwrap().display());
}
schemeses.push(scheme);
}
}
}
@ -287,8 +330,14 @@ async fn sync_toml(
#[tokio::main]
async fn main() -> anyhow::Result<()> {
env_logger::init();
// They color us! my precious!
let mut schemeses = iterm2::sync_iterm2().await.context("sync iterm2")?;
let mut schemeses = vec![];
accumulate(
&mut schemeses,
iterm2::sync_iterm2().await.context("sync iterm2")?,
);
sync_toml(
"https://github.com/catppuccin/wezterm",
"main",

View File

@ -1,6 +1,6 @@
use super::*;
#[derive(Debug, PartialEq)]
#[derive(Debug, PartialEq, Clone)]
pub struct Scheme {
pub name: String,
pub file_name: Option<String>,