@@ -15,26 +15,72 @@ pub fn expand_manifest(
15
15
path : & std:: path:: Path ,
16
16
config : & Config ,
17
17
) -> CargoResult < String > {
18
- let comment = match extract_comment ( content) {
19
- Ok ( comment) => Some ( comment) ,
20
- Err ( err) => {
21
- tracing:: trace!( "failed to extract doc comment: {err}" ) ;
22
- None
18
+ let source = split_source ( content) ?;
19
+ if let Some ( frontmatter) = source. frontmatter {
20
+ match source. info {
21
+ Some ( "cargo" ) => { }
22
+ None => {
23
+ anyhow:: bail!( "frontmatter is missing an infostring; specify `cargo` for embedding a manifest" ) ;
24
+ }
25
+ Some ( other) => {
26
+ if let Some ( remainder) = other. strip_prefix ( "cargo," ) {
27
+ anyhow:: bail!( "cargo does not support frontmatter infostring attributes like `{remainder}` at this time" )
28
+ } else {
29
+ anyhow:: bail!( "frontmatter infostring `{other}` is unsupported by cargo; specify `cargo` for embedding a manifest" )
30
+ }
31
+ }
23
32
}
24
- }
25
- . unwrap_or_default ( ) ;
26
- let manifest = match extract_manifest ( & comment) ? {
27
- Some ( manifest) => Some ( manifest) ,
28
- None => {
29
- tracing:: trace!( "failed to extract manifest" ) ;
30
- None
33
+
34
+ // HACK: until rustc has native support for this syntax, we have to remove it from the
35
+ // source file
36
+ use std:: fmt:: Write as _;
37
+ let hash = crate :: util:: hex:: short_hash ( & path. to_string_lossy ( ) ) ;
38
+ let mut rel_path = std:: path:: PathBuf :: new ( ) ;
39
+ rel_path. push ( "target" ) ;
40
+ rel_path. push ( & hash[ 0 ..2 ] ) ;
41
+ rel_path. push ( & hash[ 2 ..] ) ;
42
+ let target_dir = config. home ( ) . join ( rel_path) ;
43
+ let hacked_path = target_dir
44
+ . join (
45
+ path. file_name ( )
46
+ . expect ( "always a name for embedded manifests" ) ,
47
+ )
48
+ . into_path_unlocked ( ) ;
49
+ let mut hacked_source = String :: new ( ) ;
50
+ if let Some ( shebang) = source. shebang {
51
+ writeln ! ( hacked_source, "{shebang}" ) ?;
52
+ }
53
+ writeln ! ( hacked_source) ?; // open
54
+ for _ in 0 ..frontmatter. lines ( ) . count ( ) {
55
+ writeln ! ( hacked_source) ?;
31
56
}
57
+ writeln ! ( hacked_source) ?; // close
58
+ writeln ! ( hacked_source, "{}" , source. content) ?;
59
+ if let Some ( parent) = hacked_path. parent ( ) {
60
+ cargo_util:: paths:: create_dir_all ( parent) ?;
61
+ }
62
+ cargo_util:: paths:: write_if_changed ( & hacked_path, hacked_source) ?;
63
+
64
+ let manifest = expand_manifest_ ( & frontmatter, & hacked_path, config)
65
+ . with_context ( || format ! ( "failed to parse manifest at {}" , path. display( ) ) ) ?;
66
+ let manifest = toml:: to_string_pretty ( & manifest) ?;
67
+ Ok ( manifest)
68
+ } else {
69
+ // Legacy doc-comment support; here only for transitional purposes
70
+ let comment = extract_comment ( content) ?. unwrap_or_default ( ) ;
71
+ let manifest = match extract_manifest ( & comment) ? {
72
+ Some ( manifest) => Some ( manifest) ,
73
+ None => {
74
+ tracing:: trace!( "failed to extract manifest" ) ;
75
+ None
76
+ }
77
+ }
78
+ . unwrap_or_default ( ) ;
79
+ let manifest = expand_manifest_ ( & manifest, path, config)
80
+ . with_context ( || format ! ( "failed to parse manifest at {}" , path. display( ) ) ) ?;
81
+ let manifest = toml:: to_string_pretty ( & manifest) ?;
82
+ Ok ( manifest)
32
83
}
33
- . unwrap_or_default ( ) ;
34
- let manifest = expand_manifest_ ( & manifest, path, config)
35
- . with_context ( || format ! ( "failed to parse manifest at {}" , path. display( ) ) ) ?;
36
- let manifest = toml:: to_string_pretty ( & manifest) ?;
37
- Ok ( manifest)
38
84
}
39
85
40
86
fn expand_manifest_ (
@@ -66,10 +112,8 @@ fn expand_manifest_(
66
112
anyhow:: bail!( "`package.{key}` is not allowed in embedded manifests" )
67
113
}
68
114
}
69
- let file_name = path
70
- . file_name ( )
71
- . ok_or_else ( || anyhow:: format_err!( "no file name" ) ) ?
72
- . to_string_lossy ( ) ;
115
+ // HACK: Using an absolute path while `hacked_path` is in use
116
+ let bin_path = path. to_string_lossy ( ) . into_owned ( ) ;
73
117
let file_stem = path
74
118
. file_stem ( )
75
119
. ok_or_else ( || anyhow:: format_err!( "no file name" ) ) ?
@@ -103,10 +147,7 @@ fn expand_manifest_(
103
147
104
148
let mut bin = toml:: Table :: new ( ) ;
105
149
bin. insert ( "name" . to_owned ( ) , toml:: Value :: String ( bin_name) ) ;
106
- bin. insert (
107
- "path" . to_owned ( ) ,
108
- toml:: Value :: String ( file_name. into_owned ( ) ) ,
109
- ) ;
150
+ bin. insert ( "path" . to_owned ( ) , toml:: Value :: String ( bin_path) ) ;
110
151
manifest. insert (
111
152
"bin" . to_owned ( ) ,
112
153
toml:: Value :: Array ( vec ! [ toml:: Value :: Table ( bin) ] ) ,
@@ -159,8 +200,82 @@ fn sanitize_name(name: &str) -> String {
159
200
name
160
201
}
161
202
203
+ struct Source < ' s > {
204
+ shebang : Option < & ' s str > ,
205
+ info : Option < & ' s str > ,
206
+ frontmatter : Option < & ' s str > ,
207
+ content : & ' s str ,
208
+ }
209
+
210
+ fn split_source ( input : & str ) -> CargoResult < Source < ' _ > > {
211
+ let mut source = Source {
212
+ shebang : None ,
213
+ info : None ,
214
+ frontmatter : None ,
215
+ content : input,
216
+ } ;
217
+
218
+ // See rust-lang/rust's compiler/rustc_lexer/src/lib.rs's `strip_shebang`
219
+ // Shebang must start with `#!` literally, without any preceding whitespace.
220
+ // For simplicity we consider any line starting with `#!` a shebang,
221
+ // regardless of restrictions put on shebangs by specific platforms.
222
+ if let Some ( rest) = source. content . strip_prefix ( "#!" ) {
223
+ // Ok, this is a shebang but if the next non-whitespace token is `[`,
224
+ // then it may be valid Rust code, so consider it Rust code.
225
+ if rest. trim_start ( ) . starts_with ( '[' ) {
226
+ return Ok ( source) ;
227
+ }
228
+
229
+ // No other choice than to consider this a shebang.
230
+ let ( shebang, content) = source
231
+ . content
232
+ . split_once ( '\n' )
233
+ . unwrap_or ( ( source. content , "" ) ) ;
234
+ source. shebang = Some ( shebang) ;
235
+ source. content = content;
236
+ }
237
+
238
+ let tick_end = source
239
+ . content
240
+ . char_indices ( )
241
+ . find_map ( |( i, c) | ( c != '`' ) . then_some ( i) )
242
+ . unwrap_or ( source. content . len ( ) ) ;
243
+ let ( fence_pattern, rest) = match tick_end {
244
+ 0 => {
245
+ return Ok ( source) ;
246
+ }
247
+ 1 | 2 => {
248
+ anyhow:: bail!( "found {tick_end} backticks in rust frontmatter, expected at least 3" )
249
+ }
250
+ _ => source. content . split_at ( tick_end) ,
251
+ } ;
252
+ let ( info, content) = rest. split_once ( "\n " ) . unwrap_or ( ( rest, "" ) ) ;
253
+ if !info. is_empty ( ) {
254
+ source. info = Some ( info. trim_end ( ) ) ;
255
+ }
256
+ source. content = content;
257
+
258
+ let Some ( ( frontmatter, content) ) = source. content . split_once ( fence_pattern) else {
259
+ anyhow:: bail!( "no closing `{fence_pattern}` found for frontmatter" ) ;
260
+ } ;
261
+ source. frontmatter = Some ( frontmatter) ;
262
+ source. content = content;
263
+
264
+ let ( line, content) = source
265
+ . content
266
+ . split_once ( "\n " )
267
+ . unwrap_or ( ( source. content , "" ) ) ;
268
+ let line = line. trim ( ) ;
269
+ if !line. is_empty ( ) {
270
+ anyhow:: bail!( "unexpected trailing content on closing fence: `{line}`" ) ;
271
+ }
272
+ source. content = content;
273
+
274
+ Ok ( source)
275
+ }
276
+
162
277
/// Locates a "code block manifest" in Rust source.
163
- fn extract_comment ( input : & str ) -> CargoResult < String > {
278
+ fn extract_comment ( input : & str ) -> CargoResult < Option < String > > {
164
279
let mut doc_fragments = Vec :: new ( ) ;
165
280
let file = syn:: parse_file ( input) ?;
166
281
// HACK: `syn` doesn't tell us what kind of comment was used, so infer it from how many
@@ -181,7 +296,7 @@ fn extract_comment(input: &str) -> CargoResult<String> {
181
296
}
182
297
}
183
298
if doc_fragments. is_empty ( ) {
184
- anyhow :: bail! ( "no doc-comment found" ) ;
299
+ return Ok ( None ) ;
185
300
}
186
301
unindent_doc_fragments ( & mut doc_fragments) ;
187
302
@@ -190,7 +305,7 @@ fn extract_comment(input: &str) -> CargoResult<String> {
190
305
add_doc_fragment ( & mut doc_comment, frag) ;
191
306
}
192
307
193
- Ok ( doc_comment)
308
+ Ok ( Some ( doc_comment) )
194
309
}
195
310
196
311
/// A `#[doc]`
@@ -496,7 +611,7 @@ mod test_expand {
496
611
snapbox:: assert_eq (
497
612
r#"[[bin]]
498
613
name = "test-"
499
- path = "test.rs"
614
+ path = "/home/me/ test.rs"
500
615
501
616
[package]
502
617
autobenches = false
@@ -523,7 +638,7 @@ strip = true
523
638
snapbox:: assert_eq (
524
639
r#"[[bin]]
525
640
name = "test-"
526
- path = "test.rs"
641
+ path = "/home/me/ test.rs"
527
642
528
643
[dependencies]
529
644
time = "0.1.25"
@@ -561,38 +676,38 @@ mod test_comment {
561
676
562
677
macro_rules! ec {
563
678
( $s: expr) => {
564
- extract_comment( $s) . unwrap_or_else( |err| panic!( "{}" , err) )
679
+ extract_comment( $s)
680
+ . unwrap_or_else( |err| panic!( "{}" , err) )
681
+ . unwrap( )
565
682
} ;
566
683
}
567
684
568
685
#[ test]
569
686
fn test_no_comment ( ) {
570
- snapbox :: assert_eq (
571
- "no doc-comment found" ,
687
+ assert_eq ! (
688
+ None ,
572
689
extract_comment(
573
690
r#"
574
691
fn main () {
575
692
}
576
693
"# ,
577
694
)
578
- . unwrap_err ( )
579
- . to_string ( ) ,
695
+ . unwrap( )
580
696
) ;
581
697
}
582
698
583
699
#[ test]
584
700
fn test_no_comment_she_bang ( ) {
585
- snapbox :: assert_eq (
586
- "no doc-comment found" ,
701
+ assert_eq ! (
702
+ None ,
587
703
extract_comment(
588
704
r#"#!/usr/bin/env cargo-eval
589
705
590
706
fn main () {
591
707
}
592
708
"# ,
593
709
)
594
- . unwrap_err ( )
595
- . to_string ( ) ,
710
+ . unwrap( )
596
711
) ;
597
712
}
598
713
0 commit comments