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 /////////////////////////////////////////////////////////////////////////////
7 * @file MergeCmdLineInfo.cpp
9 * @brief MergeCmdLineInfo class implementation.
15 #include "MergeCmdLineInfo.h"
17 #include "OptionsDef.h"
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.
29 const tchar_t *MergeCmdLineInfo::EatParam(const tchar_t *p, String ¶m, bool *flag /*= nullptr*/)
31 if (p != nullptr && *(p += tc::tcsspn(p, _T(" \t\r\n"))) == _T('\0'))
43 } while (c != _T('\0') && (quoted ||
44 c != _T(' ') && c != _T('\t') && c != _T('\r') && c != _T('\n')));
48 if (*p == _T('-') || *p == _T('/'))
52 for (const tchar_t *i = q; i >= p; --i)
65 param.assign(p ? p : _T(""), q - p);
66 if (q > p && flag != nullptr)
68 param = strutils::makelower(param);
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);
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.
83 const tchar_t *MergeCmdLineInfo::SetOption(const tchar_t *q, const String& key, const tchar_t *value)
91 value = s.c_str() + 1;
93 m_Options.insert_or_assign(key, value);
97 const tchar_t *MergeCmdLineInfo::SetConfig(const tchar_t *q)
103 size_t pos = s.find_first_of('=');
104 if (pos != String::npos)
106 String key = s.substr(0, pos);
107 String value = s.c_str() + pos + 1;
108 m_Options.insert_or_assign(key, value);
114 * @brief MergeCmdLineParser's constructor.
115 * @param [in] q Points to the beginning of the command line.
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)
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)
137 , m_bEnableExitCode(false)
140 q = EatParam(q, exeName);
141 ParseWinMergeCmdLine(q);
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.
150 void MergeCmdLineInfo::AddPath(const String &path)
154 // Set flag indicating path is from command line
155 const size_t ord = m_Files.GetSize();
157 m_dwLeftFlags |= FFILEOPEN_CMDLINE;
159 m_dwRightFlags |= FFILEOPEN_CMDLINE;
161 m_dwMiddleFlags |= FFILEOPEN_CMDLINE;
163 if (!paths::IsURLorCLSID(path))
165 // Convert paths given in Linux-style ('/' as separator) given from
166 // Cygwin to Windows style ('\' as separator)
167 strutils::replace(param, _T("/"), _T("\\"));
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);
177 m_Files.SetPath(m_Files.GetSize(), param, false);
182 * @brief Parse native WinMerge command line.
183 * @param [in] p Points into the command line.
185 void MergeCmdLineInfo::ParseWinMergeCmdLine(const tchar_t *q)
190 while ((q = EatParam(q, param, &flag)) != 0)
194 // Its not a flag so it is a path
197 else if (param == _T("?"))
199 // -? to show common command line arguments.
202 else if (param == _T("o"))
204 // -o "outputfilename"
205 q = EatParam(q, m_sOutputpath);
207 else if (param == _T("or"))
209 // -or "reportfilename"
210 q = EatParam(q, m_sReportFile);
212 else if (param == _T("dl"))
214 // -dl "desc" - description for left file
215 q = EatParam(q, m_sLeftDesc);
217 else if (param == _T("dm"))
219 // -dr "desc" - description for middle file
220 q = EatParam(q, m_sMiddleDesc);
222 else if (param == _T("dr"))
224 // -dr "desc" - description for right file
225 q = EatParam(q, m_sRightDesc);
227 else if (param == _T("e"))
229 // -e to allow closing with single esc press
230 m_bEscShutdown = true;
232 else if (param == _T("f"))
234 // -f "mask" - file filter mask ("*.h *.cpp")
235 q = EatParam(q, m_sFileFilter);
237 else if (param == _T("t"))
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;
255 m_sErrorMessages.emplace_back(_T("Unknown window type '") + param + _T("' specified"));
257 else if (param == _T("show-dialog"))
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;
267 m_sErrorMessages.emplace_back(_T("Unknown dialog type '") + param + _T("' specified"));
269 else if (param == _T("show-compare-as-menu"))
271 m_bShowCompareAsMenu = true;
273 else if (param == _T("set-usertasks-to-jumplist"))
275 q = EatParam(q, param);
276 m_dwUserTasksFlags = tc::ttoi(param.c_str());
278 else if (param == _T("m"))
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;
300 m_sErrorMessages.emplace_back(_T("Unknown compare method '") + param + _T("' specified"));
302 else if (param == _T("r"))
304 // -r to compare recursively
307 q = EatParam(q + 1, param);
308 m_bRecurse = (tc::ttoi(param.c_str()) != 0);
313 else if (param == _T("r-"))
315 // -r to compare non-recursively
318 else if (param == _T("s-"))
320 // -s- to not allow only one instance
321 m_nSingleInstance = 0;
323 else if (param == _T("sw"))
325 // -sw to allow only one instance and wait for the instance to terminate
326 m_nSingleInstance = 2;
328 else if (param == _T("s"))
330 // -s to allow only one instance
333 q = EatParam(q + 1, param);
334 m_nSingleInstance = tc::ttoi(param.c_str());
338 m_nSingleInstance = 1;
340 else if (param == _T("noninteractive"))
342 // -noninteractive to suppress message boxes & close with result code
343 m_bNonInteractive = true;
345 else if (param == _T("noprefs"))
347 // -noprefs means do not load or remember options (preferences)
350 else if (param == _T("self-compare"))
352 // -self-compare means compare a specified file with a copy of the file
353 m_bSelfCompare = true;
355 else if (param == _T("clipboard-compare"))
357 // -clipboard-compare means to compare the two most recent contents of the clipboard history.
358 m_bClipboardCompare = true;
360 else if (param == _T("new"))
362 // -new means to display a new blank window
363 m_bNewCompare = true;
365 else if (param == _T("enableexitcode"))
367 m_bEnableExitCode = true;
369 else if (param == _T("minimize"))
371 // -minimize means minimize the main window.
372 m_nCmdShow = MINIMIZE;
374 else if (param == _T("maximize"))
376 // -maximize means maximize the main window.
377 m_nCmdShow = MAXIMIZE;
379 else if (param == _T("unpacker"))
381 // Get unpacker if specified (otherwise unpacker will be blank, which is default)
382 q = EatParam(q, m_sUnpacker);
384 else if (param == _T("prediffer"))
386 // Get prediffer if specified (otherwise prediffer will be blank, which is default)
387 q = EatParam(q, m_sPreDiffer);
389 else if (param == _T("wl"))
391 // -wl to open left path as read-only
392 m_dwLeftFlags |= FFILEOPEN_READONLY;
394 else if (param == _T("wm"))
396 // -wm to open middle path as read-only
397 m_dwMiddleFlags |= FFILEOPEN_READONLY;
399 else if (param == _T("wr"))
401 // -wr to open right path as read-only
402 m_dwRightFlags |= FFILEOPEN_READONLY;
404 else if (param == _T("ul"))
406 // -ul to not add left path to MRU
407 m_dwLeftFlags |= FFILEOPEN_NOMRU;
409 else if (param == _T("um"))
411 // -um to not add middle path to MRU
412 m_dwMiddleFlags |= FFILEOPEN_NOMRU;
414 else if (param == _T("ur"))
416 // -ur to not add right path to MRU
417 m_dwRightFlags |= FFILEOPEN_NOMRU;
419 else if (param == _T("u") || param == _T("ub"))
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;
426 else if (param == _T("fl"))
428 // -fl to set focus to the left panbe
429 m_dwLeftFlags |= FFILEOPEN_SETFOCUS;
431 else if (param == _T("fm"))
433 // -fm to set focus to the middle pane
434 m_dwMiddleFlags |= FFILEOPEN_SETFOCUS;
436 else if (param == _T("fr"))
438 // -fr to set focus to the right pane
439 m_dwRightFlags |= FFILEOPEN_SETFOCUS;
441 else if (param == _T("l"))
443 // -l to set the destination line nubmer
445 q = EatParam(q, line);
446 m_nLineIndex = tc::ttoi(line.c_str());
447 if (m_nLineIndex <= 0)
450 m_sErrorMessages.push_back(_T("Invalid line number specified"));
457 else if (param == _T("c"))
459 // -c to set the destination character position
461 q = EatParam(q, charpos);
462 m_nCharIndex = tc::ttoi(charpos.c_str());
463 if (m_nCharIndex <= 0)
466 m_sErrorMessages.push_back(_T("Invalid character position specified"));
473 else if (param == _T("table-delimiter"))
476 q = EatParam(q, value);
477 m_cTableDelimiter = strutils::from_charstr(value);
479 else if (param == _T("table-quote"))
482 q = EatParam(q, value);
483 m_cTableQuote = strutils::from_charstr(value);
485 else if (param == _T("table-allownewlinesinquotes"))
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');
492 else if (param == _T("al"))
494 // -al to auto-merge at the left pane
495 m_dwLeftFlags |= FFILEOPEN_AUTOMERGE;
497 else if (param == _T("am"))
499 // -am to auto-merge at the middle pane
500 m_dwMiddleFlags |= FFILEOPEN_AUTOMERGE;
502 else if (param == _T("ar"))
504 // -ar to auto-merge at the right pane
505 m_dwRightFlags |= FFILEOPEN_AUTOMERGE;
507 else if (param == _T("x"))
509 // -x to close application if files are identical.
510 m_bExitIfNoDiff = Exit;
512 else if (param == _T("xq"))
514 // -xn to close application if files are identical without showing
516 m_bExitIfNoDiff = ExitQuiet;
518 else if (param == _T("cp"))
521 q = EatParam(q, codepage);
522 m_nCodepage = atoi(ucr::toUTF8(codepage).c_str());
524 else if (param == _T("fileext"))
526 q = EatParam(q, m_sFileExt);
527 if (!m_sFileExt.empty() && m_sFileExt[0] != '.')
528 m_sFileExt = _T(".") + m_sFileExt;
530 else if (param == _T("ignorews"))
532 q = SetOption(q, OPT_CMP_IGNORE_WHITESPACE);
534 else if (param == _T("ignoreblanklines"))
536 q = SetOption(q, OPT_CMP_IGNORE_BLANKLINES);
538 else if (param == _T("ignorecase"))
540 q = SetOption(q, OPT_CMP_IGNORE_CASE);
542 else if (param == _T("ignoreeol"))
544 q = SetOption(q, OPT_CMP_IGNORE_EOL);
546 else if (param == _T("ignorecodepage"))
548 q = SetOption(q, OPT_CMP_IGNORE_CODEPAGE);
550 else if (param == _T("ignorecomments"))
552 q = SetOption(q, OPT_CMP_FILTER_COMMENTLINES);
554 else if (param == _T("cfg") || param == _T("config"))
558 else if (param == _T("inifile"))
560 q = EatParam(q, m_sIniFilepath);
564 m_sErrorMessages.emplace_back(_T("Unknown option '/") + param + _T("'"));
567 // If "compare file dir" make it "compare file dir\file".
568 if (m_Files.GetSize() >= 2)
570 paths::PATH_EXISTENCE p1 = paths::DoesPathExist(m_Files[0]);
571 paths::PATH_EXISTENCE p2 = paths::DoesPathExist(m_Files[1]);
573 if ((p1 == paths::IS_EXISTING_FILE) && (p2 == paths::IS_EXISTING_DIR))
575 m_Files[1] = paths::ConcatPath(m_Files[1], paths::FindFileName(m_Files[0]));
580 m_bNonInteractive = false;