Add methods to serialize Config to TOML.

Two different modes:

 - Serialize the full Config object. This is useful as
   `Config::default().to_toml()` to output a rustfmt.toml with defaults
   (#317).
 - Serialize only the options that have been accessed. This could be
   useful to output a minimal rustfmt.toml for a project. (If the
   default value of any unused config item changes, you'll then get the
   new default when you come to use it).

This commit doesn't expose this anywhere - deciding a sensible CLI is a
bit trickier.

This commit also has very simple error reporting (Result<String,
String>) - once the CLI is decided, a more sensible method of reporting
errors might become obvious.
This commit is contained in:
Michael Killough 2017-05-16 18:08:24 +07:00
parent 4d879662a9
commit 7a4955f705
4 changed files with 71 additions and 10 deletions

View File

@ -25,7 +25,7 @@ macro_rules! configuration_option_enum{
$( $x ),+
}
impl_enum_decodable!($e, $( $x ),+);
impl_enum_serialize_and_deserialize!($e, $( $x ),+);
}
}
@ -247,10 +247,10 @@ macro_rules! create_config {
// Just like the Config struct but with each property wrapped
// as Option<T>. This is used to parse a rustfmt.toml that doesn't
// specity all properties of `Config`.
// We first parse into `ParsedConfig`, then create a default `Config`
// and overwrite the properties with corresponding values from `ParsedConfig`
#[derive(Deserialize, Clone)]
pub struct ParsedConfig {
// We first parse into `PartialConfig`, then create a default `Config`
// and overwrite the properties with corresponding values from `PartialConfig`.
#[derive(Deserialize, Serialize, Clone)]
struct PartialConfig {
$(pub $i: Option<$ty>),+
}
@ -263,7 +263,7 @@ macro_rules! create_config {
}
)+
fn fill_from_parsed_config(mut self, parsed: ParsedConfig) -> Config {
fn fill_from_parsed_config(mut self, parsed: PartialConfig) -> Config {
$(
if let Some(val) = parsed.$i {
self.$i = val;
@ -306,6 +306,38 @@ macro_rules! create_config {
}
}
pub fn used_to_toml(&self) -> Result<String, String> {
let mut partial = PartialConfig {
$(
$i: if self.tracker.was_accessed(stringify!($i)) {
Some(self.$i.clone())
} else {
None
},
)+
};
// file_lines is special and can't be specified in toml.
partial.file_lines = None;
toml::to_string(&partial)
.map_err(|e| format!("Could not output config: {}", e.to_string()))
}
pub fn to_toml(&self) -> Result<String, String> {
let mut partial = PartialConfig {
$(
$i: Some(self.$i.clone()),
)+
};
// file_lines is special and can't be specified in toml.
partial.file_lines = None;
toml::to_string(&partial)
.map_err(|e| format!("Could not output config: {}", e.to_string()))
}
pub fn override_value(&mut self, key: &str, val: &str)
-> result::Result<(), Box<error::Error + Send + Sync>>
{

View File

@ -216,6 +216,16 @@ impl<'de> ::serde::de::Deserialize<'de> for FileLines {
}
}
// We also want to avoid attempting to serialize a FileLines to toml. The
// `Config` struct should ensure this impl is never reached.
impl ::serde::ser::Serialize for FileLines {
fn serialize<S>(&self, _: S) -> Result<S::Ok, S::Error>
where S: ::serde::ser::Serializer
{
panic!("FileLines cannot be serialized. This is a rustfmt bug.");
}
}
#[cfg(test)]
mod test {
use super::Range;

View File

@ -35,7 +35,7 @@ pub enum ListTactic {
Mixed,
}
impl_enum_decodable!(ListTactic, Vertical, Horizontal, HorizontalVertical, Mixed);
impl_enum_serialize_and_deserialize!(ListTactic, Vertical, Horizontal, HorizontalVertical, Mixed);
#[derive(Eq, PartialEq, Debug, Copy, Clone)]
pub enum SeparatorTactic {
@ -44,7 +44,7 @@ pub enum SeparatorTactic {
Vertical,
}
impl_enum_decodable!(SeparatorTactic, Always, Never, Vertical);
impl_enum_serialize_and_deserialize!(SeparatorTactic, Always, Never, Vertical);
impl SeparatorTactic {
pub fn from_bool(b: bool) -> SeparatorTactic {

View File

@ -189,10 +189,29 @@ pub fn trim_newlines(input: &str) -> &str {
}
}
// Macro for deriving implementations of Decodable for enums
// Macro for deriving implementations of Serialize/Deserialize for enums
#[macro_export]
macro_rules! impl_enum_decodable {
macro_rules! impl_enum_serialize_and_deserialize {
( $e:ident, $( $x:ident ),* ) => {
impl ::serde::ser::Serialize for $e {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where S: ::serde::ser::Serializer
{
use serde::ser::Error;
// We don't know whether the user of the macro has given us all options.
#[allow(unreachable_patterns)]
match *self {
$(
$e::$x => serializer.serialize_str(stringify!($x)),
)*
_ => {
Err(S::Error::custom(format!("Cannot serialize {:?}", self)))
}
}
}
}
impl<'de> ::serde::de::Deserialize<'de> for $e {
fn deserialize<D>(d: D) -> Result<Self, D::Error>
where D: ::serde::Deserializer<'de> {