Skip to content

Commit 9543e3d

Browse files
committed
Allow for chaining hotfixes
1 parent 2ee4431 commit 9543e3d

File tree

11 files changed

+128
-50
lines changed

11 files changed

+128
-50
lines changed

fontspector-checkapi/src/check.rs

+9-4
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ pub enum CheckImplementation<'a> {
4646
CheckAll(&'a CheckAllSignature),
4747
}
4848

49+
/// The function signature for a hotfix function
50+
pub type HotfixFunction = dyn Fn(&mut Testable) -> FixFnResult;
51+
4952
#[derive(Clone)]
5053
/// A check definition
5154
pub struct Check<'a> {
@@ -60,7 +63,7 @@ pub struct Check<'a> {
6063
/// Function pointer implementing the actual check
6164
pub implementation: CheckImplementation<'a>,
6265
/// Function pointer implementing a hotfix to the binary file
63-
pub hotfix: Option<&'a dyn Fn(&Testable) -> FixFnResult>,
66+
pub hotfix: Option<&'a HotfixFunction>,
6467
/// Function pointer implementing a hotfix to the font source file
6568
pub fix_source: Option<&'a dyn Fn(&Testable) -> FixFnResult>,
6669
/// A registered file type that this check applies to
@@ -114,6 +117,7 @@ impl<'a> Check<'a> {
114117
&'a self,
115118
fn_result: CheckFnResult,
116119
filename: Option<&str>,
120+
source_filename: Option<&str>,
117121
section: Option<&str>,
118122
duration: Duration,
119123
) -> CheckResult {
@@ -127,7 +131,7 @@ impl<'a> Check<'a> {
127131
} else {
128132
subresults
129133
};
130-
CheckResult::new(self, filename, section, res, duration)
134+
CheckResult::new(self, filename, source_filename, section, res, duration)
131135
}
132136

133137
/// Run the check, either on a collection or a single file.
@@ -155,7 +159,8 @@ impl<'a> Check<'a> {
155159

156160
Some(self.clarify_result(
157161
result,
158-
Some(f).and_then(|f| f.filename.to_str()),
162+
f.filename.to_str(),
163+
f.source.as_ref().and_then(|x| x.to_str()),
159164
section,
160165
duration,
161166
))
@@ -169,7 +174,7 @@ impl<'a> Check<'a> {
169174
#[cfg(target_family = "wasm")]
170175
let duration = Duration::from_secs(0);
171176

172-
Some(self.clarify_result(result, Some(&f.directory), section, duration))
177+
Some(self.clarify_result(result, Some(&f.directory), None, section, duration))
173178
}
174179
}
175180
}

fontspector-checkapi/src/checkresult.rs

+4
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ pub struct CheckResult {
3434
pub check_rationale: String,
3535
/// The file which was checked; if None, the check was run on all files
3636
pub filename: Option<String>,
37+
/// The source where this file came from, if any
38+
pub source_filename: Option<String>,
3739
/// The section of the profile this check belongs to
3840
pub section: Option<String>,
3941
/// The individual results of the check
@@ -73,6 +75,7 @@ impl CheckResult {
7375
pub fn new(
7476
check: &Check,
7577
filename: Option<&str>,
78+
source_filename: Option<&str>,
7679
section: Option<&str>,
7780
subresults: Vec<Status>,
7881
duration: Duration,
@@ -82,6 +85,7 @@ impl CheckResult {
8285
check_name: check.title.to_string(),
8386
check_rationale: check.rationale.to_string(),
8487
filename: filename.map(|x| x.to_string()),
88+
source_filename: source_filename.map(|x| x.to_string()),
8589
section: section.map(|x| x.to_string()),
8690
subresults,
8791
hotfix_result: None,

fontspector-checkapi/src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ mod status;
4141
mod testable;
4242
/// Common utility functions for check implementors
4343
mod utils;
44-
pub use check::{return_result, Check, CheckFlags, CheckId, CheckImplementation};
44+
pub use check::{return_result, Check, CheckFlags, CheckId, CheckImplementation, HotfixFunction};
4545
pub use checkresult::{CheckResult, FixResult};
4646
pub use context::Context;
4747
pub use filetype::{FileType, FileTypeConvert};

fontspector-checkapi/src/testable.rs

+10
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,16 @@ impl Testable {
7676
.and_then(|x| x.to_str())
7777
.map(|x| x.to_string())
7878
}
79+
80+
/// Set the new contents of a file
81+
pub fn set(&mut self, new_bytes: Vec<u8>) {
82+
self.contents = new_bytes;
83+
}
84+
85+
/// Save the contents of a file to disk
86+
pub fn save(&self) -> Result<(), std::io::Error> {
87+
std::fs::write(&self.filename, &self.contents)
88+
}
7989
}
8090

8191
/// A related set of files which will be checked together.

fontspector-cli/src/main.rs

+88-35
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ use args::Args;
1414
use clap::Parser;
1515
use fontbakery_bridge::FontbakeryBridge;
1616
use fontspector_checkapi::{
17-
Check, CheckResult, Context, FixResult, Plugin, Registry, TestableCollection, TestableType,
17+
Check, CheckResult, Context, FixResult, HotfixFunction, Plugin, Registry, Status, StatusCode,
18+
Testable, TestableCollection, TestableType,
1819
};
1920
use itertools::Either;
2021
use profile_googlefonts::GoogleFonts;
@@ -187,7 +188,7 @@ fn main() {
187188
);
188189
// }
189190

190-
// Run all the things! Check all the fonts! Fix all the binaries! Fix all the sources!
191+
// Run all the things! Check all the fonts!
191192

192193
// Do this in parallel for release, serial for debug
193194
#[cfg(debug_assertions)]
@@ -203,21 +204,22 @@ fn main() {
203204
Either::Right(checkorder.par_iter())
204205
};
205206

206-
#[allow(clippy::unwrap_used)] // We check for is_some before unwrapping
207-
let results: RunResults = checkorder_iterator
207+
let mut results: RunResults = checkorder_iterator
208208
.map(|(sectionname, testable, check, context)| {
209209
(
210210
testable,
211211
check,
212212
check.run(testable, context, Some(sectionname)),
213213
)
214214
})
215-
.filter_map(|(testable, check, result)| {
216-
result.map(|result| apply_fixes(testable, check, result, &args))
217-
})
215+
.filter_map(|(_, _, result)| result)
218216
.collect::<Vec<CheckResult>>()
219217
.into();
220218

219+
if args.hotfix || args.fix_sources {
220+
try_fixing_stuff(&mut results, &args, &registry);
221+
}
222+
221223
let worst_status = results.worst_status();
222224

223225
let mut reporters: Vec<Box<dyn Reporter>> = vec![];
@@ -330,37 +332,88 @@ fn load_configuration(args: &Args) -> Map<String, serde_json::Value> {
330332
.unwrap_or_default()
331333
}
332334

333-
fn apply_fixes(
334-
testable: &&TestableType,
335-
check: &&Check,
336-
mut result: CheckResult,
337-
args: &Args,
338-
) -> CheckResult {
339-
if let TestableType::Single(testable) = testable {
340-
if args.hotfix {
341-
if let Some(fix) = check.hotfix {
342-
result.hotfix_result = match fix(testable) {
343-
Ok(_) => Some(FixResult::Fixed),
344-
Err(e) => Some(FixResult::FixError(e)),
345-
}
346-
} else {
347-
result.hotfix_result = Some(FixResult::Unfixable);
348-
}
349-
} else if check.hotfix.is_some() {
350-
result.hotfix_result = Some(FixResult::Available);
335+
// fn apply_fixes(
336+
// testable: &mut TestableType,
337+
// check: &&Check,
338+
// mut result: CheckResult,
339+
// args: &Args,
340+
// ) -> CheckResult {
341+
// if let TestableType::Single(testable) = testable {
342+
// if args.hotfix {
343+
// if let Some(fix) = check.hotfix {
344+
// result.hotfix_result = match fix(testable) {
345+
// Ok(_) => Some(FixResult::Fixed),
346+
// Err(e) => Some(FixResult::FixError(e)),
347+
// }
348+
// } else {
349+
// result.hotfix_result = Some(FixResult::Unfixable);
350+
// }
351+
// } else if check.hotfix.is_some() {
352+
// result.hotfix_result = Some(FixResult::Available);
353+
// }
354+
// if args.fix_sources {
355+
// if let Some(fix) = check.fix_source {
356+
// result.sourcefix_result = match fix(testable) {
357+
// Ok(_) => Some(FixResult::Fixed),
358+
// Err(e) => Some(FixResult::FixError(e)),
359+
// }
360+
// } else {
361+
// result.sourcefix_result = Some(FixResult::Unfixable);
362+
// }
363+
// } else if check.fix_source.is_some() {
364+
// result.sourcefix_result = Some(FixResult::Available);
365+
// }
366+
// }
367+
// result
368+
// }
369+
370+
fn try_fixing_stuff(results: &mut RunResults, args: &Args, registry: &Registry) {
371+
let failed_checks = results
372+
.iter_mut()
373+
.filter(|x| x.worst_status() >= StatusCode::Fail)
374+
.collect::<Vec<_>>();
375+
// Group the fixes by filename because we want to provide testables
376+
// // let mut fix_sources = HashMap::new();
377+
let mut fix_binaries: HashMap<String, Vec<(&HotfixFunction, &mut CheckResult)>> =
378+
HashMap::new();
379+
for result in failed_checks.into_iter() {
380+
let Some(check) = registry.checks.get(&result.check_id) else {
381+
log::warn!(
382+
"A check called {} just mysteriously vanished",
383+
result.check_id
384+
);
385+
continue;
386+
};
387+
if args.hotfix && result.filename.is_some() && check.hotfix.is_some() {
388+
#[allow(clippy::unwrap_used)] // We know this is Some
389+
fix_binaries
390+
.entry(result.filename.clone().unwrap())
391+
.or_default()
392+
.push((check.hotfix.unwrap(), result));
351393
}
352-
if args.fix_sources {
353-
if let Some(fix) = check.fix_source {
354-
result.sourcefix_result = match fix(testable) {
355-
Ok(_) => Some(FixResult::Fixed),
356-
Err(e) => Some(FixResult::FixError(e)),
394+
}
395+
396+
for (file, fixes) in fix_binaries.into_iter() {
397+
let mut testable = Testable::new(&file).unwrap_or_else(|e| {
398+
log::error!("Could not load files from {:?}: {:}", file, e);
399+
std::process::exit(1)
400+
});
401+
let mut modified = false;
402+
for (fix, result) in fixes.into_iter() {
403+
result.hotfix_result = match fix(&mut testable) {
404+
Ok(hotfix_behaviour) => {
405+
modified |= hotfix_behaviour;
406+
Some(FixResult::Fixed)
357407
}
358-
} else {
359-
result.sourcefix_result = Some(FixResult::Unfixable);
408+
Err(e) => Some(FixResult::FixError(e)),
360409
}
361-
} else if check.fix_source.is_some() {
362-
result.sourcefix_result = Some(FixResult::Available);
410+
}
411+
if modified {
412+
// save it
413+
testable.save().unwrap_or_else(|e| {
414+
log::error!("Could not save file {:?}: {:}", file, e);
415+
std::process::exit(1)
416+
});
363417
}
364418
}
365-
result
366419
}

fontspector-cli/src/reporters/mod.rs

+4
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ impl RunResults {
1616
pub fn iter(&self) -> impl Iterator<Item = &CheckResult> {
1717
self.results.iter()
1818
}
19+
/// Iterate over each check mutably
20+
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut CheckResult> {
21+
self.results.iter_mut()
22+
}
1923

2024
/// Get the worst status of all checks
2125
pub fn worst_status(&self) -> StatusCode {

profile-universal/src/checks/name/trailing_spaces.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,6 @@ fn trailing_spaces(f: &Testable, _context: &Context) -> CheckFnResult {
3636
return_result(problems)
3737
}
3838

39-
fn fix_trailing_spaces(_f: &Testable) -> FixFnResult {
39+
fn fix_trailing_spaces(_f: &mut Testable) -> FixFnResult {
4040
Ok(false)
4141
}

profile-universal/src/checks/nested_components.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ fn get_depth(glyph_id: GlyphId, loca: &Loca, glyf: &Glyf) -> u32 {
8282
depth
8383
}
8484

85-
fn decompose_nested_components(t: &Testable) -> FixFnResult {
85+
fn decompose_nested_components(t: &mut Testable) -> FixFnResult {
8686
let font = fixfont!(t);
8787
let loca = font
8888
.font()

profile-universal/src/checks/transformed_components.rs

+6-4
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ fn transformed_components(f: &Testable, context: &Context) -> CheckFnResult {
100100
}
101101
}
102102

103-
fn decompose_transformed_components(t: &Testable) -> FixFnResult {
103+
fn decompose_transformed_components(t: &mut Testable) -> FixFnResult {
104104
let f = fixfont!(t);
105105
let loca = f
106106
.font()
@@ -133,7 +133,10 @@ fn decompose_transformed_components(t: &Testable) -> FixFnResult {
133133
decompose_components_impl(t, &bad_composites)
134134
}
135135

136-
pub(crate) fn decompose_components_impl(t: &Testable, decompose_order: &[GlyphId]) -> FixFnResult {
136+
pub(crate) fn decompose_components_impl(
137+
t: &mut Testable,
138+
decompose_order: &[GlyphId],
139+
) -> FixFnResult {
137140
let f = fixfont!(t);
138141
let mut new_font = FontBuilder::new();
139142
let mut builder = GlyfLocaBuilder::new();
@@ -180,8 +183,7 @@ pub(crate) fn decompose_components_impl(t: &Testable, decompose_order: &[GlyphId
180183
new_font.add_table(&new_loca).map_err(|x| x.to_string())?;
181184
new_font.copy_missing_tables(f.font());
182185
let new_bytes = new_font.build();
183-
std::fs::write(&t.filename, new_bytes).map_err(|_| "Couldn't write file".to_string())?;
184-
186+
t.set(new_bytes);
185187
Ok(true)
186188
}
187189

profile-universal/src/checks/unwanted_aat_tables.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ fn unwanted_aat_tables(t: &Testable, _context: &Context) -> CheckFnResult {
4949
})
5050
}
5151

52-
fn delete_unwanted_aat_tables(t: &Testable) -> FixFnResult {
52+
fn delete_unwanted_aat_tables(t: &mut Testable) -> FixFnResult {
5353
let f = fixfont!(t);
5454
let mut new_font = FontBuilder::new();
5555
for table in f.font().table_directory.table_records() {
@@ -61,6 +61,6 @@ fn delete_unwanted_aat_tables(t: &Testable) -> FixFnResult {
6161
}
6262
}
6363
let new_bytes = new_font.build();
64-
std::fs::write(&t.filename, new_bytes).map_err(|_| "Couldn't write file".to_string())?;
64+
t.set(new_bytes);
6565
Ok(true)
6666
}

profile-universal/src/checks/unwanted_tables.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ fn unwanted_tables(t: &Testable, _context: &Context) -> CheckFnResult {
4949
})
5050
}
5151

52-
fn delete_unwanted_tables(t: &Testable) -> FixFnResult {
52+
fn delete_unwanted_tables(t: &mut Testable) -> FixFnResult {
5353
let f = fixfont!(t);
5454
let unwanted_tags = UNWANTED_TABLES
5555
.iter()
@@ -65,6 +65,6 @@ fn delete_unwanted_tables(t: &Testable) -> FixFnResult {
6565
}
6666
}
6767
let new_bytes = new_font.build();
68-
std::fs::write(&t.filename, new_bytes).map_err(|_| "Couldn't write file".to_string())?;
68+
t.set(new_bytes);
6969
Ok(true)
7070
}

0 commit comments

Comments
 (0)