git ssb

0+

Rômulo Alves / plainbudget



Tree: 9964c6e5e9636cdc8b3f37a5cda9e898425b9583

Files: 9964c6e5e9636cdc8b3f37a5cda9e898425b9583 / index.js

6184 bytesRaw
1
2const NAME_REGEX = /^([a-z0-9\-.\/ ]+)$/i // eslint-disable-line
3const MULTIPLIER_REGEX = /^(.*?)\s+x\s+(\d+)$/
4
5function round(value) {
6 return Math.round(value * 100) / 100
7}
8
9module.exports = class Plainbudget {
10
11 static computeSheet (sheet) {
12 const pb = new Plainbudget(sheet)
13
14 return pb.compute()
15 }
16
17 static computeSheets (sheets) {
18 const allGroups = []
19 let allNamed = {}
20
21 const instances = Object.keys(sheets)
22 .reduce((obj, s) => {
23 const pb = new Plainbudget(sheets[s])
24
25 pb.parse()
26 pb.calcNamed()
27 allGroups.splice(allGroups.length, 0, Object.assign({}, pb.groups))
28 allNamed = Object.assign({}, allNamed, pb.named)
29
30 return Object.assign({}, obj, { [s]: pb })
31 }, {})
32
33 return Object.keys(instances)
34 .reduce((obj, key) => {
35 const i = instances[key]
36
37 i.named = allNamed
38 i.padding = i.getPadding(allGroups)
39 i.calcFlows()
40 i.compute(false)
41
42 return Object.assign({}, obj, { [key]: i.text })
43 }, {})
44 }
45
46 constructor (text) {
47 this.padding = 3
48 this.lines = []
49 this.groups = []
50 this.named = {}
51 this.text = text
52 }
53
54 parseValue (line) {
55 let value = line.slice(1).match(/^\s+(\d+(.\d+)?)/)
56
57 if (value) {
58 value = parseFloat(value[1])
59
60 if (isNaN(value)) {
61 value = '?'
62 }
63 }
64
65 return value
66 }
67
68 parseLine (line) {
69 const value = this.parseValue(line)
70 let label = line.slice(1).match(/\d+\s+(.+)/)
71
72 label = label ? label[1] : line.slice(1).trim()
73
74 return [line[0], value, label]
75 }
76
77 parseMultiplier (label) {
78 const m = label.trim().match(MULTIPLIER_REGEX)
79
80 if (m) {
81 return [m[1], m[2]]
82 }
83 }
84
85 groupEquals (groupA, groupB) {
86 return (
87 groupA[0] === groupB[0] &&
88 groupA[1] === groupB[1] &&
89 groupA[2] === groupB[2]
90 )
91 }
92
93 parse () {
94 this.named = {}
95 this.groups = []
96 this.lines = this.text.split(/\n/)
97
98 let group = null
99
100 for (let i = 0; i < this.lines.length; i++) {
101 const line = this.lines[i].trim()
102 const op = line[0]
103
104 if ('=+'.includes(op) && group === null) {
105 group = [this.parseLine(line)]
106 } else if ('-~+x'.includes(op)) {
107 group.push(this.parseLine(line))
108 } else if (line.match(/^\s*$/) && group !== null && group.length > 1) {
109 this.groups.push(group)
110 group = null
111 }
112 }
113
114 if (group !== null && !this.groupEquals(this.groups.slice(-1), group) && group.length > 1) {
115 this.groups.push(group)
116 }
117 }
118
119 compute (calc = true) {
120 if (calc) {
121 this.parse()
122 this.calcNamed()
123 this.calcFlows()
124 }
125
126 const padding = Math.max(this.padding, this.getPadding())
127 const updated = []
128
129 for (let x = 0; x < this.groups.length; x++) {
130 const group = this.groups[x]
131
132 for (let y = 0; y < group.length; y++) {
133 const op = group[y]
134
135 updated.push(`${op[0]} ${op[1].toString().padStart(padding)} ${op[2]}\n`)
136 }
137
138 updated.push('\n')
139 }
140
141 this.text = `\n${updated.join('').trim()}\n`
142
143 return this.text
144 }
145
146 setNamed (label, value) {
147 const varMatch = label.match(NAME_REGEX)
148
149 if (varMatch) {
150 label = varMatch[1]
151 this.named[label] = value
152 }
153 }
154
155 getNamed (label) {
156 if (label.match(MULTIPLIER_REGEX)) {
157 label = label.split(/\s+x\s+/)[0]
158 }
159
160 const varMatch = label.match(NAME_REGEX)
161
162 if (varMatch && varMatch[1] in this.named) {
163 return [true, this.named[varMatch[1]]]
164 }
165
166 return [false]
167 }
168
169 calcNamed () {
170 this.calc(this.groups.reduce((arr, g, i) => {
171 if (g[0][0] === '=') {
172 return arr.concat([i])
173 }
174
175 return arr
176 }, []))
177 }
178
179 calcFlows () {
180 this.calc(this.groups.reduce((arr, g, i) => {
181 if (g[0][0] !== '=') {
182 return arr.concat([i])
183 }
184
185 return arr
186 }, []))
187 }
188
189 processCashflowEntry (op) {
190 const multiplier = this.parseMultiplier(op[2])
191 let named
192
193 if (multiplier) {
194 named = this.getNamed(multiplier[0], op[1])
195
196 if (named[0]) {
197 op[1] = named[1] * parseInt(multiplier[1])
198 return op[1]
199 }
200
201 return op[1] * parseInt(multiplier[1])
202 }
203
204 named = this.getNamed(op[2], op[1])
205
206 if (named[0]) {
207 op[1] = named[1]
208 }
209
210 return op[1]
211 }
212
213 calc (groupIndices) {
214 if (!groupIndices.length) {
215 return
216 }
217
218 let value
219
220 for (let g = 0; g < groupIndices.length; g++) {
221 const group = this.groups[groupIndices[g]]
222
223 if ('='.includes(group[0][0])) {
224 if (group[0][2] === '') {
225 group[0][2] = '\n'
226 }
227
228 value = 0
229 const topOps = group.slice(1)
230
231 for (let x = 0; x < topOps.length; x++) {
232 const topOp = topOps[x]
233
234 if (topOp[1] === '?' || topOp[0] === 'x') {
235 continue
236 }
237
238 const multiplier = this.parseMultiplier(topOp[2])
239
240 if (multiplier) {
241 value += topOp[1] * parseFloat(multiplier[1])
242 } else {
243 value += topOp[1]
244 }
245 }
246 } else if (group[0][0] === '+') {
247 value = group[0][1]
248 const ops = group.slice(1)
249
250 for (let y = 0; y < ops.length; y++) {
251 const op = ops[y]
252
253 if (op[1] === '?' || op[0] === 'x') {
254 continue
255 }
256
257 if (op[0] === '+') {
258 value += this.processCashflowEntry(op)
259 } else if ('-~'.includes(op[0])) {
260 value -= this.processCashflowEntry(op)
261 }
262 }
263 }
264
265 value = round(value)
266
267 if (group[0][0] === '=') {
268 group[0][1] = value
269 this.setNamed(group[0][2], value)
270 } else {
271 group.push(['=', value, ''])
272 }
273 }
274 }
275
276 getPadding (groups) {
277 groups = groups || this.groups
278
279 let p = 3
280
281 for (let x = 0; x < groups.length; x++) {
282 const group = groups[x]
283
284 for (let y = 0; y < group.length; y++) {
285 if (group[y][1] !== null) {
286 const nlen = group[y][1].toString().length
287
288 if (nlen > (p + 1)) {
289 p = nlen + 1
290 }
291 }
292 }
293 }
294
295 return p + 1
296 }
297}
298

Built with git-ssb-web