1
1
#![ allow( non_upper_case_globals) ]
2
2
#![ deny( clippy:: unwrap_used, clippy:: expect_used) ]
3
+
3
4
use fontspector_checkapi:: { prelude:: * , StatusCode } ;
4
- use pyo3:: prelude:: * ;
5
- mod checks ;
6
- struct FontbakeryBridge ;
5
+ use pyo3:: { prelude:: * , types :: PyList } ;
6
+ use serde_json :: json ;
7
+ pub struct FontbakeryBridge ;
7
8
8
9
// We isolate the Python part to avoid type/result madness.
9
- fn call_python ( module : & str , function : & str , testable : & Testable ) -> PyResult < CheckFnResult > {
10
+ fn python_checkrunner_impl (
11
+ module : & str ,
12
+ function : & str ,
13
+ testable : & Testable ,
14
+ ) -> PyResult < CheckFnResult > {
10
15
let filename = testable. filename . to_string_lossy ( ) ;
11
16
Python :: with_gil ( |py| {
12
17
let module = PyModule :: import_bound ( py, module) ?;
@@ -47,12 +52,22 @@ fn call_python(module: &str, function: &str, testable: &Testable) -> PyResult<Ch
47
52
"Fontbakery returned unknown status code" . to_string ( ) ,
48
53
)
49
54
} ) ?;
50
- let code = value. get_item ( 1 ) ?. getattr ( "code" ) ?. extract :: < String > ( ) ?;
51
- let message = value. get_item ( 1 ) ?. getattr ( "message" ) ?. extract :: < String > ( ) ?;
55
+ let code = if value. get_item ( 1 ) ?. hasattr ( "code" ) ? {
56
+ Some ( value. get_item ( 1 ) ?. getattr ( "code" ) ?. extract :: < String > ( ) ?)
57
+ } else {
58
+ None
59
+ } ;
60
+ let message = if value. get_item ( 1 ) ?. hasattr ( "message" ) ? {
61
+ value. get_item ( 1 ) ?. getattr ( "message" ) ?
62
+ } else {
63
+ value. get_item ( 1 ) ?
64
+ }
65
+ . extract :: < String > ( ) ?;
66
+
52
67
messages. push ( Status {
53
68
message : Some ( message) ,
54
69
severity : status,
55
- code : Some ( code ) ,
70
+ code,
56
71
} ) ;
57
72
}
58
73
Ok ( return_result ( messages) )
@@ -61,7 +76,7 @@ fn call_python(module: &str, function: &str, testable: &Testable) -> PyResult<Ch
61
76
62
77
// This wrapper will work for any fontbakery check that takes a single
63
78
// Font or ttFont object as an argument.
64
- fn run_a_python_test ( c : & Testable , context : & Context ) -> CheckFnResult {
79
+ fn python_checkrunner ( c : & Testable , context : & Context ) -> CheckFnResult {
65
80
let module = context
66
81
. check_metadata
67
82
. get ( "module" )
@@ -74,27 +89,149 @@ fn run_a_python_test(c: &Testable, context: &Context) -> CheckFnResult {
74
89
. ok_or_else ( || CheckError :: Error ( "No function specified" . to_string ( ) ) ) ?
75
90
. as_str ( )
76
91
. ok_or_else ( || CheckError :: Error ( "function in metadata was not a string!" . to_string ( ) ) ) ?;
77
- call_python ( module, function, c)
92
+ python_checkrunner_impl ( module, function, c)
78
93
. unwrap_or_else ( |e| Err ( CheckError :: Error ( format ! ( "Python error: {}" , e) ) ) )
79
94
}
80
95
96
+ fn register_python_checks ( modulename : & str , source : & str , cr : & mut Registry ) -> Result < ( ) , String > {
97
+ Python :: with_gil ( |py| {
98
+ // Assert that we have loaded the FB prelude
99
+ let _prelude = PyModule :: import_bound ( py, "fontbakery.prelude" ) ?;
100
+ let callable = PyModule :: import_bound ( py, "fontbakery.callable" ) ?;
101
+ let full_source = "from fontbakery.prelude import *\n \n " . to_string ( ) + source;
102
+ let module =
103
+ PyModule :: from_code_bound ( py, & full_source, & format ! ( "{}.py" , modulename) , modulename) ?;
104
+ // let check = module.getattr("check_hinting_impact")?;
105
+ // Find all functions in the module which are checks
106
+ let checktype = callable. getattr ( "FontBakeryCheck" ) ?;
107
+ for name in module. dir ( ) ?. iter ( ) {
108
+ let name_str: String = name. extract ( ) ?;
109
+ let obj = module. getattr ( name. downcast ( ) ?) ?;
110
+ if let Ok ( true ) = obj. is_instance ( & checktype) {
111
+ let id: String = obj. getattr ( "id" ) ?. extract ( ) ?;
112
+ // Check the mandatory arguments
113
+ let args = obj. getattr ( "mandatoryArgs" ) ?. extract :: < Vec < String > > ( ) ?;
114
+ if args. len ( ) != 1 || !( args[ 0 ] == "font" || args[ 0 ] == "ttFont" ) {
115
+ log:: warn!(
116
+ "Can't load check {}; unable to support arguments: {}" ,
117
+ id,
118
+ args. join( ", " )
119
+ ) ;
120
+ continue ;
121
+ }
122
+ let title: String = obj. getattr ( "__doc__" ) ?. extract ( ) ?;
123
+ let py_rationale = obj. getattr ( "rationale" ) ?;
124
+ let rationale: String = if py_rationale. is_instance_of :: < PyList > ( ) {
125
+ let r: Vec < String > = py_rationale. extract ( ) ?;
126
+ r. join ( ", " )
127
+ } else {
128
+ py_rationale. extract ( ) ?
129
+ } ;
130
+ let py_proposal = obj. getattr ( "proposal" ) ?;
131
+ let proposal: String = if py_proposal. is_instance_of :: < PyList > ( ) {
132
+ let r: Vec < String > = py_proposal. extract ( ) ?;
133
+ r. join ( ", " )
134
+ } else {
135
+ py_proposal. extract ( ) ?
136
+ } ;
137
+ log:: info!( "Registered check: {}" , id) ;
138
+ let metadata = json ! ( {
139
+ "module" : modulename,
140
+ "function" : name_str,
141
+ } ) ;
142
+ cr. register_check ( Check {
143
+ id : id. leak ( ) ,
144
+ title : title. leak ( ) ,
145
+ rationale : rationale. leak ( ) ,
146
+ proposal : proposal. leak ( ) ,
147
+ hotfix : None ,
148
+ fix_source : None ,
149
+ applies_to : "TTF" ,
150
+ flags : CheckFlags :: default ( ) ,
151
+ implementation : CheckImplementation :: CheckOne ( & python_checkrunner) ,
152
+ _metadata : Some ( metadata. to_string ( ) . leak ( ) ) ,
153
+ } )
154
+ }
155
+ }
156
+ Ok ( ( ) )
157
+ } )
158
+ . map_err ( |e : PyErr | format ! ( "Error loading checks: {}" , e) )
159
+ }
160
+
81
161
impl fontspector_checkapi:: Plugin for FontbakeryBridge {
82
162
fn register ( & self , cr : & mut Registry ) -> Result < ( ) , String > {
83
- cr. register_check ( checks:: hinting_impact) ;
84
- cr. register_check ( checks:: opentype_name_empty_records) ;
85
- cr. register_check ( checks:: monospace) ;
86
163
pyo3:: prepare_freethreaded_python ( ) ;
164
+ // Load needed FB modules
165
+ let ok: PyResult < ( ) > = Python :: with_gil ( |py| {
166
+ PyModule :: from_code_bound (
167
+ py,
168
+ include_str ! ( "../fontbakery/Lib/fontbakery/callable.py" ) ,
169
+ "callable.py" ,
170
+ "fontbakery.callable" ,
171
+ ) ?;
172
+ PyModule :: from_code_bound (
173
+ py,
174
+ include_str ! ( "../fontbakery/Lib/fontbakery/status.py" ) ,
175
+ "status.py" ,
176
+ "fontbakery.status" ,
177
+ ) ?;
178
+ PyModule :: from_code_bound (
179
+ py,
180
+ include_str ! ( "../fontbakery/Lib/fontbakery/message.py" ) ,
181
+ "message.py" ,
182
+ "fontbakery.message" ,
183
+ ) ?;
184
+ Ok ( ( ) )
185
+ } ) ;
186
+ ok. map_err ( |e| format ! ( "Error loading FB modules: {}" , e) ) ?;
187
+
188
+ register_python_checks (
189
+ "fontbakery.checks.opentype.kern" ,
190
+ include_str ! ( "../fontbakery/Lib/fontbakery/checks/opentype/kern.py" ) ,
191
+ cr,
192
+ ) ?;
193
+ register_python_checks (
194
+ "fontbakery.checks.opentype.cff" ,
195
+ include_str ! ( "../fontbakery/Lib/fontbakery/checks/opentype/cff.py" ) ,
196
+ cr,
197
+ ) ?;
198
+ register_python_checks (
199
+ "fontbakery.checks.opentype.fvar" ,
200
+ include_str ! ( "../fontbakery/Lib/fontbakery/checks/opentype/fvar.py" ) ,
201
+ cr,
202
+ ) ?;
203
+ register_python_checks (
204
+ "fontbakery.checks.opentype.gdef" ,
205
+ include_str ! ( "../fontbakery/Lib/fontbakery/checks/opentype/gdef.py" ) ,
206
+ cr,
207
+ ) ?;
208
+ register_python_checks (
209
+ "fontbakery.checks.opentype.gpos" ,
210
+ include_str ! ( "../fontbakery/Lib/fontbakery/checks/opentype/gpos.py" ) ,
211
+ cr,
212
+ ) ?;
213
+ register_python_checks (
214
+ "fontbakery.checks.opentype.head" ,
215
+ include_str ! ( "../fontbakery/Lib/fontbakery/checks/opentype/head.py" ) ,
216
+ cr,
217
+ ) ?;
218
+ register_python_checks (
219
+ "fontbakery.checks.opentype.hhea" ,
220
+ include_str ! ( "../fontbakery/Lib/fontbakery/checks/opentype/hhea.py" ) ,
221
+ cr,
222
+ ) ?;
87
223
cr. register_profile (
88
224
"fontbakery" ,
89
225
Profile :: from_toml (
90
226
r#"
91
- [sections]
92
- "Test profile" = [
93
- "hinting_impact",
94
- "opentype/name/empty_records",
95
- "opentype/monospace",
96
- ]
97
- "# ,
227
+ [sections]
228
+ "Test profile" = [
229
+ "hinting_impact",
230
+ "opentype/name/empty_records",
231
+ "opentype/monospace",
232
+ "opentype/cff_call_depth",
233
+ ]
234
+ "# ,
98
235
)
99
236
. map_err ( |_| "Couldn't parse profile" ) ?,
100
237
)
0 commit comments