diff --git a/src/format.rs b/src/format.rs index b3fe6d6..0610580 100644 --- a/src/format.rs +++ b/src/format.rs @@ -1,31 +1,222 @@ use std::fmt; +use crate::{Config, meta::{Meta, FieldKind, LeafKind, Expr}}; -/// Adds zero, one or two line breaks to make sure that there are at least two -/// line breaks at the end of the string. Except if the buffer is completely -/// empty, in which case it is not modified. -pub(crate) fn add_empty_line(out: &mut String) { - match () { - () if out.is_empty() => {}, - () if out.ends_with("\n\n") => {}, - () if out.ends_with('\n') => out.push('\n'), - _ => out.push_str("\n\n"), + +/// Trait abstracting over the format differences when it comes to formatting a +/// configuration template. +/// +/// To implement this yourself, take a look at the existing impls for guidance. +pub trait Formatter { + /// A type that is used to print expressions. + type ExprPrinter: fmt::Display + From<&'static Expr>; + + /// Internal buffer, mainly used for `make_gap` and similar methods. + fn buffer(&mut self) -> &mut String; + + /// Returns internal buffer by value. + fn finish(self) -> String; + + /// Write a comment, e.g. `format!("#{comment}")`. Don't add a space after + /// your comment token. + fn comment(&mut self, comment: impl fmt::Display); + + /// Write a commented-out field with optional value, e.g. `format!("#{name} = {value}")`. + fn disabled_field(&mut self, name: &'static str, value: Option<&'static Expr>); + + /// Start a nested configuration section with the given name. + fn start_nested(&mut self, name: &'static str, doc: &[&'static str]); + + /// End a nested configuration section. + fn end_nested(&mut self); + + /// Called after the global docs are written and before and fields are + /// emitted. Default impl does nothing. + fn start_main(&mut self) {} + + /// Called after all fields have been emitted (basically the very end). + /// Default impl does nothing. + fn end_main(&mut self) {} + + /// Emits a comment describing that this field can be loaded from the given + /// env var. Default impl is likely sufficient. + fn env_comment(&mut self, env_key: &'static str) { + self.comment(format_args!(" Can also be specified via environment variable `{env_key}`.")); } -} -pub(crate) fn assert_single_trailing_newline(out: &mut String) { - while out.ends_with("\n\n") { - out.pop(); + /// Emits a comment either stating that this field is required, or + /// specifying the default value. Default impl is likely sufficient. + fn default_or_required_comment(&mut self, default_value: Option<&'static Expr>) { + match default_value { + None => self.comment(format_args!(" Required! This value must be specified.")), + Some(v) => self.comment(format_args!(" Default value: {}", Self::ExprPrinter::from(v))), + } } -} -pub(crate) struct DefaultValueComment(pub(crate) Option); + /// Makes sure that there is a gap of at least `size` many empty lines at + /// the end of the buffer. Does nothing when the buffer is empty. + fn make_gap(&mut self, size: u8) { + if !self.buffer().is_empty() { + let num_trailing_newlines = self.buffer().chars() + .rev() + .take_while(|c| *c == '\n') + .count(); -impl fmt::Display for DefaultValueComment { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match &self.0 { - None => "Required! This value must be specified.".fmt(f), - Some(v) => write!(f, "Default value: {v}"), + let newlines_needed = (size as usize + 1).saturating_sub(num_trailing_newlines); + let buffer = self.buffer(); + for _ in 0..newlines_needed { + buffer.push('\n'); + } + } + } + + /// Makes sure the buffer ends with a single trailing newline. + fn assert_single_trailing_newline(&mut self) { + let buffer = self.buffer(); + if buffer.ends_with('\n') { + while buffer.ends_with("\n\n") { + buffer.pop(); + } + } else { + buffer.push('\n'); } } } + +/// General (non format-dependent) formatting options. +pub struct Options { + /// Whether to include doc comments (with your own text and information + /// about whether a value is required and/or has a default). Default: + /// `true`. + pub comments: bool, + + /// If `comments` and this field are `true`, leaf fields with `env = "FOO"` + /// attribute will have a line like this added: + /// + /// ```text + /// # Can also be specified via environment variable `FOO`. + /// ``` + /// + /// Default: `true`. + pub env_keys: bool, + + /// Number of lines between leaf fields. Gap between leaf and nested fields + /// is the bigger of this and `nested_field_gap`. + /// + /// Default: `if self.comments { 1 } else { 0 }`. + pub leaf_field_gap: Option, + + /// Number of lines between nested fields. Gap between leaf and nested + /// fields is the bigger of this and `leaf_field_gap`. + /// + /// Default: 1. + pub nested_field_gap: u8, + + // Potential future options: + // - Comment out default values (`#foo = 3` vs `foo = 3`) + // - Which docs to include from nested objects +} + +impl Options { + fn leaf_field_gap(&self) -> u8 { + self.leaf_field_gap.unwrap_or(self.comments as u8) + } +} + +impl Default for Options { + fn default() -> Self { + Self { + comments: true, + env_keys: true, + leaf_field_gap: None, + nested_field_gap: 1, + } + } +} + +/// Formats a configuration template with the given formatter. +/// +/// If you don't need to use a custom formatter, rather look at the `format` +/// functions in the format-specific modules (e.g. `toml::format`, +/// `yaml::format`). +pub fn format(out: &mut impl Formatter, options: Options) { + let meta = &C::META; + + // Print root docs. + if options.comments { + meta.doc.iter().for_each(|doc| out.comment(doc)); + } + + // Recursively format all nested objects and fields + out.start_main(); + format_impl(out, meta, &options); + out.end_main(); + out.assert_single_trailing_newline(); +} + + +fn format_impl(out: &mut impl Formatter, meta: &Meta, options: &Options) { + // Output all leaf fields first + let leaf_fields = meta.fields.iter().filter_map(|f| match &f.kind { + FieldKind::Leaf { kind, env } => Some((f, kind, env)), + _ => None, + }); + let mut emitted_anything = false; + for (i, (field, kind, env)) in leaf_fields.enumerate() { + emitted_anything = true; + + if i > 0 { + out.make_gap(options.leaf_field_gap()); + } + + let mut emitted_something = false; + macro_rules! empty_sep_doc_line { + () => { + if emitted_something { + out.comment(""); + } + }; + } + + if options.comments { + field.doc.iter().for_each(|doc| out.comment(doc)); + emitted_something = !field.doc.is_empty(); + + if let Some(env) = env { + empty_sep_doc_line!(); + out.env_comment(env); + } + } + + match kind { + LeafKind::Optional => out.disabled_field(field.name, None), + LeafKind::Required { default } => { + // Emit comment about default value or the value being required. + if options.comments { + empty_sep_doc_line!(); + out.default_or_required_comment(default.as_ref()) + } + + // Emit the actual line with the name and optional value + out.disabled_field(field.name, default.as_ref()); + } + } + } + + // Then all nested fields recursively + let nested_fields = meta.fields.iter().filter_map(|f| match &f.kind { + FieldKind::Nested { meta } => Some((f, meta)), + _ => None, + }); + for (field, meta) in nested_fields { + if emitted_anything { + out.make_gap(options.nested_field_gap); + } + emitted_anything = true; + + let comments = if options.comments { field.doc } else { &[] }; + out.start_nested(&field.name, comments); + format_impl(out, meta, options); + out.end_nested(); + } +} diff --git a/src/test_utils/example2.rs b/src/test_utils/example2.rs new file mode 100644 index 0000000..3e23961 --- /dev/null +++ b/src/test_utils/example2.rs @@ -0,0 +1,44 @@ +use std::path::PathBuf; + +use crate::Config; +use crate as confique; + +#[derive(Debug, Config)] +/// A sample configuration for our app. +pub struct Conf { + #[config(nested)] + pub http: Http, +} + +/// Configuring the HTTP server of our app. +#[derive(Debug, Config)] +pub struct Http { + #[config(nested)] + pub headers: Headers, + + #[config(nested)] + pub log: LogConfig, +} + +#[derive(Debug, Config)] +pub struct Headers { + /// The header in which the reverse proxy specifies the username. + #[config(default = "x-username")] + pub username: String, + + /// The header in which the reverse proxy specifies the display name. + #[config(default = "x-display-name")] + pub display_name: String, +} + + +#[derive(Debug, Config)] +pub struct LogConfig { + /// If set to `true`, the app will log to stdout. + #[config(default = true)] + pub stdout: bool, + + /// If this is set, the app will write logs to the given file. Of course, + /// the app has to have write access to that file. + pub file: Option, +} diff --git a/src/test_utils/mod.rs b/src/test_utils/mod.rs index db3a445..373c8f0 100644 --- a/src/test_utils/mod.rs +++ b/src/test_utils/mod.rs @@ -1,4 +1,5 @@ pub(crate) mod example1; +pub(crate) mod example2; #[allow(unused_macros)] diff --git a/src/toml.rs b/src/toml.rs index 68be572..6ba68c9 100644 --- a/src/toml.rs +++ b/src/toml.rs @@ -5,45 +5,26 @@ use std::fmt::{self, Write}; use crate::{ Config, - format::{DefaultValueComment, add_empty_line, assert_single_trailing_newline}, - meta::{Expr, FieldKind, LeafKind, Meta}, + format::{self, Formatter}, + meta::Expr, }; /// Options for generating a TOML template. pub struct FormatOptions { - // TODO: think about forward/backwards compatibility. - /// Indentation for nested tables. Default: 0. pub indent: u8, - /// Whether to include doc comments (with your own text and information - /// about whether a value is required and/or has a default). Default: - /// true. - pub comments: bool, - - /// If `comments` and this field are `true`, leaf fields with `env = "FOO"` - /// attribute will have a line like this added: - /// - /// ```text - /// # Can also be specified via environment variable `FOO`. - /// ``` - /// - /// Default: `true`. - pub env_keys: bool, - - // Potential future options: - // - Comment out default values (`#foo = 3` vs `foo = 3`) - // - Which docs to include from nested objects + /// Non-TOML specific options. + general: format::Options, } impl Default for FormatOptions { fn default() -> Self { Self { indent: 0, - comments: true, - env_keys: true, + general: Default::default(), } } } @@ -91,7 +72,6 @@ impl Default for FormatOptions { /// ## Required! This value must be specified. /// ##color = /// -/// /// [log] /// ## If set to `true`, the app will log to stdout. /// ## @@ -111,105 +91,69 @@ impl Default for FormatOptions { /// } /// ``` pub fn format(options: FormatOptions) -> String { - let mut out = String::new(); - let meta = &C::META; - - // Print root docs. - if options.comments { - meta.doc.iter().for_each(|doc| writeln!(out, "#{doc}").unwrap()); - if !meta.doc.is_empty() { - add_empty_line(&mut out); - } - } - - // Recursively format all nested objects and fields - format_impl(&mut out, meta, vec![], &options); - assert_single_trailing_newline(&mut out); - - out + let mut out = TomlFormatter::new(&options); + format::format::(&mut out, options.general); + out.finish() } -fn format_impl( - s: &mut String, - meta: &Meta, - path: Vec<&str>, - options: &FormatOptions, -) { - /// Like `println!` but into `s` and with indentation. - macro_rules! emit { - ($fmt:literal $(, $args:expr)* $(,)?) => {{ - // Writing to a string never fails, we can unwrap. - let indent = path.len().saturating_sub(1) * options.indent as usize; - write!(s, "{: <1$}", "", indent).unwrap(); - writeln!(s, $fmt $(, $args)*).unwrap(); - }}; - } +struct TomlFormatter { + indent: u8, + buffer: String, + stack: Vec<&'static str>, +} - // Output all leaf fields first - let leaf_fields = meta.fields.iter().filter_map(|f| match &f.kind { - FieldKind::Leaf { kind, env } => Some((f, kind, env)), - _ => None, - }); - for (field, kind, env) in leaf_fields { - let mut emitted_something = false; - macro_rules! empty_sep_doc_line { - () => { - if emitted_something { - emit!("#"); - } - }; - } - - if options.comments { - field.doc.iter().for_each(|doc| emit!("#{doc}")); - emitted_something = !field.doc.is_empty(); - - if let Some(env) = env { - empty_sep_doc_line!(); - emit!("# Can also be specified via environment variable `{env}`.") - } - } - - if let LeafKind::Required { default } = kind { - // Emit comment about default value or the value being required - if options.comments { - empty_sep_doc_line!(); - emit!("# {}", DefaultValueComment(default.as_ref().map(PrintExpr))); - } - - // Emit the actual line with the name and optional value - match default { - Some(v) => emit!("#{} = {}", field.name, PrintExpr(v)), - None => emit!("#{} =", field.name), - } - } else { - emit!("#{} =", field.name); - } - - if options.comments { - add_empty_line(s); +impl TomlFormatter { + fn new(options: &FormatOptions) -> Self { + Self { + indent: options.indent, + buffer: String::new(), + stack: Vec::new(), } } - // Then all nested fields recursively - let nested_fields = meta.fields.iter().filter_map(|f| match &f.kind { - FieldKind::Nested { meta } => Some((f, meta)), - _ => None, - }); - for (field, meta) in nested_fields { - emit!(""); - // add_empty_line(s); - if options.comments { - field.doc.iter().for_each(|doc| emit!("#{doc}")); - } + fn emit_indentation(&mut self) { + let num_spaces = self.stack.len() * self.indent as usize; + write!(self.buffer, "{: <1$}", "", num_spaces).unwrap(); + } +} - let child_path = path.iter().copied().chain([field.name]).collect::>(); - emit!("[{}]", child_path.join(".")); - format_impl(s, meta, child_path, options); +impl Formatter for TomlFormatter { + type ExprPrinter = PrintExpr; - if options.comments { - add_empty_line(s); - } + fn buffer(&mut self) -> &mut String { + &mut self.buffer + } + + fn comment(&mut self, comment: impl fmt::Display) { + self.emit_indentation(); + writeln!(self.buffer, "#{comment}").unwrap(); + } + + fn disabled_field(&mut self, name: &str, value: Option<&'static Expr>) { + match value.map(PrintExpr) { + None => self.comment(format_args!("{name} =")), + Some(v) => self.comment(format_args!("{name} = {v}")), + }; + } + + fn start_nested(&mut self, name: &'static str, doc: &[&'static str]) { + self.stack.push(name); + doc.iter().for_each(|doc| self.comment(doc)); + self.emit_indentation(); + writeln!(self.buffer, "[{}]", self.stack.join(".")).unwrap(); + } + + fn end_nested(&mut self) { + self.stack.pop().expect("formatter bug: stack empty"); + } + + fn start_main(&mut self) { + self.make_gap(1); + } + + fn finish(self) -> String { + assert!(self.stack.is_empty(), "formatter bug: stack not empty"); + self.buffer } } @@ -244,10 +188,31 @@ mod tests { #[test] fn no_comments() { - let out = format::(FormatOptions { - comments: false, - .. FormatOptions::default() - }); + let mut options = FormatOptions::default(); + options.general.comments = false; + let out = format::(options); assert_str_eq!(&out, include_format_output!("1-no-comments.toml")); } + + #[test] + fn indent_2() { + let mut options = FormatOptions::default(); + options.indent = 2; + let out = format::(options); + assert_str_eq!(&out, include_format_output!("1-indent-2.toml")); + } + + #[test] + fn nested_gap_2() { + let mut options = FormatOptions::default(); + options.general.nested_field_gap = 2; + let out = format::(options); + assert_str_eq!(&out, include_format_output!("1-nested-gap-2.toml")); + } + + #[test] + fn immediately_nested() { + let out = format::(Default::default()); + assert_str_eq!(&out, include_format_output!("2-default.toml")); + } } diff --git a/src/yaml.rs b/src/yaml.rs index a27f27d..0cb2638 100644 --- a/src/yaml.rs +++ b/src/yaml.rs @@ -5,45 +5,26 @@ use std::fmt::{self, Write}; use crate::{ Config, - format::{DefaultValueComment, add_empty_line, assert_single_trailing_newline}, - meta::{Expr, FieldKind, LeafKind, Meta}, + format::{self, Formatter}, + meta::Expr, }; /// Options for generating a YAML template. pub struct FormatOptions { - // TODO: think about forward/backwards compatibility. - /// Amount of indentation in spaces. Default: 2. pub indent: u8, - /// Whether to include doc comments (with your own text and information - /// about whether a value is required and/or has a default). Default: - /// true. - pub comments: bool, - - /// If `comments` and this field are `true`, leaf fields with `env = "FOO"` - /// attribute will have a line like this added: - /// - /// ```text - /// # Can also be specified via environment variable `FOO`. - /// ``` - /// - /// Default: `true`. - pub env_keys: bool, - - // Potential future options: - // - Comment out default values (`#foo = 3` vs `foo = 3`) - // - Which docs to include from nested objects + /// Non-TOML specific options. + general: format::Options, } impl Default for FormatOptions { fn default() -> Self { Self { indent: 2, - comments: true, - env_keys: true, + general: Default::default(), } } } @@ -112,92 +93,81 @@ impl Default for FormatOptions { /// } /// ``` pub fn format(options: FormatOptions) -> String { - let mut out = String::new(); - let meta = &C::META; - - // Print root docs. - if options.comments { - meta.doc.iter().for_each(|doc| writeln!(out, "#{doc}").unwrap()); - if !meta.doc.is_empty() { - add_empty_line(&mut out); - } - } - - // Recursively format all nested objects and fields - format_impl(&mut out, meta, 0, &options); - assert_single_trailing_newline(&mut out); - - out + let mut out = YamlFormatter::new(&options); + format::format::(&mut out, options.general); + out.finish() } -fn format_impl( - s: &mut String, - meta: &Meta, - depth: usize, - options: &FormatOptions, -) { - /// Like `println!` but into `s` and with indentation. - macro_rules! emit { - ($fmt:literal $(, $args:expr)* $(,)?) => {{ - // Writing to a string never fails, we can unwrap. - let indent = depth * options.indent as usize; - write!(s, "{: <1$}", "", indent).unwrap(); - writeln!(s, $fmt $(, $args)*).unwrap(); - }}; +struct YamlFormatter { + indent: u8, + buffer: String, + depth: u8, +} + +impl YamlFormatter { + fn new(options: &FormatOptions) -> Self { + Self { + indent: options.indent, + buffer: String::new(), + depth: 0, + } } - for field in meta.fields { - let mut emitted_something = false; - macro_rules! empty_sep_doc_line { - () => { - if emitted_something { - emit!("#"); - } - }; - } + fn emit_indentation(&mut self) { + let num_spaces = self.depth as usize * self.indent as usize; + write!(self.buffer, "{: <1$}", "", num_spaces).unwrap(); + } +} - if options.comments { - field.doc.iter().for_each(|doc| emit!("#{doc}")); - emitted_something = !field.doc.is_empty(); +impl format::Formatter for YamlFormatter { + type ExprPrinter = PrintExpr; - if let FieldKind::Leaf { env: Some(env), .. } = field.kind { - empty_sep_doc_line!(); - emit!("# Can also be specified via environment variable `{env}`.") - } - } + fn buffer(&mut self) -> &mut String { + &mut self.buffer + } - match &field.kind { - FieldKind::Leaf { kind: LeafKind::Required { default }, .. } => { - // Emit comment about default value or the value being required - if options.comments { - empty_sep_doc_line!(); - emit!("# {}", DefaultValueComment(default.as_ref().map(PrintExpr))); - } + fn comment(&mut self, comment: impl fmt::Display) { + self.emit_indentation(); + writeln!(self.buffer, "#{comment}").unwrap(); + } - // Emit the actual line with the name and optional value - match default { - Some(v) => emit!("#{}: {}", field.name, PrintExpr(v)), - None => emit!("#{}:", field.name), - } - } + fn disabled_field(&mut self, name: &str, value: Option<&'static Expr>) { + match value.map(PrintExpr) { + None => self.comment(format_args!("{name}:")), + Some(v) => self.comment(format_args!("{name}: {v}")), + }; + } - FieldKind::Leaf { kind: LeafKind::Optional, .. } => emit!("#{}:", field.name), + fn start_nested(&mut self, name: &'static str, doc: &[&'static str]) { + doc.iter().for_each(|doc| self.comment(doc)); + self.emit_indentation(); + writeln!(self.buffer, "{name}:").unwrap(); + self.depth += 1; + } - FieldKind::Nested { meta } => { - emit!("{}:", field.name); - format_impl(s, meta, depth + 1, options); - } - } + fn end_nested(&mut self) { + self.depth = self.depth.checked_sub(1).expect("formatter bug: ended too many nested"); + } - if options.comments { - add_empty_line(s); - } + fn start_main(&mut self) { + self.make_gap(1); + } + + fn finish(self) -> String { + assert_eq!(self.depth, 0, "formatter bug: lingering nested objects"); + self.buffer } } /// Helper to emit `meta::Expr` into YAML. struct PrintExpr(&'static Expr); +impl From<&'static Expr> for PrintExpr { + fn from(expr: &'static Expr) -> Self { + Self(expr) + } +} + impl fmt::Display for PrintExpr { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self.0 { @@ -247,10 +217,15 @@ mod tests { #[test] fn no_comments() { - let out = format::(FormatOptions { - comments: false, - .. FormatOptions::default() - }); + let mut options = FormatOptions::default(); + options.general.comments = false; + let out = format::(options); assert_str_eq!(&out, include_format_output!("1-no-comments.yaml")); } + + #[test] + fn immediately_nested() { + let out = format::(Default::default()); + assert_str_eq!(&out, include_format_output!("2-default.yaml")); + } } diff --git a/tests/format-output/1-default.toml b/tests/format-output/1-default.toml index 334b63e..e110c18 100644 --- a/tests/format-output/1-default.toml +++ b/tests/format-output/1-default.toml @@ -5,7 +5,6 @@ # Required! This value must be specified. #site_name = - # Configurations related to the HTTP communication. [http] # The port the server will listen on. @@ -21,7 +20,6 @@ # Default value: "127.0.0.1" #bind = "127.0.0.1" - [http.headers] # The header in which the reverse proxy specifies the username. # @@ -33,7 +31,6 @@ # Default value: "x-display-name" #display_name = "x-display-name" - # Configuring the logging. [log] # If set to `true`, the app will log to stdout. diff --git a/tests/format-output/1-default.yaml b/tests/format-output/1-default.yaml index d3e17ec..a5bd3e9 100644 --- a/tests/format-output/1-default.yaml +++ b/tests/format-output/1-default.yaml @@ -14,6 +14,12 @@ http: # Required! This value must be specified. #port: + # The bind address of the server. Can be set to `0.0.0.0` for example, to + # allow other users of the network to access the server. + # + # Default value: 127.0.0.1 + #bind: 127.0.0.1 + headers: # The header in which the reverse proxy specifies the username. # @@ -25,12 +31,6 @@ http: # Default value: x-display-name #display_name: x-display-name - # The bind address of the server. Can be set to `0.0.0.0` for example, to - # allow other users of the network to access the server. - # - # Default value: 127.0.0.1 - #bind: 127.0.0.1 - # Configuring the logging. log: # If set to `true`, the app will log to stdout. diff --git a/tests/format-output/1-indent-2.toml b/tests/format-output/1-indent-2.toml new file mode 100644 index 0000000..1b446cb --- /dev/null +++ b/tests/format-output/1-indent-2.toml @@ -0,0 +1,43 @@ +# A sample configuration for our app. + +# Name of the website. +# +# Required! This value must be specified. +#site_name = + + # Configurations related to the HTTP communication. + [http] + # The port the server will listen on. + # + # Can also be specified via environment variable `PORT`. + # + # Required! This value must be specified. + #port = + + # The bind address of the server. Can be set to `0.0.0.0` for example, to + # allow other users of the network to access the server. + # + # Default value: "127.0.0.1" + #bind = "127.0.0.1" + + [http.headers] + # The header in which the reverse proxy specifies the username. + # + # Default value: "x-username" + #username = "x-username" + + # The header in which the reverse proxy specifies the display name. + # + # Default value: "x-display-name" + #display_name = "x-display-name" + + # Configuring the logging. + [log] + # If set to `true`, the app will log to stdout. + # + # Default value: true + #stdout = true + + # If this is set, the app will write logs to the given file. Of course, + # the app has to have write access to that file. + #file = diff --git a/tests/format-output/1-nested-gap-2.toml b/tests/format-output/1-nested-gap-2.toml new file mode 100644 index 0000000..334b63e --- /dev/null +++ b/tests/format-output/1-nested-gap-2.toml @@ -0,0 +1,46 @@ +# A sample configuration for our app. + +# Name of the website. +# +# Required! This value must be specified. +#site_name = + + +# Configurations related to the HTTP communication. +[http] +# The port the server will listen on. +# +# Can also be specified via environment variable `PORT`. +# +# Required! This value must be specified. +#port = + +# The bind address of the server. Can be set to `0.0.0.0` for example, to +# allow other users of the network to access the server. +# +# Default value: "127.0.0.1" +#bind = "127.0.0.1" + + +[http.headers] +# The header in which the reverse proxy specifies the username. +# +# Default value: "x-username" +#username = "x-username" + +# The header in which the reverse proxy specifies the display name. +# +# Default value: "x-display-name" +#display_name = "x-display-name" + + +# Configuring the logging. +[log] +# If set to `true`, the app will log to stdout. +# +# Default value: true +#stdout = true + +# If this is set, the app will write logs to the given file. Of course, +# the app has to have write access to that file. +#file = diff --git a/tests/format-output/1-no-comments.yaml b/tests/format-output/1-no-comments.yaml index d2952aa..eeb46a9 100644 --- a/tests/format-output/1-no-comments.yaml +++ b/tests/format-output/1-no-comments.yaml @@ -1,10 +1,13 @@ #site_name: + http: #port: + #bind: 127.0.0.1 + headers: #username: x-username #display_name: x-display-name - #bind: 127.0.0.1 + log: #stdout: true #file: diff --git a/tests/format-output/2-default.toml b/tests/format-output/2-default.toml new file mode 100644 index 0000000..3aac696 --- /dev/null +++ b/tests/format-output/2-default.toml @@ -0,0 +1,23 @@ +# A sample configuration for our app. + +[http] +[http.headers] +# The header in which the reverse proxy specifies the username. +# +# Default value: "x-username" +#username = "x-username" + +# The header in which the reverse proxy specifies the display name. +# +# Default value: "x-display-name" +#display_name = "x-display-name" + +[http.log] +# If set to `true`, the app will log to stdout. +# +# Default value: true +#stdout = true + +# If this is set, the app will write logs to the given file. Of course, +# the app has to have write access to that file. +#file = diff --git a/tests/format-output/2-default.yaml b/tests/format-output/2-default.yaml new file mode 100644 index 0000000..3794553 --- /dev/null +++ b/tests/format-output/2-default.yaml @@ -0,0 +1,23 @@ +# A sample configuration for our app. + +http: + headers: + # The header in which the reverse proxy specifies the username. + # + # Default value: x-username + #username: x-username + + # The header in which the reverse proxy specifies the display name. + # + # Default value: x-display-name + #display_name: x-display-name + + log: + # If set to `true`, the app will log to stdout. + # + # Default value: true + #stdout: true + + # If this is set, the app will write logs to the given file. Of course, + # the app has to have write access to that file. + #file: