use std::collections::HashMap;
use clap::{Parser, ValueEnum};
macro_rules! features {
($($name:ident = $enabled:literal, $url:literal),* $(,)?) => {
paste::paste! {
pub const CFG: &[&str] = &[
$(
stringify!([<experimental_ $name:snake>]),
)*
];
#[derive(Copy, Clone, Debug, ValueEnum)]
#[value(rename_all = "snake")]
pub enum Feature {
$(
[<$name:camel>],
)*
}
impl std::str::FromStr for Feature {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
$(
stringify!([<$name:snake>]) => {
Ok(Self::[<$name:camel>])
},
)*
_ => Err(Error::UnknownFeature(s.to_string())),
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct ExperimentalFeatures {
$(
pub [<$name:snake>]: bool,
)*
}
impl std::default::Default for ExperimentalFeatures {
fn default() -> Self {
Self {
$(
[<$name:snake>]: $enabled,
)*
}
}
}
impl ExperimentalFeatures {
pub fn set_enabled_by_name(&mut self, feature: &str, enabled: bool) -> Result<(), Error> {
let feature = feature.trim();
match feature {
$(
stringify!([<$name:snake>]) => {
self.[<$name:snake>] = enabled;
Ok(())
},
)*
"" => Ok(()),
_ => Err(Error::UnknownFeature(feature.to_string())),
}
}
pub fn set_enabled(&mut self, feature: Feature, enabled: bool) {
match feature {
$(
Feature::[<$name:camel>] => {
self.[<$name:snake>] = enabled
},
)*
}
}
pub fn is_enabled_for_cfg(&self, cfg: &str) -> Result<bool, Error> {
match cfg {
$(
stringify!([<experimental_ $name:snake>]) => Ok(self.[<$name:snake>]),
)*
_ => Err(Error::UnknownFeature(cfg.to_string()))
}
}
$(
pub fn [<with_ $name:snake>](mut self, enabled: bool) -> Self {
self.[<$name:snake>] = enabled;
self
}
)*
}
}
};
}
impl ExperimentalFeatures {
pub fn new(
manifest: &HashMap<String, bool>,
cli_experimental: &[Feature],
cli_no_experimental: &[Feature],
) -> Result<ExperimentalFeatures, Error> {
let mut experimental = ExperimentalFeatures::default();
experimental.parse_from_package_manifest(manifest)?;
for f in cli_no_experimental {
experimental.set_enabled(*f, false);
}
for f in cli_experimental {
experimental.set_enabled(*f, true);
}
experimental.parse_from_environment_variables()?;
Ok(experimental)
}
}
features! {
new_encoding = true,
"https://github.com/FuelLabs/sway/issues/5727",
storage_domains = false,
"https://github.com/FuelLabs/sway/issues/6701",
}
#[derive(Clone, Debug, Default, Parser)]
pub struct CliFields {
#[clap(long, value_delimiter = ',')]
pub experimental: Vec<Feature>,
#[clap(long, value_delimiter = ',')]
pub no_experimental: Vec<Feature>,
}
#[derive(Debug)]
pub enum Error {
ParseError(String),
UnknownFeature(String),
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Error::ParseError(feature) => f.write_fmt(format_args!(
"Experimental feature \"{feature}\" cannot be parsed."
)),
Error::UnknownFeature(feature) => {
f.write_fmt(format_args!("Unknown experimental feature: \"{feature}\"."))
}
}
}
}
impl ExperimentalFeatures {
pub fn parse_from_package_manifest(
&mut self,
experimental: &std::collections::HashMap<String, bool>,
) -> Result<(), Error> {
for (feature, enabled) in experimental {
self.set_enabled_by_name(feature, *enabled)?;
}
Ok(())
}
pub fn parse_from_environment_variables(&mut self) -> Result<(), Error> {
if let Ok(features) = std::env::var("FORC_NO_EXPERIMENTAL") {
self.parse_comma_separated_list(&features, false)?;
}
if let Ok(features) = std::env::var("FORC_EXPERIMENTAL") {
self.parse_comma_separated_list(&features, true)?;
}
Ok(())
}
pub fn parse_comma_separated_list(
&mut self,
features: impl AsRef<str>,
enabled: bool,
) -> Result<(), Error> {
for feature in features.as_ref().split(',') {
self.set_enabled_by_name(feature, enabled)?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
struct RollbackEnvVar(String, Option<String>);
impl RollbackEnvVar {
pub fn new(name: &str) -> Self {
let old = std::env::var(name).ok();
RollbackEnvVar(name.to_string(), old)
}
}
impl Drop for RollbackEnvVar {
fn drop(&mut self) {
if let Some(old) = self.1.take() {
std::env::set_var(&self.0, old);
}
}
}
#[test]
fn ok_parse_experimental_features() {
let _old = RollbackEnvVar::new("FORC_EXPERIMENTAL");
let _old = RollbackEnvVar::new("FORC_NO_EXPERIMENTAL");
let mut features = ExperimentalFeatures {
new_encoding: false,
..Default::default()
};
std::env::set_var("FORC_EXPERIMENTAL", "new_encoding");
std::env::set_var("FORC_NO_EXPERIMENTAL", "");
assert!(!features.new_encoding);
let _ = features.parse_from_environment_variables();
assert!(features.new_encoding);
std::env::set_var("FORC_EXPERIMENTAL", "");
std::env::set_var("FORC_NO_EXPERIMENTAL", "new_encoding");
assert!(features.new_encoding);
let _ = features.parse_from_environment_variables();
assert!(!features.new_encoding);
}
}