@@ -19,11 +19,11 @@ import { isAbsolute as pathIsAbsolute, dirname as pathDirname, join as pathJoin
19
19
import { Arguments , isError } from '@kui-shell/core'
20
20
import { loadNotebook } from '@kui-shell/plugin-client-common/notebook'
21
21
22
- import { stripFrontmatter } from '../components/Content/Markdown/frontmatter-parser'
22
+ import { tryFrontmatter } from '../components/Content/Markdown/frontmatter-parser'
23
23
24
24
const debug = Debug ( 'plugin-client-common/markdown/snippets' )
25
25
26
- const RE_SNIPPET = / ^ - - ( - * ) 8 < - - ( - * ) \s + " ( [ ^ " ] + ) " ( \s + " ( [ ^ " ] + ) " ) ? \s * $ /
26
+ const RE_SNIPPET = / ^ ( \s * ) - - ( - * ) 8 < - - ( - * ) \s + " ( [ ^ " ] + ) " ( \s + " ( [ ^ " ] + ) " ) ? \s * $ /
27
27
28
28
function isUrl ( a : string ) {
29
29
return / ^ h t t p s ? : / . test ( a )
@@ -78,77 +78,103 @@ function rerouteLinks(basePath: string, data: string) {
78
78
* Simplistic approximation of
79
79
* https://facelessuser.github.io/pymdown-extensions/extensions/snippets/.
80
80
*/
81
- export default function inlineSnippets ( snippetBasePath ?: string ) {
82
- return async ( data : string , srcFilePath : string , args : Pick < Arguments , 'REPL' > ) : Promise < string > =>
83
- Promise . all (
84
- data . split ( / \n / ) . map ( async line => {
85
- const match = line . match ( RE_SNIPPET )
86
- if ( ! match ) {
87
- return line
88
- } else {
89
- const snippetFileName = match [ 3 ]
81
+ export default function inlineSnippets (
82
+ args : Pick < Arguments , 'REPL' > ,
83
+ snippetBasePath ?: string ,
84
+ includeFrontmatter = true
85
+ ) {
86
+ const fetchRecursively = async (
87
+ snippetFileName : string ,
88
+ srcFilePath : string ,
89
+ provenance : string [ ] ,
90
+ optionalSnippetBasePathInSnippetLine ?: string
91
+ ) => {
92
+ const getBasePath = ( snippetBasePath : string ) => {
93
+ try {
94
+ const basePath = optionalSnippetBasePathInSnippetLine || snippetBasePath
95
+
96
+ return isAbsolute ( basePath ) ? basePath : srcFilePath ? join ( srcFilePath , basePath ) : undefined
97
+ } catch ( err ) {
98
+ debug ( err )
99
+ return undefined
100
+ }
101
+ }
90
102
91
- const getBasePath = ( snippetBasePath : string ) => {
92
- try {
93
- const basePath = match [ 5 ] || snippetBasePath
103
+ // Call ourselves recursively, in case a fetched snippet
104
+ // fetches other files. We also may need to reroute relative
105
+ // <img> and <a> links according to the given `basePath`.
106
+ const recurse = ( basePath : string , recursedSnippetFileName : string , data : string ) => {
107
+ // Note: intentionally using `snippetBasePath` for the
108
+ // first argument, as this represents the "root" base
109
+ // path, either from the URL of the original filepath (we
110
+ // may be recursing here) or from the command line or from
111
+ // the topmatter of the original document. The second
112
+ // represents the current base path in the recursion.
113
+ const base = isAbsolute ( basePath ) || ! snippetBasePath ? basePath : snippetBasePath
114
+ return inlineSnippets ( args , base , false ) ( rerouteLinks ( base , data ) , recursedSnippetFileName , provenance )
115
+ }
94
116
95
- return isAbsolute ( basePath ) ? basePath : srcFilePath ? join ( srcFilePath , basePath ) : undefined
96
- } catch ( err ) {
97
- debug ( err )
98
- return undefined
99
- }
100
- }
117
+ const candidates = optionalSnippetBasePathInSnippetLine
118
+ ? [ optionalSnippetBasePathInSnippetLine ]
119
+ : [ './' , snippetBasePath , '../' , '../snippets' , '../../snippets' , '../../' , '../../../' ] . filter ( Boolean )
120
+
121
+ const snippetDatas = isUrl ( snippetFileName )
122
+ ? [
123
+ await loadNotebook ( snippetFileName , args )
124
+ . then ( async data => ( {
125
+ filepath : snippetFileName ,
126
+ snippetData : await recurse ( snippetBasePath || dirname ( snippetFileName ) , snippetFileName , toString ( data ) )
127
+ } ) )
128
+ . catch ( err => {
129
+ debug ( 'Warning: could not fetch inlined content 1' , snippetBasePath , snippetFileName , err )
130
+ return err
131
+ } )
132
+ ]
133
+ : await Promise . all (
134
+ candidates
135
+ . map ( getBasePath )
136
+ . filter ( Boolean )
137
+ . map ( myBasePath => ( {
138
+ myBasePath,
139
+ filepath : join ( myBasePath , snippetFileName )
140
+ } ) )
141
+ . filter ( _ => _ && _ . filepath !== srcFilePath ) // avoid cycles
142
+ . map ( ( { myBasePath, filepath } ) =>
143
+ loadNotebook ( filepath , args )
144
+ . then ( async data => ( { filepath, snippetData : await recurse ( myBasePath , filepath , toString ( data ) ) } ) )
145
+ . catch ( err => {
146
+ debug ( 'Warning: could not fetch inlined content 2' , myBasePath , snippetFileName , err )
147
+ return err
148
+ } )
149
+ )
150
+ ) . then ( _ => _ . filter ( Boolean ) )
151
+
152
+ const snippetData =
153
+ snippetDatas . find ( _ => _ . snippetData && ! isError ( _ . snippetData ) ) ||
154
+ snippetDatas . find ( _ => isError ( _ . snippetData ) && ! / E N O T D I R / . test ( _ . snippetData . message ) ) ||
155
+ snippetDatas [ 0 ]
156
+
157
+ return snippetData
158
+ }
101
159
102
- // Call ourselves recursively, in case a fetched snippet
103
- // fetches other files. We also may need to reroute relative
104
- // <img> and <a> links according to the given `basePath`.
105
- const recurse = ( basePath : string , snippetFileName : string , data : string ) => {
106
- // Note: intentionally using `snippetBasePath` for the
107
- // first argument, as this represents the "root" base
108
- // path, either from the URL of the original filepath (we
109
- // may be recursing here) or from the command line or from
110
- // the topmatter of the original document. The second
111
- // represents the current base path in the recursion.
112
- const base = isAbsolute ( basePath ) || ! snippetBasePath ? basePath : snippetBasePath
113
- return inlineSnippets ( base ) ( rerouteLinks ( base , data ) , snippetFileName , args )
114
- }
160
+ return async ( data : string , srcFilePath : string , provenance : string [ ] = [ ] ) : Promise < string > => {
161
+ const { body } = tryFrontmatter ( data )
115
162
116
- const candidates = match [ 5 ]
117
- ? [ match [ 5 ] ]
118
- : [ './' , snippetBasePath , '../' , '../snippets' , '../../snippets' , '../../' , '../../../' ] . filter ( Boolean )
119
-
120
- const snippetDatas = isUrl ( snippetFileName )
121
- ? [
122
- await loadNotebook ( snippetFileName , args )
123
- . then ( data => recurse ( snippetBasePath || dirname ( snippetFileName ) , snippetFileName , toString ( data ) ) )
124
- . catch ( err => {
125
- debug ( 'Warning: could not fetch inlined content 1' , snippetBasePath , snippetFileName , err )
126
- return err
127
- } )
128
- ]
129
- : await Promise . all (
130
- candidates
131
- . map ( getBasePath )
132
- . filter ( Boolean )
133
- . map ( myBasePath => ( {
134
- myBasePath,
135
- filepath : join ( myBasePath , snippetFileName )
136
- } ) )
137
- . filter ( _ => _ && _ . filepath !== srcFilePath ) // avoid cycles
138
- . map ( ( { myBasePath, filepath } ) =>
139
- loadNotebook ( filepath , args )
140
- . then ( data => recurse ( myBasePath , filepath , toString ( data ) ) )
141
- . catch ( err => {
142
- debug ( 'Warning: could not fetch inlined content 2' , myBasePath , snippetFileName , err )
143
- return err
144
- } )
145
- )
146
- ) . then ( _ => _ . filter ( Boolean ) )
147
-
148
- const snippetData =
149
- snippetDatas . find ( _ => _ && ! isError ( _ ) ) ||
150
- snippetDatas . find ( _ => isError ( _ ) && ! / E N O T D I R / . test ( _ . message ) ) ||
151
- snippetDatas [ 0 ]
163
+ const mainContent = await Promise . all (
164
+ ( includeFrontmatter ? data : body ) . split ( / \n / ) . map ( async line => {
165
+ const match = line . match ( RE_SNIPPET )
166
+ if ( ! match ) {
167
+ return line
168
+ } else {
169
+ const indentation = match [ 1 ]
170
+ const snippetFileName = match [ 4 ]
171
+ const optionalSnippetBasePathInSnippetLine = match [ 6 ]
172
+ const { snippetData } = await fetchRecursively (
173
+ snippetFileName ,
174
+ srcFilePath ,
175
+ provenance ,
176
+ optionalSnippetBasePathInSnippetLine
177
+ )
152
178
153
179
if ( ! snippetData ) {
154
180
return line
@@ -157,12 +183,20 @@ export default function inlineSnippets(snippetBasePath?: string) {
157
183
${ indent ( snippetData . message ) } `
158
184
} else {
159
185
debug ( 'successfully fetched inlined content' , snippetFileName )
160
-
161
- // for now, we completely strip off the topmatter from
162
- // snippets. TODO?
163
- return stripFrontmatter ( toString ( snippetData ) )
186
+ const data = toString ( snippetData )
187
+ if ( indentation && indentation . length > 0 ) {
188
+ return data
189
+ . split ( / \n / )
190
+ . map ( line => `${ indentation } ${ line } ` )
191
+ . join ( '\n' )
192
+ } else {
193
+ return data
194
+ }
164
195
}
165
196
}
166
197
} )
167
198
) . then ( _ => _ . join ( '\n' ) )
199
+
200
+ return mainContent
201
+ }
168
202
}
0 commit comments