-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.coffee
203 lines (167 loc) · 5.22 KB
/
index.coffee
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
try
require('simple-colors') # TODO: remove
util = require('util') # TODO: remove
inDebugMode = ->
module.exports.debug # or imports, rather
heading = (title)->
dashes = ""
dashes += "-" for character in title
"""
+-#{dashes}-+
| #{title } |
+-#{dashes}-+
"""
module.exports =
helpers: {heading}
parse: (sgc)->
config =
screens: {}
links: {}
aliases: {}
options: {}
meta: comments: []
lines = sgc.split "\n"
# This loop is kind of clever in that it accesses a line in the
# 0-based array and then increments lineno to be 1-based
lineno = 0
line = ""
nextLine = ->
line = lines[lineno++]
if line?
# find and record first indent style
tab = (line.match /^\s+/)?[0]
config.meta.tab ?= tab if tab
# strip (and record) comments
comment = (line.match /\s*#.*$/)?[0]
line = line.replace /\s*#.*$/, ""
if comment
config.meta.comments.push {comment, line, lineno}
# empty line?
if line.match /^\s*$/
console.log "#{lineno} ...".grey() if inDebugMode()
nextLine() # skip this line; recurse
else
console.log "#{lineno} | #{line}" if inDebugMode()
yes # continue loop
else
no # end loop
while nextLine()
parse = (str, pattern)->
captures = []
regExpPattern = pattern
.replace /[A-Z]+/g, (match)->
captures.push match.toLowerCase()
///(
# this matches a bunch of stuff
[\w().\-+_~,]+
(?:
# sometimes stuff will even have spaces in it
[\w().\-+_~,\ ]+
# but it shouldn't end with a space
[\w().\-+_~,]+
)?
)///.source
.replace /\s+/g, "\\s*"
regExp = new RegExp "^\\s*#{regExpPattern}\\s*$"
m = regExp.exec str
o = {}
for capture, i in captures
o[capture] = m?[i+1]
console.log "#{lineno} as".grey(), pattern.green(), "?".grey(), util.inspect(regExp).blue(), util.inspect(o).yellow() if inDebugMode()
o
parseBlock = (o, pattern, parseSubBlock)->
while nextLine()
if line is "end"
console.log "END OF SECTION; EXIT".red(), pattern.magenta(), "BLOCK".red() if inDebugMode()
--lineno # reinterpret this "end" line in the parent block
break # break out of all blocks
{key, value} = parse line, pattern
if value
if o instanceof Array
o.push value
else
o[key] = value
else if parseSubBlock
parseSubBlock key
else
console.log "NO MATCH; EXIT".red(), pattern.magenta(), "BLOCK".red() if inDebugMode()
--lineno # try this line again, as something else
break
{section} = parse line, "section: SECTION"
if section
console.log heading "PARSE #{section.toUpperCase()} SECTION" if inDebugMode()
switch section
when "options"
# Options are simply a list of KEY = VALUE pairs
parseBlock config.options, "KEY = VALUE"
when "screens", "aliases", "links"
# These sections are all keyed with screen names
parseBlock config[section], "KEY:", (screenName)->
# with sub-blocks
switch section
when "screens"
parseBlock (config[section][screenName] = {}), "KEY = VALUE"
when "aliases"
parseBlock (config[section][screenName] = []), "VALUE"
when "links"
parseBlock (config[section][screenName] = {}), "KEY = VALUE"
else
throw new Error "Unknown section name on line #{lineno}"
parseValue = (val)->
switch val
when "true" then true
when "false" then false
else
if val.match /\d+(?:\.\d+)?/
parseFloat val
else if val.match /\s/
val.split /\s/
else
val
for screenName, screen of config.screens
for key, val of screen
screen[key] = parseValue val
for key, val of config.options
config.options[key] = parseValue val
config
stringify: (config)->
sgc = ""
tab = config.meta?.tab ? "\t" or " " or " " or whatever
indent = (str)-> tab + str.replace /\n/gm, "\n#{tab}"
has = (o)-> Object.keys(o ? {}).length
stringifySection = (name, stringify)->
if has config[name]
sgc += """
section: #{name}
#{indent stringify config[name]}
end
"""
stringifyBlock = (o, pattern, stringifySubBlock)->
stringified = ""
for key, val of o
str = pattern.replace "KEY", key
if stringifySubBlock
str += "\n" + indent stringifySubBlock val
else if val instanceof Array
str = str.replace "VALUE", val.join " "
else
str = str.replace "VALUE", val
stringified += str + "\n"
stringified.replace /\n$/m, ""
stringifySection "screens", (screens)->
stringifyBlock screens, "KEY:", (o)->
stringifyBlock o, "KEY = VALUE"
stringifySection "aliases", (aliases)->
stringifyBlock aliases, "KEY:", (o)->
stringifyBlock o, "VALUE"
stringifySection "links", (links)->
stringifyBlock links, "KEY:", (o)->
stringifyBlock o, "KEY = VALUE"
stringifySection "options", (options)->
stringifyBlock options, "KEY = VALUE"
# lines = sgc.split "\n"
# @TODO: match up comments with lines better?
if config.meta?.comments?
for comment in config.meta.comments
sgc = sgc.replace comment.line, comment.line + comment.comment+"\n"
sgc