11use crate :: CargoResult ;
22
3+ type Span = std:: ops:: Range < usize > ;
4+
35#[ derive( Debug ) ]
46pub 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 ,
922}
1023
1124impl < ' 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 ( ) ;
1332 let mut source = Self {
33+ raw,
1434 shebang : None ,
35+ open : None ,
1536 info : None ,
1637 frontmatter : None ,
17- content : input,
38+ close : None ,
39+ content : 0 ..content_end,
1840 } ;
1941
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) ;
2543
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+ }
2751
2852 // 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 ) ;
3155 }
3256
3357 // Opens with a line that starts with 3 or more `-` followed by an optional identifier
3458 const FENCE_CHAR : char = '-' ;
35- let fence_length = rest
59+ let fence_length = input
60+ . as_ref ( )
3661 . char_indices ( )
3762 . find_map ( |( i, c) | ( c != FENCE_CHAR ) . then_some ( i) )
38- . unwrap_or ( rest . len ( ) ) ;
63+ . unwrap_or_else ( || input . eof_offset ( ) ) ;
3964 match fence_length {
4065 0 => {
4166 return Ok ( source) ;
@@ -48,39 +73,50 @@ impl<'s> ScriptSource<'s> {
4873 }
4974 _ => { }
5075 }
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 {
5381 anyhow:: bail!( "no closing `{fence_pattern}` found for frontmatter" ) ;
5482 } ;
55- let ( info, rest ) = rest . split_at ( info_end_index ) ;
83+ let info = input . next_slice ( info_nl . start ) ;
5684 let info = info. trim_matches ( is_whitespace) ;
5785 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) ;
5989 }
6090
6191 // Ends with a line that starts with a matching number of `-` only followed by whitespace
6292 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 {
6494 anyhow:: bail!( "no closing `{fence_pattern}` found for frontmatter" ) ;
6595 } ;
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 ( ) ;
74111 let after_closing_fence = after_closing_fence. trim_matches ( is_whitespace) ;
75112 if !after_closing_fence. is_empty ( ) {
76113 // extra characters beyond the original fence pattern, even if they are extra `-`
77114 anyhow:: bail!( "trailing characters found after frontmatter close" ) ;
78115 }
79116
80- let frontmatter_len = input. len ( ) - rest. len ( ) ;
81- source. content = & input[ frontmatter_len..] ;
117+ source. content = content_start..content_end;
82118
83- let repeat = Self :: parse ( source. content ) ?;
119+ let repeat = Self :: parse ( source. content ( ) ) ?;
84120 if repeat. frontmatter . is_some ( ) {
85121 anyhow:: bail!( "only one frontmatter is supported" ) ;
86122 }
@@ -89,19 +125,43 @@ impl<'s> ScriptSource<'s> {
89125 }
90126
91127 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 ( )
93137 }
94138
95139 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 ( )
97145 }
98146
99147 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 ( )
101157 }
102158
103159 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 ( )
105165 }
106166}
107167
0 commit comments