1
1
use crate :: CargoResult ;
2
2
3
+ type Span = std:: ops:: Range < usize > ;
4
+
3
5
#[ derive( Debug ) ]
4
6
pub struct ScriptSource < ' s > {
5
- shebang : Option < & ' s str > ,
6
- info : Option < & ' s str > ,
7
- frontmatter : Option < & ' s str > ,
8
- content : & ' s str ,
7
+ /// The full file
8
+ raw : & ' s str ,
9
+ /// The `#!/usr/bin/env cargo` line, if present
10
+ shebang : Option < Span > ,
11
+ /// The code fence opener (`---`)
12
+ open : Option < Span > ,
13
+ /// Trailing text after `ScriptSource::open` that identifies the meaning of
14
+ /// `ScriptSource::frontmatter`
15
+ info : Option < Span > ,
16
+ /// The lines between `ScriptSource::open` and `ScriptSource::close`
17
+ frontmatter : Option < Span > ,
18
+ /// The code fence closer (`---`)
19
+ close : Option < Span > ,
20
+ /// All content after the frontmatter and shebang
21
+ content : Span ,
9
22
}
10
23
11
24
impl < ' s > ScriptSource < ' s > {
12
- pub fn parse ( input : & ' s str ) -> CargoResult < Self > {
25
+ pub fn parse ( raw : & ' s str ) -> CargoResult < Self > {
26
+ use winnow:: stream:: FindSlice as _;
27
+ use winnow:: stream:: Location as _;
28
+ use winnow:: stream:: Offset as _;
29
+ use winnow:: stream:: Stream as _;
30
+
31
+ let content_end = raw. len ( ) ;
13
32
let mut source = Self {
33
+ raw,
14
34
shebang : None ,
35
+ open : None ,
15
36
info : None ,
16
37
frontmatter : None ,
17
- content : input,
38
+ close : None ,
39
+ content : 0 ..content_end,
18
40
} ;
19
41
20
- if let Some ( shebang_end) = strip_shebang ( source. content ) {
21
- let ( shebang, content) = source. content . split_at ( shebang_end) ;
22
- source. shebang = Some ( shebang) ;
23
- source. content = content;
24
- }
42
+ let mut input = winnow:: stream:: LocatingSlice :: new ( raw) ;
25
43
26
- let mut rest = source. content ;
44
+ if let Some ( shebang_end) = strip_shebang ( input. as_ref ( ) ) {
45
+ let shebang_start = input. current_token_start ( ) ;
46
+ let _ = input. next_slice ( shebang_end) ;
47
+ let shebang_end = input. current_token_start ( ) ;
48
+ source. shebang = Some ( shebang_start..shebang_end) ;
49
+ source. content = shebang_end..content_end;
50
+ }
27
51
28
52
// Whitespace may precede a frontmatter but must end with a newline
29
- if let Some ( nl_end) = strip_ws_lines ( rest ) {
30
- rest = & rest [ nl_end.. ] ;
53
+ if let Some ( nl_end) = strip_ws_lines ( input . as_ref ( ) ) {
54
+ let _ = input . next_slice ( nl_end ) ;
31
55
}
32
56
33
57
// Opens with a line that starts with 3 or more `-` followed by an optional identifier
34
58
const FENCE_CHAR : char = '-' ;
35
- let fence_length = rest
59
+ let fence_length = input
60
+ . as_ref ( )
36
61
. char_indices ( )
37
62
. find_map ( |( i, c) | ( c != FENCE_CHAR ) . then_some ( i) )
38
- . unwrap_or ( rest . len ( ) ) ;
63
+ . unwrap_or_else ( || input . eof_offset ( ) ) ;
39
64
match fence_length {
40
65
0 => {
41
66
return Ok ( source) ;
@@ -48,39 +73,50 @@ impl<'s> ScriptSource<'s> {
48
73
}
49
74
_ => { }
50
75
}
51
- let ( fence_pattern, rest) = rest. split_at ( fence_length) ;
52
- let Some ( info_end_index) = rest. find ( '\n' ) else {
76
+ let open_start = input. current_token_start ( ) ;
77
+ let fence_pattern = input. next_slice ( fence_length) ;
78
+ let open_end = input. current_token_start ( ) ;
79
+ source. open = Some ( open_start..open_end) ;
80
+ let Some ( info_nl) = input. find_slice ( "\n " ) else {
53
81
anyhow:: bail!( "no closing `{fence_pattern}` found for frontmatter" ) ;
54
82
} ;
55
- let ( info, rest ) = rest . split_at ( info_end_index ) ;
83
+ let info = input . next_slice ( info_nl . start ) ;
56
84
let info = info. trim_matches ( is_whitespace) ;
57
85
if !info. is_empty ( ) {
58
- source. info = Some ( info) ;
86
+ let info_start = info. offset_from ( & raw ) ;
87
+ let info_end = info_start + info. len ( ) ;
88
+ source. info = Some ( info_start..info_end) ;
59
89
}
60
90
61
91
// Ends with a line that starts with a matching number of `-` only followed by whitespace
62
92
let nl_fence_pattern = format ! ( "\n {fence_pattern}" ) ;
63
- let Some ( frontmatter_nl) = rest . find ( & nl_fence_pattern) else {
93
+ let Some ( frontmatter_nl) = input . find_slice ( nl_fence_pattern. as_str ( ) ) else {
64
94
anyhow:: bail!( "no closing `{fence_pattern}` found for frontmatter" ) ;
65
95
} ;
66
- let frontmatter = & rest[ ..frontmatter_nl + 1 ] ;
67
- let frontmatter = frontmatter
68
- . strip_prefix ( '\n' )
69
- . expect ( "earlier `found` + `split_at` left us here" ) ;
70
- source. frontmatter = Some ( frontmatter) ;
71
- let rest = & rest[ frontmatter_nl + nl_fence_pattern. len ( ) ..] ;
72
-
73
- let ( after_closing_fence, rest) = rest. split_once ( "\n " ) . unwrap_or ( ( rest, "" ) ) ;
96
+ let frontmatter_start = input. current_token_start ( ) + 1 ; // skip nl from infostring
97
+ let _ = input. next_slice ( frontmatter_nl. start + 1 ) ;
98
+ let frontmatter_end = input. current_token_start ( ) ;
99
+ source. frontmatter = Some ( frontmatter_start..frontmatter_end) ;
100
+ let close_start = input. current_token_start ( ) ;
101
+ let _ = input. next_slice ( fence_length) ;
102
+ let close_end = input. current_token_start ( ) ;
103
+ source. close = Some ( close_start..close_end) ;
104
+
105
+ let nl = input. find_slice ( "\n " ) ;
106
+ let after_closing_fence = input. next_slice (
107
+ nl. map ( |span| span. end )
108
+ . unwrap_or_else ( || input. eof_offset ( ) ) ,
109
+ ) ;
110
+ let content_start = input. current_token_start ( ) ;
74
111
let after_closing_fence = after_closing_fence. trim_matches ( is_whitespace) ;
75
112
if !after_closing_fence. is_empty ( ) {
76
113
// extra characters beyond the original fence pattern, even if they are extra `-`
77
114
anyhow:: bail!( "trailing characters found after frontmatter close" ) ;
78
115
}
79
116
80
- let frontmatter_len = input. len ( ) - rest. len ( ) ;
81
- source. content = & input[ frontmatter_len..] ;
117
+ source. content = content_start..content_end;
82
118
83
- let repeat = Self :: parse ( source. content ) ?;
119
+ let repeat = Self :: parse ( source. content ( ) ) ?;
84
120
if repeat. frontmatter . is_some ( ) {
85
121
anyhow:: bail!( "only one frontmatter is supported" ) ;
86
122
}
@@ -89,19 +125,43 @@ impl<'s> ScriptSource<'s> {
89
125
}
90
126
91
127
pub fn shebang ( & self ) -> Option < & ' s str > {
92
- self . shebang
128
+ self . shebang . clone ( ) . map ( |span| & self . raw [ span] )
129
+ }
130
+
131
+ pub fn shebang_span ( & self ) -> Option < Span > {
132
+ self . shebang . clone ( )
133
+ }
134
+
135
+ pub fn open_span ( & self ) -> Option < Span > {
136
+ self . open . clone ( )
93
137
}
94
138
95
139
pub fn info ( & self ) -> Option < & ' s str > {
96
- self . info
140
+ self . info . clone ( ) . map ( |span| & self . raw [ span] )
141
+ }
142
+
143
+ pub fn info_span ( & self ) -> Option < Span > {
144
+ self . info . clone ( )
97
145
}
98
146
99
147
pub fn frontmatter ( & self ) -> Option < & ' s str > {
100
- self . frontmatter
148
+ self . frontmatter . clone ( ) . map ( |span| & self . raw [ span] )
149
+ }
150
+
151
+ pub fn frontmatter_span ( & self ) -> Option < Span > {
152
+ self . frontmatter . clone ( )
153
+ }
154
+
155
+ pub fn close_span ( & self ) -> Option < Span > {
156
+ self . close . clone ( )
101
157
}
102
158
103
159
pub fn content ( & self ) -> & ' s str {
104
- self . content
160
+ & self . raw [ self . content . clone ( ) ]
161
+ }
162
+
163
+ pub fn content_span ( & self ) -> Span {
164
+ self . content . clone ( )
105
165
}
106
166
}
107
167
0 commit comments