Skip to content

Use trait to abstract emit modes #3616

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 18 commits into from
Jun 12, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 0 additions & 102 deletions src/checkstyle.rs

This file was deleted.

50 changes: 50 additions & 0 deletions src/emitter.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
pub(crate) use self::checkstyle::*;
pub(crate) use self::diff::*;
pub(crate) use self::files::*;
pub(crate) use self::files_with_backup::*;
pub(crate) use self::modified_lines::*;
pub(crate) use self::stdout::*;
use crate::FileName;
use std::io::{self, Write};
use std::path::Path;

mod checkstyle;
mod diff;
mod files;
mod files_with_backup;
mod modified_lines;
mod stdout;

pub(crate) struct FormattedFile<'a> {
pub(crate) filename: &'a FileName,
pub(crate) original_text: &'a str,
pub(crate) formatted_text: &'a str,
}

#[derive(Debug, Default, Clone)]
pub(crate) struct EmitterResult {
pub(crate) has_diff: bool,
}

pub(crate) trait Emitter {
fn emit_formatted_file(
&self,
output: &mut dyn Write,
formatted_file: FormattedFile<'_>,
) -> Result<EmitterResult, io::Error>;

fn emit_header(&self, _output: &mut dyn Write) -> Result<(), io::Error> {
Ok(())
}

fn emit_footer(&self, _output: &mut dyn Write) -> Result<(), io::Error> {
Ok(())
}
}

fn ensure_real_path(filename: &FileName) -> &Path {
match *filename {
FileName::Real(ref path) => path,
_ => panic!("cannot format `{}` and emit to files", filename),
}
}
64 changes: 64 additions & 0 deletions src/emitter/checkstyle.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
use self::xml::XmlEscaped;
use super::*;
use crate::rustfmt_diff::{make_diff, DiffLine, Mismatch};
use std::io::{self, Write};
use std::path::Path;

mod xml;

#[derive(Debug, Default)]
pub(crate) struct CheckstyleEmitter;

impl Emitter for CheckstyleEmitter {
fn emit_header(&self, output: &mut dyn Write) -> Result<(), io::Error> {
writeln!(output, r#"<?xml version="1.0" encoding="utf-8"?>"#)?;
write!(output, r#"<checkstyle version="4.3">"#)?;
Ok(())
}

fn emit_footer(&self, output: &mut dyn Write) -> Result<(), io::Error> {
writeln!(output, "</checkstyle>")
}

fn emit_formatted_file(
&self,
output: &mut dyn Write,
FormattedFile {
filename,
original_text,
formatted_text,
}: FormattedFile<'_>,
) -> Result<EmitterResult, io::Error> {
const CONTEXT_SIZE: usize = 3;
let filename = ensure_real_path(filename);
let diff = make_diff(original_text, formatted_text, CONTEXT_SIZE);
output_checkstyle_file(output, filename, diff)?;
Ok(EmitterResult::default())
}
}

pub(crate) fn output_checkstyle_file<T>(
mut writer: T,
filename: &Path,
diff: Vec<Mismatch>,
) -> Result<(), io::Error>
where
T: Write,
{
write!(writer, r#"<file name="{}">"#, filename.display())?;
for mismatch in diff {
for line in mismatch.lines {
// Do nothing with `DiffLine::Context` and `DiffLine::Resulting`.
if let DiffLine::Expected(message) = line {
write!(
writer,
r#"<error line="{}" severity="warning" message="Should be `{}`" />"#,
mismatch.line_number,
XmlEscaped(&message)
)?;
}
}
}
write!(writer, "</file>")?;
Ok(())
}
52 changes: 52 additions & 0 deletions src/emitter/checkstyle/xml.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
use std::fmt::{self, Display};

/// Convert special characters into XML entities.
/// This is needed for checkstyle output.
pub(super) struct XmlEscaped<'a>(pub(super) &'a str);

impl<'a> Display for XmlEscaped<'a> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
for char in self.0.chars() {
match char {
'<' => write!(formatter, "&lt;"),
'>' => write!(formatter, "&gt;"),
'"' => write!(formatter, "&quot;"),
'\'' => write!(formatter, "&apos;"),
'&' => write!(formatter, "&amp;"),
_ => write!(formatter, "{}", char),
}?;
}

Ok(())
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn special_characters_are_escaped() {
assert_eq!(
"&lt;&gt;&quot;&apos;&amp;",
format!("{}", XmlEscaped(r#"<>"'&"#)),
);
}

#[test]
fn special_characters_are_escaped_in_string_with_other_characters() {
assert_eq!(
"The quick brown &quot;🦊&quot; jumps &lt;over&gt; the lazy 🐶",
format!(
"{}",
XmlEscaped(r#"The quick brown "🦊" jumps <over> the lazy 🐶"#)
),
);
}

#[test]
fn other_characters_are_not_escaped() {
let string = "The quick brown 🦊 jumps over the lazy 🐶";
assert_eq!(string, format!("{}", XmlEscaped(string)));
}
}
35 changes: 35 additions & 0 deletions src/emitter/diff.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
use super::*;
use crate::config::Config;
use crate::rustfmt_diff::{make_diff, print_diff};

pub(crate) struct DiffEmitter {
config: Config,
}

impl DiffEmitter {
pub(crate) fn new(config: Config) -> Self {
Self { config }
}
}

impl Emitter for DiffEmitter {
fn emit_formatted_file(
&self,
_output: &mut dyn Write,
FormattedFile {
filename,
original_text,
formatted_text,
}: FormattedFile<'_>,
) -> Result<EmitterResult, io::Error> {
const CONTEXT_SIZE: usize = 3;
let mismatch = make_diff(&original_text, formatted_text, CONTEXT_SIZE);
let has_diff = !mismatch.is_empty();
print_diff(
mismatch,
|line_num| format!("Diff in {} at line {}:", filename, line_num),
&self.config,
);
return Ok(EmitterResult { has_diff });
}
}
24 changes: 24 additions & 0 deletions src/emitter/files.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
use super::*;
use std::fs;

#[derive(Debug, Default)]
pub(crate) struct FilesEmitter;

impl Emitter for FilesEmitter {
fn emit_formatted_file(
&self,
_output: &mut dyn Write,
FormattedFile {
filename,
original_text,
formatted_text,
}: FormattedFile<'_>,
) -> Result<EmitterResult, io::Error> {
// Write text directly over original file if there is a diff.
let filename = ensure_real_path(filename);
if original_text != formatted_text {
fs::write(filename, formatted_text)?;
}
Ok(EmitterResult::default())
}
}
31 changes: 31 additions & 0 deletions src/emitter/files_with_backup.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
use super::*;
use std::fs;

#[derive(Debug, Default)]
pub(crate) struct FilesWithBackupEmitter;

impl Emitter for FilesWithBackupEmitter {
fn emit_formatted_file(
&self,
_output: &mut dyn Write,
FormattedFile {
filename,
original_text,
formatted_text,
}: FormattedFile<'_>,
) -> Result<EmitterResult, io::Error> {
let filename = ensure_real_path(filename);
if original_text != formatted_text {
// Do a little dance to make writing safer - write to a temp file
// rename the original to a .bk, then rename the temp file to the
// original.
let tmp_name = filename.with_extension("tmp");
let bk_name = filename.with_extension("bk");

fs::write(&tmp_name, formatted_text)?;
fs::rename(filename, bk_name)?;
fs::rename(tmp_name, filename)?;
}
Ok(EmitterResult::default())
}
}
24 changes: 24 additions & 0 deletions src/emitter/modified_lines.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
use super::*;
use crate::rustfmt_diff::{make_diff, ModifiedLines};
use std::io::Write;

#[derive(Debug, Default)]
pub(crate) struct ModifiedLinesEmitter;

impl Emitter for ModifiedLinesEmitter {
fn emit_formatted_file(
&self,
output: &mut dyn Write,
FormattedFile {
original_text,
formatted_text,
..
}: FormattedFile<'_>,
) -> Result<EmitterResult, io::Error> {
const CONTEXT_SIZE: usize = 0;
let mismatch = make_diff(original_text, formatted_text, CONTEXT_SIZE);
let has_diff = !mismatch.is_empty();
write!(output, "{}", ModifiedLines::from(mismatch))?;
Ok(EmitterResult { has_diff })
}
}
Loading