UNPKG

3.57 kBJavaScriptView Raw
1'use strict'
2
3const Mdx = require('./index')
4const usePreludes = require('./filters/preludes')
5const useFormat = require('./filters/format')
6const normalize = require('./md_block').normalize
7
8/**
9 * A document parsing context.
10 */
11
12class Context {
13 constructor (fname, src) {
14 const lang = Mdx.detectLanguage(fname)
15 if (!lang) throw new Error(`Unknown language for '${fname}'`)
16
17 // <String> The filename.
18 this.filename = fname
19
20 // <String> The source code to be parsed.
21 this.src = src
22
23 // <Object> The block to be parsed
24 this.block = undefined
25
26 // <Array> The block
27 this.blocks = []
28
29 // <Language> Language instance
30 this.language = lang
31
32 // <Matcher> The rule set of the language
33 this.rules = this.language.rules()
34 }
35
36 /**
37 * Considers the last `block` as "done" and pushes it to the blocks list,
38 * then it starts a new block.
39 *
40 * ctx.flush()
41 */
42
43 flush () {
44 this.finalizeBlock()
45 this.block = this.newBlock()
46 }
47
48 /**
49 * Considers the last `block` as "done" and pushes it to the blocks list.
50 */
51
52 finalizeBlock () {
53 if (this.block && this.block.location.doc.start !== null) {
54 this.block.raw = this.block._rawlines.join('\n') + '\n'
55 delete this.block._rawlines
56
57 // Parse out preludes into title and such
58 this.block = usePreludes(this.block, this)
59 this.block = useFormat(this.block, this)
60
61 this.block.markdown = normalize(this.block.raw, { lang: this.language.slug() })
62
63 // If the filters decided it's not a block, don't add it
64 if (!this.block) return
65
66 // If it's an unrecognized block, don't add it
67 if (!this.block.explicit && !this.block.title) return
68
69 this.blocks.push(this.block)
70 }
71 }
72
73 /**
74 * Creates the initial state for a block object.
75 *
76 * this.block = this.newBlock()
77 */
78
79 newBlock () {
80 return {
81 location: {
82 file: this.filename,
83 doc: { start: null, end: null },
84 code: { start: null }
85 },
86 _rawlines: []
87 }
88 }
89
90 /*
91 * Goes through each line of the source and creates the blocks.
92 *
93 * ctx = new Context(...)
94 * ctx.process()
95 * ctx.blocks //=> ...
96 */
97
98 process () {
99 const ctx = this
100 ctx.flush()
101
102 eachLine(ctx.src, function (line, i) {
103 ctx.rules.switch(line, {
104
105 // single line block comment
106 docshort (m) {
107 return
108 },
109
110 // opening block, explicit
111 docstartex (m) {
112 ctx.flush()
113 ctx.block.location.doc.start = i + 1
114 if (m.doc) ctx.block._rawlines.push(m.doc)
115 ctx.block.explicit = true
116 },
117
118 // opening block
119 docstart (m) {
120 ctx.flush()
121 ctx.block.location.doc.start = i + 1
122 if (m.doc) ctx.block._rawlines.push(m.doc)
123 },
124
125 // closing block
126 // skip other */'s if it's not needed
127 docend (m) {
128 if (ctx.block.location.doc.end !== null) return
129 ctx.block.location.doc.end = i + 1
130 },
131
132 // inside a block
133 doc (m) {
134 ctx.block._rawlines.push(m.doc)
135 },
136
137 // code
138 // skip if prelude was already saved
139 else (m) {
140 if (ctx.block.prelude) return
141
142 line = line.trim()
143 if (line === '') return
144 ctx.block.prelude = line
145 ctx.block.location.code.start = i + 1
146 }
147 })
148 })
149
150 ctx.finalizeBlock()
151 }
152}
153
154/*
155 * Helpers
156 */
157
158function eachLine (src, fn) {
159 src.split('\n').forEach(fn)
160}
161
162module.exports = Context