OSDN Git Service

Allow NUL and \\.\NUL in paths specified as command line arguments (#2056)
[winmerge-jp/winmerge-jp.git] / Src / MergeCmdLineInfo.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 //    WinMerge: An interactive diff/merge utility
3 //    Copyright (C) 1997 Dean P. Grimm
4 //    SPDX-License-Identifier: GPL-2.0-or-later
5 /////////////////////////////////////////////////////////////////////////////
6 /** 
7  * @file  MergeCmdLineInfo.cpp
8  *
9  * @brief MergeCmdLineInfo class implementation.
10  *
11  */
12
13
14 #include "pch.h"
15 #include "MergeCmdLineInfo.h"
16 #include "Paths.h"
17 #include "OptionsDef.h"
18 #include "unicoder.h"
19
20 // MergeCmdLineInfo
21
22 /**
23  * @brief Eat and digest a command line parameter.
24  * @param [in] p Points into the command line.
25  * @param [out] param Receives the digested command line parameter.
26  * @param [out] flag Tells whether param is the name of a flag.
27  * @return Points to the remaining portion of the command line.
28  */
29 const tchar_t *MergeCmdLineInfo::EatParam(const tchar_t *p, String &param, bool *flag /*= nullptr*/)
30 {
31         if (p != nullptr && *(p += tc::tcsspn(p, _T(" \t\r\n"))) == _T('\0'))
32                 p = nullptr;
33         const tchar_t *q = p;
34         if (q != nullptr)
35         {
36                 tchar_t c = *q;
37                 bool quoted = false;
38                 do
39                 {
40                         if (c == _T('"'))
41                                 quoted = !quoted;
42                         c = *++q;
43                 } while (c != _T('\0') && (quoted ||
44                         c != _T(' ') && c != _T('\t') && c != _T('\r') && c != _T('\n')));
45         }
46         if (q > p && flag)
47         {
48                 if (*p == _T('-') || *p == _T('/'))
49                 {
50                         *flag = true;
51                         ++p;
52                         for (const tchar_t *i = q; i >= p; --i)
53                                 if (*i == ':')
54                                 {
55                                         q = i;
56                                         break;
57                                 }
58                 }
59                 else
60                 {
61                         *flag = false;
62                         flag = nullptr;
63                 }
64         }
65         param.assign(p ? p : _T(""), q - p);
66         if (q > p && flag != nullptr)
67         {
68                 param = strutils::makelower(param);
69         }
70         // Strip any leading or trailing whitespace or quotes
71         param.erase(0, param.find_first_not_of(_T(" \t\r\n\"")));
72         param.erase(param.find_last_not_of(_T(" \t\r\n\"")) + 1);
73         return q;
74 }
75
76 /**
77  * @brief Set WinMerge option from command line.
78  * @param [in] p Points into the command line.
79  * @param [in] key Name of WinMerge option to set.
80  * @param [in] value Default value in case none is specified.
81  * @return Points to the remaining portion of the command line.
82  */
83 const tchar_t *MergeCmdLineInfo::SetOption(const tchar_t *q, const String& key, const tchar_t *value)
84 {
85         if (!q)
86                 return nullptr;
87         String s;
88         if (*q == _T(':'))
89         {
90                 q = EatParam(q, s);
91                 value = s.c_str() + 1;
92         }
93         m_Options.insert_or_assign(key, value);
94         return q;
95 }
96
97 const tchar_t *MergeCmdLineInfo::SetConfig(const tchar_t *q)
98 {
99         String s;
100         if (*q == ':')
101                 ++q;
102         q = EatParam(q, s);
103         size_t pos = s.find_first_of('=');
104         if (pos != String::npos)
105         {
106                 String key = s.substr(0, pos);
107                 String value = s.c_str() + pos + 1;
108                 m_Options.insert_or_assign(key, value);
109         }
110         return q;
111 }
112
113 /**
114  * @brief MergeCmdLineParser's constructor.
115  * @param [in] q Points to the beginning of the command line.
116  */
117 MergeCmdLineInfo::MergeCmdLineInfo(const tchar_t* q)
118         : m_nCmdShow(SHOWNORMAL)
119         , m_nWindowType(AUTOMATIC)
120         , m_nDialogType(NO_DIALOG)
121         , m_bShowCompareAsMenu(false)
122         , m_bEscShutdown(false)
123         , m_bExitIfNoDiff(Disabled)
124         , m_bNonInteractive(false)
125         , m_nSingleInstance()
126         , m_bShowUsage(false)
127         , m_bNoPrefs(false)
128         , m_nCodepage(0)
129         , m_bSelfCompare(false)
130         , m_bClipboardCompare(false)
131         , m_bNewCompare(false)
132         , m_dwLeftFlags(FFILEOPEN_NONE)
133         , m_dwMiddleFlags(FFILEOPEN_NONE)
134         , m_dwRightFlags(FFILEOPEN_NONE)
135         , m_nLineIndex(-1)
136         , m_nCharIndex(-1)
137         , m_bEnableExitCode(false)
138 {
139         String exeName;
140         q = EatParam(q, exeName);
141         ParseWinMergeCmdLine(q);
142 }
143
144 /**
145  * @brief Add path to list of paths.
146  * This method adds given string as a path to the list of paths. Path
147  * are converted if needed, shortcuts expanded etc.
148  * @param [in] path Path string to add.
149  */
150 void MergeCmdLineInfo::AddPath(const String &path)
151 {
152         String param(path);
153
154         // Set flag indicating path is from command line
155         const size_t ord = m_Files.GetSize();
156         if (ord == 0)
157                 m_dwLeftFlags |= FFILEOPEN_CMDLINE;
158         else if (ord == 1)
159                 m_dwRightFlags |= FFILEOPEN_CMDLINE;
160         else if (ord == 2)
161                 m_dwMiddleFlags |= FFILEOPEN_CMDLINE;
162
163         if (!paths::IsURLorCLSID(path))
164         {
165                 // Convert paths given in Linux-style ('/' as separator) given from
166                 // Cygwin to Windows style ('\' as separator)
167                 strutils::replace(param, _T("/"), _T("\\"));
168
169                 // If shortcut, expand it first
170                 if (paths::IsShortcut(param))
171                         param = paths::ExpandShortcut(param);
172                 param = paths::GetLongPath(param);
173                 m_Files.SetPath(m_Files.GetSize(), param);
174         }
175         else
176         {
177                 m_Files.SetPath(m_Files.GetSize(), param, false);
178         }
179 }
180
181 /**
182  * @brief Parse native WinMerge command line.
183  * @param [in] p Points into the command line.
184  */
185 void MergeCmdLineInfo::ParseWinMergeCmdLine(const tchar_t *q)
186 {
187         String param;
188         bool flag;
189
190         while ((q = EatParam(q, param, &flag)) != 0)
191         {
192                 if (!flag)
193                 {
194                         // Its not a flag so it is a path
195                         AddPath(param);
196                 }
197                 else if (param == _T("?"))
198                 {
199                         // -? to show common command line arguments.
200                         m_bShowUsage = true;
201                 }
202                 else if (param == _T("o"))
203                 {
204                         // -o "outputfilename"
205                         q = EatParam(q, m_sOutputpath);
206                 }
207                 else if (param == _T("or"))
208                 {
209                         // -or "reportfilename"
210                         q = EatParam(q, m_sReportFile);
211                 }
212                 else if (param == _T("dl"))
213                 {
214                         // -dl "desc" - description for left file
215                         q = EatParam(q, m_sLeftDesc);
216                 }
217                 else if (param == _T("dm"))
218                 {
219                         // -dr "desc" - description for middle file
220                         q = EatParam(q, m_sMiddleDesc);
221                 }
222                 else if (param == _T("dr"))
223                 {
224                         // -dr "desc" - description for right file
225                         q = EatParam(q, m_sRightDesc);
226                 }
227                 else if (param == _T("e"))
228                 {
229                         // -e to allow closing with single esc press
230                         m_bEscShutdown = true;
231                 }
232                 else if (param == _T("f"))
233                 {
234                         // -f "mask" - file filter mask ("*.h *.cpp")
235                         q = EatParam(q, m_sFileFilter);
236                 }
237                 else if (param == _T("t"))
238                 {
239                         // -t "type" - window type
240                         q = EatParam(q, param);
241                         param = strutils::makelower(param);
242                         if (param == _T("automatic"))
243                                 m_nWindowType = WindowType::AUTOMATIC;
244                         else if (param == _T("text"))
245                                 m_nWindowType = WindowType::TEXT;
246                         else if (param == _T("table"))
247                                 m_nWindowType = WindowType::TABLE;
248                         else if (param == _T("binary"))
249                                 m_nWindowType = WindowType::BINARY;
250                         else if (param == _T("image"))
251                                 m_nWindowType = WindowType::IMAGE;
252                         else if (param.substr(0, 3) == _T("web"))
253                                 m_nWindowType = WindowType::WEBPAGE;
254                         else
255                                 m_sErrorMessages.emplace_back(_T("Unknown window type '") + param + _T("' specified"));
256                 }
257                 else if (param == _T("show-dialog"))
258                 {
259                         // -show-dialog "type" - dialog type
260                         q = EatParam(q, param);
261                         param = strutils::makelower(param);
262                         if (param == _T("options"))
263                                 m_nDialogType = DialogType::OPTIONS_DIALOG;
264                         else if (param == _T("about"))
265                                 m_nDialogType = DialogType::ABOUT_DIALOG;
266                         else
267                                 m_sErrorMessages.emplace_back(_T("Unknown dialog type '") + param + _T("' specified"));
268                 }
269                 else if (param == _T("show-compare-as-menu"))
270                 {
271                         m_bShowCompareAsMenu = true;
272                 }
273                 else if (param == _T("set-usertasks-to-jumplist"))
274                 {
275                         q = EatParam(q, param);
276                         m_dwUserTasksFlags = tc::ttoi(param.c_str());
277                 }
278                 else if (param == _T("m"))
279                 {
280                         // -m "method" - compare method
281                         q = EatParam(q, param);
282                         param = strutils::makelower(param);
283                         strutils::replace(param, _T("and"), _T(""));
284                         strutils::replace(param, _T("contents"), _T(""));
285                         strutils::replace(param, _T("modified"), _T(""));
286                         strutils::replace(param, _T(" "), _T(""));
287                         if (param == _T("full"))
288                                 m_nCompMethod = CompareMethodType::CONTENT;
289                         else if (param == _T("quick"))
290                                 m_nCompMethod = CompareMethodType::QUICK_CONTENT;
291                         else if (param == _T("binary"))
292                                 m_nCompMethod = CompareMethodType::BINARY_CONTENT;
293                         else if (param == _T("date"))
294                                 m_nCompMethod = CompareMethodType::DATE;
295                         else if (param == _T("sizedate") || param == _T("datesize"))
296                                 m_nCompMethod = CompareMethodType::DATE_SIZE;
297                         else if (param == _T("size"))
298                                 m_nCompMethod = CompareMethodType::SIZE;
299                         else
300                                 m_sErrorMessages.emplace_back(_T("Unknown compare method '") + param + _T("' specified"));
301                 }
302                 else if (param == _T("r"))
303                 {
304                         // -r to compare recursively
305                         if (*q == ':')
306                         {
307                                 q = EatParam(q + 1, param);
308                                 m_bRecurse = (tc::ttoi(param.c_str()) != 0);
309                         }
310                         else
311                                 m_bRecurse = true;
312                 }
313                 else if (param == _T("r-"))
314                 {
315                         // -r to compare non-recursively
316                         m_bRecurse = false;
317                 }
318                 else if (param == _T("s-"))
319                 {
320                         // -s- to not allow only one instance
321                         m_nSingleInstance = 0;
322                 }
323                 else if (param == _T("sw"))
324                 {
325                         // -sw to allow only one instance and wait for the instance to terminate
326                         m_nSingleInstance = 2;
327                 }
328                 else if (param == _T("s"))
329                 {
330                         // -s to allow only one instance
331                         if (*q == ':')
332                         {
333                                 q = EatParam(q + 1, param);
334                                 m_nSingleInstance = tc::ttoi(param.c_str());
335                         }
336                                 
337                         else
338                                 m_nSingleInstance = 1;
339                 }
340                 else if (param == _T("noninteractive"))
341                 {
342                         // -noninteractive to suppress message boxes & close with result code
343                         m_bNonInteractive = true;
344                 }
345                 else if (param == _T("noprefs"))
346                 {
347                         // -noprefs means do not load or remember options (preferences)
348                         m_bNoPrefs = true;
349                 }
350                 else if (param == _T("self-compare"))
351                 {
352                         // -self-compare means compare a specified file with a copy of the file
353                         m_bSelfCompare = true;
354                 }
355                 else if (param == _T("clipboard-compare"))
356                 {
357                         // -clipboard-compare means to compare the two most recent contents of the clipboard history.
358                         m_bClipboardCompare = true;
359                 }
360                 else if (param == _T("new"))
361                 {
362                         // -new means to display a new blank window
363                         m_bNewCompare = true;
364                 }
365                 else if (param == _T("enableexitcode"))
366                 {
367                         m_bEnableExitCode = true;
368                 }
369                 else if (param == _T("minimize"))
370                 {
371                         // -minimize means minimize the main window.
372                         m_nCmdShow = MINIMIZE;
373                 }
374                 else if (param == _T("maximize"))
375                 {
376                         // -maximize means maximize the main window.
377                         m_nCmdShow = MAXIMIZE;
378                 }
379                 else if (param == _T("unpacker"))
380                 {
381                         // Get unpacker if specified (otherwise unpacker will be blank, which is default)
382                         q = EatParam(q, m_sUnpacker);
383                 }
384                 else if (param == _T("prediffer"))
385                 {
386                         // Get prediffer if specified (otherwise prediffer will be blank, which is default)
387                         q = EatParam(q, m_sPreDiffer);
388                 }
389                 else if (param == _T("wl"))
390                 {
391                         // -wl to open left path as read-only
392                         m_dwLeftFlags |= FFILEOPEN_READONLY;
393                 }
394                 else if (param == _T("wm"))
395                 {
396                         // -wm to open middle path as read-only
397                         m_dwMiddleFlags |= FFILEOPEN_READONLY;
398                 }
399                 else if (param == _T("wr"))
400                 {
401                         // -wr to open right path as read-only
402                         m_dwRightFlags |= FFILEOPEN_READONLY;
403                 }
404                 else if (param == _T("ul"))
405                 {
406                         // -ul to not add left path to MRU
407                         m_dwLeftFlags |= FFILEOPEN_NOMRU;
408                 }
409                 else if (param == _T("um"))
410                 {
411                         // -um to not add middle path to MRU
412                         m_dwMiddleFlags |= FFILEOPEN_NOMRU;
413                 }
414                 else if (param == _T("ur"))
415                 {
416                         // -ur to not add right path to MRU
417                         m_dwRightFlags |= FFILEOPEN_NOMRU;
418                 }
419                 else if (param == _T("u") || param == _T("ub"))
420                 {
421                         // -u or -ub (deprecated) to add neither right nor left path to MRU
422                         m_dwLeftFlags |= FFILEOPEN_NOMRU;
423                         m_dwMiddleFlags |= FFILEOPEN_NOMRU;
424                         m_dwRightFlags |= FFILEOPEN_NOMRU;
425                 }
426                 else if (param == _T("fl"))
427                 {
428                         // -fl to set focus to the left panbe
429                         m_dwLeftFlags |= FFILEOPEN_SETFOCUS;
430                 }
431                 else if (param == _T("fm"))
432                 {
433                         // -fm to set focus to the middle pane
434                         m_dwMiddleFlags |= FFILEOPEN_SETFOCUS;
435                 }
436                 else if (param == _T("fr"))
437                 {
438                         // -fr to set focus to the right pane
439                         m_dwRightFlags |= FFILEOPEN_SETFOCUS;
440                 }
441                 else if (param == _T("l"))
442                 {
443                         // -l to set the destination line nubmer
444                         String line;
445                         q = EatParam(q, line);
446                         m_nLineIndex = tc::ttoi(line.c_str());
447                         if (m_nLineIndex <= 0)
448                         {
449                                 m_nLineIndex = -1;
450                                 m_sErrorMessages.push_back(_T("Invalid line number specified"));
451                         }
452                         else
453                         {
454                                 m_nLineIndex--;
455                         }
456                 }
457                 else if (param == _T("c"))
458                 {
459                         // -c to set the destination character position 
460                         String charpos;
461                         q = EatParam(q, charpos);
462                         m_nCharIndex = tc::ttoi(charpos.c_str());
463                         if (m_nCharIndex <= 0)
464                         {
465                                 m_nCharIndex = -1;
466                                 m_sErrorMessages.push_back(_T("Invalid character position specified"));
467                         }
468                         else
469                         {
470                                 m_nCharIndex--;
471                         }
472                 }
473                 else if (param == _T("table-delimiter"))
474                 {
475                         String value;
476                         q = EatParam(q, value);
477                         m_cTableDelimiter = strutils::from_charstr(value);
478                 }
479                 else if (param == _T("table-quote"))
480                 {
481                         String value;
482                         q = EatParam(q, value);
483                         m_cTableQuote = strutils::from_charstr(value);
484                 }
485                 else if (param == _T("table-allownewlinesinquotes"))
486                 {
487                         String value;
488                         q = EatParam(q, value);
489                         tchar_t c = strutils::makelower(value).c_str()[0];
490                         m_bTableAllowNewlinesInQuotes = (c == 0 || c == 'y' || c == 't' || c == '1');
491                 }
492                 else if (param == _T("al"))
493                 {
494                         // -al to auto-merge at the left pane
495                         m_dwLeftFlags |= FFILEOPEN_AUTOMERGE;
496                 }
497                 else if (param == _T("am"))
498                 {
499                         // -am to auto-merge at the middle pane
500                         m_dwMiddleFlags |= FFILEOPEN_AUTOMERGE;
501                 }
502                 else if (param == _T("ar"))
503                 {
504                         // -ar to auto-merge at the right pane
505                         m_dwRightFlags |= FFILEOPEN_AUTOMERGE;
506                 }
507                 else if (param == _T("x"))
508                 {
509                         // -x to close application if files are identical.
510                         m_bExitIfNoDiff = Exit;
511                 }
512                 else if (param == _T("xq"))
513                 {
514                         // -xn to close application if files are identical without showing
515                         // any messages
516                         m_bExitIfNoDiff = ExitQuiet;
517                 }
518                 else if (param == _T("cp"))
519                 {
520                         String codepage;
521                         q = EatParam(q, codepage);
522                         m_nCodepage = atoi(ucr::toUTF8(codepage).c_str());
523                 }
524                 else if (param == _T("fileext"))
525                 {
526                         q = EatParam(q, m_sFileExt);
527                         if (!m_sFileExt.empty() && m_sFileExt[0] != '.')
528                                 m_sFileExt = _T(".") + m_sFileExt;
529                 }
530                 else if (param == _T("ignorews"))
531                 {
532                         q = SetOption(q, OPT_CMP_IGNORE_WHITESPACE);
533                 }
534                 else if (param == _T("ignoreblanklines"))
535                 {
536                         q = SetOption(q, OPT_CMP_IGNORE_BLANKLINES);
537                 }
538                 else if (param == _T("ignorecase"))
539                 {
540                         q = SetOption(q, OPT_CMP_IGNORE_CASE);
541                 }
542                 else if (param == _T("ignoreeol"))
543                 {
544                         q = SetOption(q, OPT_CMP_IGNORE_EOL);
545                 }
546                 else if (param == _T("ignorecodepage"))
547                 {
548                         q = SetOption(q, OPT_CMP_IGNORE_CODEPAGE);
549                 }
550                 else if (param == _T("ignorecomments"))
551                 {
552                         q = SetOption(q, OPT_CMP_FILTER_COMMENTLINES);
553                 }
554                 else if (param == _T("cfg") || param == _T("config"))
555                 {
556                         q = SetConfig(q);
557                 }
558                 else if (param == _T("inifile"))
559                 {
560                         q = EatParam(q, m_sIniFilepath);
561                 }
562                 else
563                 {
564                         m_sErrorMessages.emplace_back(_T("Unknown option '/") + param + _T("'"));
565                 }
566         }
567         // If "compare file dir" make it "compare file dir\file".
568         if (m_Files.GetSize() >= 2)
569         {
570                 paths::PATH_EXISTENCE p1 = paths::DoesPathExist(m_Files[0]);
571                 paths::PATH_EXISTENCE p2 = paths::DoesPathExist(m_Files[1]);
572
573                 if ((p1 == paths::IS_EXISTING_FILE) && (p2 == paths::IS_EXISTING_DIR))
574                 {
575                         m_Files[1] = paths::ConcatPath(m_Files[1], paths::FindFileName(m_Files[0]));
576                 }
577         }
578         if (m_bShowUsage)
579         {
580                 m_bNonInteractive = false;
581         }
582 }