@@ -16,6 +16,30 @@ interface EntityClass {
1616 attributes : EntityAttribute [ ] ;
1717}
1818
19+ interface ApiParameter {
20+ name : string ;
21+ description : string ;
22+ required ?: boolean ;
23+ type ?: string ;
24+ }
25+
26+ interface ApiMethod {
27+ name : string ;
28+ httpMethod : string ;
29+ endpoint : string ;
30+ description : string ;
31+ parameters ?: ApiParameter [ ] ;
32+ returns ?: string ;
33+ oauth ?: string ;
34+ version ?: string ;
35+ }
36+
37+ interface ApiMethodsFile {
38+ name : string ;
39+ description : string ;
40+ methods : ApiMethod [ ] ;
41+ }
42+
1943class EntityParser {
2044 private entitiesPath : string ;
2145
@@ -129,6 +153,163 @@ class EntityParser {
129153 }
130154}
131155
156+ class MethodParser {
157+ private methodsPath : string ;
158+
159+ constructor ( ) {
160+ this . methodsPath = path . join ( __dirname , '../mastodon-documentation/content/en/methods' ) ;
161+ }
162+
163+ public parseAllMethods ( ) : ApiMethodsFile [ ] {
164+ const methodFiles : ApiMethodsFile [ ] = [ ] ;
165+
166+ if ( ! fs . existsSync ( this . methodsPath ) ) {
167+ console . error ( `Methods path does not exist: ${ this . methodsPath } ` ) ;
168+ return methodFiles ;
169+ }
170+
171+ const files = fs . readdirSync ( this . methodsPath ) . filter ( file =>
172+ file . endsWith ( '.md' ) && fs . statSync ( path . join ( this . methodsPath , file ) ) . isFile ( )
173+ ) ;
174+
175+ for ( const file of files ) {
176+ try {
177+ const methodFile = this . parseMethodFile ( path . join ( this . methodsPath , file ) ) ;
178+ if ( methodFile ) {
179+ methodFiles . push ( methodFile ) ;
180+ }
181+ } catch ( error ) {
182+ console . error ( `Error parsing method file ${ file } :` , error ) ;
183+ }
184+ }
185+
186+ return methodFiles ;
187+ }
188+
189+ private parseMethodFile ( filePath : string ) : ApiMethodsFile | null {
190+ const content = fs . readFileSync ( filePath , 'utf-8' ) ;
191+ const parsed = matter ( content ) ;
192+
193+ // Extract file name from frontmatter title
194+ const fileName = parsed . data . title || path . basename ( filePath , '.md' ) ;
195+ if ( ! fileName ) {
196+ console . warn ( `No title found in ${ filePath } ` ) ;
197+ return null ;
198+ }
199+
200+ // Extract description from frontmatter
201+ const description = parsed . data . description || '' ;
202+
203+ // Parse methods from markdown content
204+ const methods = this . parseMethods ( parsed . content ) ;
205+
206+ return {
207+ name : fileName ,
208+ description,
209+ methods
210+ } ;
211+ }
212+
213+ private parseMethods ( content : string ) : ApiMethod [ ] {
214+ const methods : ApiMethod [ ] = [ ] ;
215+
216+ // Match method sections: ## Method Name {#anchor}
217+ const methodSections = content . split ( / (? = ^ # # [ ^ { ] * \{ # [ ^ } ] + \} ) / m) ;
218+
219+ for ( const section of methodSections ) {
220+ if ( section . trim ( ) === '' ) continue ;
221+
222+ const method = this . parseMethodSection ( section ) ;
223+ if ( method ) {
224+ methods . push ( method ) ;
225+ }
226+ }
227+
228+ return methods ;
229+ }
230+
231+ private parseMethodSection ( section : string ) : ApiMethod | null {
232+ // Extract method name from header: ## Method Name {#anchor}
233+ const nameMatch = section . match ( / ^ # # ( [ ^ { ] + ) \{ # [ ^ } ] + \} / m) ;
234+ if ( ! nameMatch ) return null ;
235+
236+ const name = nameMatch [ 1 ] . trim ( ) ;
237+
238+ // Extract HTTP method and endpoint: ```http\nMETHOD /path\n```
239+ const httpMatch = section . match ( / ` ` ` h t t p \s * \n ( [ A - Z ] + ) \s + ( [ ^ \s \n ] + ) [ ^ \n ] * \n ` ` ` / ) ;
240+ if ( ! httpMatch ) return null ;
241+
242+ const httpMethod = httpMatch [ 1 ] . trim ( ) ;
243+ const endpoint = httpMatch [ 2 ] . trim ( ) ;
244+
245+ // Extract description (first paragraph after the endpoint)
246+ const descriptionMatch = section . match ( / ` ` ` h t t p [ ^ ` ] * ` ` ` \s * \n \n ( [ ^ * \n ] [ ^ \n ] * ) / ) ;
247+ const description = descriptionMatch ? descriptionMatch [ 1 ] . trim ( ) : '' ;
248+
249+ // Extract returns, oauth, version info
250+ const returnsMatch = section . match ( / \* \* R e t u r n s : \* \* \s * ( [ ^ \\ \n ] + ) / ) ;
251+ const returns = returnsMatch ? this . cleanMarkdown ( returnsMatch [ 1 ] . trim ( ) ) : undefined ;
252+
253+ const oauthMatch = section . match ( / \* \* O A u t h : \* \* \s * ( [ ^ \\ \n ] + ) / ) ;
254+ const oauth = oauthMatch ? this . cleanMarkdown ( oauthMatch [ 1 ] . trim ( ) ) : undefined ;
255+
256+ const versionMatch = section . match ( / \* \* V e r s i o n h i s t o r y : \* \* \s * ( [ ^ \n ] * ) / ) ;
257+ const version = versionMatch ? this . cleanMarkdown ( versionMatch [ 1 ] . trim ( ) ) : undefined ;
258+
259+ // Parse parameters from Form data parameters section
260+ const parameters = this . parseParameters ( section ) ;
261+
262+ return {
263+ name,
264+ httpMethod,
265+ endpoint,
266+ description,
267+ parameters : parameters . length > 0 ? parameters : undefined ,
268+ returns,
269+ oauth,
270+ version
271+ } ;
272+ }
273+
274+ private parseParameters ( section : string ) : ApiParameter [ ] {
275+ const parameters : ApiParameter [ ] = [ ] ;
276+
277+ // Find parameters section
278+ const paramMatch = section . match ( / # # # # # F o r m d a t a p a r a m e t e r s \s * ( [ \s \S ] * ?) (? = \n # | $ ) / ) ;
279+ if ( ! paramMatch ) return parameters ;
280+
281+ const paramSection = paramMatch [ 1 ] ;
282+
283+ // Match parameter definitions: parameter_name\n: description
284+ const paramRegex = / ^ ( [ a - z A - Z _ ] [ a - z A - Z 0 - 9 _ ] * ) \s * \n : \s * ( [ ^ ] * ?) (? = \n [ a - z A - Z _ ] | \n \n | $ ) / gm;
285+
286+ let match ;
287+ while ( ( match = paramRegex . exec ( paramSection ) ) !== null ) {
288+ const [ , name , desc ] = match ;
289+
290+ const cleanDesc = this . cleanMarkdown ( desc . trim ( ) ) ;
291+ const required = cleanDesc . includes ( '{{<required>}}' ) || cleanDesc . includes ( 'required' ) ;
292+
293+ parameters . push ( {
294+ name : name . trim ( ) ,
295+ description : cleanDesc . replace ( / \{ \{ < r e q u i r e d > \} \} \s * / g, '' ) ,
296+ required : required ? true : undefined
297+ } ) ;
298+ }
299+
300+ return parameters ;
301+ }
302+
303+ private cleanMarkdown ( text : string ) : string {
304+ return text
305+ . replace ( / \* \* / g, '' ) // Remove bold markdown
306+ . replace ( / \{ \{ < [ ^ > ] + > \} \} / g, '' ) // Remove Hugo shortcodes
307+ . replace ( / \[ [ ^ \] ] * \] \( [ ^ ) ] * \) / g, '' ) // Remove markdown links
308+ . replace ( / \\ \s * $ / , '' ) // Remove trailing backslashes
309+ . trim ( ) ;
310+ }
311+ }
312+
132313function main ( ) {
133314 console . log ( 'Parsing Mastodon entity files...' ) ;
134315
@@ -154,10 +335,43 @@ function main() {
154335 }
155336
156337 console . log ( `Total entities parsed: ${ entities . length } ` ) ;
338+
339+ console . log ( '\nParsing Mastodon API method files...' ) ;
340+
341+ const methodParser = new MethodParser ( ) ;
342+ const methodFiles = methodParser . parseAllMethods ( ) ;
343+
344+ console . log ( `\nFound ${ methodFiles . length } method files:\n` ) ;
345+
346+ for ( const methodFile of methodFiles ) {
347+ console . log ( `File: ${ methodFile . name } ` ) ;
348+ console . log ( `Description: ${ methodFile . description } ` ) ;
349+ console . log ( `Methods (${ methodFile . methods . length } ):` ) ;
350+
351+ for ( const method of methodFile . methods ) {
352+ console . log ( ` - ${ method . httpMethod } ${ method . endpoint } ` ) ;
353+ console . log ( ` Name: ${ method . name } ` ) ;
354+ console . log ( ` Description: ${ method . description } ` ) ;
355+ if ( method . returns ) console . log ( ` Returns: ${ method . returns } ` ) ;
356+ if ( method . oauth ) console . log ( ` OAuth: ${ method . oauth } ` ) ;
357+ if ( method . parameters && method . parameters . length > 0 ) {
358+ console . log ( ` Parameters (${ method . parameters . length } ):` ) ;
359+ for ( const param of method . parameters ) {
360+ const reqText = param . required ? ' [required]' : '' ;
361+ console . log ( ` - ${ param . name } : ${ param . description } ${ reqText } ` ) ;
362+ }
363+ }
364+ }
365+ console . log ( '' ) ;
366+ }
367+
368+ console . log ( `Total method files parsed: ${ methodFiles . length } ` ) ;
369+ const totalMethods = methodFiles . reduce ( ( sum , file ) => sum + file . methods . length , 0 ) ;
370+ console . log ( `Total API methods parsed: ${ totalMethods } ` ) ;
157371}
158372
159373if ( require . main === module ) {
160374 main ( ) ;
161375}
162376
163- export { EntityParser , EntityClass , EntityAttribute } ;
377+ export { EntityParser , EntityClass , EntityAttribute , MethodParser , ApiMethodsFile , ApiMethod , ApiParameter } ;
0 commit comments