Skip to content

Commit

Permalink
Use CLI11 to parse command line options
Browse files Browse the repository at this point in the history
This has a few advantages over the previous manual approach:

- The option parsing is handled by the library, so it doesn't need to be
  done manually.
- More detailed help usage is now shown when passing the -h/--help flag.
  Information about each of the available options is shown.
- Options can be marked as mutually exclusive. This is done with the
  --remove_as_default and --set_as_default flags, for example.
- Options can be passed in in windows style (e.g. /help).
- Although not used at the moment, the library also offers other
  features, such as the ability to validate and transform input values.
  • Loading branch information
derceg committed May 5, 2019
1 parent f36160f commit b69ef8a
Show file tree
Hide file tree
Showing 13 changed files with 5,057 additions and 238 deletions.
4 changes: 4 additions & 0 deletions Documentation/User/History.txt
Expand Up @@ -88,6 +88,10 @@ Misc:
- Builds are now digitally signed.
- All menus are now drawn in the standard system style, rather
than a custom style.
- More detailed help information is now available when starting
Explorer++ from the command line with the --help flag. Each of
the available options is listed and briefly described. The
names of some of the options have also been updated.
- The format used to display dates has been updated. Previously,
the date and time were separated by a comma. Now, they're
separated by a space.
Expand Down
226 changes: 226 additions & 0 deletions Explorer++/Explorer++/CommandLine.cpp
@@ -0,0 +1,226 @@
// Copyright (C) Explorer++ Project
// SPDX-License-Identifier: GPL-3.0-only
// See LICENSE in the top level directory

#include "stdafx.h"
#include "CommandLine.h"
#include "Explorer++_internal.h"
#include "MainResource.h"
#include "../Helper/Macros.h"
#include "../Helper/ProcessHelper.h"
#include "../Helper/SetDefaultFileManager.h"
#include "../Helper/ShellHelper.h"
#include "../Helper/StringHelper.h"
#include "../ThirdParty/CLI11/CLI11.hpp"
#include <boost/filesystem.hpp>
#include <boost/log/core.hpp>

extern std::vector<std::wstring> g_TabDirs;

struct CommandLineSettings
{
bool clearRegistrySettings;
bool enableLogging;
bool enablePlugins;
bool removeAsDefault;
bool setAsDefault;
std::string language;
bool jumplistNewTab;
std::vector<std::string> directories;
};

boost::optional<CommandLine::ExitInfo> ProcessCommandLineSettings(const CommandLineSettings& commandLineSettings);
void OnClearRegistrySettings();
void OnRemoveAsDefault();
void OnSetAsDefault();
void OnJumplistNewTab();

boost::optional<CommandLine::ExitInfo> CommandLine::ProcessCommandLine()
{
CLI::App app("Explorer++");

CommandLineSettings commandLineSettings;

commandLineSettings.clearRegistrySettings = false;
app.add_flag("--clear-registry-settings", commandLineSettings.clearRegistrySettings, "Clear existing registry settings");

commandLineSettings.enableLogging = false;
app.add_flag("--enable-logging", commandLineSettings.enableLogging, "Enable logging");

commandLineSettings.enablePlugins = false;
app.add_flag("--enable-plugins", commandLineSettings.enablePlugins, "Enable the Lua plugin system");

commandLineSettings.removeAsDefault = false;
auto removeAsDefaultOption = app.add_flag("--remove-as-default", commandLineSettings.removeAsDefault, "Remove Explorer++ as the default file manager (requires administrator privileges)");

commandLineSettings.setAsDefault = false;
auto setAsDefaultOption = app.add_flag("--set-as-default", commandLineSettings.setAsDefault, "Set Explorer++ as the default file manager (requires administrator privileges)");

removeAsDefaultOption->excludes(setAsDefaultOption);
setAsDefaultOption->excludes(removeAsDefaultOption);

app.add_option("--language", commandLineSettings.language, "Allows you to select your desired language. Should be a two-letter language code (e.g. FR, RU, etc).");

app.add_option("directories", commandLineSettings.directories, "Directories to open");

// The options in this group are only used internally by the
// application. They're not directly exposed to users.
CLI::App *privateCommands = app.add_subcommand("private");
privateCommands->group("");

privateCommands->add_flag(wstrToStr(NExplorerplusplus::JUMPLIST_TASK_NEWTAB_ARGUMENT), commandLineSettings.jumplistNewTab);

try
{
app.parse(__argc, __argv);
}
catch (const CLI::ParseError & e)
{
return ExitInfo{ app.exit(e) };
}

return ProcessCommandLineSettings(commandLineSettings);
}

boost::optional<CommandLine::ExitInfo> ProcessCommandLineSettings(const CommandLineSettings& commandLineSettings)
{
if (commandLineSettings.jumplistNewTab)
{
OnJumplistNewTab();
return CommandLine::ExitInfo{ EXIT_SUCCESS };
}

if (commandLineSettings.clearRegistrySettings)
{
OnClearRegistrySettings();
}

if (commandLineSettings.enableLogging)
{
boost::log::core::get()->set_logging_enabled(true);
}

if (commandLineSettings.enablePlugins)
{
g_enablePlugins = true;
}

if (commandLineSettings.removeAsDefault)
{
OnRemoveAsDefault();
}
else if (commandLineSettings.setAsDefault)
{
OnSetAsDefault();
}

if (!commandLineSettings.language.empty())
{
g_bForceLanguageLoad = TRUE;

StringCchCopy(g_szLang, SIZEOF_ARRAY(g_szLang), strToWstr(commandLineSettings.language).c_str());
}

TCHAR processImageName[MAX_PATH];
GetProcessImageName(GetCurrentProcessId(), processImageName, SIZEOF_ARRAY(processImageName));

boost::filesystem::path processDirectoryPath(processImageName);
processDirectoryPath.remove_filename();

for (const std::string& directory : commandLineSettings.directories)
{
TCHAR szParsingPath[MAX_PATH];
DecodePath(strToWstr(directory).c_str(), processDirectoryPath.wstring().c_str(), szParsingPath, SIZEOF_ARRAY(szParsingPath));

g_TabDirs.push_back(szParsingPath);
}

return boost::none;
}

void OnClearRegistrySettings()
{
LSTATUS lStatus;

lStatus = SHDeleteKey(HKEY_CURRENT_USER, NExplorerplusplus::REG_MAIN_KEY);

if (lStatus == ERROR_SUCCESS)
MessageBox(NULL, _T("Settings cleared successfully."), NExplorerplusplus::APP_NAME, MB_OK);
else
MessageBox(NULL, _T("Settings could not be cleared."), NExplorerplusplus::APP_NAME, MB_ICONWARNING);
}

void OnRemoveAsDefault()
{
BOOL bSuccess = NDefaultFileManager::RemoveAsDefaultFileManagerFileSystem(SHELL_DEFAULT_INTERNAL_COMMAND_NAME);

/* Language hasn't been fully specified at this point, so
can't load success/error message from language dll. Simply show
a hardcoded success/error message. */
if (bSuccess)
{
MessageBox(NULL, _T("Explorer++ successfully removed as default file manager."),
NExplorerplusplus::APP_NAME, MB_OK);
}
else
{
MessageBox(NULL, _T("Could not remove Explorer++ as default file manager. Please \
ensure you have administrator privileges."), NExplorerplusplus::APP_NAME, MB_ICONWARNING | MB_OK);
}
}

void OnSetAsDefault()
{
TCHAR menuText[256];
LoadString(GetModuleHandle(0), IDS_OPEN_IN_EXPLORERPLUSPLUS,
menuText, SIZEOF_ARRAY(menuText));

BOOL bSuccess = NDefaultFileManager::SetAsDefaultFileManagerFileSystem(
SHELL_DEFAULT_INTERNAL_COMMAND_NAME, menuText);

if (bSuccess)
{
MessageBox(NULL, _T("Explorer++ successfully set as default file manager."),
NExplorerplusplus::APP_NAME, MB_OK);
}
else
{
MessageBox(NULL, _T("Could not set Explorer++ as default file manager. Please \
ensure you have administrator privileges."), NExplorerplusplus::APP_NAME, MB_ICONWARNING | MB_OK);
}
}

void OnJumplistNewTab()
{
/* This will be called when the user clicks the
'New Tab' item on the tasks menu in Windows 7 and above.
Find the already opened version of Explorer++,
and tell it to open a new tab. */
HANDLE hMutex;

hMutex = CreateMutex(NULL, TRUE, _T("Explorer++"));

if (GetLastError() == ERROR_ALREADY_EXISTS)
{
HWND hPrev;

hPrev = FindWindow(NExplorerplusplus::CLASS_NAME, NULL);

if (hPrev != NULL)
{
COPYDATASTRUCT cds;

cds.cbData = 0;
cds.lpData = NULL;
SendMessage(hPrev, WM_COPYDATA, NULL, (LPARAM)& cds);

SetForegroundWindow(hPrev);
ShowWindow(hPrev, SW_SHOW);
}
}

if (hMutex != NULL)
{
CloseHandle(hMutex);
}
}
17 changes: 17 additions & 0 deletions Explorer++/Explorer++/CommandLine.h
@@ -0,0 +1,17 @@
// Copyright (C) Explorer++ Project
// SPDX-License-Identifier: GPL-3.0-only
// See LICENSE in the top level directory

#pragma once

#include <boost/optional.hpp>

namespace CommandLine
{
struct ExitInfo
{
int exitCode;
};

boost::optional<ExitInfo> ProcessCommandLine();
}
104 changes: 104 additions & 0 deletions Explorer++/Explorer++/Console.cpp
@@ -0,0 +1,104 @@
// Copyright (C) Explorer++ Project
// SPDX-License-Identifier: GPL-3.0-only
// See LICENSE in the top level directory

#include "stdafx.h"
#include "Console.h"
#include <iostream>

bool RedirectConsoleIO();

// The code in this file has been sourced from the following Stack Overflow
// answer:
// https://stackoverflow.com/a/55875595
// It doesn't make much sense to rewrite it, given that it's relatively simple
// and performs a specific function (so any rewritten code would look about the
// same).

bool Console::AttachParentConsole()
{
bool result = false;

// Release any current console and redirect IO to NUL
ReleaseConsole();

// Attempt to attach to parent process's console
if (AttachConsole(ATTACH_PARENT_PROCESS))
{
result = RedirectConsoleIO();
}

return result;
}

bool Console::ReleaseConsole()
{
bool result = true;
FILE* fp;

// Just to be safe, redirect standard IO to NUL before releasing.

// Redirect STDIN to NUL
if (freopen_s(&fp, "NUL:", "r", stdin) != 0)
result = false;
else
setvbuf(stdin, NULL, _IONBF, 0);

// Redirect STDOUT to NUL
if (freopen_s(&fp, "NUL:", "w", stdout) != 0)
result = false;
else
setvbuf(stdout, NULL, _IONBF, 0);

// Redirect STDERR to NUL
if (freopen_s(&fp, "NUL:", "w", stderr) != 0)
result = false;
else
setvbuf(stderr, NULL, _IONBF, 0);

// Detach from console
if (!FreeConsole() || !result)
return false;

return true;
}

bool RedirectConsoleIO()
{
bool result = true;
FILE* fp;

// Redirect STDIN if the console has an input handle
if (GetStdHandle(STD_INPUT_HANDLE) != INVALID_HANDLE_VALUE)
if (freopen_s(&fp, "CONIN$", "r", stdin) != 0)
result = false;
else
setvbuf(stdin, NULL, _IONBF, 0);

// Redirect STDOUT if the console has an output handle
if (GetStdHandle(STD_OUTPUT_HANDLE) != INVALID_HANDLE_VALUE)
if (freopen_s(&fp, "CONOUT$", "w", stdout) != 0)
result = false;
else
setvbuf(stdout, NULL, _IONBF, 0);

// Redirect STDERR if the console has an error handle
if (GetStdHandle(STD_ERROR_HANDLE) != INVALID_HANDLE_VALUE)
if (freopen_s(&fp, "CONOUT$", "w", stderr) != 0)
result = false;
else
setvbuf(stderr, NULL, _IONBF, 0);

// Make C++ standard streams point to console as well.
std::ios::sync_with_stdio(true);

// Clear the error state for each of the C++ standard streams.
std::wcout.clear();
std::cout.clear();
std::wcerr.clear();
std::cerr.clear();
std::wcin.clear();
std::cin.clear();

return result;
}
11 changes: 11 additions & 0 deletions Explorer++/Explorer++/Console.h
@@ -0,0 +1,11 @@
// Copyright (C) Explorer++ Project
// SPDX-License-Identifier: GPL-3.0-only
// See LICENSE in the top level directory

#pragma once

namespace Console
{
bool AttachParentConsole();
bool ReleaseConsole();
}
4 changes: 4 additions & 0 deletions Explorer++/Explorer++/Explorer++.vcxproj
Expand Up @@ -319,6 +319,8 @@
<ClCompile Include="ColorRuleDialog.cpp" />
<ClCompile Include="ColorRuleHelper.cpp" />
<ClCompile Include="CommandInvoked.cpp" />
<ClCompile Include="CommandLine.cpp" />
<ClCompile Include="Console.cpp" />
<ClCompile Include="CustomizeColorsDialog.cpp" />
<ClCompile Include="DestroyFilesDialog.cpp" />
<ClCompile Include="DialogHelper.cpp" />
Expand Down Expand Up @@ -449,6 +451,8 @@
<ClInclude Include="ColorRuleDialog.h" />
<ClInclude Include="ColorRuleHelper.h" />
<ClInclude Include="CommandInvoked.h" />
<ClInclude Include="CommandLine.h" />
<ClInclude Include="Console.h" />
<ClInclude Include="CoreInterface.h" />
<ClInclude Include="CustomizeColorsDialog.h" />
<ClInclude Include="DefaultColumns.h" />
Expand Down

0 comments on commit b69ef8a

Please sign in to comment.