forked from poppa/pike-for-sublime
-
Notifications
You must be signed in to change notification settings - Fork 0
/
to-tmlanguage
executable file
·271 lines (211 loc) · 6.25 KB
/
to-tmlanguage
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
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
#!/usr/bin/env pike
//! This program converts a JSON file to a Sublime language definition file.
//!
//! run like:
//!
//! user@computer:~$ to-tmlanguage /path/to/json-file
//!
//! The resulting file will be placed in the same directory as the original
//! file. NOTE! The resulting file will be named like
//! [first-part-of-orgiginal-file.tmLanguage]. So if the input file is named
//! Lang.json the resulting file will be named Lang.tmLanguage.
//!
//! * To generate a UUID give the "key" uuid in the property list file the value
//! #uuid. The #uuid part will be replaced with a real UUID.
//!
//! * $scopeName in the JSON file will be expanded to the value of the
//! "scopeName" key.
//!
//! { "name" : "Lang",
//! "scopeName" : "source.lang",
//! "uuid" : "#uuid",
//! "test" : "some.stuff.$scopeName"
//! }
//!
//! Will result in
//!
//! <dict>
//! <key>name</key>
//! <string>Lang</string>
//! <key>scopeName</key>
//! <string>source.lang</string>
//! <key>uuid</key>
//! <string>f0c5f4ad-3e8a-4add-9ebf-2ce1d5e5262f</string>
//! <key>test</key>
//! <string>some.stuff.source.lang</string>
//! </dict>
String.Buffer buffer;
function add;
int level = 1; // Indentation level of the XML file
// Replace < and > with XML entities
#define rep(X) replace((string)(X), ([ "&":"&", "<":"<", ">":">" ]))
// Create an opening tag
#define tag1(X) "\n" + ("\t" * level) + "<" + X + ">"
// Create a closing tag
#define tag2(X) "</" + X + ">"
// Add tag to buffer
#define tag(TYPE,VAL) add(tag1(TYPE), rep((VAL)), tag2(TYPE))
// Append newline and indentation to buffer
#define end() ("\n" + (" " * level))
// Regexp for removing our own comments in the JSON file
Regexp.PCRE comment_re = Regexp.PCRE("^[ \t]*//-.*$",
Regexp.PCRE.OPTION.MULTILINE);
// Replace the most common control characters to make the JSON parser happy
#define newline_marker "__NEWLINE_MARKER__"
#define tab_marker "__TAB_MARKER__"
int main(int argc, array(string) argv)
{
if (argc < 2) {
werror("Missing Sublime Syntax file as argument\n");
return 1;
}
string file = argv[1];
if (!Stdio.exist(file) || !Stdio.is_file(file)) {
werror("Argument is not a file!\n");
return 1;
}
buffer = String.Buffer();
add = buffer->add;
add("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
"<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" "
"\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
"<plist version=\"1.0\">");
string file_content = Stdio.read_file(file);
// Remove our special comments
file_content = comment_re->replace(file_content, "");
file_content = fix_multiline(file_content);
#ifdef DEBUG
werror("%s\n", file_content);
Stdio.write_file(file+".debug", file_content);
return 0;
#endif
mixed json = Standards.JSON.decode(file_content);
if (json->uuid && json->uuid == "#uuid") {
json->uuid = (string) Standards.UUID.make_version4();
file_content = replace(file_content, "#uuid", json->uuid);
Stdio.write_file(file, file_content);
}
encode_xml(json);
add("\n</plist>");
string data = buffer->get();
foreach (json; string key; mixed v) {
if (stringp(v))
data = replace(data, "$" + key, v);
}
string ofname, fname, path;
path = dirname(file);
ofname = fname = basename(file);
if (search(fname, ".") > -1)
fname = (fname/".")[0];
fname += ".tmLanguage";
if (fname == ofname) {
werror("The input file has the same name as the output file! Please "
"rename the input file!\n");
return 1;
}
path = combine_path(path, fname);
Stdio.write_file(path, data);
#ifdef DEVMODE
string config_path = combine_path(getenv("HOME"),
".config/sublime-text-2/Packages/User/Pike");
write("Copying tmLanguage to %O\n", config_path);
Stdio.write_file(combine_path(config_path, fname), data);
#endif
return 0;
}
string fix_multiline(string s)
{
int len = sizeof(s);
int pos = 0;
int(0..1) in_str = 0;
string ns = "";
// Pad so that we don't peek beyond the end
s += "\0";
do {
// Remove multiline comments like /* ... */
if (s[pos] == '/' && s[pos+1] == '*') {
pos++;
while (pos++ < len) {
if (s[pos] == '*' && s[pos+1] == '/')
break;
}
pos++;
continue;
}
if (s[pos] == '\\' && s[pos+1] == '\n') {
// Remove beginning whitespace on next line
sscanf (reverse(ns), "%*[ \t]%s", ns);
ns = reverse(ns[1..]);
pos += 2;
// Skip whitespaces
while (s[pos++] == ' ' || s[pos] == '\t')
;
ns += s[pos..pos];
continue;
}
// Check for a multiline string: #"
if (!in_str && s[pos] == '#' && s[pos+1] == '"') {
in_str = 1;
ns += "\"";
pos++;
continue;
}
// If in a string and a " if found with no \ before we're at the string end
else if (in_str && s[pos] == '"' && s[pos-1] != '\\')
in_str = 0;
// If in a string and a newline, remove and replace it with mumbojumbo
// that will make the JSON parser happy. Mumbojumbo will be replaced with
// a real newline in the end result.
if (in_str && s[pos] == '\n')
ns += newline_marker;
else if (in_str && s[pos] == '\t')
ns += tab_marker;
else
ns += s[pos..pos];
} while (pos++ < len);
// Remove null padding
ns = ns[..sizeof(ns)-2];
return ns;
}
string type_to_string(mixed x)
{
if (intp(x)) return "int";
if (arrayp(x)) return "array";
if (floatp(x)) return "float";
if (stringp(x)) return "string";
if (mappingp(x)) return "dict";
error("Unhandled Pike type: %O\n", x);
}
mixed encode_xml(mixed in)
{
string type = type_to_string(in);
switch (type)
{
case "dict":
add(tag1(type));
foreach (sort(indices(in)), string key) {
mixed value = in[key];
level++;
tag("key", key);
encode_xml(value);
level--;
}
add(end(), tag2(type));
break;
case "array":
add(tag1(type));
foreach (in, mixed v) {
level++;
encode_xml(v);
level--;
}
add(end(), tag2(type));
break;
case "string":
in = replace(in, ({ newline_marker, tab_marker }), ({ "\n", "\t" }));
case "float":
case "int":
tag(type, in);
break;
}
}