Current Version: 1.0.32
Project Name: csspp
csspp.cpp
Go to the documentation of this file.
1 // Copyright (c) 2015-2022 Made to Order Software Corp. All Rights Reserved
2 //
3 // This program is free software; you can redistribute it and/or modify
4 // it under the terms of the GNU General Public License as published by
5 // the Free Software Foundation; either version 2 of the License, or
6 // (at your option) any later version.
7 //
8 // This program is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 // GNU General Public License for more details.
12 //
13 // You should have received a copy of the GNU General Public License along
14 // with this program; if not, write to the Free Software Foundation, Inc.,
15 // 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
16 
227 // csspp
228 //
229 #include <csspp/assembler.h>
230 #include <csspp/compiler.h>
231 #include <csspp/exception.h>
232 #include <csspp/parser.h>
233 
234 
235 // advgetopt
236 //
237 #include <advgetopt/advgetopt.h>
238 #include <advgetopt/exception.h>
239 
240 
241 // boost
242 //
243 #include <boost/preprocessor/stringize.hpp>
244 
245 
246 // C++
247 //
248 #include <cstdlib>
249 #include <fstream>
250 #include <iostream>
251 
252 
253 // C
254 //
255 #include <unistd.h>
256 
257 
258 // last include
259 //
260 #include <snapdev/poison.h>
261 
262 
263 
264 namespace
265 {
266 
267 void free_char(char * ptr)
268 {
269  free(ptr);
270 }
271 
272 // TODO: add support for configuration files & the variable
273 
274 constexpr advgetopt::option g_options[] =
275 {
276  advgetopt::define_option(
277  advgetopt::Name("args")
278  , advgetopt::ShortName('a')
279  , advgetopt::Flags(advgetopt::command_flags<
280  advgetopt::GETOPT_FLAG_REQUIRED
281  , advgetopt::GETOPT_FLAG_MULTIPLE>())
282  , nullptr
283  , "define values in the $_csspp_args variable map"
284  , nullptr
285  ),
286  advgetopt::define_option(
287  advgetopt::Name("debug")
288  , advgetopt::ShortName('d')
289  , advgetopt::Flags(advgetopt::standalone_command_flags<>())
290  , advgetopt::Help("show all messages, including @debug messages")
291  ),
292  advgetopt::define_option(
293  advgetopt::Name("include")
294  , advgetopt::ShortName('I')
295  , advgetopt::Flags(advgetopt::command_flags<
296  advgetopt::GETOPT_FLAG_REQUIRED
297  , advgetopt::GETOPT_FLAG_MULTIPLE>())
298  , advgetopt::Help("specify one or more paths to various user defined CSS files; \"-\" to clear the list (i.e. \"-I -\")")
299  ),
300  advgetopt::define_option(
301  advgetopt::Name("no-logo")
302  , advgetopt::ShortName('\0')
303  , advgetopt::Flags(advgetopt::standalone_command_flags<>())
304  , advgetopt::Help("prevent the \"logo\" from appearing in the output file")
305  ),
306  advgetopt::define_option(
307  advgetopt::Name("empty-on-undefined-variable")
308  , advgetopt::ShortName('\0')
309  , advgetopt::Flags(advgetopt::standalone_command_flags<>())
310  , advgetopt::Help("if accessing an undefined variable, return an empty string, otherwise generate an error.")
311  ),
312  advgetopt::define_option(
313  advgetopt::Name("output")
314  , advgetopt::ShortName('o')
315  , advgetopt::Flags(advgetopt::command_flags<
316  advgetopt::GETOPT_FLAG_REQUIRED>())
317  , advgetopt::Help("save the results in the specified file if specified; otherwise send output to stdout.")
318  ),
319  advgetopt::define_option(
320  advgetopt::Name("precision")
321  , advgetopt::ShortName('p')
322  , advgetopt::Flags(advgetopt::command_flags<
323  advgetopt::GETOPT_FLAG_REQUIRED>())
324  , advgetopt::Help("define the number of digits to use after the decimal point, defaults to 3; note that for percent values, the precision is always 2.")
325  ),
326  advgetopt::define_option(
327  advgetopt::Name("quiet")
328  , advgetopt::ShortName('q')
329  , advgetopt::Flags(advgetopt::standalone_command_flags<>())
330  , advgetopt::Help("suppress @info and @warning messages.")
331  ),
332  advgetopt::define_option(
333  advgetopt::Name("style")
334  , advgetopt::ShortName('s')
335  , advgetopt::Flags(advgetopt::command_flags<
336  advgetopt::GETOPT_FLAG_REQUIRED>())
337  , advgetopt::Help("output style: compressed, tidy, compact, expanded.")
338  ),
339  advgetopt::define_option(
340  advgetopt::Name("Werror")
341  , advgetopt::Flags(advgetopt::standalone_command_flags<>())
342  , advgetopt::Help("make warnings count as errors.")
343  ),
344  advgetopt::define_option(
345  advgetopt::Name("--")
346  , advgetopt::Flags(advgetopt::command_flags<
347  advgetopt::GETOPT_FLAG_MULTIPLE
348  , advgetopt::GETOPT_FLAG_DEFAULT_OPTION
349  , advgetopt::GETOPT_FLAG_SHOW_USAGE_ON_ERROR>())
350  , advgetopt::Help("[file.css ...]; use stdin if no filename specified.")
351  ),
352  advgetopt::end_options()
353 };
354 
355 // TODO: once we have stdc++20, remove all defaults
356 #pragma GCC diagnostic ignored "-Wpedantic"
357 advgetopt::options_environment const g_options_environment =
358 {
359  .f_project_name = "csspp",
360  .f_group_name = nullptr,
361  .f_options = g_options,
362  .f_options_files_directory = nullptr,
363  .f_environment_variable_name = "CSSPPFLAGS",
364  .f_environment_variable_intro = nullptr,
365  .f_section_variables_name = nullptr,
366  .f_configuration_files = nullptr,
367  .f_configuration_filename = nullptr,
368  .f_configuration_directories = nullptr,
369  .f_environment_flags = advgetopt::GETOPT_ENVIRONMENT_FLAG_PROCESS_SYSTEM_PARAMETERS,
370  .f_help_header = "Usage: %p [-<opt>] [file.css ...] [-o out.css]\n"
371  "where -<opt> is one or more of:",
372  .f_help_footer = "%c",
373  .f_version = CSSPP_VERSION,
374  .f_license = "GNU GPL v2",
375  .f_copyright = "Copyright (c) 2015-"
376  BOOST_PP_STRINGIZE(UTC_BUILD_YEAR)
377  " by Made to Order Software Corporation -- All Rights Reserved",
378  //.f_build_date = UTC_BUILD_DATE,
379  //.f_build_time = UTC_BUILD_TIME
380 };
381 
382 
383 
384 class pp
385 {
386 public:
387  pp(int argc, char * argv[]);
388 
389  int compile();
390 
391 private:
392  advgetopt::getopt f_opt;
393  int f_precision = 3;
394 };
395 
396 
397 pp::pp(int argc, char * argv[])
398  : f_opt(g_options_environment, argc, argv)
399 {
400  if(f_opt.is_defined("quiet"))
401  {
402  csspp::error::instance().set_hide_all(true);
403  }
404 
405  if(f_opt.is_defined("debug"))
406  {
407  csspp::error::instance().set_show_debug(true);
408  }
409 
410  if(f_opt.is_defined("Werror"))
411  {
412  csspp::error::instance().set_count_warnings_as_errors(true);
413  }
414 
415  if(f_opt.is_defined("precision"))
416  {
417  f_precision = f_opt.get_long("precision");
418  }
419 }
420 
422 {
423  csspp::lexer::pointer_t l;
424  csspp::position::pointer_t pos;
425  std::unique_ptr<std::stringstream> ss;
426 
427  csspp::safe_precision_t safe_precision(f_precision);
428 
429  if(f_opt.is_defined("--"))
430  {
431  // one or more filename specified
432  int const arg_count(f_opt.size("--"));
433  if(arg_count == 1
434  && f_opt.get_string("--") == "-")
435  {
436  // user asked for stdin
437  pos.reset(new csspp::position("-"));
438  l.reset(new csspp::lexer(std::cin, *pos));
439  }
440  else
441  {
442  std::unique_ptr<char, void (*)(char *)> cwd(get_current_dir_name(), free_char);
443  ss.reset(new std::stringstream);
444  pos.reset(new csspp::position("csspp.css"));
445  for(int idx(0); idx < arg_count; ++idx)
446  {
447  // full paths so the -I have no effects on those files
448  std::string filename(f_opt.get_string("--", idx));
449  if(filename.empty())
450  {
451  csspp::error::instance() << *pos
452  << "You cannot include a file with an empty name."
453  << csspp::error_mode_t::ERROR_WARNING;
454  return 1;
455  }
456  if(filename == "-")
457  {
458  csspp::error::instance() << *pos
459  << "You cannot currently mix files and stdin. You may use @import \"filename\"; in your stdin data though."
460  << csspp::error_mode_t::ERROR_WARNING;
461  return 1;
462  }
463  if(filename[0] == '/')
464  {
465  // already absolute
466  *ss << "@import \"" << filename << "\";\n";
467  }
468  else
469  {
470  // make absolute so we do not need to have a "." path
471  *ss << "@import \"" << cwd.get() << "/" << filename << "\";\n";
472  }
473  }
474  l.reset(new csspp::lexer(*ss, *pos));
475  }
476  }
477  else
478  {
479  // default to stdin
480  pos.reset(new csspp::position("-"));
481  l.reset(new csspp::lexer(std::cin, *pos));
482  }
483 
484  // run the lexer and parser
485  csspp::error_happened_t error_tracker;
486  csspp::parser p(l);
487  csspp::node::pointer_t root(p.stylesheet());
488  if(error_tracker.error_happened())
489  {
490  return 1;
491  }
492 
493  csspp::node::pointer_t csspp_args(new csspp::node(csspp::node_type_t::LIST, root->get_position()));
494  csspp::node::pointer_t args_var(new csspp::node(csspp::node_type_t::VARIABLE, root->get_position()));
495  args_var->set_string("_csspp_args");
496  csspp::node::pointer_t wrapper(new csspp::node(csspp::node_type_t::LIST, root->get_position()));
497  csspp::node::pointer_t array(new csspp::node(csspp::node_type_t::ARRAY, root->get_position()));
498  wrapper->add_child(array);
499  csspp_args->add_child(args_var);
500  csspp_args->add_child(wrapper);
501  if(f_opt.is_defined("args"))
502  {
503  int const count(f_opt.size("args"));
504  for(int idx(0); idx < count; ++idx)
505  {
506  csspp::node::pointer_t arg(new csspp::node(csspp::node_type_t::STRING, root->get_position()));
507  arg->set_string(f_opt.get_string("args", idx));
508  array->add_child(arg);
509  }
510  }
511  root->set_variable("_csspp_args", csspp_args);
512 
513  // run the compiler
514  csspp::compiler c;
515  c.set_root(root);
516  c.set_date_time_variables(time(nullptr));
517 
518  // add paths to the compiler (i.e. for the user and system @imports)
519  if(f_opt.is_defined("include"))
520  {
521  std::size_t const count(f_opt.size("include"));
522  for(std::size_t idx(0); idx < count; ++idx)
523  {
524  std::string const path(f_opt.get_string("include", idx));
525  if(path == "-")
526  {
527  c.clear_paths();
528  }
529  else
530  {
531  c.add_path(path);
532  }
533  }
534  }
535 
536  if(f_opt.is_defined("no-logo"))
537  {
538  c.set_no_logo();
539  }
540 
541  if(f_opt.is_defined("empty-on-undefined-variable"))
542  {
543  c.set_empty_on_undefined_variable(true);
544  }
545 
546  c.compile(false);
547  if(error_tracker.error_happened())
548  {
549  return 1;
550  }
551 
552  csspp::output_mode_t output_mode(csspp::output_mode_t::COMPRESSED);
553  if(f_opt.is_defined("style"))
554  {
555  std::string const mode(f_opt.get_string("style"));
556  if(mode == "compressed")
557  {
558  output_mode = csspp::output_mode_t::COMPRESSED;
559  }
560  else if(mode == "tidy")
561  {
562  output_mode = csspp::output_mode_t::TIDY;
563  }
564  else if(mode == "compact")
565  {
566  output_mode = csspp::output_mode_t::COMPACT;
567  }
568  else if(mode == "expanded")
569  {
570  output_mode = csspp::output_mode_t::EXPANDED;
571  }
572  else
573  {
574  csspp::error::instance() << root->get_position()
575  << "The output mode \""
576  << mode
577  << "\" is not supported. Try one of: compressed, tidy, compact, expanded instead."
578  << csspp::error_mode_t::ERROR_WARNING;
579  return 1;
580  }
581  }
582 
583  std::ostream * out(nullptr);
584  bool user_output(false);
585  std::string output_filename;
586  if(f_opt.is_defined("output"))
587  {
588  output_filename = f_opt.get_string("output");
589  user_output = output_filename != "-";
590  }
591  if(user_output)
592  {
593  out = new std::ofstream(output_filename);
594  }
595  else
596  {
597  out = &std::cout;
598  }
599  csspp::assembler a(*out);
600  a.output(c.get_root(), output_mode);
601  if(user_output)
602  {
603  delete out;
604  }
605  if(error_tracker.error_happened())
606  {
607  // this should be rare as the assembler generally does not generate
608  // errors (it may throw though.)
609  return 1;
610  }
611 
612  return 0;
613 }
614 
615 } // no name namespace
616 
617 int main(int argc, char *argv[])
618 {
619  try
620  {
621  pp preprocessor(argc, argv);
622  return preprocessor.compile();
623  }
624  catch(advgetopt::getopt_exit const & except)
625  {
626  return except.code();
627  }
628  catch(csspp::csspp_exception_exit const & e)
629  {
630  // something went wrong in the library
631  return e.exit_code();
632  }
633  catch(csspp::csspp_exception_logic const & e)
634  {
635  std::cerr << "fatal error: a logic exception, which should NEVER occur, occurred: " << e.what() << std::endl;
636  exit(1);
637  }
638  catch(csspp::csspp_exception_overflow const & e)
639  {
640  std::cerr << "fatal error: an overflow exception occurred: " << e.what() << std::endl;
641  exit(1);
642  }
643  catch(csspp::csspp_exception_runtime const & e)
644  {
645  std::cerr << "fatal error: a runtime exception occurred: " << e.what() << std::endl;
646  exit(1);
647  }
648  catch(advgetopt::getopt_undefined const & e)
649  {
650  std::cerr << "fatal error: an undefined exception occurred because of your command line: " << e.what() << std::endl;
651  exit(1);
652  }
653  catch(advgetopt::getopt_invalid const & e)
654  {
655  std::cerr << "fatal error: there is an error on your command line, an exception occurred: " << e.what() << std::endl;
656  exit(1);
657  }
658  catch(advgetopt::getopt_invalid_default const & e)
659  {
660  std::cerr << "fatal error: there is an error on your command line, you used a parameter without a value and there is no default. The exception says: " << e.what() << std::endl;
661  exit(1);
662  }
663 }
664 
665 // Local Variables:
666 // mode: cpp
667 // indent-tabs-mode: nil
668 // c-basic-offset: 4
669 // tab-width: 4
670 // End:
671 
672 // vim: ts=4 sw=4 et
int main(int argc, char *argv[])
Definition: csspp.cpp:617
void free_char(char *ptr)
Definition: csspp.cpp:267
constexpr advgetopt::option g_options[]
Definition: csspp.cpp:274
advgetopt::options_environment const g_options_environment
Definition: csspp.cpp:357

Documentation of CSS Preprocessor.

This document is part of the Snap! Websites Project.

Copyright by Made to Order Software Corp.