1
+ import commonmark = require( 'commonmark' ) ;
2
+
3
+ /**
4
+ * Convert MarkDown to RST
5
+ *
6
+ * This is hard, and I'm doing it very hackily to get something out quickly.
7
+ *
8
+ * Preferably, the next person to look at this should a little more OO
9
+ * instead of procedural.
10
+ */
11
+ export function md2rst ( text : string ) {
12
+ const parser = new commonmark . Parser ( { smart : false } ) ;
13
+ const ast = parser . parse ( text ) ;
14
+
15
+ const ret = new Array < string > ( ) ;
16
+
17
+ let indent = 0 ;
18
+ function line ( ...xs : string [ ] ) {
19
+ for ( const x of xs ) {
20
+ ret . push ( ( ' ' . repeat ( indent ) + x ) . trimRight ( ) ) ;
21
+ }
22
+ }
23
+
24
+ function directive ( name : string , opening : boolean ) {
25
+ if ( opening ) {
26
+ line ( `.. ${ name } ::` ) ;
27
+ brk ( ) ;
28
+ indent += 3 ;
29
+ } else {
30
+ indent -= 3 ;
31
+ }
32
+ }
33
+
34
+ function brk ( ) {
35
+ if ( ret . length > 0 && ret [ ret . length - 1 ] . trim ( ) !== '' ) { ret . push ( '' ) ; }
36
+ }
37
+
38
+ function textOf ( node : commonmark . Node ) {
39
+ return node . literal || '' ;
40
+ }
41
+
42
+ let para = new Paragraph ( ) ; // Where to accumulate text fragments
43
+ let lastParaLine : number ; // Where the last paragraph ended, in order to add ::
44
+ let nextParaPrefix : string | undefined ;
45
+
46
+ pump ( ast , {
47
+ block_quote ( _node , entering ) {
48
+ directive ( 'epigraph' , entering ) ;
49
+ } ,
50
+
51
+ heading ( node , _entering ) {
52
+ line ( node . literal || '' ) ;
53
+ line ( headings [ node . level - 1 ] . repeat ( textOf ( node ) . length ) ) ;
54
+ } ,
55
+
56
+ paragraph ( node , entering ) {
57
+ if ( entering ) {
58
+ para = new Paragraph ( nextParaPrefix ) ;
59
+ nextParaPrefix = undefined ;
60
+ } else {
61
+ // Don't break inside list item
62
+ if ( node . parent == null || node . parent . type !== 'item' ) {
63
+ brk ( ) ;
64
+ }
65
+ line ( ...para . lines ( ) ) ;
66
+ lastParaLine = ret . length - 1 ;
67
+ }
68
+ } ,
69
+
70
+ text ( node ) { para . add ( textOf ( node ) ) ; } ,
71
+ softbreak ( ) { para . newline ( ) ; } ,
72
+ linebreak ( ) { para . newline ( ) ; } ,
73
+ thematic_break ( ) { line ( '------' ) ; } ,
74
+ code ( node ) { para . add ( '``' + textOf ( node ) + '``' ) ; } ,
75
+ strong ( ) { para . add ( '**' ) ; } ,
76
+ emph ( ) { para . add ( '*' ) ; } ,
77
+
78
+ list ( ) {
79
+ brk ( ) ;
80
+ } ,
81
+
82
+ link ( node , entering ) {
83
+ if ( entering ) {
84
+ para . add ( '`' ) ;
85
+ } else {
86
+ para . add ( ' <' + ( node . destination || '' ) + '>`_' ) ;
87
+ }
88
+ } ,
89
+
90
+ item ( node , _entering ) {
91
+ // AST hierarchy looks like list -> item -> paragraph -> text
92
+ if ( node . listType === 'bullet' ) {
93
+ nextParaPrefix = '- ' ;
94
+ } else {
95
+ nextParaPrefix = `${ node . listStart } . ` ;
96
+ }
97
+
98
+ } ,
99
+
100
+ code_block ( node ) {
101
+ // Poke a double :: at the end of the previous line as per ReST "literal block" syntax.
102
+ if ( lastParaLine !== undefined ) {
103
+ const lastLine = ret [ lastParaLine ] ;
104
+ ret [ lastParaLine ] = lastLine . replace ( / [ \W ] $ / , '::' ) ;
105
+ if ( ret [ lastParaLine ] === lastLine ) { ret [ lastParaLine ] = lastLine + '::' ; }
106
+ } else {
107
+ line ( 'Example::' ) ;
108
+ }
109
+
110
+ brk ( ) ;
111
+
112
+ indent += 3 ;
113
+
114
+ for ( const l of textOf ( node ) . split ( '\n' ) ) {
115
+ line ( l ) ;
116
+ }
117
+
118
+ indent -= 3 ;
119
+ }
120
+
121
+ } ) ;
122
+
123
+ return ret . join ( '\n' ) . trimRight ( ) ;
124
+ }
125
+
126
+ class Paragraph {
127
+ private readonly parts = new Array < string > ( ) ;
128
+
129
+ constructor ( text ?: string ) {
130
+ if ( text !== undefined ) { this . parts . push ( text ) ; }
131
+ }
132
+
133
+ public add ( text : string ) {
134
+ this . parts . push ( text ) ;
135
+ }
136
+
137
+ public newline ( ) {
138
+ this . parts . push ( '\n' ) ;
139
+ }
140
+
141
+ public lines ( ) : string [ ] {
142
+ return this . parts . length > 0 ? this . toString ( ) . split ( '\n' ) : [ ] ;
143
+ }
144
+
145
+ public toString ( ) {
146
+ return this . parts . join ( '' ) . trimRight ( ) ;
147
+ }
148
+ }
149
+
150
+ const headings = [ '=' , '-' , '^' , '"' ] ;
151
+
152
+ type Handler = ( node : commonmark . Node , entering : boolean ) => void ;
153
+ type Handlers = { [ key in commonmark . NodeType ] ?: Handler } ;
154
+
155
+ function pump ( ast : commonmark . Node , handlers : Handlers ) {
156
+ const walker = ast . walker ( ) ;
157
+ let event = walker . next ( ) ;
158
+ while ( event ) {
159
+ const h = handlers [ event . node . type ] ;
160
+ if ( h ) {
161
+ h ( event . node , event . entering ) ;
162
+ }
163
+
164
+ event = walker . next ( ) ;
165
+ }
166
+ }
0 commit comments