1 /***************************************************************************
2                           main.cpp  -  description
3                              -------------------
4     begin                : Die Apr 23 22:16:35 CEST 2002
5     copyright            : (C) 2002-2021 by Andre Simon
6     email                : a.simon@mailbox.org
7 
8    Highlight is a universal source code to formatted text converter.
9    Syntax highlighting is formatted by Cascading Style Sheets.
10    It's possible to easily enhance highlight's parsing database.
11 
12  ***************************************************************************/
13 
14 
15 /*
16 This file is part of Highlight.
17 
18 Highlight is free software: you can redistribute it and/or modify
19 it under the terms of the GNU General Public License as published by
20 the Free Software Foundation, either version 3 of the License, or
21 (at your option) any later version.
22 
23 Highlight is distributed in the hope that it will be useful,
24 but WITHOUT ANY WARRANTY; without even the implied warranty of
25 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26 GNU General Public License for more details.
27 
28 You should have received a copy of the GNU General Public License
29 along with Highlight.  If not, see <http://www.gnu.org/licenses/>.
30 */
31 
32 #include <memory>
33 #include <algorithm>
34 #include <Diluculum/LuaState.hpp>
35 
36 #include "main.h"
37 #include "datadir.h"
38 #include "syntaxreader.h"
39 #include "lspprofile.h"
40 
41 #define MAX_LINE__WIDTH       80
42 
43 using namespace std;
44 
printVersionInfo(bool quietMode)45 void HLCmdLineApp::printVersionInfo(bool quietMode)
46 {
47     if (quietMode) {
48         cout << highlight::Info::getVersion() << "\n";
49     } else {
50         cout << "\n highlight version "
51             << highlight::Info::getVersion()
52             << "\n Copyright (C) 2002-2021 Andre Simon <a dot simon at mailbox.org>"
53             << "\n\n Argparser class"
54             << "\n Copyright (C) 2006-2008 Antonio Diaz Diaz <ant_diaz at teleline.es>"
55             << "\n\n Artistic Style Classes (3.1 rev. 672)"
56             << "\n Copyright (C) 2006-2018 by Jim Pattee <jimp03 at email.com>"
57             << "\n Copyright (C) 1998-2002 by Tal Davidson"
58             << "\n\n Diluculum Lua wrapper (1.0)"
59             << "\n Copyright (C) 2005-2013 by Leandro Motta Barros"
60             << "\n\n xterm 256 color matching functions"
61             << "\n Copyright (C) 2006 Wolfgang Frisch <wf at frexx.de>"
62             << "\n\n PicoJSON library"
63             << "\n Copyright (C) 2009-2010 Cybozu Labs, Inc."
64             << "\n Copyright (C) 2011-2014 Kazuho Oku"
65             << "\n\n This software is released under the terms of the GNU General "
66             << "Public License."
67             << "\n For more information about these matters, see the file named "
68             << "COPYING.\n\n";
69     }
70 }
71 
printBadInstallationInfo()72 void HLCmdLineApp::printBadInstallationInfo()
73 {
74     cerr << "highlight: Data directory not found ("<<DataDir::LSB_DATA_DIR<<")."
75          " Bad installation or wrong "<< OPT_DATADIR << " parameter."
76          << "\n\nCopy the highlight files into one of the directories listed "
77          << "in INSTALL.\nYou may also set the data directory with "
78          << OPT_DATADIR << ".\n";
79 }
80 
printInstalledFiles(const string & where,const string & wildcard,const string & kind,const string & option,const string & categoryFilterList)81 int HLCmdLineApp::printInstalledFiles(const string& where, const string& wildcard, const string& kind, const string& option, const string& categoryFilterList)
82 {
83     vector <string> filePaths;
84     string searchDir = where + wildcard;
85 
86     bool directoryOK = Platform::getDirectoryEntries ( filePaths, searchDir, true );
87     if ( !directoryOK ) {
88         cerr << "highlight: Could not access directory "
89              <<  searchDir
90              << ", aborted.\n";
91         return EXIT_FAILURE;
92     }
93 
94     sort ( filePaths.begin(), filePaths.end() );
95     string suffix, desc;
96     Diluculum::LuaValueMap categoryMap;
97     cout << "\nInstalled " << kind << "s";
98 
99     if (categoryFilterList.size())
100         cout << " matching \""<<categoryFilterList<<"\"";
101 
102     cout << " (located in " << where << "):\n\n";
103     int matchedFileCnt=0;
104     std::set<string> categoryNames;
105     std::set<string> categoryFilters;
106 
107     istringstream valueStream;
108     string catFilter;
109     valueStream.str ( StringTools::change_case ( categoryFilterList,StringTools::CASE_LOWER ) );
110     while ( getline ( valueStream, catFilter, ';' ) ) {
111         categoryFilters.insert ( catFilter );
112     }
113 
114     for (const auto &path : filePaths ) {
115         try {
116             Diluculum::LuaState ls;
117             highlight::SyntaxReader::initLuaState(ls, path,"");
118             ls.doFile(path);
119             desc = ls["Description"].value().asString();
120 
121             suffix = ( path ).substr ( where.length() ) ;
122             suffix = suffix.substr ( 0, suffix.length()- wildcard.length()+1 );
123 
124             unsigned int filterOKCnt=categoryFilters.size();
125             if (ls["Categories"].value() !=Diluculum::Nil){
126                 filterOKCnt=0;
127 
128                 categoryMap = ls["Categories"].value().asTable();
129 
130                 for (Diluculum::LuaValueMap::const_iterator it = categoryMap.begin(); it != categoryMap.end(); ++it)
131                 {
132                     categoryNames.insert(it->second.asString());
133                     if (categoryFilters.size() && categoryFilters.count(it->second.asString())) {
134                         ++filterOKCnt;
135                     }
136                 }
137             }
138 
139             if (filterOKCnt!=categoryFilters.size() && categoryFilters.size() ) continue;
140 
141             matchedFileCnt++;
142             if (kind=="langDef") {
143                 cout << setw ( 30 ) <<setiosflags ( ios::left ) <<desc<<": "<<suffix;
144 
145                 int extCnt=0;
146                 for (StringMap::iterator it=dataDir.assocByExtension.begin(); it!=dataDir.assocByExtension.end(); it++) {
147                     if (it->second==suffix ) {
148                         cout << ((++extCnt==1)?" ( ":" ")<<it->first;
149                     }
150                 }
151                 cout << ((extCnt)?" )":"");
152             } else {
153                 cout << setw ( 30 ) <<setiosflags ( ios::left ) <<suffix<<": "<<desc;
154 
155             }
156             cout << endl;
157         } catch (std::runtime_error &error) {
158             cout << "Failed to read '" << path<< "': " << error.what() << endl;
159         }
160     }
161 
162     if (!matchedFileCnt) {
163         cout <<"No files found." << endl;
164     } else {
165 
166         if (!categoryFilters.size()){
167             cout << "\nFound "<<kind<<" categories:\n\n";
168             for (std::set<string>::iterator it=categoryNames.begin(); it!=categoryNames.end(); ++it)
169                 std::cout << *it<< ' ';
170             cout << "\n";
171         }
172 
173         cout <<"\nUse name of the desired "<<kind
174             << " with --" << option << ". Filter categories with --list-cat." << endl;
175 
176         if (kind=="theme") {
177             cout <<"\nAdd base16/ prefix to apply a Base16 theme." << endl;
178         }
179 
180         printConfigInfo();
181     }
182 
183     return EXIT_SUCCESS;
184 }
185 
printDebugInfo(const highlight::SyntaxReader * lang,const string & langDefPath,int level)186 void HLCmdLineApp::printDebugInfo ( const highlight::SyntaxReader *lang,
187                                     const string & langDefPath, int level )
188 {
189     if (!lang) return;
190 
191     map <int, string> HLStateMap;
192 
193     cerr << "\nLoading language definition:\n" << langDefPath;
194     cerr << "\n\nDescription: " << lang->getDescription();
195     if (level>1) {
196         Diluculum::LuaState* luaState=lang->getLuaState();
197         if (luaState) {
198             cerr << "\n\nLUA GLOBALS:\n" ;
199             Diluculum::LuaValueMap::iterator it;
200             Diluculum::LuaValueMap glob =luaState->globals();
201             string elemName;
202             for(it = glob.begin(); it != glob.end(); it++) {
203                 Diluculum::LuaValue first = it->first;
204                 Diluculum::LuaValue second = it->second;
205                 elemName = first.asString();
206                 std::cerr << elemName<<": ";
207                 switch (second.type()) {
208                     case  LUA_TSTRING:
209                         cerr << "string [ "<<second.asString()<<" ]";
210                         break;
211                     case  LUA_TNUMBER:
212                         cerr << "number [ "<<second.asNumber()<<" ]";
213                         if (elemName.find("HL_")==0 && elemName.find("HL_FORMAT")==string::npos)
214                             HLStateMap[second.asNumber()] = elemName;
215                         break;
216                     case  LUA_TBOOLEAN:
217                         cerr << "boolean [ "<<second.asBoolean()<<" ]";
218                         break;
219                     default:
220                         cerr << second.typeName();
221                 }
222                 cerr << endl;
223             }
224 
225         }
226 
227         highlight::RegexElement *re=NULL;
228         for ( unsigned int i=0; i<lang->getRegexElements().size(); i++ )
229         {
230             if (i==0)
231                 cerr << "\nREGEX:\n";
232 
233             re = lang->getRegexElements() [i];
234             cerr << "State "<<re->open<< " ("<< HLStateMap[re->open]<<  "):\t"<<re->pattern <<"\n";
235         }
236 
237         highlight::KeywordMap::iterator it;
238         highlight::KeywordMap keys=lang->getKeywords();
239         for ( it=keys.begin(); it!=keys.end(); it++ ) {
240             if (it==keys.begin())
241                 cerr << "\nKEYWORDS:\n";
242 
243             cerr << " "<< it->first << "("<< it->second << ")";
244         }
245     }
246     cerr <<"\n\n";
247 }
248 
printConfigInfo()249 void HLCmdLineApp::printConfigInfo ( )
250 {
251     cout << "\nConfig file search directories:\n";
252     dataDir.printConfigPaths();
253     cout << "\nFiletype config file:\n"<<dataDir.getFiletypesConfPath ( "filetypes" ) <<"\n";
254     cout << endl;
255 #ifdef HL_DATA_DIR
256     cout << "Compiler directive HL_DATA_DIR = " <<HL_DATA_DIR<< "\n";
257 #endif
258 #ifdef HL_CONFIG_DIR
259     cout << "Compiler directive HL_CONFIG_DIR = " <<HL_CONFIG_DIR<< "\n";
260 #endif
261     cout << endl;
262 }
263 
getNumDigits(int i)264 int HLCmdLineApp::getNumDigits ( int i )
265 {
266     int res=0;
267     while ( i ) {
268         i/=10;
269         ++res;
270     }
271     return res;
272 }
273 
printProgressBar(int total,int count)274 void HLCmdLineApp::printProgressBar ( int total, int count )
275 {
276     if ( !total ) return;
277     int p=100*count / total;
278     int numProgressItems=p/10;
279     cout << "\r[";
280     for ( int i=0; i<10; i++ ) {
281         cout << ( ( i<numProgressItems ) ?"#":" " );
282     }
283     cout<< "] " <<setw ( 3 ) <<p<<"%, "<<count << " / " << total << "  " <<flush;
284     if ( p==100 ) {
285         cout << endl;
286     }
287 }
288 
printCurrentAction(const string & outfilePath,int total,int count,int countWidth)289 void HLCmdLineApp::printCurrentAction ( const string&outfilePath,
290                                         int total, int count, int countWidth )
291 {
292     cout << "Writing file "
293          << setw ( countWidth ) << count
294          << " of "
295          << total
296          << ": "
297          << outfilePath
298          << "\n";
299 }
300 
printIOErrorReport(unsigned int numberErrorFiles,vector<string> & fileList,const string & action,const string & streamName)301 void HLCmdLineApp::printIOErrorReport ( unsigned int numberErrorFiles,
302                                         vector<string> & fileList,
303                                         const string &action, const string &streamName  )
304 {
305     cerr << "highlight: Could not "
306          << action
307          << " file"
308          << ( ( numberErrorFiles>1 ) ?"s":"" ) <<":\n";
309 
310     if (numberErrorFiles==1 && fileList[0].size()==0){
311         cerr<<streamName<<"\n";
312     }
313     else {
314         copy ( fileList.begin(), fileList.end(), ostream_iterator<string> ( cerr, "\n" ) );
315     }
316 
317     if ( fileList.size() < numberErrorFiles ) {
318         cerr << "... ["
319              << ( numberErrorFiles - fileList.size() )
320              << " of "
321              << numberErrorFiles
322              << " failures not shown, use --"
323              << OPT_VERBOSE
324              << " switch to print all failures]\n";
325     }
326 }
327 
collectPluginPaths(const vector<string> & plugins)328 vector <string> HLCmdLineApp::collectPluginPaths(const vector<string>& plugins)
329 {
330     vector<string> absolutePaths;
331     for (unsigned int i=0; i<plugins.size(); i++) {
332         if (Platform::fileExists(plugins[i])) {
333             absolutePaths.push_back(plugins[i]);
334         } else {
335             absolutePaths.push_back(dataDir.getPluginPath(plugins[i]+".lua"));
336         }
337     }
338     return absolutePaths;
339 }
340 
run(const int argc,const char * argv[])341 int HLCmdLineApp::run ( const int argc, const char*argv[] )
342 {
343     CmdLineOptions options ( argc, argv );
344 
345     // set data directory path, where /langDefs and /themes reside
346     string dataDirPath = ( options.getDataDir().empty() ) ?  Platform::getAppPath() :options.getDataDir();
347 
348     if ( options.printVersion() ) {
349         printVersionInfo(options.quietMode());
350         return EXIT_SUCCESS;
351     }
352 
353     dataDir.initSearchDirectories ( dataDirPath );
354 
355     if ( options.printHelp() ) {
356         Help::printHelp(options.getHelpTopic());
357         return EXIT_SUCCESS;
358     }
359 
360     if ( options.printConfigInfo() ) {
361         printConfigInfo();
362         return EXIT_SUCCESS;
363     }
364 
365     //call before printInstalledLanguages!
366     dataDir.loadFileTypeConfig ( "filetypes" );
367 
368     // set CLI options; if profle is defined read from lsp.conf
369     std::string lsProfile(options.getLsProfile());
370     std::string lsExecutable(options.getLsExecutable());         ///< server executable path
371     std::string lsSyntax(options.getLsSyntax());                   ///< language definition which can be enhanced using the LS
372     int lsDelay=options.getLsDelay();
373     std::vector<std::string> lsOptions = options.getLSOptions(); ///< server executable start options
374 
375     if (lsProfile.size()) {
376         dataDir.loadLSPConfig("lsp");
377         if (dataDir.profileExists(lsProfile)) {
378             highlight::LSPProfile profile = dataDir.getProfile(lsProfile);
379             if (lsExecutable.empty())
380                 lsExecutable = profile.executable;
381             if (lsSyntax.empty())
382                 lsSyntax = profile.syntax;
383             if (lsOptions.empty())
384                 lsOptions = profile.options;
385             if (lsDelay==0)
386                 lsDelay = profile.delay;
387         } else {
388             cerr << "highlight: Unknown LSP profile '"<< lsProfile << "'.\n";
389             return EXIT_FAILURE;
390         }
391     }
392 
393     string scriptKind=options.getListScriptKind();
394     if (scriptKind.length()) {
395         if ( scriptKind.find("theme")==0 ) {
396             return printInstalledFiles(dataDir.getThemePath(""), "*.theme", "theme", OPT_STYLE, options.getCategories());
397         }
398         else if ( scriptKind.find("plug")==0 ) {
399             return printInstalledFiles(dataDir.getPluginPath(""), "*.lua", "plug-in", OPT_PLUGIN, options.getCategories());
400         }
401         else if (  scriptKind.find("lang")==0 ) {
402             return printInstalledFiles(dataDir.getLangPath(""), "*.lang", "langDef", OPT_SYNTAX, options.getCategories());
403         } else {
404             cerr << "highlight: Unknown script type '"<< scriptKind << "'. Apply one of 'themes', 'langs' or 'plug-ins'.\n";
405             return EXIT_FAILURE;
406         }
407     }
408 
409     const vector <string> inFileList=options.getInputFileNames();
410 
411     if ( options.enableBatchMode() && inFileList[0].empty() ) {
412         return EXIT_FAILURE;
413     }
414 
415     string themePath=options.getAbsThemePath().empty() ? dataDir.getThemePath ( options.getThemeName(), options.useBase16Theme() ): options.getAbsThemePath();
416 
417     unique_ptr<highlight::CodeGenerator> generator ( highlight::CodeGenerator::getInstance ( options.getOutputType() ) );
418 
419     if (options.checkSyntaxSupport()) {
420 
421         if (!options.syntaxGiven() ) {
422             cerr << "highlight: Define a syntax to use this option\n";
423             return EXIT_FAILURE;
424         } else {
425             string syntaxByFile=options.getSyntaxByFilename();
426             string testSuffix = syntaxByFile.empty() ? options.getSyntax() : dataDir.getFileSuffix(syntaxByFile);
427             string resolvedSuffix (dataDir.guessFileType (testSuffix, syntaxByFile, syntaxByFile.empty(),false ));
428             string langDefPath (options.getAbsLangPath().empty() ? dataDir.getLangPath ( resolvedSuffix +".lang") : options.getAbsLangPath());
429 
430             if (generator->loadLanguage( langDefPath ) == highlight::LOAD_OK) {
431                 cout << "highlight: This syntax is supported\n";
432                 return EXIT_SUCCESS;
433             } else {
434                 cerr << "highlight: This syntax is not supported\n";
435                 return EXIT_FAILURE;
436             }
437         }
438     }
439 
440     generator->setHTMLAttachAnchors ( options.attachLineAnchors() );
441     generator->setHTMLOrderedList ( options.orderedList() );
442     generator->setHTMLInlineCSS ( options.inlineCSS() );
443     generator->setHTMLEnclosePreTag ( options.enclosePreTag() );
444     generator->setHTMLAnchorPrefix ( options.getAnchorPrefix() );
445     generator->setHTMLClassName ( options.getClassName() );
446 
447     generator->setLATEXReplaceQuotes ( options.replaceQuotes() );
448     generator->setLATEXNoShorthands ( options.disableBabelShorthands() );
449     generator->setLATEXPrettySymbols ( options.prettySymbols() );
450     generator->setLATEXBeamerMode ( options.enableBeamerMode() );
451 
452     generator->setRTFPageSize ( options.getPageSize() );
453     generator->setRTFCharStyles ( options.includeCharStyles() );
454     generator->setRTFPageColor ( options.includePageColor() );
455 
456     generator->setSVGSize ( options.getSVGWidth(),  options.getSVGHeight() );
457 
458     generator->setESCCanvasPadding ( options.getCanvasPadding() );
459 
460     if (options.useCRDelimiter())
461         generator->setEOLDelimiter('\r');
462 
463     generator->setValidateInput ( options.validateInput() );
464     generator->setNumberWrappedLines ( options.numberWrappedLines() );
465 
466     generator->setStyleInputPath ( options.getStyleInFilename() );
467     generator->setStyleOutputPath ( options.getStyleOutFilename() );
468     generator->setIncludeStyle ( options.includeStyleDef() );
469     generator->setPrintLineNumbers ( options.printLineNumbers(), options.getNumberStart() );
470     generator->setPrintZeroes ( options.fillLineNrZeroes() );
471     generator->setFragmentCode ( options.fragmentOutput() );
472     generator->setOmitVersionComment ( options.omitVersionInfo() );
473     generator->setIsolateTags ( options.isolateTags() );
474 
475     generator->setKeepInjections ( options.keepInjections());
476     generator->setPreformatting ( options.getWrappingStyle(),
477                                   ( generator->getPrintLineNumbers() ) ?
478                                   options.getLineLength() - options.getNumberWidth() : options.getLineLength(),
479                                   options.getNumberSpaces() );
480 
481     generator->setBaseFont ( options.getBaseFont() ) ;
482     generator->setBaseFontSize ( options.getBaseFontSize() ) ;
483     generator->setLineNumberWidth ( options.getNumberWidth() );
484     generator->disableTrailingNL(options.disableTrailingNL());
485     generator->setPluginParameter(options.getPluginParameter());
486 
487     if (options.getLineRangeStart()>0 && options.getLineRangeEnd()>0){
488         generator->setStartingInputLine(options.getLineRangeStart());
489         generator->setMaxInputLineCnt(options.getLineRangeEnd());
490     }
491 
492     bool styleFileWanted = !options.fragmentOutput() || options.styleOutPathDefined();
493 
494     const  vector <string> pluginFileList=collectPluginPaths( options.getPluginPaths());
495     for (unsigned int i=0; i<pluginFileList.size(); i++) {
496         if ( !generator->initPluginScript(pluginFileList[i]) ) {
497             cerr << "highlight: "
498                  << generator->getPluginScriptError()
499                  << " in "
500                  << pluginFileList[i]
501                  <<"\n";
502             return EXIT_FAILURE;
503         }
504     }
505 
506     if ( !generator->initTheme ( themePath, options.isLsSemantic() ) ) {
507         cerr << "highlight: "
508              << generator->getThemeInitError()
509              << "\n";
510         return EXIT_FAILURE;
511     }
512 
513     if ( options.printOnlyStyle() ) {
514         if (!options.formatSupportsExtStyle()) {
515             cerr << "highlight: output format supports no external styles.\n";
516             return EXIT_FAILURE;
517         }
518         bool useStdout =  options.getStyleOutFilename() =="stdout" || options.forceStdout();
519         string cssOutFile=options.getOutDirectory()  + options.getStyleOutFilename();
520         bool success=generator->printExternalStyle ( useStdout?"":cssOutFile );
521         if ( !success ) {
522             cerr << "highlight: Could not write " << cssOutFile <<".\n";
523             return EXIT_FAILURE;
524         }
525         return EXIT_SUCCESS;
526     }
527 
528     bool formattingEnabled = generator->initIndentationScheme ( options.getIndentScheme() );
529 
530     if ( !formattingEnabled && !options.getIndentScheme().empty() ) {
531         cerr << "highlight: Undefined indentation scheme "
532              << options.getIndentScheme()
533              << ".\n";
534         return EXIT_FAILURE;
535     }
536 
537     //generator->setIndentationOptions(options.getAStyleOptions());
538 
539     string outDirectory = options.getOutDirectory();
540 #ifndef WIN32
541     ifstream dirTest ( outDirectory.c_str() );
542     if ( !outDirectory.empty() && !options.quietMode() && !dirTest ) {
543         cerr << "highlight: Output directory \""
544              << outDirectory
545              << "\" does not exist.\n";
546         return EXIT_FAILURE;
547     }
548     dirTest.close();
549 #endif
550 
551     bool initError=false, IOError=false, twoPassMode=false;
552     unsigned int fileCount=inFileList.size(),
553                  fileCountWidth=getNumDigits ( fileCount ),
554                  i=0,
555                  numBadFormatting=0,
556                  numBadInput=0,
557                  numBadOutput=0;
558 
559     vector<string> badFormattedFiles, badInputFiles, badOutputFiles;
560     std::set<string> usedFileNames;
561     string inFileName, outFilePath;
562     string suffix, lastSuffix;
563     string twoPassOutFile=Platform::getTempFilePath();
564     bool usesLSClient=false;
565 
566     if ( options.syntaxGiven() ) { // user defined language definition, valid for all files
567         string syntaxByFile=options.getSyntaxByFilename();
568         string testSuffix = syntaxByFile.empty() ? options.getSyntax() : dataDir.getFileSuffix(syntaxByFile);
569         suffix = dataDir.guessFileType (testSuffix, syntaxByFile, syntaxByFile.empty(), options.getSingleOutFilename().length()==0 );
570     }
571 
572     generator->setFilesCnt(fileCount);
573 
574     while ( i < fileCount && !initError ) {
575 
576         if ( Platform::fileSize(inFileList[i]) > options.getMaxFileSize() ) {
577 
578             if ( numBadInput++ < IO_ERROR_REPORT_LENGTH || options.verbosityLevel() ) {
579                 badInputFiles.push_back ( inFileList[i] + " (size)" );
580             }
581             ++i;
582             continue;
583         }
584 
585         if (i==0 && twoPassMode) {
586              if ( !generator->initPluginScript(twoPassOutFile) ) {
587                 cerr << "highlight: "
588                  << generator->getPluginScriptError()
589                  << " in "
590                  << twoPassOutFile
591                  <<"\n";
592                 initError = true;
593                 break;
594             }
595         }
596 
597         if ( !options.syntaxGiven() ) { // determine file type for each file
598             suffix = dataDir.guessFileType ( dataDir.getFileSuffix ( inFileList[i] ), inFileList[i] );
599         }
600 
601         if ( suffix.empty()  && options.forceOutput()) suffix=options.getFallbackSyntax(); //avoid segfault
602 
603         if ( suffix.empty() ) {
604             if ( !options.enableBatchMode() )
605                 cerr << "highlight: Undefined language definition. Use --"
606                      << OPT_SYNTAX << " option.\n";
607             if ( !options.forceOutput() ) {
608                 initError = true;
609                 break;
610             }
611         }
612 
613         if ( suffix != lastSuffix ) {
614 
615             string langDefPath=options.getAbsLangPath().empty() ? dataDir.getLangPath ( suffix+".lang" ) : options.getAbsLangPath();
616 
617             if (!Platform::fileExists(langDefPath) && !options.getFallbackSyntax().empty()) {
618                 langDefPath = dataDir.getLangPath ( options.getFallbackSyntax()+".lang" );
619             }
620 
621             highlight::LoadResult loadRes= generator->loadLanguage( langDefPath );
622 
623             if ( loadRes==highlight::LOAD_FAILED_REGEX ) {
624                 cerr << "highlight: Regex error ( "
625                      << generator->getSyntaxRegexError()
626                      << " ) in "<<suffix<<".lang\n";
627                 initError = true;
628                 break;
629             } else if ( loadRes==highlight::LOAD_FAILED_LUA ) {
630                 cerr << "highlight: Lua error ( "
631                      << generator->getSyntaxLuaError()
632                      << " ) in "<<suffix<<".lang\n";
633                 initError = true;
634                 break;
635             } else if ( loadRes==highlight::LOAD_FAILED ) {
636                 // do also ignore error msg if --syntax parameter should be skipped
637                 if ( ! (options.forceOutput() || options.quietMode() || options.isSkippedExt ( suffix )) ) {
638                     cerr << "highlight: Unknown source file extension \""
639                          << suffix
640                          << "\". Consider the "
641                          << (options.enableBatchMode() ? "--skip" : "--force or --syntax")
642                          << " option.\n";
643                 }
644                 if ( !options.forceOutput() ) {
645                     initError = true;
646                     break;
647                 }
648             }
649             if ( options.verbosityLevel() && loadRes==highlight::LOAD_OK ) {
650                 printDebugInfo ( generator->getSyntaxReader(), langDefPath, options.verbosityLevel() );
651             }
652             lastSuffix = suffix;
653 
654             string encoding= options.getEncoding();
655             //user has explicitly defined the encoding:
656             if (!options.encodingDefined()) {
657 
658                 //syntax definition setting:
659                 string encodingHint= generator->getSyntaxEncodingHint();
660                 if (encodingHint.size())
661                     encoding=encodingHint;
662 
663                 // filetypes.conf setting has higher priority:
664                 encodingHint= dataDir.getEncodingHint(suffix);
665                 if (encodingHint.size())
666                     encoding=encodingHint;
667             }
668             generator->setEncoding (encoding);
669 
670             if (lsSyntax==suffix) {
671 
672                 if (options.getWrappingStyle()!=highlight::WRAP_DISABLED || options.getIndentScheme().size()) {
673                     cerr << "highlight: no reformatting allowed with LSP options.\n";
674                     initError = true;
675                     break;
676                 }
677 
678                 //LSP requires absolute paths
679                 if (inFileList[i].empty()) {
680                     cerr << "highlight: no input file path defined.\n";
681                     initError = true;
682                     break;
683                 }
684 
685                 if ( lsExecutable.empty() ) {
686                     cerr << "highlight: no LS executable defined. Consider the --ls-exec or --ls-profile options.\n";
687                     initError = true;
688                     break;
689                 }
690 
691                 highlight::LSResult lsInitRes=generator->initLanguageServer ( lsExecutable, lsOptions,
692                                                                               options.getLsWorkspace(), lsSyntax,
693                                                                               lsDelay,
694                                                                               options.verbosityLevel() );
695                 if ( lsInitRes==highlight::INIT_BAD_PIPE ) {
696                     cerr << "highlight: language server connection failed\n";
697                     initError = true;
698                     break;
699                 } else if ( lsInitRes==highlight::INIT_BAD_REQUEST ) {
700                     cerr << "highlight: language server initialization failed\n";
701                     initError = true;
702                     break;
703                 }
704                 usesLSClient=true;
705 
706                 generator->lsAddSyntaxErrorInfo( (options.isLsHover() || options.isLsSemantic()) && options.isLsSyntaxError() );
707 
708                 if (options.isLsHover()) {
709                     if (!generator->isHoverProvider()) {
710                         cerr << "highlight: language server is no hover provider\n";
711                         initError = true;
712                         break;
713                     }
714                     generator->lsAddHoverInfo( true );
715                 }
716             }
717         }
718 
719         if (usesLSClient && lsSyntax==suffix) {
720             generator->lsOpenDocument(inFileList[i], suffix);
721 
722             if (options.isLsSemantic()) {
723                 if (!generator->isSemanticTokensProvider()) {
724                     cerr << "highlight: language server is no semantic token provider\n";
725                     initError = true;
726                     break;
727                 }
728                 generator->lsAddSemanticInfo(inFileList[i], suffix);
729             }
730         }
731 
732         if (twoPassMode && !generator->syntaxRequiresTwoPassRun()) {
733             ++i;
734             continue;
735         }
736 
737         string::size_type pos= ( inFileList[i] ).find_last_of ( Platform::pathSeparator );
738         inFileName = inFileList[i].substr ( pos+1 );
739 
740         if ( options.enableBatchMode() ) {
741             if (usedFileNames.count(inFileName)) {
742                 string prefix=inFileList[i].substr (2, pos-1 );
743                 replace (prefix.begin(), prefix.end(), Platform::pathSeparator, '_');
744                 inFileName.insert(0, prefix);
745             } else {
746                 usedFileNames.insert(inFileName);
747             }
748             if (!options.forceStdout()){
749                 outFilePath = outDirectory;
750                 outFilePath += inFileName;
751                 outFilePath += options.getOutFileSuffix();
752             }
753             if ( !options.quietMode() &&  !options.forceStdout() ) {
754                 if ( options.printProgress() ) {
755                     printProgressBar ( fileCount, i+1 );
756                 } else {
757                     printCurrentAction ( outFilePath, fileCount, i+1, fileCountWidth );
758                 }
759             }
760         } else if (!options.forceStdout()) {
761             outFilePath = options.getSingleOutFilename();
762             if ( outFilePath.size() && outFilePath==options.getSingleInFilename() ) {
763                 cerr << "highlight: Output path equals input path: \""
764                      << outFilePath << "\".\n";
765                 initError = true;
766                 break;
767             }
768         }
769 
770         if ( options.useFNamesAsAnchors() ) {
771             generator->setHTMLAnchorPrefix ( inFileName );
772         }
773 
774         generator->setTitle ( options.getDocumentTitle().empty() ?
775                               inFileList[i]:options.getDocumentTitle() );
776 
777         generator->setKeyWordCase ( options.getKeywordCase() );
778         highlight::ParseError error = generator->generateFile ( inFileList[i], outFilePath );
779 
780         if ( error==highlight::BAD_INPUT ) {
781             if ( numBadInput++ < IO_ERROR_REPORT_LENGTH || options.verbosityLevel() ) {
782                 badInputFiles.push_back ( inFileList[i] );
783             }
784         } else if ( error==highlight::BAD_OUTPUT ) {
785             if ( numBadOutput++ < IO_ERROR_REPORT_LENGTH || options.verbosityLevel() ) {
786                 badOutputFiles.push_back ( outFilePath );
787             }
788         }
789         if ( formattingEnabled && !generator->formattingIsPossible() ) {
790             if ( numBadFormatting++ < IO_ERROR_REPORT_LENGTH || options.verbosityLevel() ) {
791                 badFormattedFiles.push_back ( outFilePath );
792             }
793         }
794 
795         if (usesLSClient && lsSyntax==suffix) {
796             //pyls hangs
797             generator->lsCloseDocument(inFileList[i], suffix);
798         }
799 
800         ++i;
801 
802         if (i==fileCount && outFilePath.size() && generator->requiresTwoPassParsing() && twoPassOutFile.size()
803             && !numBadInput && !numBadOutput && !twoPassMode) {
804 
805             bool success=generator->printPersistentState(twoPassOutFile);
806             if ( !success ) {
807                 cerr << "highlight: Could not write "<< twoPassOutFile <<".\n";
808                 IOError = true;
809             } else {
810                 twoPassMode=true;
811                 if ( !options.quietMode() &&  !options.forceStdout() ) {
812                     cout << "Enabling two-pass mode using "<<twoPassOutFile<<"\n";
813                 }
814                 //start over, add plug-in to list in next iteration
815                 usedFileNames.clear();
816                 generator->resetSyntaxReaders();
817                 i=0;
818                 lastSuffix.clear();
819                 numBadFormatting=0;
820                 badFormattedFiles.clear();
821             }
822         }
823     }
824 
825     if ( i  && !options.includeStyleDef()
826             && !options.inlineCSS()
827             && styleFileWanted
828             && options.formatSupportsExtStyle() ) {
829         string cssOutFile=outDirectory  + options.getStyleOutFilename();
830         bool success=generator->printExternalStyle ( cssOutFile );
831         if ( !success ) {
832             cerr << "highlight: Could not write " << cssOutFile <<".\n";
833             IOError = true;
834         }
835     }
836 
837     if ( i && options.printIndexFile() ) {
838         bool success=generator -> printIndexFile ( inFileList, outDirectory );
839         if ( !success ) {
840             cerr << "highlight: Could not write index file.\n";
841             IOError = true;
842         }
843     }
844 
845     if ( numBadInput ) {
846         printIOErrorReport ( numBadInput, badInputFiles, "read input", "<stdin>" );
847         IOError = true;
848     }
849     if ( numBadOutput ) {
850         printIOErrorReport ( numBadOutput, badOutputFiles, "write output",  "<stdout>" );
851         IOError = true;
852     }
853     if ( numBadFormatting ) {
854         printIOErrorReport ( numBadFormatting, badFormattedFiles, "reformat", "<stdout>" );
855     }
856 
857     vector<string> posTestErrors = generator->getPosTestErrors();
858     if (posTestErrors.size()){
859         IOError = true;
860         printIOErrorReport ( posTestErrors.size(), posTestErrors, "validate", "<stdin>" );
861     }
862 
863     if (twoPassMode) {
864         unlink(twoPassOutFile.c_str());
865     }
866 
867     if (usesLSClient) {
868         generator->exitLanguageServer();
869     }
870 
871     return ( initError || IOError ) ? EXIT_FAILURE : EXIT_SUCCESS;
872 }
873 
main(const int argc,const char * argv[])874 int main ( const int argc, const char *argv[] )
875 {
876     HLCmdLineApp app;
877     return app.run ( argc, argv );
878 }
879