{{ const dotUtils = require('./dotUtils.js'); const champions = require('./.champion.json') .filter(({ id }) => id > 0) .sortBy(({ name }) => name); const constName = name => dotUtils.changeCase.constantCase(name).replace(/[^_A-Z0-9]+/g, ''); const constNamePad = 12; }}{{= dotUtils.preamble() }} newtype_enum! { /// A League of Legends champion. /// /// This newtype acts as a C-like enum; each variant corresponds to an /// integer value. Using a newtype allows _unknown_ variants to be /// represented. This is important when Riot adds new champions. /// /// Field | Name | Identifier | Id /// ---|---|---|--- {{ for (const { id, alias, name } of champions) { }} /// `{{= constName(name) }}` | "{{= name }}" | "{{= alias }}" | {{= id }} {{ } }} #[derive(serde::Serialize, serde::Deserialize)] #[serde(transparent)] pub newtype_enum Champion(i16) { {{ for (const { id, alias, name } of champions) { }} /// `{{= id }}`. {{= constName(name) }} = {{= id }}, {{ } }} } } impl Champion { /// The champion's name (`en_US` localization). pub const fn name(self) -> Option<&'static str> { match self { {{ for (const { name } of champions) { }} Self::{{= constName(name).padEnd(constNamePad) }} => Some("{{= name }}"), {{ } }} _ => None, } } /// The champion's identifier key. Somtimes called "key", "identifier", or "alias". /// This is mainly used in DDragon paths. /// /// This is generally the `en_US` name with spaces and punctuation removed, /// capitalization preserved, however the follow are exceptions: /// /// Field | Name | Identifier | Id /// ---|---|---|--- {{ for (const { id, alias, name } of champions) { if (name.replace(/[^a-zA-Z0-9]+/, '') !== alias) { }} /// `{{= constName(name) }}` | "{{= name }}" | "{{= alias }}" | {{= id }} {{ } } }} pub const fn identifier(self) -> Option<&'static str> { match self { {{ for (const { name, alias } of champions) { }} Self::{{= constName(name).padEnd(constNamePad) }} => Some("{{= alias }}"), {{ } }} _ => None, } } /// https://github.com/MingweiSamuel/Riven/issues/36 pub(crate) fn serialize_result( val: &Result, serializer: S, ) -> Result where S: serde::ser::Serializer, { use serde::ser::Serialize; val.unwrap_or(Champion(-1)).serialize(serializer) } /// https://github.com/MingweiSamuel/Riven/issues/36 pub(crate) fn deserialize_result<'de, D>( deserializer: D, ) -> Result, D::Error> where D: serde::de::Deserializer<'de>, { use std::convert::TryInto; ::deserialize(deserializer).map(|id| id.try_into().map(Self)) } } /// The error used for failures in [`Champion::from_str`]. /// /// Currently only internally stores the four characters used to parse the /// champion, but may change in the future. #[derive(Debug)] pub struct ParseChampionError([char; 4]); impl std::fmt::Display for ParseChampionError { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { let s: String = self.0.iter().copied().take_while(|&c| '\0' != c).collect(); write!(f, "Failed to parse unknown champion prefix: {:?}", s) } } impl std::error::Error for ParseChampionError {} impl std::str::FromStr for Champion { type Err = ParseChampionError; fn from_str(s: &str) -> Result { let mut chars = ['\0'; 4]; s.chars() .take(4) .filter(|c| c.is_ascii_alphanumeric()) .map(|c| c.to_ascii_uppercase()) .enumerate() .for_each(|(i, c)| chars[i] = c); match chars { {{ const keyStrings = (name, alias) => new Set([].concat(...[ name, alias ].map(s => s.toUpperCase()) .map(s => [ s.replace(/[^A-Z0-9]+/, '').substring(0, 4), s.split(/[^A-Z0-9]/, 1)[0].substring(0, 4), s.split(/[^A-Z]/, 1)[0].substring(0, 4), ]))); for (const { id, alias, name } of champions) { for (const prefix of keyStrings(name, alias)) { const chars = Object.assign(Array(4).fill('\\0'), Array.from(prefix)) .map(c => `'${c}'`) .map(c => c.padStart(4)); }} /* {{= prefix.padEnd(4) }} */ [{{= chars.join(', ') }}] => Ok(Champion::{{= constName(name) }}), {{ } } }} unknown => Err(ParseChampionError(unknown)), } } } impl std::convert::TryFrom<&str> for Champion { type Error = ::Err; fn try_from(value: &str) -> Result { ::from_str(value) } }