1 | #ifdef _MSC_VER |
2 | #ifndef _CRT_SECURE_NO_WARNINGS |
3 | #define _CRT_SECURE_NO_WARNINGS 1 |
4 | #endif |
5 | #ifndef _CRT_NONSTDC_NO_WARNINGS |
6 | #define _CRT_NONSTDC_NO_WARNINGS 1 |
7 | #endif |
8 | #endif |
9 | |
10 | #include <stdio.h> |
11 | #include <string.h> |
12 | #include <stdlib.h> |
13 | #include <getopt.h> |
14 | #include <sass.h> |
15 | #include "sassc_version.h" |
16 | |
17 | #define BUFSIZE 512 |
18 | #ifdef _WIN32 |
19 | #define PATH_SEP ';' |
20 | #else |
21 | #define PATH_SEP ':' |
22 | #endif |
23 | |
24 | #ifdef _WIN32 |
25 | #include <io.h> |
26 | #include <fcntl.h> |
27 | #include <windows.h> |
28 | |
29 | #define isatty(h) _isatty(h) |
30 | #define fileno(m) _fileno(m) |
31 | |
32 | int get_argv_utf8(int* argc_ptr, char*** argv_ptr) { |
33 | int argc; |
34 | char** argv; |
35 | wchar_t** argv_utf16 = CommandLineToArgvW(GetCommandLineW(), &argc); |
36 | int i; |
37 | int offset = (argc + 1) * sizeof(char*); |
38 | int size = offset; |
39 | for (i = 0; i < argc; i++) |
40 | size += WideCharToMultiByte(CP_UTF8, 0, argv_utf16[i], -1, 0, 0, 0, 0); |
41 | argv = malloc(size); |
42 | for (i = 0; i < argc; i++) { |
43 | argv[i] = (char*) argv + offset; |
44 | offset += WideCharToMultiByte(CP_UTF8, 0, argv_utf16[i], -1, |
45 | argv[i], size-offset, 0, 0); |
46 | } |
47 | *argc_ptr = argc; |
48 | *argv_ptr = argv; |
49 | return 0; |
50 | } |
51 | #else |
52 | #include <unistd.h> |
53 | #include <sysexits.h> |
54 | #endif |
55 | |
56 | int output(int error_status, const char* error_message, const char* output_string, const char* outfile) { |
57 | if (error_status) { |
58 | if (error_message) { |
59 | fprintf(stderr, format: "%s" , error_message); |
60 | } else { |
61 | fprintf(stderr, format: "An error occurred; no error message available.\n" ); |
62 | } |
63 | return 1; |
64 | } else if (output_string) { |
65 | if(outfile) { |
66 | FILE* fp = fopen(filename: outfile, modes: "wb" ); |
67 | if(!fp) { |
68 | perror(s: "Error opening output file" ); |
69 | return 1; |
70 | } |
71 | if(fprintf(stream: fp, format: "%s" , output_string) < 0) { |
72 | perror(s: "Error writing to output file" ); |
73 | fclose(stream: fp); |
74 | return 1; |
75 | } |
76 | fclose(stream: fp); |
77 | } |
78 | else { |
79 | #ifdef _WIN32 |
80 | setmode(fileno(stdout), O_BINARY); |
81 | #endif |
82 | printf(format: "%s" , output_string); |
83 | } |
84 | return 0; |
85 | } else { |
86 | fprintf(stderr, format: "Unknown internal error.\n" ); |
87 | return 2; |
88 | } |
89 | } |
90 | |
91 | int compile_stdin(struct Sass_Options* options, char* outfile) { |
92 | int ret; |
93 | struct Sass_Data_Context* ctx; |
94 | char buffer[BUFSIZE]; |
95 | size_t size = 1; |
96 | char *source_string = malloc(size: sizeof(char) * BUFSIZE); |
97 | |
98 | if(source_string == NULL) { |
99 | perror(s: "Allocation failed" ); |
100 | #ifdef _WIN32 |
101 | exit(ERROR_OUTOFMEMORY); |
102 | #else |
103 | exit(EX_OSERR); // system error (e.g., can't fork) |
104 | #endif |
105 | } |
106 | |
107 | source_string[0] = '\0'; |
108 | |
109 | while(fgets(s: buffer, BUFSIZE, stdin)) { |
110 | char *old = source_string; |
111 | size += strlen(s: buffer); |
112 | source_string = realloc(ptr: source_string, size: size); |
113 | if(source_string == NULL) { |
114 | perror(s: "Reallocation failed" ); |
115 | free(ptr: old); |
116 | #ifdef _WIN32 |
117 | exit(ERROR_OUTOFMEMORY); |
118 | #else |
119 | exit(EX_OSERR); // system error (e.g., can't fork) |
120 | #endif |
121 | } |
122 | strcat(dest: source_string, src: buffer); |
123 | } |
124 | |
125 | if(ferror(stdin)) { |
126 | free(ptr: source_string); |
127 | perror(s: "Error reading standard input" ); |
128 | #ifdef _WIN32 |
129 | exit(ERROR_READ_FAULT); // |
130 | #else |
131 | exit(EX_IOERR); // input/output error |
132 | #endif |
133 | } |
134 | |
135 | ctx = sass_make_data_context(source_string); |
136 | struct Sass_Context* ctx_out = sass_data_context_get_context(data_ctx: ctx); |
137 | sass_data_context_set_options(data_ctx: ctx, opt: options); |
138 | sass_compile_data_context(ctx); |
139 | ret = output( |
140 | error_status: sass_context_get_error_status(ctx: ctx_out), |
141 | error_message: sass_context_get_error_message(ctx: ctx_out), |
142 | output_string: sass_context_get_output_string(ctx: ctx_out), |
143 | outfile |
144 | ); |
145 | sass_delete_data_context(ctx); |
146 | return ret; |
147 | } |
148 | |
149 | int compile_file(struct Sass_Options* options, char* input_path, char* outfile) { |
150 | int ret; |
151 | struct Sass_File_Context* ctx = sass_make_file_context(input_path); |
152 | struct Sass_Context* ctx_out = sass_file_context_get_context(file_ctx: ctx); |
153 | if (outfile) sass_option_set_output_path(options, output_path: outfile); |
154 | const char* srcmap_file = sass_option_get_source_map_file(options); |
155 | sass_option_set_input_path(options, input_path); |
156 | sass_file_context_set_options(file_ctx: ctx, opt: options); |
157 | |
158 | sass_compile_file_context(ctx); |
159 | |
160 | ret = output( |
161 | error_status: sass_context_get_error_status(ctx: ctx_out), |
162 | error_message: sass_context_get_error_message(ctx: ctx_out), |
163 | output_string: sass_context_get_output_string(ctx: ctx_out), |
164 | outfile |
165 | ); |
166 | |
167 | if (ret == 0 && srcmap_file) { |
168 | ret = output( |
169 | error_status: sass_context_get_error_status(ctx: ctx_out), |
170 | error_message: sass_context_get_error_message(ctx: ctx_out), |
171 | output_string: sass_context_get_source_map_string(ctx: ctx_out), |
172 | outfile: srcmap_file |
173 | ); |
174 | } |
175 | |
176 | sass_delete_file_context(ctx); |
177 | return ret; |
178 | } |
179 | |
180 | struct |
181 | { |
182 | char* style_string; |
183 | int output_style; |
184 | } style_option_strings[] = { |
185 | { "compressed" , SASS_STYLE_COMPRESSED }, |
186 | { "compact" , SASS_STYLE_COMPACT }, |
187 | { "expanded" , SASS_STYLE_EXPANDED }, |
188 | { "nested" , SASS_STYLE_NESTED } |
189 | }; |
190 | |
191 | #define NUM_STYLE_OPTION_STRINGS \ |
192 | sizeof(style_option_strings) / sizeof(style_option_strings[0]) |
193 | |
194 | void print_version() { |
195 | printf(format: "sassc: %s\n" , SASSC_VERSION); |
196 | printf(format: "libsass: %s\n" , libsass_version()); |
197 | printf(format: "sass2scss: %s\n" , sass2scss_version()); |
198 | printf(format: "sass: %s\n" , libsass_language_version()); |
199 | } |
200 | |
201 | void print_usage(char* argv0) { |
202 | int i; |
203 | printf(format: "Usage: %s [options] [INPUT] [OUTPUT]\n\n" , argv0); |
204 | printf(format: "Options:\n" ); |
205 | printf(format: " -s, --stdin Read input from standard input instead of an input file.\n" ); |
206 | printf(format: " -t, --style NAME Output style. Can be:" ); |
207 | for(i = NUM_STYLE_OPTION_STRINGS - 1; i >= 0; i--) { |
208 | printf(format: " %s" , style_option_strings[i].style_string); |
209 | printf(format: i == 0 ? ".\n" : "," ); |
210 | } |
211 | printf(format: " -l, --line-numbers Emit comments showing original line numbers.\n" ); |
212 | printf(format: " --line-comments\n" ); |
213 | printf(format: " -I, --load-path PATH Set Sass import path.\n" ); |
214 | printf(format: " -P, --plugin-path PATH Set path to autoload plugins.\n" ); |
215 | printf(format: " -m, --sourcemap[=TYPE] Emit source map (auto or inline).\n" ); |
216 | printf(format: " -M, --omit-map-comment Omits the source map url comment.\n" ); |
217 | printf(format: " -p, --precision Set the precision for numbers.\n" ); |
218 | printf(format: " -a, --sass Treat input as indented syntax.\n" ); |
219 | printf(format: " -v, --version Display compiled versions.\n" ); |
220 | printf(format: " -h, --help Display this help message.\n" ); |
221 | printf(format: "\n" ); |
222 | } |
223 | |
224 | void invalid_usage(char* argv0) { |
225 | fprintf(stderr, format: "See '%s -h'\n" , argv0); |
226 | #ifdef _WIN32 |
227 | exit(ERROR_BAD_ARGUMENTS); // One or more arguments are not correct. |
228 | #else |
229 | exit(EX_USAGE); // command line usage error |
230 | #endif |
231 | |
232 | } |
233 | |
234 | int main(int argc, char** argv) { |
235 | #ifdef _MSC_VER |
236 | _set_error_mode(_OUT_TO_STDERR); |
237 | _set_abort_behavior( 0, _WRITE_ABORT_MSG); |
238 | #endif |
239 | #ifdef _WIN32 |
240 | get_argv_utf8(&argc, &argv); |
241 | #endif |
242 | if ((argc == 1) && isatty(fd: fileno(stdin))) { |
243 | print_usage(argv0: argv[0]); |
244 | return 0; |
245 | } |
246 | |
247 | char *outfile = 0; |
248 | int from_stdin = 0; |
249 | bool auto_source_map = false; |
250 | bool generate_source_map = false; |
251 | struct Sass_Options* options = sass_make_options(); |
252 | sass_option_set_output_style(options, output_style: SASS_STYLE_NESTED); |
253 | sass_option_set_precision(options, precision: 10); |
254 | |
255 | int c; |
256 | size_t i; |
257 | int long_index = 0; |
258 | static struct option long_options[] = |
259 | { |
260 | { "stdin" , no_argument, 0, 's' }, |
261 | { "load-path" , required_argument, 0, 'I' }, |
262 | { "plugin-path" , required_argument, 0, 'P' }, |
263 | { "style" , required_argument, 0, 't' }, |
264 | { "line-numbers" , no_argument, 0, 'l' }, |
265 | { "line-comments" , no_argument, 0, 'l' }, |
266 | { "sourcemap" , optional_argument, 0, 'm' }, |
267 | { "omit-map-comment" , no_argument, 0, 'M' }, |
268 | { "precision" , required_argument, 0, 'p' }, |
269 | { "version" , no_argument, 0, 'v' }, |
270 | { "sass" , no_argument, 0, 'a' }, |
271 | { "help" , no_argument, 0, 'h' }, |
272 | { NULL, 0, NULL, 0} |
273 | }; |
274 | while ((c = getopt_long(argc: argc, argv: argv, shortopts: "vhslm::Map:t:I:P:" , longopts: long_options, longind: &long_index)) != -1) { |
275 | switch (c) { |
276 | case 's': |
277 | from_stdin = 1; |
278 | break; |
279 | case 'I': |
280 | sass_option_push_include_path(options, path: optarg); |
281 | break; |
282 | case 'P': |
283 | sass_option_push_plugin_path(options, path: optarg); |
284 | break; |
285 | case 't': |
286 | for(i = 0; i < NUM_STYLE_OPTION_STRINGS; ++i) { |
287 | if(strcmp(s1: optarg, s2: style_option_strings[i].style_string) == 0) { |
288 | sass_option_set_output_style(options, output_style: style_option_strings[i].output_style); |
289 | break; |
290 | } |
291 | } |
292 | if(i == NUM_STYLE_OPTION_STRINGS) { |
293 | fprintf(stderr, format: "Invalid argument for -t flag: '%s'. Allowed arguments are:" , optarg); |
294 | for(i = 0; i < NUM_STYLE_OPTION_STRINGS; ++i) { |
295 | fprintf(stderr, format: " %s" , style_option_strings[i].style_string); |
296 | } |
297 | fprintf(stderr, format: "\n" ); |
298 | invalid_usage(argv0: argv[0]); |
299 | } |
300 | break; |
301 | case 'l': |
302 | sass_option_set_source_comments(options, true); |
303 | break; |
304 | case 'm': |
305 | if (optarg) { // optional argument |
306 | if (strcmp(s1: optarg, s2: "auto" ) == 0) { |
307 | auto_source_map = true; |
308 | } else if (strcmp(s1: optarg, s2: "inline" ) == 0) { |
309 | sass_option_set_source_map_embed(options, true); |
310 | } else { |
311 | fprintf(stderr, format: "Invalid argument for -m flag: '%s'. Allowed arguments are:" , optarg); |
312 | fprintf(stderr, format: " %s" , "auto inline" ); |
313 | fprintf(stderr, format: "\n" ); |
314 | invalid_usage(argv0: argv[0]); |
315 | } |
316 | } else { |
317 | auto_source_map = true; |
318 | } |
319 | generate_source_map = true; |
320 | break; |
321 | case 'M': |
322 | sass_option_set_omit_source_map_url(options, true); |
323 | break; |
324 | case 'p': |
325 | sass_option_set_precision(options, precision: atoi(nptr: optarg)); // TODO: make this more robust |
326 | if (sass_option_get_precision(options) < 0) sass_option_set_precision(options, precision: 10); |
327 | break; |
328 | case 'a': |
329 | sass_option_set_is_indented_syntax_src(options, true); |
330 | break; |
331 | case 'v': |
332 | print_version(); |
333 | sass_delete_options(options); |
334 | return 0; |
335 | case 'h': |
336 | print_usage(argv0: argv[0]); |
337 | sass_delete_options(options); |
338 | return 0; |
339 | case '?': |
340 | /* Unrecognized flag or missing an expected value */ |
341 | /* getopt should produce it's own error message for this case */ |
342 | invalid_usage(argv0: argv[0]); |
343 | default: |
344 | fprintf(stderr, format: "Unknown error while processing arguments\n" ); |
345 | sass_delete_options(options); |
346 | return 2; |
347 | } |
348 | } |
349 | |
350 | if(optind < argc - 2) { |
351 | fprintf(stderr, format: "Error: Too many arguments.\n" ); |
352 | invalid_usage(argv0: argv[0]); |
353 | } |
354 | |
355 | int result; |
356 | const char* dash = "-" ; |
357 | if(optind < argc && strcmp(s1: argv[optind], s2: dash) != 0 && !from_stdin) { |
358 | if (optind + 1 < argc) { |
359 | outfile = argv[optind + 1]; |
360 | } |
361 | if (generate_source_map && outfile) { |
362 | const char* extension = ".map" ; |
363 | char* source_map_file = calloc(nmemb: strlen(s: outfile) + strlen(s: extension) + 1, size: sizeof(char)); |
364 | strcpy(dest: source_map_file, src: outfile); |
365 | strcat(dest: source_map_file, src: extension); |
366 | sass_option_set_source_map_file(options, source_map_file); |
367 | } else if (auto_source_map) { |
368 | sass_option_set_source_map_embed(options, true); |
369 | } |
370 | result = compile_file(options, input_path: argv[optind], outfile); |
371 | } else { |
372 | if (optind < argc) { |
373 | outfile = argv[optind]; |
374 | } |
375 | result = compile_stdin(options, outfile); |
376 | } |
377 | |
378 | sass_delete_options(options); |
379 | |
380 | #ifdef _WIN32 |
381 | return result ? ERROR_INVALID_DATA : 0; // The data is invalid. |
382 | #else |
383 | return result ? EX_DATAERR : 0; // data format error |
384 | #endif |
385 | } |
386 | |