From e044e8b661ba671e229503139dd4198268b15c02 Mon Sep 17 00:00:00 2001 From: Ruben Guerrero Samaniego Date: Tue, 16 Jun 2020 16:47:21 -0700 Subject: [PATCH 01/13] Implement winget settings feature and add two settings: source auto update time and progress bar visual style. TODO: Localize strings, add settings readme and implement tests --- cgmanifest.json | 9 + src/AppInstallerCLI.sln | 20 + src/AppInstallerCLI/AppInstallerCLI.vcxproj | 3 + .../AppInstallerCLICore.vcxproj | 18 +- .../AppInstallerCLICore.vcxproj.filters | 6 + src/AppInstallerCLICore/Argument.cpp | 6 +- .../Commands/RootCommand.cpp | 2 + .../Commands/SettingsCommand.cpp | 65 + .../Commands/SettingsCommand.h | 22 + src/AppInstallerCLICore/Core.cpp | 8 + src/AppInstallerCLICore/ExecutionArgs.h | 2 +- src/AppInstallerCLICore/ExecutionContext.cpp | 10 +- src/AppInstallerCLICore/ExecutionProgress.cpp | 2 +- src/AppInstallerCLICore/ExecutionProgress.h | 11 +- .../AppInstallerCLITests.vcxproj | 11 +- .../AppInstallerCommonCore.vcxproj | 27 +- .../AppInstallerCommonCore.vcxproj.filters | 39 + .../AppInstallerStrings.cpp | 5 + src/AppInstallerCommonCore/JsonUtil.cpp | 50 + .../Public/AppInstallerStrings.h | 3 + .../Public/AppInstallerUserSettings.h | 65 + .../Public/winget/JsonUtil.h | 15 + .../Public/winget/settings/Source.h | 35 + .../Public/winget/settings/Visual.h | 39 + .../UserSettings/SettingValidation.h | 19 + .../UserSettings/SettingsValidation.cpp | 36 + .../UserSettings/Source.cpp | 57 + .../UserSettings/UserSettings.cpp | 133 + .../UserSettings/Visual.cpp | 74 + .../AppInstallerRepositoryCore.vcxproj | 16 +- .../RepositorySource.cpp | 21 +- src/JsonCppLib/JsonCppLib.sln | 31 + src/JsonCppLib/JsonCppLib.vcxproj | 219 + src/JsonCppLib/JsonCppLib.vcxproj.filters | 30 + src/JsonCppLib/json/json-forwards.h | 447 ++ src/JsonCppLib/json/json.h | 2342 +++++++ src/JsonCppLib/jsoncpp.cpp | 5380 +++++++++++++++++ src/WinGetUtil/WinGetUtil.vcxproj | 11 +- 38 files changed, 9232 insertions(+), 57 deletions(-) create mode 100644 src/AppInstallerCLICore/Commands/SettingsCommand.cpp create mode 100644 src/AppInstallerCLICore/Commands/SettingsCommand.h create mode 100644 src/AppInstallerCommonCore/JsonUtil.cpp create mode 100644 src/AppInstallerCommonCore/Public/AppInstallerUserSettings.h create mode 100644 src/AppInstallerCommonCore/Public/winget/JsonUtil.h create mode 100644 src/AppInstallerCommonCore/Public/winget/settings/Source.h create mode 100644 src/AppInstallerCommonCore/Public/winget/settings/Visual.h create mode 100644 src/AppInstallerCommonCore/UserSettings/SettingValidation.h create mode 100644 src/AppInstallerCommonCore/UserSettings/SettingsValidation.cpp create mode 100644 src/AppInstallerCommonCore/UserSettings/Source.cpp create mode 100644 src/AppInstallerCommonCore/UserSettings/UserSettings.cpp create mode 100644 src/AppInstallerCommonCore/UserSettings/Visual.cpp create mode 100644 src/JsonCppLib/JsonCppLib.sln create mode 100644 src/JsonCppLib/JsonCppLib.vcxproj create mode 100644 src/JsonCppLib/JsonCppLib.vcxproj.filters create mode 100644 src/JsonCppLib/json/json-forwards.h create mode 100644 src/JsonCppLib/json/json.h create mode 100644 src/JsonCppLib/jsoncpp.cpp diff --git a/cgmanifest.json b/cgmanifest.json index eaa905da71..e1577629f7 100644 --- a/cgmanifest.json +++ b/cgmanifest.json @@ -17,6 +17,15 @@ "commitHash": "9a3624205e8774953ef18f57067b3426c1c5ada6" } } + }, + { + "component": { + "type": "git", + "git": { + "repositoryUrl": "https://github.com/open-source-parsers/jsoncpp.git", + "commitHash": "9be589598595963f94ba264d7b416d0533421106" + } + } } ], "Version": 1 diff --git a/src/AppInstallerCLI.sln b/src/AppInstallerCLI.sln index ea16992dc7..22a47b0a95 100644 --- a/src/AppInstallerCLI.sln +++ b/src/AppInstallerCLI.sln @@ -25,6 +25,7 @@ EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AppInstallerCLITests", "AppInstallerCLITests\AppInstallerCLITests.vcxproj", "{89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}" ProjectSection(ProjectDependencies) = postProject {6AA3791A-0713-4548-A357-87A323E7AC3A} = {6AA3791A-0713-4548-A357-87A323E7AC3A} + {82B39FDA-E86B-4713-A873-9D56DE00247A} = {82B39FDA-E86B-4713-A873-9D56DE00247A} EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "YamlCppLib", "YamlCppLib\YamlCppLib.vcxproj", "{8BB94BB8-374F-4294-BCA1-C7811514A6B7}" @@ -47,6 +48,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tool", "Tool", "{EA8CD934-0 EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IndexCreationTool", "IndexCreationTool\IndexCreationTool.csproj", "{3B8466CF-4FDD-4329-9C80-91321C4AAC99}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "JsonCppLib", "JsonCppLib\JsonCppLib.vcxproj", "{82B39FDA-E86B-4713-A873-9D56DE00247A}" +EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution catch2\catch2.vcxitems*{5295e21e-9868-4de2-a177-fbb97b36579b}*SharedItemsImports = 9 @@ -270,6 +273,22 @@ Global {3B8466CF-4FDD-4329-9C80-91321C4AAC99}.Release|x64.Build.0 = Release|x64 {3B8466CF-4FDD-4329-9C80-91321C4AAC99}.Release|x86.ActiveCfg = Release|x86 {3B8466CF-4FDD-4329-9C80-91321C4AAC99}.Release|x86.Build.0 = Release|x86 + {82B39FDA-E86B-4713-A873-9D56DE00247A}.Debug|ARM.ActiveCfg = Debug|ARM + {82B39FDA-E86B-4713-A873-9D56DE00247A}.Debug|ARM.Build.0 = Debug|ARM + {82B39FDA-E86B-4713-A873-9D56DE00247A}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {82B39FDA-E86B-4713-A873-9D56DE00247A}.Debug|ARM64.Build.0 = Debug|ARM64 + {82B39FDA-E86B-4713-A873-9D56DE00247A}.Debug|x64.ActiveCfg = Debug|x64 + {82B39FDA-E86B-4713-A873-9D56DE00247A}.Debug|x64.Build.0 = Debug|x64 + {82B39FDA-E86B-4713-A873-9D56DE00247A}.Debug|x86.ActiveCfg = Debug|Win32 + {82B39FDA-E86B-4713-A873-9D56DE00247A}.Debug|x86.Build.0 = Debug|Win32 + {82B39FDA-E86B-4713-A873-9D56DE00247A}.Release|ARM.ActiveCfg = Release|ARM + {82B39FDA-E86B-4713-A873-9D56DE00247A}.Release|ARM.Build.0 = Release|ARM + {82B39FDA-E86B-4713-A873-9D56DE00247A}.Release|ARM64.ActiveCfg = Release|ARM64 + {82B39FDA-E86B-4713-A873-9D56DE00247A}.Release|ARM64.Build.0 = Release|ARM64 + {82B39FDA-E86B-4713-A873-9D56DE00247A}.Release|x64.ActiveCfg = Release|x64 + {82B39FDA-E86B-4713-A873-9D56DE00247A}.Release|x64.Build.0 = Release|x64 + {82B39FDA-E86B-4713-A873-9D56DE00247A}.Release|x86.ActiveCfg = Release|Win32 + {82B39FDA-E86B-4713-A873-9D56DE00247A}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -279,6 +298,7 @@ Global {8BB94BB8-374F-4294-BCA1-C7811514A6B7} = {60618CAC-2995-4DF9-9914-45C6FC02C995} {6E36DDD7-1602-474E-B1D7-D0A7E1D5AD86} = {8D53D749-D51C-46F8-A162-9371AAA6C2E7} {3B8466CF-4FDD-4329-9C80-91321C4AAC99} = {EA8CD934-0702-4911-A2C5-A40600E616DE} + {82B39FDA-E86B-4713-A873-9D56DE00247A} = {60618CAC-2995-4DF9-9914-45C6FC02C995} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {B6FDB70C-A751-422C-ACD1-E35419495857} diff --git a/src/AppInstallerCLI/AppInstallerCLI.vcxproj b/src/AppInstallerCLI/AppInstallerCLI.vcxproj index db70e78cec..e656d737b0 100644 --- a/src/AppInstallerCLI/AppInstallerCLI.vcxproj +++ b/src/AppInstallerCLI/AppInstallerCLI.vcxproj @@ -208,6 +208,9 @@ {5eb88068-5fb9-4e69-89b2-72dbc5e068f9} + + {82b39fda-e86b-4713-a873-9d56de00247a} + {8bb94bb8-374f-4294-bca1-c7811514a6b7} diff --git a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj index 116a992020..9b6fd840be 100644 --- a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj +++ b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj @@ -122,9 +122,9 @@ Disabled _DEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD - $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories) true true true @@ -139,7 +139,7 @@ WIN32;%(PreprocessorDefinitions);CLICOREDLLBUILD - $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories) true @@ -152,10 +152,10 @@ true true NDEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD - $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories) true true true @@ -181,6 +181,7 @@ + @@ -209,6 +210,7 @@ + diff --git a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters index 0e1ecbb044..e4405dbbbc 100644 --- a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters +++ b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters @@ -102,6 +102,9 @@ Header Files + + Commands + @@ -170,6 +173,9 @@ Source Files + + Commands + diff --git a/src/AppInstallerCLICore/Argument.cpp b/src/AppInstallerCLICore/Argument.cpp index 78448f53ab..494b3fa3a8 100644 --- a/src/AppInstallerCLICore/Argument.cpp +++ b/src/AppInstallerCLICore/Argument.cpp @@ -72,8 +72,8 @@ namespace AppInstaller::CLI return Argument{ "no-vt", None, Args::Type::NoVT, Resource::String::NoVTArgumentDescription, ArgumentType::Flag, Visibility::Hidden }; case Args::Type::RainbowStyle: return Argument{ "rainbow", None, Args::Type::RainbowStyle, Resource::String::RainbowArgumentDescription, ArgumentType::Flag, Visibility::Hidden }; - case Args::Type::PlainStyle: - return Argument{ "plain", None, Args::Type::PlainStyle, Resource::String::PlainArgumentDescription, ArgumentType::Flag, Visibility::Hidden }; + case Args::Type::RetroStyle: + return Argument{ "retro", None, Args::Type::RetroStyle, Resource::String::PlainArgumentDescription, ArgumentType::Flag, Visibility::Hidden }; case Args::Type::Force: return Argument{ "force", None, Args::Type::Force, Resource::String::ForceArgumentDescription, ArgumentType::Flag }; case Args::Type::VerboseLogs: @@ -88,7 +88,7 @@ namespace AppInstaller::CLI args.push_back(ForType(Args::Type::Help)); args.push_back(ForType(Args::Type::NoVT)); args.push_back(ForType(Args::Type::RainbowStyle)); - args.push_back(ForType(Args::Type::PlainStyle)); + args.push_back(ForType(Args::Type::RetroStyle)); args.push_back(ForType(Args::Type::VerboseLogs)); } } diff --git a/src/AppInstallerCLICore/Commands/RootCommand.cpp b/src/AppInstallerCLICore/Commands/RootCommand.cpp index 45b3422f3f..7fdd0ba269 100644 --- a/src/AppInstallerCLICore/Commands/RootCommand.cpp +++ b/src/AppInstallerCLICore/Commands/RootCommand.cpp @@ -9,6 +9,7 @@ #include "SearchCommand.h" #include "HashCommand.h" #include "ValidateCommand.h" +#include "SettingsCommand.h" #include "Resources.h" #include "TableOutput.h" @@ -26,6 +27,7 @@ namespace AppInstaller::CLI std::make_unique(FullName()), std::make_unique(FullName()), std::make_unique(FullName()), + std::make_unique(FullName()), }); } diff --git a/src/AppInstallerCLICore/Commands/SettingsCommand.cpp b/src/AppInstallerCLICore/Commands/SettingsCommand.cpp new file mode 100644 index 0000000000..1160bc3545 --- /dev/null +++ b/src/AppInstallerCLICore/Commands/SettingsCommand.cpp @@ -0,0 +1,65 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "SettingsCommand.h" +#include "Workflows/WorkflowBase.h" +#include "Resources.h" +#include + + +namespace AppInstaller::CLI +{ + using namespace Utility::literals; + using namespace AppInstaller::Settings; + + using namespace std::string_view_literals; + + std::vector SettingsCommand::GetArguments() const + { + return {}; + } + + Resource::LocString SettingsCommand::ShortDescription() const + { + // TODO: Create a string and localize. + return { Resource::String::HashCommandShortDescription }; + } + + Resource::LocString SettingsCommand::LongDescription() const + { + // TODO: Create a string and localize. + return { Resource::String::HashCommandLongDescription }; + } + + std::string SettingsCommand::HelpLink() const + { + // TODO: Create and point to readme + return "https://aka.ms/winget-settings"; + } + + void SettingsCommand::ExecuteInternal(Execution::Context& ) const + { + UserSettingsType userSettingType = UserSettings::Instance().GetType(); + + if (userSettingType == UserSettingsType::Default) + { + // Create settings file if it doesn't exist. + UserSettings::Instance().CreateSettingsFile(); + } + else if (userSettingType == UserSettingsType::Standard) + { + // Settings file was loaded correctly, create backup. + UserSettings::Instance().CreateBackupFile(); + } + + std::string filePathUTF8Str = UserSettings::Instance().SettingsFilePath().u8string(); + + // Kudos to the terminal team for this work around. + HINSTANCE res = ShellExecuteA(nullptr, nullptr, filePathUTF8Str.c_str(), nullptr, nullptr, SW_SHOW); + if (static_cast(reinterpret_cast(res)) <= 32) + { + // User doesn't have file type association. Default to notepad + ShellExecuteA(nullptr, nullptr, "notepad", filePathUTF8Str.c_str(), nullptr, SW_SHOW); + } + } +} \ No newline at end of file diff --git a/src/AppInstallerCLICore/Commands/SettingsCommand.h b/src/AppInstallerCLICore/Commands/SettingsCommand.h new file mode 100644 index 0000000000..a170543d88 --- /dev/null +++ b/src/AppInstallerCLICore/Commands/SettingsCommand.h @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Command.h" + +namespace AppInstaller::CLI +{ + struct SettingsCommand final : public Command + { + SettingsCommand(std::string_view parent) : Command("settings", parent) {} + + virtual std::vector GetArguments() const override; + + virtual Resource::LocString ShortDescription() const override; + virtual Resource::LocString LongDescription() const override; + + std::string HelpLink() const override; + + protected: + void ExecuteInternal(Execution::Context& context) const override; + }; +} diff --git a/src/AppInstallerCLICore/Core.cpp b/src/AppInstallerCLICore/Core.cpp index f0b9b8a41b..1079208488 100644 --- a/src/AppInstallerCLICore/Core.cpp +++ b/src/AppInstallerCLICore/Core.cpp @@ -4,6 +4,7 @@ #include "Public/AppInstallerCLICore.h" #include "Commands/RootCommand.h" #include "ExecutionContext.h" +#include using namespace winrt; using namespace winrt::Windows::Foundation; @@ -78,6 +79,13 @@ namespace AppInstaller::CLI // The root command is our fallback in the event of very bad or very little input std::unique_ptr command = std::make_unique(); + // Load the settings file and warn if necessary. + auto warnings = Settings::UserSettings::Instance().GetWarnings(); + for (const auto& warning : warnings) + { + context.Reporter.Warn() << warning << std::endl; + } + try { std::unique_ptr subCommand = command->FindSubCommand(invocation); diff --git a/src/AppInstallerCLICore/ExecutionArgs.h b/src/AppInstallerCLICore/ExecutionArgs.h index dbeb5e3b4d..d9a9efdc77 100644 --- a/src/AppInstallerCLICore/ExecutionArgs.h +++ b/src/AppInstallerCLICore/ExecutionArgs.h @@ -54,7 +54,7 @@ namespace AppInstaller::CLI::Execution Force, // Generic flag to enable a command to skip some check ListVersions, // Used in Show command to list all available versions of an app NoVT, // Disable VirtualTerminal outputs - PlainStyle, // Makes progress display as plain + RetroStyle, // Makes progress display as plain RainbowStyle, // Makes progress display as a rainbow Help, // Show command usage Info, // Show general info about WinGet diff --git a/src/AppInstallerCLICore/ExecutionContext.cpp b/src/AppInstallerCLICore/ExecutionContext.cpp index caf964e2fe..ee4f75e0e8 100644 --- a/src/AppInstallerCLICore/ExecutionContext.cpp +++ b/src/AppInstallerCLICore/ExecutionContext.cpp @@ -2,7 +2,7 @@ // Licensed under the MIT License. #include "pch.h" #include "ExecutionContext.h" - +#include "AppInstallerUserSettings.h" namespace AppInstaller::CLI::Execution { @@ -80,14 +80,18 @@ namespace AppInstaller::CLI::Execution { Reporter.SetStyle(VisualStyle::NoVT); } - else if (Args.Contains(Args::Type::PlainStyle)) + else if (Args.Contains(Args::Type::RetroStyle)) { - Reporter.SetStyle(VisualStyle::Plain); + Reporter.SetStyle(VisualStyle::Retro); } else if (Args.Contains(Args::Type::RainbowStyle)) { Reporter.SetStyle(VisualStyle::Rainbow); } + else + { + Reporter.SetStyle(UserSettings::Instance().GetVisual().GetProgressBarVisualStyle()); + } } void Context::Terminate(HRESULT hr) diff --git a/src/AppInstallerCLICore/ExecutionProgress.cpp b/src/AppInstallerCLICore/ExecutionProgress.cpp index 3d81c7cfdc..5daf30c27a 100644 --- a/src/AppInstallerCLICore/ExecutionProgress.cpp +++ b/src/AppInstallerCLICore/ExecutionProgress.cpp @@ -138,7 +138,7 @@ namespace AppInstaller::CLI::Execution case AppInstaller::CLI::Execution::VisualStyle::NoVT: // No VT means no style set break; - case AppInstaller::CLI::Execution::VisualStyle::Plain: + case AppInstaller::CLI::Execution::VisualStyle::Retro: if (enabled) { m_out << TextFormat::Default; diff --git a/src/AppInstallerCLICore/ExecutionProgress.h b/src/AppInstallerCLICore/ExecutionProgress.h index 326452ab46..2fa2b0fa0e 100644 --- a/src/AppInstallerCLICore/ExecutionProgress.h +++ b/src/AppInstallerCLICore/ExecutionProgress.h @@ -3,6 +3,7 @@ #pragma once #include "VTSupport.h" #include +#include #include @@ -13,17 +14,9 @@ #include #include - namespace AppInstaller::CLI::Execution { - // The visual style of the progress bar. - enum class VisualStyle - { - NoVT, - Plain, - Accent, - Rainbow, - }; + using namespace Settings; namespace details { diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj index 7c20e6084e..83d46cf826 100644 --- a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj +++ b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj @@ -108,7 +108,7 @@ Disabled _DEBUG;%(PreprocessorDefinitions) - $(MSBuildThisFileDirectory)..\AppInstallerCommonCore;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore;$(MSBuildThisFileDirectory)..\AppInstallerCommonCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerCLICore\Public;$(MSBuildThisFileDirectory)..\YamlCppLib\yaml-cpp\include;$(MSBuildThisFileDirectory)..\AppInstallerCLICore;%(AdditionalIncludeDirectories) + $(MSBuildThisFileDirectory)..\AppInstallerCommonCore;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore;$(MSBuildThisFileDirectory)..\AppInstallerCommonCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerCLICore\Public;$(MSBuildThisFileDirectory)..\YamlCppLib\yaml-cpp\include;$(MSBuildThisFileDirectory)..\AppInstallerCLICore;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories) true @@ -126,7 +126,7 @@ WIN32;%(PreprocessorDefinitions) - $(MSBuildThisFileDirectory)..\AppInstallerCommonCore;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore;$(MSBuildThisFileDirectory)..\AppInstallerCommonCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerCLICore\Public;$(MSBuildThisFileDirectory)..\YamlCppLib\yaml-cpp\include;$(MSBuildThisFileDirectory)..\AppInstallerCLICore;%(AdditionalIncludeDirectories) + $(MSBuildThisFileDirectory)..\AppInstallerCommonCore;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore;$(MSBuildThisFileDirectory)..\AppInstallerCommonCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerCLICore\Public;$(MSBuildThisFileDirectory)..\YamlCppLib\yaml-cpp\include;$(MSBuildThisFileDirectory)..\AppInstallerCLICore;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories) true @@ -145,8 +145,8 @@ true true NDEBUG;%(PreprocessorDefinitions) - $(MSBuildThisFileDirectory)..\AppInstallerCommonCore;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore;$(MSBuildThisFileDirectory)..\AppInstallerCommonCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerCLICore\Public;$(MSBuildThisFileDirectory)..\YamlCppLib\yaml-cpp\include;$(MSBuildThisFileDirectory)..\AppInstallerCLICore;%(AdditionalIncludeDirectories) - $(MSBuildThisFileDirectory)..\AppInstallerCommonCore;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore;$(MSBuildThisFileDirectory)..\AppInstallerCommonCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerCLICore\Public;$(MSBuildThisFileDirectory)..\YamlCppLib\yaml-cpp\include;$(MSBuildThisFileDirectory)..\AppInstallerCLICore;%(AdditionalIncludeDirectories) + $(MSBuildThisFileDirectory)..\AppInstallerCommonCore;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore;$(MSBuildThisFileDirectory)..\AppInstallerCommonCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerCLICore\Public;$(MSBuildThisFileDirectory)..\YamlCppLib\yaml-cpp\include;$(MSBuildThisFileDirectory)..\AppInstallerCLICore;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories) + $(MSBuildThisFileDirectory)..\AppInstallerCommonCore;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore;$(MSBuildThisFileDirectory)..\AppInstallerCommonCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerCLICore\Public;$(MSBuildThisFileDirectory)..\YamlCppLib\yaml-cpp\include;$(MSBuildThisFileDirectory)..\AppInstallerCLICore;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories) true true @@ -393,6 +393,9 @@ {5eb88068-5fb9-4e69-89b2-72dbc5e068f9} + + {82b39fda-e86b-4713-a873-9d56de00247a} + {8bb94bb8-374f-4294-bca1-c7811514a6b7} diff --git a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj index f771d597f3..549a07d086 100644 --- a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj +++ b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj @@ -122,9 +122,9 @@ Disabled _DEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD - $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\binver;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\binver;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\binver;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\binver;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\binver;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\binver;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories) true true true @@ -139,7 +139,7 @@ WIN32;%(PreprocessorDefinitions);CLICOREDLLBUILD - $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\binver;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\binver;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories) true @@ -152,10 +152,10 @@ true true NDEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD - $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\binver;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\binver;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\binver;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\binver;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\binver;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\binver;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\binver;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\binver;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories) true true true @@ -191,12 +191,18 @@ + + + + + + @@ -210,6 +216,7 @@ + @@ -220,6 +227,10 @@ + + + + diff --git a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters index 43b45c8b27..468098c12a 100644 --- a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters +++ b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters @@ -25,6 +25,12 @@ {41035fd6-dc74-4464-b9b1-4ffe95d6789c} + + {dd43556d-10de-433a-99f4-1282590e68a0} + + + {95046c4f-633b-4cf0-995a-430c41d1ab62} + @@ -102,6 +108,24 @@ Public\winget + + Public + + + Public\winget\settings + + + Public\winget\settings + + + Header Files + + + Public\winget + + + UserSettings + @@ -164,6 +188,21 @@ Source Files + + UserSettings + + + UserSettings + + + UserSettings + + + Source Files + + + UserSettings + diff --git a/src/AppInstallerCommonCore/AppInstallerStrings.cpp b/src/AppInstallerCommonCore/AppInstallerStrings.cpp index 29b7e4ef48..af67285bb6 100644 --- a/src/AppInstallerCommonCore/AppInstallerStrings.cpp +++ b/src/AppInstallerCommonCore/AppInstallerStrings.cpp @@ -268,4 +268,9 @@ namespace AppInstaller::Utility return result; } + + std::string ToString(const std::string_view key) + { + return static_cast(key); + } } diff --git a/src/AppInstallerCommonCore/JsonUtil.cpp b/src/AppInstallerCommonCore/JsonUtil.cpp new file mode 100644 index 0000000000..c51d61070d --- /dev/null +++ b/src/AppInstallerCommonCore/JsonUtil.cpp @@ -0,0 +1,50 @@ +// Copyright(c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Public/winget/JsonUtil.h" +#include "AppInstallerStrings.h" + +namespace AppInstaller::Utility +{ + std::optional GetValueString(const std::string_view& key, const Json::Value& node) + { + std::optional value = std::nullopt; + + // jsoncpp doesn't support std::string_view yet. + auto keyStr = Utility::ToString(key); + + if (node.isMember(keyStr)) + { + auto keyValue = node[keyStr]; + + if (keyValue.isString()) + { + value = keyValue.asString(); + } + } + + return value; + } + + std::optional GetValueUInt(const std::string_view& key, const Json::Value& node) + { + std::optional value = std::nullopt; + + // jsoncpp doesn't support std::string_view yet. + auto keyStr = Utility::ToString(key); + + if (node.isMember(keyStr)) + { + auto keyValue = node[keyStr]; + + if (keyValue.isUInt()) + { + value = keyValue.asUInt(); + } + } + + return value; + } + +} + diff --git a/src/AppInstallerCommonCore/Public/AppInstallerStrings.h b/src/AppInstallerCommonCore/Public/AppInstallerStrings.h index e0b5cf637b..36f22f8313 100644 --- a/src/AppInstallerCommonCore/Public/AppInstallerStrings.h +++ b/src/AppInstallerCommonCore/Public/AppInstallerStrings.h @@ -97,4 +97,7 @@ namespace AppInstaller::Utility // Reads the entire stream into a string. std::string ReadEntireStream(std::istream& stream); + + // Converts string view to string + std::string ToString(const std::string_view key); } diff --git a/src/AppInstallerCommonCore/Public/AppInstallerUserSettings.h b/src/AppInstallerCommonCore/Public/AppInstallerUserSettings.h new file mode 100644 index 0000000000..22587a6a66 --- /dev/null +++ b/src/AppInstallerCommonCore/Public/AppInstallerUserSettings.h @@ -0,0 +1,65 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once + +#include "AppInstallerStrings.h" +#include "winget/settings/Source.h" +#include "winget/settings/Visual.h" +#include + +#include +#include +#include + +namespace AppInstaller::Settings +{ + // The type of argument. + enum class UserSettingsType + { + // Settings files don't exist. A file is created on the first call to the settings command. + Default, + // Loaded settings.json + Standard, + // Loaded settings.json.backup + Backup, + }; + + // Representation of the parsed settings file. + struct UserSettings + { + static UserSettings& Instance() + { + // If we go multi-threaded secure this. + static UserSettings userSettings; + return userSettings; + } + + UserSettings(const UserSettings&) = delete; + UserSettings& operator=(const UserSettings&) = delete; + + UserSettingsType GetType() const { return m_type; } + std::vector GetWarnings() const { return m_warnings; } + + std::filesystem::path SettingsFilePath(); + std::filesystem::path SettingsBackupPath(); + void CreateSettingsFile(); + void CreateBackupFile(); + + // Settings + inline const Source& GetSource() const { return *m_source; } + inline const Visual& GetVisual() const { return *m_visual; } + + private: + UserSettings(); + ~UserSettings() {}; + + std::optional ParseFile(const std::filesystem::path& path); + + UserSettingsType m_type = UserSettingsType::Default; + std::vector m_warnings; + + std::unique_ptr m_source; + std::unique_ptr m_visual; + + }; +} diff --git a/src/AppInstallerCommonCore/Public/winget/JsonUtil.h b/src/AppInstallerCommonCore/Public/winget/JsonUtil.h new file mode 100644 index 0000000000..477c05df6a --- /dev/null +++ b/src/AppInstallerCommonCore/Public/winget/JsonUtil.h @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include + +#include +#include + +namespace AppInstaller::Utility +{ + std::optional GetValueString(const std::string_view& key, const Json::Value& node); + + std::optional GetValueUInt(const std::string_view& key, const Json::Value& node); + +} diff --git a/src/AppInstallerCommonCore/Public/winget/settings/Source.h b/src/AppInstallerCommonCore/Public/winget/settings/Source.h new file mode 100644 index 0000000000..ca2dbc3fa8 --- /dev/null +++ b/src/AppInstallerCommonCore/Public/winget/settings/Source.h @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include + +#include + +namespace AppInstaller::Settings +{ + using namespace std::chrono_literals; + + struct Source final + { + Source(const Json::Value& node); + virtual ~Source() = default; + + Source(const Source&) = default; + Source& operator=(const Source&) = default; + + Source(Source&&) = default; + Source& operator=(Source&&) = default; + + std::vector Warnings() const { return m_warnings; } + + uint32_t GetAutoUpdateTimeInMinutes() const noexcept { return m_autoUpdateIntervalInMinutes; } + + static std::string_view GetPropertyName(); + + private: + uint32_t m_autoUpdateIntervalInMinutes; + + std::vector m_warnings; + + }; +} diff --git a/src/AppInstallerCommonCore/Public/winget/settings/Visual.h b/src/AppInstallerCommonCore/Public/winget/settings/Visual.h new file mode 100644 index 0000000000..5773f760d8 --- /dev/null +++ b/src/AppInstallerCommonCore/Public/winget/settings/Visual.h @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include + +namespace AppInstaller::Settings +{ + // The visual style of the progress bar. + enum class VisualStyle + { + NoVT, + Retro, + Accent, + Rainbow, + }; + + struct Visual final + { + Visual(const Json::Value& node); + virtual ~Visual() = default; + + Visual(const Visual&) = default; + Visual& operator=(const Visual&) = default; + + Visual(Visual&&) = default; + Visual& operator=(Visual&&) = default; + + std::vector Warnings() const { return m_warnings; } + + VisualStyle GetProgressBarVisualStyle() const noexcept { return m_progressBar; } + + static std::string_view GetPropertyName(); + + private: + VisualStyle m_progressBar; + + std::vector m_warnings; + }; +} diff --git a/src/AppInstallerCommonCore/UserSettings/SettingValidation.h b/src/AppInstallerCommonCore/UserSettings/SettingValidation.h new file mode 100644 index 0000000000..37b6e61d4c --- /dev/null +++ b/src/AppInstallerCommonCore/UserSettings/SettingValidation.h @@ -0,0 +1,19 @@ +#pragma once +#include + +#include +#include +#include + +namespace AppInstaller::Settings +{ + struct SettingFieldInfo + { + std::string Name; + std::function ProcessFunc; + }; + + std::vector ValidateAndProcessFields( + const Json::Value& rootNode, + const std::vector fieldInfos); +} diff --git a/src/AppInstallerCommonCore/UserSettings/SettingsValidation.cpp b/src/AppInstallerCommonCore/UserSettings/SettingsValidation.cpp new file mode 100644 index 0000000000..ef89027b3f --- /dev/null +++ b/src/AppInstallerCommonCore/UserSettings/SettingsValidation.cpp @@ -0,0 +1,36 @@ +#pragma once +#include "pch.h" +#include "SettingValidation.h" + +namespace AppInstaller::Settings +{ + std::vector ValidateAndProcessFields( + const Json::Value& root, + const std::vector fieldInfos) + { + if (root.isNull()) + { + return {}; + } + + std::vector warnings; + + for (const std::string& key : root.getMemberNames()) { + + auto fieldIter = std::find_if(fieldInfos.begin(), fieldInfos.end(), [&](auto const& s) { return s.Name == key; }); + + if (fieldIter != fieldInfos.end()) + { + fieldIter->ProcessFunc(key, root); + } + else + { + // TODO: Localize + std::string warning = "Unrecognized field " + key; + warnings.emplace_back(warning); + } + } + + return warnings; + } +} \ No newline at end of file diff --git a/src/AppInstallerCommonCore/UserSettings/Source.cpp b/src/AppInstallerCommonCore/UserSettings/Source.cpp new file mode 100644 index 0000000000..69ac972420 --- /dev/null +++ b/src/AppInstallerCommonCore/UserSettings/Source.cpp @@ -0,0 +1,57 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "AppInstallerStrings.h" +#include "SettingValidation.h" +#include "winget\JsonUtil.h" +#include "winget\settings\Source.h" + +#include + +#include + +namespace AppInstaller::Settings +{ + using namespace std::string_view_literals; + + // Visual root + static constexpr std::string_view s_SourceKey = "source"sv; + + // Default values; + static constexpr uint32_t Five = 5; + + Source::Source(const Json::Value& node) : m_autoUpdateIntervalInMinutes(Five) + { + std::vector sourceSettings = + { + { + "autoUpdateIntervalInMinutes", + [this](const std::string& key, const Json::Value& node) + { + if (!node.isNull()) + { + auto autoUpdateInterval = Utility::GetValueUInt(key, node); + if (autoUpdateInterval.has_value()) + { + m_autoUpdateIntervalInMinutes = autoUpdateInterval.value(); + } + else + { + // TODO: Localize + std::string warning = "Invalid format for: " + key; + m_warnings.emplace_back(warning); + } + } + } + }, + }; + + auto warnings = ValidateAndProcessFields(node, sourceSettings); + std::move(warnings.begin(), warnings.end(), std::inserter(m_warnings, m_warnings.end())); + } + + std::string_view Source::GetPropertyName() + { + return s_SourceKey; + } +} diff --git a/src/AppInstallerCommonCore/UserSettings/UserSettings.cpp b/src/AppInstallerCommonCore/UserSettings/UserSettings.cpp new file mode 100644 index 0000000000..6284369f8b --- /dev/null +++ b/src/AppInstallerCommonCore/UserSettings/UserSettings.cpp @@ -0,0 +1,133 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "AppInstallerUserSettings.h" +#include + +#include "winget/settings/Source.h" +#include "winget/settings/Visual.h" + +#include + +namespace AppInstaller::Settings +{ + using namespace std::string_view_literals; + using namespace Utility; + + static constexpr std::string_view s_SettingFileName = "settings.json"sv; + static constexpr std::string_view s_SettingBackupFileName = "settings.json.backup"sv; + + static constexpr std::string_view s_SettingEmpty = + R"""({ + "source": { + "autoUpdateIntervalInMinutes": 15 + }, + "visual": { + "progressBar": "rainbow" + } +})"""sv; + + UserSettings::UserSettings() : m_type(UserSettingsType::Default) + { + Json::Value settingsRoot = Json::Value::nullSingleton(); + + // Settings can be loaded from settings.json or settings.json.backup files. + // 1 - Use settings.json if exists and passes parsing. + // 2 - Use settings.backup.json if settings.json fails to parse. + // 3 - Use default (empty) if both settings files fail to load. + auto settingsPath = SettingsFilePath(); + if (std::filesystem::exists(settingsPath)) + { + auto settingsJson = ParseFile(settingsPath); + if (settingsJson.has_value()) + { + m_type = UserSettingsType::Standard; + settingsRoot = settingsJson.value(); + } + } + + // Settings didn't parse or doesn't exist, try with backup. + if (settingsRoot.isNull()) + { + auto settingsBackup = SettingsBackupPath(); + if (std::filesystem::exists(settingsBackup)) + { + auto settingsBackupJson = ParseFile(settingsBackup); + + // We don't expect users to modify manually this file so don't warn. + if (settingsBackupJson.has_value()) + { + // TODO: Localize + m_warnings.emplace_back("Loaded settings from backup file."); + m_type = UserSettingsType::Backup; + settingsRoot = settingsBackupJson.value(); + } + } + } + + // Populate the settings. + auto sourceStr = Utility::ToString(Source::GetPropertyName()); + m_source = std::make_unique(settingsRoot[sourceStr]); + auto sourceWarnings = m_source->Warnings(); + std::move(sourceWarnings.begin(), sourceWarnings.end(), std::inserter(m_warnings, m_warnings.end())); + + auto visualStr = Utility::ToString(Visual::GetPropertyName()); + m_visual = std::make_unique(settingsRoot[visualStr]); + auto visualWarnings = m_visual->Warnings(); + std::move(visualWarnings.begin(), visualWarnings.end(), std::inserter(m_warnings, m_warnings.end())); + } + + std::optional UserSettings::ParseFile(const std::filesystem::path& path) + { + Json::Value root; + Json::CharReaderBuilder builder; + const std::unique_ptr reader(builder.newCharReader()); + + auto settingsStream = std::make_unique(path); + std::stringstream settingsContent; + settingsContent << settingsStream->rdbuf(); + std::string settingsContentStr = settingsContent.str(); + std::string error; + + if (reader->parse(settingsContentStr.c_str(), settingsContentStr.c_str() + settingsContentStr.size(), &root, &error)) + { + return root; + } + + std::string warning = path.filename().u8string() + " : " + error; + m_warnings.push_back(warning); + + return {}; + } + + std::filesystem::path UserSettings::SettingsFilePath() + { + return Runtime::GetPathToLocalState() / s_SettingFileName; + } + + std::filesystem::path UserSettings::SettingsBackupPath() + { + return Runtime::GetPathToLocalState() / s_SettingBackupFileName; + } + + void UserSettings::CreateSettingsFile() + { + std::filesystem::path path = SettingsFilePath(); + + // Create settings file if it doesn't exist. + if (!std::filesystem::exists(path)) + { + std::ofstream stream(path); + stream << s_SettingEmpty << std::endl; + stream.flush(); + } + } + + void UserSettings::CreateBackupFile() + { + std::filesystem::path from = SettingsFilePath(); + std::filesystem::path to = SettingsBackupPath(); + + std::filesystem::copy_file(from, to, std::filesystem::copy_options::overwrite_existing); + } +} diff --git a/src/AppInstallerCommonCore/UserSettings/Visual.cpp b/src/AppInstallerCommonCore/UserSettings/Visual.cpp new file mode 100644 index 0000000000..dcb3bc2fa0 --- /dev/null +++ b/src/AppInstallerCommonCore/UserSettings/Visual.cpp @@ -0,0 +1,74 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "AppInstallerStrings.h" +#include "SettingValidation.h" +#include "winget\JsonUtil.h" +#include "winget\settings\Visual.h" + +#include + +namespace AppInstaller::Settings +{ + using namespace std::string_view_literals; + + // Visual root + static constexpr std::string_view s_VisualKey = "visual"sv; + + // progressBar property + static constexpr std::string_view s_progressBar_Accent = "accent"; + static constexpr std::string_view s_progressBar_Rainbow = "rainbow"; + static constexpr std::string_view s_progressBar_Retro = "retro"; + + Visual::Visual(const Json::Value& node) : m_progressBar(VisualStyle::Accent) + { + std::vector visualSettings = + { + { + "progressBar", + [this](const std::string& key, const Json::Value& node) + { + if (!node.isNull()) + { + auto value = Utility::GetValueString(key, node); + if (value.has_value()) + { + if (Utility::CaseInsensitiveEquals(value.value(), s_progressBar_Accent)) + { + m_progressBar = VisualStyle::Accent; + } + else if (Utility::CaseInsensitiveEquals(value.value(), s_progressBar_Rainbow)) + { + m_progressBar = VisualStyle::Rainbow; + } + else if (Utility::CaseInsensitiveEquals(value.value(), s_progressBar_Retro)) + { + m_progressBar = VisualStyle::Retro; + } + else + { + // TODO: Localize + std::string warning = "Invalid value for " + key + ": " + value.value(); + m_warnings.emplace_back(warning); + } + } + else + { + // TODO: Localize. + std::string warning = "Invalid format for " + key; + m_warnings.emplace_back(warning); + } + } + } + }, + }; + + auto warnings = ValidateAndProcessFields(node, visualSettings); + std::move(warnings.begin(), warnings.end(), std::inserter(m_warnings, m_warnings.end())); + } + + std::string_view Visual::GetPropertyName() + { + return s_VisualKey; + } +} \ No newline at end of file diff --git a/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj b/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj index 859883daee..a84e31e81f 100644 --- a/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj +++ b/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj @@ -122,9 +122,9 @@ Disabled _DEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD - $(ProjectDir);$(ProjectDir)\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories) true true true @@ -139,7 +139,7 @@ WIN32;%(PreprocessorDefinitions);CLICOREDLLBUILD - $(ProjectDir);$(ProjectDir)\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories) true @@ -152,10 +152,10 @@ true true NDEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD - $(ProjectDir);$(ProjectDir)\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories) true true true diff --git a/src/AppInstallerRepositoryCore/RepositorySource.cpp b/src/AppInstallerRepositoryCore/RepositorySource.cpp index c10f8e59e3..0e04b86983 100644 --- a/src/AppInstallerRepositoryCore/RepositorySource.cpp +++ b/src/AppInstallerRepositoryCore/RepositorySource.cpp @@ -2,6 +2,7 @@ // Licensed under the MIT License. #include "pch.h" #include "Public/AppInstallerRepositorySource.h" +#include #include "SourceFactory.h" #include "Microsoft/PreIndexedPackageSourceFactory.h" @@ -258,16 +259,20 @@ namespace AppInstaller::Repository return true; } - // TODO: Enable some amount of user control over this. - constexpr static auto s_DefaultAutoUpdateTime = 5min; + auto autoUpdateTime = Settings::UserSettings::Instance().GetSource().GetAutoUpdateTimeInMinutes(); - auto timeSinceLastUpdate = std::chrono::system_clock::now() - details.LastUpdateTime; - if (timeSinceLastUpdate > s_DefaultAutoUpdateTime) + // A value of zero means no auto update, to get update the source run `winget update` + if (autoUpdateTime != 0) { - AICLI_LOG(Repo, Info, << "Source past auto update time [" << - std::chrono::duration_cast(s_DefaultAutoUpdateTime).count() << " mins]; it has been at least " << - std::chrono::duration_cast(timeSinceLastUpdate).count() << " mins"); - return true; + auto autoUpdateTimeMins = std::chrono::minutes(autoUpdateTime); + auto timeSinceLastUpdate = std::chrono::system_clock::now() - details.LastUpdateTime; + if (timeSinceLastUpdate > autoUpdateTimeMins) + { + AICLI_LOG(Repo, Info, << "Source past auto update time [" << + std::chrono::duration_cast(autoUpdateTimeMins).count() << " mins]; it has been at least " << + std::chrono::duration_cast(timeSinceLastUpdate).count() << " mins"); + return true; + } } return false; diff --git a/src/JsonCppLib/JsonCppLib.sln b/src/JsonCppLib/JsonCppLib.sln new file mode 100644 index 0000000000..6be6574b8a --- /dev/null +++ b/src/JsonCppLib/JsonCppLib.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30011.22 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "JsonCppLib", "JsonCppLib.vcxproj", "{82B39FDA-E86B-4713-A873-9D56DE00247A}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {82B39FDA-E86B-4713-A873-9D56DE00247A}.Debug|x64.ActiveCfg = Debug|x64 + {82B39FDA-E86B-4713-A873-9D56DE00247A}.Debug|x64.Build.0 = Debug|x64 + {82B39FDA-E86B-4713-A873-9D56DE00247A}.Debug|x86.ActiveCfg = Debug|Win32 + {82B39FDA-E86B-4713-A873-9D56DE00247A}.Debug|x86.Build.0 = Debug|Win32 + {82B39FDA-E86B-4713-A873-9D56DE00247A}.Release|x64.ActiveCfg = Release|x64 + {82B39FDA-E86B-4713-A873-9D56DE00247A}.Release|x64.Build.0 = Release|x64 + {82B39FDA-E86B-4713-A873-9D56DE00247A}.Release|x86.ActiveCfg = Release|Win32 + {82B39FDA-E86B-4713-A873-9D56DE00247A}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {E3C30FD7-B1CD-42D7-BA8E-1B3A6C225F56} + EndGlobalSection +EndGlobal diff --git a/src/JsonCppLib/JsonCppLib.vcxproj b/src/JsonCppLib/JsonCppLib.vcxproj new file mode 100644 index 0000000000..9547b0f47d --- /dev/null +++ b/src/JsonCppLib/JsonCppLib.vcxproj @@ -0,0 +1,219 @@ + + + + + Debug + ARM + + + Debug + ARM64 + + + Debug + Win32 + + + Release + ARM + + + Release + ARM64 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + {82B39FDA-E86B-4713-A873-9D56DE00247A} + Win32Proj + 10.0 + + + + StaticLibrary + true + v142 + + + StaticLibrary + false + v142 + + + StaticLibrary + true + v142 + + + StaticLibrary + true + v142 + + + StaticLibrary + true + v142 + + + StaticLibrary + false + v142 + + + StaticLibrary + false + v142 + + + StaticLibrary + false + v142 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ + + + true + $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ + + + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + + + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + + + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + + + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + + + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + + + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + + + + WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) + MultiThreadedDebugDLL + Level3 + ProgramDatabase + Disabled + stdcpp17 + json + + + MachineX86 + true + Windows + + + + + WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + MultiThreadedDLL + Level3 + ProgramDatabase + stdcpp17 + json + + + MachineX86 + true + Windows + true + true + + + + + stdcpp17 + json + + + + + stdcpp17 + json + + + + + stdcpp17 + json + + + + + stdcpp17 + json + + + + + stdcpp17 + json + + + + + stdcpp17 + json + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/JsonCppLib/JsonCppLib.vcxproj.filters b/src/JsonCppLib/JsonCppLib.vcxproj.filters new file mode 100644 index 0000000000..2106ec0480 --- /dev/null +++ b/src/JsonCppLib/JsonCppLib.vcxproj.filters @@ -0,0 +1,30 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav + + + + + Source Files + + + + + Header Files + + + Header Files + + + \ No newline at end of file diff --git a/src/JsonCppLib/json/json-forwards.h b/src/JsonCppLib/json/json-forwards.h new file mode 100644 index 0000000000..fff5ff167a --- /dev/null +++ b/src/JsonCppLib/json/json-forwards.h @@ -0,0 +1,447 @@ +/// Json-cpp amalgamated forward header (http://jsoncpp.sourceforge.net/). +/// It is intended to be used with #include "json/json-forwards.h" +/// This header provides forward declaration for all JsonCpp types. + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: LICENSE +// ////////////////////////////////////////////////////////////////////// + +/* +The JsonCpp library's source code, including accompanying documentation, +tests and demonstration applications, are licensed under the following +conditions... + +Baptiste Lepilleur and The JsonCpp Authors explicitly disclaim copyright in all +jurisdictions which recognize such a disclaimer. In such jurisdictions, +this software is released into the Public Domain. + +In jurisdictions which do not recognize Public Domain property (e.g. Germany as of +2010), this software is Copyright (c) 2007-2010 by Baptiste Lepilleur and +The JsonCpp Authors, and is released under the terms of the MIT License (see below). + +In jurisdictions which recognize Public Domain property, the user of this +software may choose to accept it either as 1) Public Domain, 2) under the +conditions of the MIT License (see below), or 3) under the terms of dual +Public Domain/MIT License conditions described here, as they choose. + +The MIT License is about as close to Public Domain as a license can get, and is +described in clear, concise terms at: + + http://en.wikipedia.org/wiki/MIT_License + +The full text of the MIT License follows: + +======================================================================== +Copyright (c) 2007-2010 Baptiste Lepilleur and The JsonCpp Authors + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, +modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +======================================================================== +(END LICENSE TEXT) + +The MIT license is compatible with both the GPL and commercial +software, affording one all of the rights of Public Domain with the +minor nuisance of being required to keep the above copyright notice +and license text in the source code. Note also that by accepting the +Public Domain "license" you can re-license your copy using whatever +license you like. + +*/ + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: LICENSE +// ////////////////////////////////////////////////////////////////////// + + + + + +#ifndef JSON_FORWARD_AMALGAMATED_H_INCLUDED +# define JSON_FORWARD_AMALGAMATED_H_INCLUDED +/// If defined, indicates that the source file is amalgamated +/// to prevent private header inclusion. +#define JSON_IS_AMALGAMATION + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: include/json/version.h +// ////////////////////////////////////////////////////////////////////// + +#ifndef JSON_VERSION_H_INCLUDED +#define JSON_VERSION_H_INCLUDED + +// Note: version must be updated in three places when doing a release. This +// annoying process ensures that amalgamate, CMake, and meson all report the +// correct version. +// 1. /meson.build +// 2. /include/json/version.h +// 3. /CMakeLists.txt +// IMPORTANT: also update the SOVERSION!! + +#define JSONCPP_VERSION_STRING "1.9.3" +#define JSONCPP_VERSION_MAJOR 1 +#define JSONCPP_VERSION_MINOR 9 +#define JSONCPP_VERSION_PATCH 3 +#define JSONCPP_VERSION_QUALIFIER +#define JSONCPP_VERSION_HEXA \ + ((JSONCPP_VERSION_MAJOR << 24) | (JSONCPP_VERSION_MINOR << 16) | \ + (JSONCPP_VERSION_PATCH << 8)) + +#ifdef JSONCPP_USING_SECURE_MEMORY +#undef JSONCPP_USING_SECURE_MEMORY +#endif +#define JSONCPP_USING_SECURE_MEMORY 0 +// If non-zero, the library zeroes any memory that it has allocated before +// it frees its memory. + +#endif // JSON_VERSION_H_INCLUDED + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: include/json/version.h +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: include/json/allocator.h +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#ifndef JSON_ALLOCATOR_H_INCLUDED +#define JSON_ALLOCATOR_H_INCLUDED + +#include +#include + +#pragma pack(push, 8) + +namespace Json { + template class SecureAllocator { + public: + // Type definitions + using value_type = T; + using pointer = T*; + using const_pointer = const T*; + using reference = T&; + using const_reference = const T&; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + + /** + * Allocate memory for N items using the standard allocator. + */ + pointer allocate(size_type n) { + // allocate using "global operator new" + return static_cast(::operator new(n * sizeof(T))); + } + + /** + * Release memory which was allocated for N items at pointer P. + * + * The memory block is filled with zeroes before being released. + * The pointer argument is tagged as "volatile" to prevent the + * compiler optimizing out this critical step. + */ + void deallocate(volatile pointer p, size_type n) { + std::memset(p, 0, n * sizeof(T)); + // free using "global operator delete" + ::operator delete(p); + } + + /** + * Construct an item in-place at pointer P. + */ + template void construct(pointer p, Args&&... args) { + // construct using "placement new" and "perfect forwarding" + ::new (static_cast(p)) T(std::forward(args)...); + } + + size_type max_size() const { return size_t(-1) / sizeof(T); } + + pointer address(reference x) const { return std::addressof(x); } + + const_pointer address(const_reference x) const { return std::addressof(x); } + + /** + * Destroy an item in-place at pointer P. + */ + void destroy(pointer p) { + // destroy using "explicit destructor" + p->~T(); + } + + // Boilerplate + SecureAllocator() {} + template SecureAllocator(const SecureAllocator&) {} + template struct rebind { using other = SecureAllocator; }; + }; + + template + bool operator==(const SecureAllocator&, const SecureAllocator&) { + return true; + } + + template + bool operator!=(const SecureAllocator&, const SecureAllocator&) { + return false; + } + +} // namespace Json + +#pragma pack(pop) + +#endif // JSON_ALLOCATOR_H_INCLUDED + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: include/json/allocator.h +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: include/json/config.h +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#ifndef JSON_CONFIG_H_INCLUDED +#define JSON_CONFIG_H_INCLUDED +#include +#include +#include +#include +#include +#include +#include +#include + +// If non-zero, the library uses exceptions to report bad input instead of C +// assertion macros. The default is to use exceptions. +#ifndef JSON_USE_EXCEPTION +#define JSON_USE_EXCEPTION 1 +#endif + +// Temporary, tracked for removal with issue #982. +#ifndef JSON_USE_NULLREF +#define JSON_USE_NULLREF 1 +#endif + +/// If defined, indicates that the source file is amalgamated +/// to prevent private header inclusion. +/// Remarks: it is automatically defined in the generated amalgamated header. +// #define JSON_IS_AMALGAMATION + +// Export macros for DLL visibility +#if defined(JSON_DLL_BUILD) +#if defined(_MSC_VER) || defined(__MINGW32__) +#define JSON_API __declspec(dllexport) +#define JSONCPP_DISABLE_DLL_INTERFACE_WARNING +#elif defined(__GNUC__) || defined(__clang__) +#define JSON_API __attribute__((visibility("default"))) +#endif // if defined(_MSC_VER) + +#elif defined(JSON_DLL) +#if defined(_MSC_VER) || defined(__MINGW32__) +#define JSON_API __declspec(dllimport) +#define JSONCPP_DISABLE_DLL_INTERFACE_WARNING +#endif // if defined(_MSC_VER) +#endif // ifdef JSON_DLL_BUILD + +#if !defined(JSON_API) +#define JSON_API +#endif + +#if defined(_MSC_VER) && _MSC_VER < 1800 +#error \ + "ERROR: Visual Studio 12 (2013) with _MSC_VER=1800 is the oldest supported compiler with sufficient C++11 capabilities" +#endif + +#if defined(_MSC_VER) && _MSC_VER < 1900 +// As recommended at +// https://stackoverflow.com/questions/2915672/snprintf-and-visual-studio-2010 +extern JSON_API int msvc_pre1900_c99_snprintf(char* outBuf, size_t size, + const char* format, ...); +#define jsoncpp_snprintf msvc_pre1900_c99_snprintf +#else +#define jsoncpp_snprintf std::snprintf +#endif + +// If JSON_NO_INT64 is defined, then Json only support C++ "int" type for +// integer +// Storages, and 64 bits integer support is disabled. +// #define JSON_NO_INT64 1 + +// JSONCPP_OVERRIDE is maintained for backwards compatibility of external tools. +// C++11 should be used directly in JSONCPP. +#define JSONCPP_OVERRIDE override + +#ifdef __clang__ +#if __has_extension(attribute_deprecated_with_message) +#define JSONCPP_DEPRECATED(message) __attribute__((deprecated(message))) +#endif +#elif defined(__GNUC__) // not clang (gcc comes later since clang emulates gcc) +#if (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5)) +#define JSONCPP_DEPRECATED(message) __attribute__((deprecated(message))) +#elif (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1)) +#define JSONCPP_DEPRECATED(message) __attribute__((__deprecated__)) +#endif // GNUC version +#elif defined(_MSC_VER) // MSVC (after clang because clang on Windows emulates + // MSVC) +#define JSONCPP_DEPRECATED(message) __declspec(deprecated(message)) +#endif // __clang__ || __GNUC__ || _MSC_VER + +#if !defined(JSONCPP_DEPRECATED) +#define JSONCPP_DEPRECATED(message) +#endif // if !defined(JSONCPP_DEPRECATED) + +#if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ >= 6)) +#define JSON_USE_INT64_DOUBLE_CONVERSION 1 +#endif + +#if !defined(JSON_IS_AMALGAMATION) + +#include "allocator.h" +#include "version.h" + +#endif // if !defined(JSON_IS_AMALGAMATION) + +namespace Json { + using Int = int; + using UInt = unsigned int; +#if defined(JSON_NO_INT64) + using LargestInt = int; + using LargestUInt = unsigned int; +#undef JSON_HAS_INT64 +#else // if defined(JSON_NO_INT64) + // For Microsoft Visual use specific types as long long is not supported +#if defined(_MSC_VER) // Microsoft Visual Studio + using Int64 = __int64; + using UInt64 = unsigned __int64; +#else // if defined(_MSC_VER) // Other platforms, use long long + using Int64 = int64_t; + using UInt64 = uint64_t; +#endif // if defined(_MSC_VER) + using LargestInt = Int64; + using LargestUInt = UInt64; +#define JSON_HAS_INT64 +#endif // if defined(JSON_NO_INT64) + + template + using Allocator = + typename std::conditional, + std::allocator>::type; + using String = std::basic_string, Allocator>; + using IStringStream = + std::basic_istringstream; + using OStringStream = + std::basic_ostringstream; + using IStream = std::istream; + using OStream = std::ostream; +} // namespace Json + +// Legacy names (formerly macros). +using JSONCPP_STRING = Json::String; +using JSONCPP_ISTRINGSTREAM = Json::IStringStream; +using JSONCPP_OSTRINGSTREAM = Json::OStringStream; +using JSONCPP_ISTREAM = Json::IStream; +using JSONCPP_OSTREAM = Json::OStream; + +#endif // JSON_CONFIG_H_INCLUDED + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: include/json/config.h +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: include/json/forwards.h +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#ifndef JSON_FORWARDS_H_INCLUDED +#define JSON_FORWARDS_H_INCLUDED + +#if !defined(JSON_IS_AMALGAMATION) +#include "config.h" +#endif // if !defined(JSON_IS_AMALGAMATION) + +namespace Json { + + // writer.h + class StreamWriter; + class StreamWriterBuilder; + class Writer; + class FastWriter; + class StyledWriter; + class StyledStreamWriter; + + // reader.h + class Reader; + class CharReader; + class CharReaderBuilder; + + // json_features.h + class Features; + + // value.h + using ArrayIndex = unsigned int; + class StaticString; + class Path; + class PathArgument; + class Value; + class ValueIteratorBase; + class ValueIterator; + class ValueConstIterator; + +} // namespace Json + +#endif // JSON_FORWARDS_H_INCLUDED + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: include/json/forwards.h +// ////////////////////////////////////////////////////////////////////// + + + + + +#endif //ifndef JSON_FORWARD_AMALGAMATED_H_INCLUDED diff --git a/src/JsonCppLib/json/json.h b/src/JsonCppLib/json/json.h new file mode 100644 index 0000000000..867cf732ba --- /dev/null +++ b/src/JsonCppLib/json/json.h @@ -0,0 +1,2342 @@ +/// Json-cpp amalgamated header (http://jsoncpp.sourceforge.net/). +/// It is intended to be used with #include "json/json.h" + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: LICENSE +// ////////////////////////////////////////////////////////////////////// + +/* +The JsonCpp library's source code, including accompanying documentation, +tests and demonstration applications, are licensed under the following +conditions... + +Baptiste Lepilleur and The JsonCpp Authors explicitly disclaim copyright in all +jurisdictions which recognize such a disclaimer. In such jurisdictions, +this software is released into the Public Domain. + +In jurisdictions which do not recognize Public Domain property (e.g. Germany as of +2010), this software is Copyright (c) 2007-2010 by Baptiste Lepilleur and +The JsonCpp Authors, and is released under the terms of the MIT License (see below). + +In jurisdictions which recognize Public Domain property, the user of this +software may choose to accept it either as 1) Public Domain, 2) under the +conditions of the MIT License (see below), or 3) under the terms of dual +Public Domain/MIT License conditions described here, as they choose. + +The MIT License is about as close to Public Domain as a license can get, and is +described in clear, concise terms at: + + http://en.wikipedia.org/wiki/MIT_License + +The full text of the MIT License follows: + +======================================================================== +Copyright (c) 2007-2010 Baptiste Lepilleur and The JsonCpp Authors + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, +modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +======================================================================== +(END LICENSE TEXT) + +The MIT license is compatible with both the GPL and commercial +software, affording one all of the rights of Public Domain with the +minor nuisance of being required to keep the above copyright notice +and license text in the source code. Note also that by accepting the +Public Domain "license" you can re-license your copy using whatever +license you like. + +*/ + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: LICENSE +// ////////////////////////////////////////////////////////////////////// + + + + + +#ifndef JSON_AMALGAMATED_H_INCLUDED +# define JSON_AMALGAMATED_H_INCLUDED +/// If defined, indicates that the source file is amalgamated +/// to prevent private header inclusion. +#define JSON_IS_AMALGAMATION + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: include/json/version.h +// ////////////////////////////////////////////////////////////////////// + +#ifndef JSON_VERSION_H_INCLUDED +#define JSON_VERSION_H_INCLUDED + +// Note: version must be updated in three places when doing a release. This +// annoying process ensures that amalgamate, CMake, and meson all report the +// correct version. +// 1. /meson.build +// 2. /include/json/version.h +// 3. /CMakeLists.txt +// IMPORTANT: also update the SOVERSION!! + +#define JSONCPP_VERSION_STRING "1.9.3" +#define JSONCPP_VERSION_MAJOR 1 +#define JSONCPP_VERSION_MINOR 9 +#define JSONCPP_VERSION_PATCH 3 +#define JSONCPP_VERSION_QUALIFIER +#define JSONCPP_VERSION_HEXA \ + ((JSONCPP_VERSION_MAJOR << 24) | (JSONCPP_VERSION_MINOR << 16) | \ + (JSONCPP_VERSION_PATCH << 8)) + +#ifdef JSONCPP_USING_SECURE_MEMORY +#undef JSONCPP_USING_SECURE_MEMORY +#endif +#define JSONCPP_USING_SECURE_MEMORY 0 +// If non-zero, the library zeroes any memory that it has allocated before +// it frees its memory. + +#endif // JSON_VERSION_H_INCLUDED + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: include/json/version.h +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: include/json/allocator.h +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#ifndef JSON_ALLOCATOR_H_INCLUDED +#define JSON_ALLOCATOR_H_INCLUDED + +#include +#include + +#pragma pack(push, 8) + +namespace Json { + template class SecureAllocator { + public: + // Type definitions + using value_type = T; + using pointer = T*; + using const_pointer = const T*; + using reference = T&; + using const_reference = const T&; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + + /** + * Allocate memory for N items using the standard allocator. + */ + pointer allocate(size_type n) { + // allocate using "global operator new" + return static_cast(::operator new(n * sizeof(T))); + } + + /** + * Release memory which was allocated for N items at pointer P. + * + * The memory block is filled with zeroes before being released. + * The pointer argument is tagged as "volatile" to prevent the + * compiler optimizing out this critical step. + */ + void deallocate(volatile pointer p, size_type n) { + std::memset(p, 0, n * sizeof(T)); + // free using "global operator delete" + ::operator delete(p); + } + + /** + * Construct an item in-place at pointer P. + */ + template void construct(pointer p, Args&&... args) { + // construct using "placement new" and "perfect forwarding" + ::new (static_cast(p)) T(std::forward(args)...); + } + + size_type max_size() const { return size_t(-1) / sizeof(T); } + + pointer address(reference x) const { return std::addressof(x); } + + const_pointer address(const_reference x) const { return std::addressof(x); } + + /** + * Destroy an item in-place at pointer P. + */ + void destroy(pointer p) { + // destroy using "explicit destructor" + p->~T(); + } + + // Boilerplate + SecureAllocator() {} + template SecureAllocator(const SecureAllocator&) {} + template struct rebind { using other = SecureAllocator; }; + }; + + template + bool operator==(const SecureAllocator&, const SecureAllocator&) { + return true; + } + + template + bool operator!=(const SecureAllocator&, const SecureAllocator&) { + return false; + } + +} // namespace Json + +#pragma pack(pop) + +#endif // JSON_ALLOCATOR_H_INCLUDED + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: include/json/allocator.h +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: include/json/config.h +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#ifndef JSON_CONFIG_H_INCLUDED +#define JSON_CONFIG_H_INCLUDED +#include +#include +#include +#include +#include +#include +#include +#include + +// If non-zero, the library uses exceptions to report bad input instead of C +// assertion macros. The default is to use exceptions. +#ifndef JSON_USE_EXCEPTION +#define JSON_USE_EXCEPTION 1 +#endif + +// Temporary, tracked for removal with issue #982. +#ifndef JSON_USE_NULLREF +#define JSON_USE_NULLREF 1 +#endif + +/// If defined, indicates that the source file is amalgamated +/// to prevent private header inclusion. +/// Remarks: it is automatically defined in the generated amalgamated header. +// #define JSON_IS_AMALGAMATION + +// Export macros for DLL visibility +#if defined(JSON_DLL_BUILD) +#if defined(_MSC_VER) || defined(__MINGW32__) +#define JSON_API __declspec(dllexport) +#define JSONCPP_DISABLE_DLL_INTERFACE_WARNING +#elif defined(__GNUC__) || defined(__clang__) +#define JSON_API __attribute__((visibility("default"))) +#endif // if defined(_MSC_VER) + +#elif defined(JSON_DLL) +#if defined(_MSC_VER) || defined(__MINGW32__) +#define JSON_API __declspec(dllimport) +#define JSONCPP_DISABLE_DLL_INTERFACE_WARNING +#endif // if defined(_MSC_VER) +#endif // ifdef JSON_DLL_BUILD + +#if !defined(JSON_API) +#define JSON_API +#endif + +#if defined(_MSC_VER) && _MSC_VER < 1800 +#error \ + "ERROR: Visual Studio 12 (2013) with _MSC_VER=1800 is the oldest supported compiler with sufficient C++11 capabilities" +#endif + +#if defined(_MSC_VER) && _MSC_VER < 1900 +// As recommended at +// https://stackoverflow.com/questions/2915672/snprintf-and-visual-studio-2010 +extern JSON_API int msvc_pre1900_c99_snprintf(char* outBuf, size_t size, + const char* format, ...); +#define jsoncpp_snprintf msvc_pre1900_c99_snprintf +#else +#define jsoncpp_snprintf std::snprintf +#endif + +// If JSON_NO_INT64 is defined, then Json only support C++ "int" type for +// integer +// Storages, and 64 bits integer support is disabled. +// #define JSON_NO_INT64 1 + +// JSONCPP_OVERRIDE is maintained for backwards compatibility of external tools. +// C++11 should be used directly in JSONCPP. +#define JSONCPP_OVERRIDE override + +#ifdef __clang__ +#if __has_extension(attribute_deprecated_with_message) +#define JSONCPP_DEPRECATED(message) __attribute__((deprecated(message))) +#endif +#elif defined(__GNUC__) // not clang (gcc comes later since clang emulates gcc) +#if (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5)) +#define JSONCPP_DEPRECATED(message) __attribute__((deprecated(message))) +#elif (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1)) +#define JSONCPP_DEPRECATED(message) __attribute__((__deprecated__)) +#endif // GNUC version +#elif defined(_MSC_VER) // MSVC (after clang because clang on Windows emulates + // MSVC) +#define JSONCPP_DEPRECATED(message) __declspec(deprecated(message)) +#endif // __clang__ || __GNUC__ || _MSC_VER + +#if !defined(JSONCPP_DEPRECATED) +#define JSONCPP_DEPRECATED(message) +#endif // if !defined(JSONCPP_DEPRECATED) + +#if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ >= 6)) +#define JSON_USE_INT64_DOUBLE_CONVERSION 1 +#endif + +#if !defined(JSON_IS_AMALGAMATION) + +#include "allocator.h" +#include "version.h" + +#endif // if !defined(JSON_IS_AMALGAMATION) + +namespace Json { + using Int = int; + using UInt = unsigned int; +#if defined(JSON_NO_INT64) + using LargestInt = int; + using LargestUInt = unsigned int; +#undef JSON_HAS_INT64 +#else // if defined(JSON_NO_INT64) + // For Microsoft Visual use specific types as long long is not supported +#if defined(_MSC_VER) // Microsoft Visual Studio + using Int64 = __int64; + using UInt64 = unsigned __int64; +#else // if defined(_MSC_VER) // Other platforms, use long long + using Int64 = int64_t; + using UInt64 = uint64_t; +#endif // if defined(_MSC_VER) + using LargestInt = Int64; + using LargestUInt = UInt64; +#define JSON_HAS_INT64 +#endif // if defined(JSON_NO_INT64) + + template + using Allocator = + typename std::conditional, + std::allocator>::type; + using String = std::basic_string, Allocator>; + using IStringStream = + std::basic_istringstream; + using OStringStream = + std::basic_ostringstream; + using IStream = std::istream; + using OStream = std::ostream; +} // namespace Json + +// Legacy names (formerly macros). +using JSONCPP_STRING = Json::String; +using JSONCPP_ISTRINGSTREAM = Json::IStringStream; +using JSONCPP_OSTRINGSTREAM = Json::OStringStream; +using JSONCPP_ISTREAM = Json::IStream; +using JSONCPP_OSTREAM = Json::OStream; + +#endif // JSON_CONFIG_H_INCLUDED + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: include/json/config.h +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: include/json/forwards.h +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#ifndef JSON_FORWARDS_H_INCLUDED +#define JSON_FORWARDS_H_INCLUDED + +#if !defined(JSON_IS_AMALGAMATION) +#include "config.h" +#endif // if !defined(JSON_IS_AMALGAMATION) + +namespace Json { + + // writer.h + class StreamWriter; + class StreamWriterBuilder; + class Writer; + class FastWriter; + class StyledWriter; + class StyledStreamWriter; + + // reader.h + class Reader; + class CharReader; + class CharReaderBuilder; + + // json_features.h + class Features; + + // value.h + using ArrayIndex = unsigned int; + class StaticString; + class Path; + class PathArgument; + class Value; + class ValueIteratorBase; + class ValueIterator; + class ValueConstIterator; + +} // namespace Json + +#endif // JSON_FORWARDS_H_INCLUDED + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: include/json/forwards.h +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: include/json/json_features.h +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#ifndef JSON_FEATURES_H_INCLUDED +#define JSON_FEATURES_H_INCLUDED + +#if !defined(JSON_IS_AMALGAMATION) +#include "forwards.h" +#endif // if !defined(JSON_IS_AMALGAMATION) + +#pragma pack(push, 8) + +namespace Json { + + /** \brief Configuration passed to reader and writer. + * This configuration object can be used to force the Reader or Writer + * to behave in a standard conforming way. + */ + class JSON_API Features { + public: + /** \brief A configuration that allows all features and assumes all strings + * are UTF-8. + * - C & C++ comments are allowed + * - Root object can be any JSON value + * - Assumes Value strings are encoded in UTF-8 + */ + static Features all(); + + /** \brief A configuration that is strictly compatible with the JSON + * specification. + * - Comments are forbidden. + * - Root object must be either an array or an object value. + * - Assumes Value strings are encoded in UTF-8 + */ + static Features strictMode(); + + /** \brief Initialize the configuration like JsonConfig::allFeatures; + */ + Features(); + + /// \c true if comments are allowed. Default: \c true. + bool allowComments_{ true }; + + /// \c true if root must be either an array or an object value. Default: \c + /// false. + bool strictRoot_{ false }; + + /// \c true if dropped null placeholders are allowed. Default: \c false. + bool allowDroppedNullPlaceholders_{ false }; + + /// \c true if numeric object key are allowed. Default: \c false. + bool allowNumericKeys_{ false }; + }; + +} // namespace Json + +#pragma pack(pop) + +#endif // JSON_FEATURES_H_INCLUDED + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: include/json/json_features.h +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: include/json/value.h +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#ifndef JSON_H_INCLUDED +#define JSON_H_INCLUDED + +#if !defined(JSON_IS_AMALGAMATION) +#include "forwards.h" +#endif // if !defined(JSON_IS_AMALGAMATION) + +// Conditional NORETURN attribute on the throw functions would: +// a) suppress false positives from static code analysis +// b) possibly improve optimization opportunities. +#if !defined(JSONCPP_NORETURN) +#if defined(_MSC_VER) && _MSC_VER == 1800 +#define JSONCPP_NORETURN __declspec(noreturn) +#else +#define JSONCPP_NORETURN [[noreturn]] +#endif +#endif + +// Support for '= delete' with template declarations was a late addition +// to the c++11 standard and is rejected by clang 3.8 and Apple clang 8.2 +// even though these declare themselves to be c++11 compilers. +#if !defined(JSONCPP_TEMPLATE_DELETE) +#if defined(__clang__) && defined(__apple_build_version__) +#if __apple_build_version__ <= 8000042 +#define JSONCPP_TEMPLATE_DELETE +#endif +#elif defined(__clang__) +#if __clang_major__ == 3 && __clang_minor__ <= 8 +#define JSONCPP_TEMPLATE_DELETE +#endif +#endif +#if !defined(JSONCPP_TEMPLATE_DELETE) +#define JSONCPP_TEMPLATE_DELETE = delete +#endif +#endif + +#include +#include +#include +#include +#include +#include + +// Disable warning C4251: : needs to have dll-interface to +// be used by... +#if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) +#pragma warning(push) +#pragma warning(disable : 4251) +#endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) + +#pragma pack(push, 8) + +/** \brief JSON (JavaScript Object Notation). + */ +namespace Json { + +#if JSON_USE_EXCEPTION + /** Base class for all exceptions we throw. + * + * We use nothing but these internally. Of course, STL can throw others. + */ + class JSON_API Exception : public std::exception { + public: + Exception(String msg); + ~Exception() noexcept override; + char const* what() const noexcept override; + + protected: + String msg_; + }; + + /** Exceptions which the user cannot easily avoid. + * + * E.g. out-of-memory (when we use malloc), stack-overflow, malicious input + * + * \remark derived from Json::Exception + */ + class JSON_API RuntimeError : public Exception { + public: + RuntimeError(String const& msg); + }; + + /** Exceptions thrown by JSON_ASSERT/JSON_FAIL macros. + * + * These are precondition-violations (user bugs) and internal errors (our bugs). + * + * \remark derived from Json::Exception + */ + class JSON_API LogicError : public Exception { + public: + LogicError(String const& msg); + }; +#endif + + /// used internally + JSONCPP_NORETURN void throwRuntimeError(String const& msg); + /// used internally + JSONCPP_NORETURN void throwLogicError(String const& msg); + + /** \brief Type of the value held by a Value object. + */ + enum ValueType { + nullValue = 0, ///< 'null' value + intValue, ///< signed integer value + uintValue, ///< unsigned integer value + realValue, ///< double value + stringValue, ///< UTF-8 string value + booleanValue, ///< bool value + arrayValue, ///< array value (ordered list) + objectValue ///< object value (collection of name/value pairs). + }; + + enum CommentPlacement { + commentBefore = 0, ///< a comment placed on the line before a value + commentAfterOnSameLine, ///< a comment just after a value on the same line + commentAfter, ///< a comment on the line after a value (only make sense for + /// root value) + numberOfCommentPlacement + }; + + /** \brief Type of precision for formatting of real values. + */ + enum PrecisionType { + significantDigits = 0, ///< we set max number of significant digits in string + decimalPlaces ///< we set max number of digits after "." in string + }; + + /** \brief Lightweight wrapper to tag static string. + * + * Value constructor and objectValue member assignment takes advantage of the + * StaticString and avoid the cost of string duplication when storing the + * string or the member name. + * + * Example of usage: + * \code + * Json::Value aValue( StaticString("some text") ); + * Json::Value object; + * static const StaticString code("code"); + * object[code] = 1234; + * \endcode + */ + class JSON_API StaticString { + public: + explicit StaticString(const char* czstring) : c_str_(czstring) {} + + operator const char* () const { return c_str_; } + + const char* c_str() const { return c_str_; } + + private: + const char* c_str_; + }; + + /** \brief Represents a JSON value. + * + * This class is a discriminated union wrapper that can represents a: + * - signed integer [range: Value::minInt - Value::maxInt] + * - unsigned integer (range: 0 - Value::maxUInt) + * - double + * - UTF-8 string + * - boolean + * - 'null' + * - an ordered list of Value + * - collection of name/value pairs (javascript object) + * + * The type of the held value is represented by a #ValueType and + * can be obtained using type(). + * + * Values of an #objectValue or #arrayValue can be accessed using operator[]() + * methods. + * Non-const methods will automatically create the a #nullValue element + * if it does not exist. + * The sequence of an #arrayValue will be automatically resized and initialized + * with #nullValue. resize() can be used to enlarge or truncate an #arrayValue. + * + * The get() methods can be used to obtain default value in the case the + * required element does not exist. + * + * It is possible to iterate over the list of member keys of an object using + * the getMemberNames() method. + * + * \note #Value string-length fit in size_t, but keys must be < 2^30. + * (The reason is an implementation detail.) A #CharReader will raise an + * exception if a bound is exceeded to avoid security holes in your app, + * but the Value API does *not* check bounds. That is the responsibility + * of the caller. + */ + class JSON_API Value { + friend class ValueIteratorBase; + + public: + using Members = std::vector; + using iterator = ValueIterator; + using const_iterator = ValueConstIterator; + using UInt = Json::UInt; + using Int = Json::Int; +#if defined(JSON_HAS_INT64) + using UInt64 = Json::UInt64; + using Int64 = Json::Int64; +#endif // defined(JSON_HAS_INT64) + using LargestInt = Json::LargestInt; + using LargestUInt = Json::LargestUInt; + using ArrayIndex = Json::ArrayIndex; + + // Required for boost integration, e. g. BOOST_TEST + using value_type = std::string; + +#if JSON_USE_NULLREF + // Binary compatibility kludges, do not use. + static const Value& null; + static const Value& nullRef; +#endif + + // null and nullRef are deprecated, use this instead. + static Value const& nullSingleton(); + + /// Minimum signed integer value that can be stored in a Json::Value. + static constexpr LargestInt minLargestInt = + LargestInt(~(LargestUInt(-1) / 2)); + /// Maximum signed integer value that can be stored in a Json::Value. + static constexpr LargestInt maxLargestInt = LargestInt(LargestUInt(-1) / 2); + /// Maximum unsigned integer value that can be stored in a Json::Value. + static constexpr LargestUInt maxLargestUInt = LargestUInt(-1); + + /// Minimum signed int value that can be stored in a Json::Value. + static constexpr Int minInt = Int(~(UInt(-1) / 2)); + /// Maximum signed int value that can be stored in a Json::Value. + static constexpr Int maxInt = Int(UInt(-1) / 2); + /// Maximum unsigned int value that can be stored in a Json::Value. + static constexpr UInt maxUInt = UInt(-1); + +#if defined(JSON_HAS_INT64) + /// Minimum signed 64 bits int value that can be stored in a Json::Value. + static constexpr Int64 minInt64 = Int64(~(UInt64(-1) / 2)); + /// Maximum signed 64 bits int value that can be stored in a Json::Value. + static constexpr Int64 maxInt64 = Int64(UInt64(-1) / 2); + /// Maximum unsigned 64 bits int value that can be stored in a Json::Value. + static constexpr UInt64 maxUInt64 = UInt64(-1); +#endif // defined(JSON_HAS_INT64) + /// Default precision for real value for string representation. + static constexpr UInt defaultRealPrecision = 17; + // The constant is hard-coded because some compiler have trouble + // converting Value::maxUInt64 to a double correctly (AIX/xlC). + // Assumes that UInt64 is a 64 bits integer. + static constexpr double maxUInt64AsDouble = 18446744073709551615.0; + // Workaround for bug in the NVIDIAs CUDA 9.1 nvcc compiler + // when using gcc and clang backend compilers. CZString + // cannot be defined as private. See issue #486 +#ifdef __NVCC__ + public: +#else + private: +#endif +#ifndef JSONCPP_DOC_EXCLUDE_IMPLEMENTATION + class CZString { + public: + enum DuplicationPolicy { noDuplication = 0, duplicate, duplicateOnCopy }; + CZString(ArrayIndex index); + CZString(char const* str, unsigned length, DuplicationPolicy allocate); + CZString(CZString const& other); + CZString(CZString&& other); + ~CZString(); + CZString& operator=(const CZString& other); + CZString& operator=(CZString&& other); + + bool operator<(CZString const& other) const; + bool operator==(CZString const& other) const; + ArrayIndex index() const; + // const char* c_str() const; ///< \deprecated + char const* data() const; + unsigned length() const; + bool isStaticString() const; + + private: + void swap(CZString& other); + + struct StringStorage { + unsigned policy_ : 2; + unsigned length_ : 30; // 1GB max + }; + + char const* cstr_; // actually, a prefixed string, unless policy is noDup + union { + ArrayIndex index_; + StringStorage storage_; + }; + }; + + public: + typedef std::map ObjectValues; +#endif // ifndef JSONCPP_DOC_EXCLUDE_IMPLEMENTATION + + public: + /** + * \brief Create a default Value of the given type. + * + * This is a very useful constructor. + * To create an empty array, pass arrayValue. + * To create an empty object, pass objectValue. + * Another Value can then be set to this one by assignment. + * This is useful since clear() and resize() will not alter types. + * + * Examples: + * \code + * Json::Value null_value; // null + * Json::Value arr_value(Json::arrayValue); // [] + * Json::Value obj_value(Json::objectValue); // {} + * \endcode + */ + Value(ValueType type = nullValue); + Value(Int value); + Value(UInt value); +#if defined(JSON_HAS_INT64) + Value(Int64 value); + Value(UInt64 value); +#endif // if defined(JSON_HAS_INT64) + Value(double value); + Value(const char* value); ///< Copy til first 0. (NULL causes to seg-fault.) + Value(const char* begin, const char* end); ///< Copy all, incl zeroes. + /** + * \brief Constructs a value from a static string. + * + * Like other value string constructor but do not duplicate the string for + * internal storage. The given string must remain alive after the call to + * this constructor. + * + * \note This works only for null-terminated strings. (We cannot change the + * size of this class, so we have nowhere to store the length, which might be + * computed later for various operations.) + * + * Example of usage: + * \code + * static StaticString foo("some text"); + * Json::Value aValue(foo); + * \endcode + */ + Value(const StaticString& value); + Value(const String& value); + Value(bool value); + Value(const Value& other); + Value(Value&& other); + ~Value(); + + /// \note Overwrite existing comments. To preserve comments, use + /// #swapPayload(). + Value& operator=(const Value& other); + Value& operator=(Value&& other); + + /// Swap everything. + void swap(Value& other); + /// Swap values but leave comments and source offsets in place. + void swapPayload(Value& other); + + /// copy everything. + void copy(const Value& other); + /// copy values but leave comments and source offsets in place. + void copyPayload(const Value& other); + + ValueType type() const; + + /// Compare payload only, not comments etc. + bool operator<(const Value& other) const; + bool operator<=(const Value& other) const; + bool operator>=(const Value& other) const; + bool operator>(const Value& other) const; + bool operator==(const Value& other) const; + bool operator!=(const Value& other) const; + int compare(const Value& other) const; + + const char* asCString() const; ///< Embedded zeroes could cause you trouble! +#if JSONCPP_USING_SECURE_MEMORY + unsigned getCStringLength() const; // Allows you to understand the length of + // the CString +#endif + String asString() const; ///< Embedded zeroes are possible. + /** Get raw char* of string-value. + * \return false if !string. (Seg-fault if str or end are NULL.) + */ + bool getString(char const** begin, char const** end) const; + Int asInt() const; + UInt asUInt() const; +#if defined(JSON_HAS_INT64) + Int64 asInt64() const; + UInt64 asUInt64() const; +#endif // if defined(JSON_HAS_INT64) + LargestInt asLargestInt() const; + LargestUInt asLargestUInt() const; + float asFloat() const; + double asDouble() const; + bool asBool() const; + + bool isNull() const; + bool isBool() const; + bool isInt() const; + bool isInt64() const; + bool isUInt() const; + bool isUInt64() const; + bool isIntegral() const; + bool isDouble() const; + bool isNumeric() const; + bool isString() const; + bool isArray() const; + bool isObject() const; + + /// The `as` and `is` member function templates and specializations. + template T as() const JSONCPP_TEMPLATE_DELETE; + template bool is() const JSONCPP_TEMPLATE_DELETE; + + bool isConvertibleTo(ValueType other) const; + + /// Number of values in array or object + ArrayIndex size() const; + + /// \brief Return true if empty array, empty object, or null; + /// otherwise, false. + bool empty() const; + + /// Return !isNull() + explicit operator bool() const; + + /// Remove all object members and array elements. + /// \pre type() is arrayValue, objectValue, or nullValue + /// \post type() is unchanged + void clear(); + + /// Resize the array to newSize elements. + /// New elements are initialized to null. + /// May only be called on nullValue or arrayValue. + /// \pre type() is arrayValue or nullValue + /// \post type() is arrayValue + void resize(ArrayIndex newSize); + + //@{ + /// Access an array element (zero based index). If the array contains less + /// than index element, then null value are inserted in the array so that + /// its size is index+1. + /// (You may need to say 'value[0u]' to get your compiler to distinguish + /// this from the operator[] which takes a string.) + Value& operator[](ArrayIndex index); + Value& operator[](int index); + //@} + + //@{ + /// Access an array element (zero based index). + /// (You may need to say 'value[0u]' to get your compiler to distinguish + /// this from the operator[] which takes a string.) + const Value& operator[](ArrayIndex index) const; + const Value& operator[](int index) const; + //@} + + /// If the array contains at least index+1 elements, returns the element + /// value, otherwise returns defaultValue. + Value get(ArrayIndex index, const Value& defaultValue) const; + /// Return true if index < size(). + bool isValidIndex(ArrayIndex index) const; + /// \brief Append value to array at the end. + /// + /// Equivalent to jsonvalue[jsonvalue.size()] = value; + Value& append(const Value& value); + Value& append(Value&& value); + + /// \brief Insert value in array at specific index + bool insert(ArrayIndex index, const Value& newValue); + bool insert(ArrayIndex index, Value&& newValue); + + /// Access an object value by name, create a null member if it does not exist. + /// \note Because of our implementation, keys are limited to 2^30 -1 chars. + /// Exceeding that will cause an exception. + Value& operator[](const char* key); + /// Access an object value by name, returns null if there is no member with + /// that name. + const Value& operator[](const char* key) const; + /// Access an object value by name, create a null member if it does not exist. + /// \param key may contain embedded nulls. + Value& operator[](const String& key); + /// Access an object value by name, returns null if there is no member with + /// that name. + /// \param key may contain embedded nulls. + const Value& operator[](const String& key) const; + /** \brief Access an object value by name, create a null member if it does not + * exist. + * + * If the object has no entry for that name, then the member name used to + * store the new entry is not duplicated. + * Example of use: + * \code + * Json::Value object; + * static const StaticString code("code"); + * object[code] = 1234; + * \endcode + */ + Value& operator[](const StaticString& key); + /// Return the member named key if it exist, defaultValue otherwise. + /// \note deep copy + Value get(const char* key, const Value& defaultValue) const; + /// Return the member named key if it exist, defaultValue otherwise. + /// \note deep copy + /// \note key may contain embedded nulls. + Value get(const char* begin, const char* end, + const Value& defaultValue) const; + /// Return the member named key if it exist, defaultValue otherwise. + /// \note deep copy + /// \param key may contain embedded nulls. + Value get(const String& key, const Value& defaultValue) const; + /// Most general and efficient version of isMember()const, get()const, + /// and operator[]const + /// \note As stated elsewhere, behavior is undefined if (end-begin) >= 2^30 + Value const* find(char const* begin, char const* end) const; + /// Most general and efficient version of object-mutators. + /// \note As stated elsewhere, behavior is undefined if (end-begin) >= 2^30 + /// \return non-zero, but JSON_ASSERT if this is neither object nor nullValue. + Value* demand(char const* begin, char const* end); + /// \brief Remove and return the named member. + /// + /// Do nothing if it did not exist. + /// \pre type() is objectValue or nullValue + /// \post type() is unchanged + void removeMember(const char* key); + /// Same as removeMember(const char*) + /// \param key may contain embedded nulls. + void removeMember(const String& key); + /// Same as removeMember(const char* begin, const char* end, Value* removed), + /// but 'key' is null-terminated. + bool removeMember(const char* key, Value* removed); + /** \brief Remove the named map member. + * + * Update 'removed' iff removed. + * \param key may contain embedded nulls. + * \return true iff removed (no exceptions) + */ + bool removeMember(String const& key, Value* removed); + /// Same as removeMember(String const& key, Value* removed) + bool removeMember(const char* begin, const char* end, Value* removed); + /** \brief Remove the indexed array element. + * + * O(n) expensive operations. + * Update 'removed' iff removed. + * \return true if removed (no exceptions) + */ + bool removeIndex(ArrayIndex index, Value* removed); + + /// Return true if the object has a member named key. + /// \note 'key' must be null-terminated. + bool isMember(const char* key) const; + /// Return true if the object has a member named key. + /// \param key may contain embedded nulls. + bool isMember(const String& key) const; + /// Same as isMember(String const& key)const + bool isMember(const char* begin, const char* end) const; + + /// \brief Return a list of the member names. + /// + /// If null, return an empty list. + /// \pre type() is objectValue or nullValue + /// \post if type() was nullValue, it remains nullValue + Members getMemberNames() const; + + /// \deprecated Always pass len. + JSONCPP_DEPRECATED("Use setComment(String const&) instead.") + void setComment(const char* comment, CommentPlacement placement) { + setComment(String(comment, strlen(comment)), placement); + } + /// Comments must be //... or /* ... */ + void setComment(const char* comment, size_t len, CommentPlacement placement) { + setComment(String(comment, len), placement); + } + /// Comments must be //... or /* ... */ + void setComment(String comment, CommentPlacement placement); + bool hasComment(CommentPlacement placement) const; + /// Include delimiters and embedded newlines. + String getComment(CommentPlacement placement) const; + + String toStyledString() const; + + const_iterator begin() const; + const_iterator end() const; + + iterator begin(); + iterator end(); + + // Accessors for the [start, limit) range of bytes within the JSON text from + // which this value was parsed, if any. + void setOffsetStart(ptrdiff_t start); + void setOffsetLimit(ptrdiff_t limit); + ptrdiff_t getOffsetStart() const; + ptrdiff_t getOffsetLimit() const; + + private: + void setType(ValueType v) { + bits_.value_type_ = static_cast(v); + } + bool isAllocated() const { return bits_.allocated_; } + void setIsAllocated(bool v) { bits_.allocated_ = v; } + + void initBasic(ValueType type, bool allocated = false); + void dupPayload(const Value& other); + void releasePayload(); + void dupMeta(const Value& other); + + Value& resolveReference(const char* key); + Value& resolveReference(const char* key, const char* end); + + // struct MemberNamesTransform + //{ + // typedef const char *result_type; + // const char *operator()( const CZString &name ) const + // { + // return name.c_str(); + // } + //}; + + union ValueHolder { + LargestInt int_; + LargestUInt uint_; + double real_; + bool bool_; + char* string_; // if allocated_, ptr to { unsigned, char[] }. + ObjectValues* map_; + } value_; + + struct { + // Really a ValueType, but types should agree for bitfield packing. + unsigned int value_type_ : 8; + // Unless allocated_, string_ must be null-terminated. + unsigned int allocated_ : 1; + } bits_; + + class Comments { + public: + Comments() = default; + Comments(const Comments& that); + Comments(Comments&& that); + Comments& operator=(const Comments& that); + Comments& operator=(Comments&& that); + bool has(CommentPlacement slot) const; + String get(CommentPlacement slot) const; + void set(CommentPlacement slot, String comment); + + private: + using Array = std::array; + std::unique_ptr ptr_; + }; + Comments comments_; + + // [start, limit) byte offsets in the source JSON text from which this Value + // was extracted. + ptrdiff_t start_; + ptrdiff_t limit_; + }; + + template <> inline bool Value::as() const { return asBool(); } + template <> inline bool Value::is() const { return isBool(); } + + template <> inline Int Value::as() const { return asInt(); } + template <> inline bool Value::is() const { return isInt(); } + + template <> inline UInt Value::as() const { return asUInt(); } + template <> inline bool Value::is() const { return isUInt(); } + +#if defined(JSON_HAS_INT64) + template <> inline Int64 Value::as() const { return asInt64(); } + template <> inline bool Value::is() const { return isInt64(); } + + template <> inline UInt64 Value::as() const { return asUInt64(); } + template <> inline bool Value::is() const { return isUInt64(); } +#endif + + template <> inline double Value::as() const { return asDouble(); } + template <> inline bool Value::is() const { return isDouble(); } + + template <> inline String Value::as() const { return asString(); } + template <> inline bool Value::is() const { return isString(); } + + /// These `as` specializations are type conversions, and do not have a + /// corresponding `is`. + template <> inline float Value::as() const { return asFloat(); } + template <> inline const char* Value::as() const { + return asCString(); + } + + /** \brief Experimental and untested: represents an element of the "path" to + * access a node. + */ + class JSON_API PathArgument { + public: + friend class Path; + + PathArgument(); + PathArgument(ArrayIndex index); + PathArgument(const char* key); + PathArgument(String key); + + private: + enum Kind { kindNone = 0, kindIndex, kindKey }; + String key_; + ArrayIndex index_{}; + Kind kind_{ kindNone }; + }; + + /** \brief Experimental and untested: represents a "path" to access a node. + * + * Syntax: + * - "." => root node + * - ".[n]" => elements at index 'n' of root node (an array value) + * - ".name" => member named 'name' of root node (an object value) + * - ".name1.name2.name3" + * - ".[0][1][2].name1[3]" + * - ".%" => member name is provided as parameter + * - ".[%]" => index is provided as parameter + */ + class JSON_API Path { + public: + Path(const String& path, const PathArgument& a1 = PathArgument(), + const PathArgument& a2 = PathArgument(), + const PathArgument& a3 = PathArgument(), + const PathArgument& a4 = PathArgument(), + const PathArgument& a5 = PathArgument()); + + const Value& resolve(const Value& root) const; + Value resolve(const Value& root, const Value& defaultValue) const; + /// Creates the "path" to access the specified node and returns a reference on + /// the node. + Value& make(Value& root) const; + + private: + using InArgs = std::vector; + using Args = std::vector; + + void makePath(const String& path, const InArgs& in); + void addPathInArg(const String& path, const InArgs& in, + InArgs::const_iterator& itInArg, PathArgument::Kind kind); + static void invalidPath(const String& path, int location); + + Args args_; + }; + + /** \brief base class for Value iterators. + * + */ + class JSON_API ValueIteratorBase { + public: + using iterator_category = std::bidirectional_iterator_tag; + using size_t = unsigned int; + using difference_type = int; + using SelfType = ValueIteratorBase; + + bool operator==(const SelfType& other) const { return isEqual(other); } + + bool operator!=(const SelfType& other) const { return !isEqual(other); } + + difference_type operator-(const SelfType& other) const { + return other.computeDistance(*this); + } + + /// Return either the index or the member name of the referenced value as a + /// Value. + Value key() const; + + /// Return the index of the referenced Value, or -1 if it is not an + /// arrayValue. + UInt index() const; + + /// Return the member name of the referenced Value, or "" if it is not an + /// objectValue. + /// \note Avoid `c_str()` on result, as embedded zeroes are possible. + String name() const; + + /// Return the member name of the referenced Value. "" if it is not an + /// objectValue. + /// \deprecated This cannot be used for UTF-8 strings, since there can be + /// embedded nulls. + JSONCPP_DEPRECATED("Use `key = name();` instead.") + char const* memberName() const; + /// Return the member name of the referenced Value, or NULL if it is not an + /// objectValue. + /// \note Better version than memberName(). Allows embedded nulls. + char const* memberName(char const** end) const; + + protected: + /*! Internal utility functions to assist with implementing + * other iterator functions. The const and non-const versions + * of the "deref" protected methods expose the protected + * current_ member variable in a way that can often be + * optimized away by the compiler. + */ + const Value& deref() const; + Value& deref(); + + void increment(); + + void decrement(); + + difference_type computeDistance(const SelfType& other) const; + + bool isEqual(const SelfType& other) const; + + void copy(const SelfType& other); + + private: + Value::ObjectValues::iterator current_; + // Indicates that iterator is for a null value. + bool isNull_{ true }; + + public: + // For some reason, BORLAND needs these at the end, rather + // than earlier. No idea why. + ValueIteratorBase(); + explicit ValueIteratorBase(const Value::ObjectValues::iterator& current); + }; + + /** \brief const iterator for object and array value. + * + */ + class JSON_API ValueConstIterator : public ValueIteratorBase { + friend class Value; + + public: + using value_type = const Value; + // typedef unsigned int size_t; + // typedef int difference_type; + using reference = const Value&; + using pointer = const Value*; + using SelfType = ValueConstIterator; + + ValueConstIterator(); + ValueConstIterator(ValueIterator const& other); + + private: + /*! \internal Use by Value to create an iterator. + */ + explicit ValueConstIterator(const Value::ObjectValues::iterator& current); + + public: + SelfType& operator=(const ValueIteratorBase& other); + + SelfType operator++(int) { + SelfType temp(*this); + ++* this; + return temp; + } + + SelfType operator--(int) { + SelfType temp(*this); + --* this; + return temp; + } + + SelfType& operator--() { + decrement(); + return *this; + } + + SelfType& operator++() { + increment(); + return *this; + } + + reference operator*() const { return deref(); } + + pointer operator->() const { return &deref(); } + }; + + /** \brief Iterator for object and array value. + */ + class JSON_API ValueIterator : public ValueIteratorBase { + friend class Value; + + public: + using value_type = Value; + using size_t = unsigned int; + using difference_type = int; + using reference = Value&; + using pointer = Value*; + using SelfType = ValueIterator; + + ValueIterator(); + explicit ValueIterator(const ValueConstIterator& other); + ValueIterator(const ValueIterator& other); + + private: + /*! \internal Use by Value to create an iterator. + */ + explicit ValueIterator(const Value::ObjectValues::iterator& current); + + public: + SelfType& operator=(const SelfType& other); + + SelfType operator++(int) { + SelfType temp(*this); + ++* this; + return temp; + } + + SelfType operator--(int) { + SelfType temp(*this); + --* this; + return temp; + } + + SelfType& operator--() { + decrement(); + return *this; + } + + SelfType& operator++() { + increment(); + return *this; + } + + /*! The return value of non-const iterators can be + * changed, so the these functions are not const + * because the returned references/pointers can be used + * to change state of the base class. + */ + reference operator*() { return deref(); } + pointer operator->() { return &deref(); } + }; + + inline void swap(Value& a, Value& b) { a.swap(b); } + +} // namespace Json + +#pragma pack(pop) + +#if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) +#pragma warning(pop) +#endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) + +#endif // JSON_H_INCLUDED + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: include/json/value.h +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: include/json/reader.h +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#ifndef JSON_READER_H_INCLUDED +#define JSON_READER_H_INCLUDED + +#if !defined(JSON_IS_AMALGAMATION) +#include "json_features.h" +#include "value.h" +#endif // if !defined(JSON_IS_AMALGAMATION) +#include +#include +#include +#include +#include + +// Disable warning C4251: : needs to have dll-interface to +// be used by... +#if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) +#pragma warning(push) +#pragma warning(disable : 4251) +#endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) + +#pragma pack(push, 8) + +namespace Json { + + /** \brief Unserialize a JSON document into a + * Value. + * + * \deprecated Use CharReader and CharReaderBuilder. + */ + + class JSONCPP_DEPRECATED( + "Use CharReader and CharReaderBuilder instead.") JSON_API Reader { + public: + using Char = char; + using Location = const Char*; + + /** \brief An error tagged with where in the JSON text it was encountered. + * + * The offsets give the [start, limit) range of bytes within the text. Note + * that this is bytes, not codepoints. + */ + struct StructuredError { + ptrdiff_t offset_start; + ptrdiff_t offset_limit; + String message; + }; + + /** \brief Constructs a Reader allowing all features for parsing. + */ + JSONCPP_DEPRECATED("Use CharReader and CharReaderBuilder instead") + Reader(); + + /** \brief Constructs a Reader allowing the specified feature set for parsing. + */ + JSONCPP_DEPRECATED("Use CharReader and CharReaderBuilder instead") + Reader(const Features & features); + + /** \brief Read a Value from a JSON + * document. + * + * \param document UTF-8 encoded string containing the document + * to read. + * \param[out] root Contains the root value of the document if it + * was successfully parsed. + * \param collectComments \c true to collect comment and allow writing + * them back during serialization, \c false to + * discard comments. This parameter is ignored + * if Features::allowComments_ is \c false. + * \return \c true if the document was successfully parsed, \c false if an + * error occurred. + */ + bool parse(const std::string & document, Value & root, + bool collectComments = true); + + /** \brief Read a Value from a JSON + * document. + * + * \param beginDoc Pointer on the beginning of the UTF-8 encoded + * string of the document to read. + * \param endDoc Pointer on the end of the UTF-8 encoded string + * of the document to read. Must be >= beginDoc. + * \param[out] root Contains the root value of the document if it + * was successfully parsed. + * \param collectComments \c true to collect comment and allow writing + * them back during serialization, \c false to + * discard comments. This parameter is ignored + * if Features::allowComments_ is \c false. + * \return \c true if the document was successfully parsed, \c false if an + * error occurred. + */ + bool parse(const char* beginDoc, const char* endDoc, Value & root, + bool collectComments = true); + + /// \brief Parse from input stream. + /// \see Json::operator>>(std::istream&, Json::Value&). + bool parse(IStream & is, Value & root, bool collectComments = true); + + /** \brief Returns a user friendly string that list errors in the parsed + * document. + * + * \return Formatted error message with the list of errors with their + * location in the parsed document. An empty string is returned if no error + * occurred during parsing. + * \deprecated Use getFormattedErrorMessages() instead (typo fix). + */ + JSONCPP_DEPRECATED("Use getFormattedErrorMessages() instead.") + String getFormatedErrorMessages() const; + + /** \brief Returns a user friendly string that list errors in the parsed + * document. + * + * \return Formatted error message with the list of errors with their + * location in the parsed document. An empty string is returned if no error + * occurred during parsing. + */ + String getFormattedErrorMessages() const; + + /** \brief Returns a vector of structured errors encountered while parsing. + * + * \return A (possibly empty) vector of StructuredError objects. Currently + * only one error can be returned, but the caller should tolerate multiple + * errors. This can occur if the parser recovers from a non-fatal parse + * error and then encounters additional errors. + */ + std::vector getStructuredErrors() const; + + /** \brief Add a semantic error message. + * + * \param value JSON Value location associated with the error + * \param message The error message. + * \return \c true if the error was successfully added, \c false if the Value + * offset exceeds the document size. + */ + bool pushError(const Value & value, const String & message); + + /** \brief Add a semantic error message with extra context. + * + * \param value JSON Value location associated with the error + * \param message The error message. + * \param extra Additional JSON Value location to contextualize the error + * \return \c true if the error was successfully added, \c false if either + * Value offset exceeds the document size. + */ + bool pushError(const Value & value, const String & message, const Value & extra); + + /** \brief Return whether there are any errors. + * + * \return \c true if there are no errors to report \c false if errors have + * occurred. + */ + bool good() const; + + private: + enum TokenType { + tokenEndOfStream = 0, + tokenObjectBegin, + tokenObjectEnd, + tokenArrayBegin, + tokenArrayEnd, + tokenString, + tokenNumber, + tokenTrue, + tokenFalse, + tokenNull, + tokenArraySeparator, + tokenMemberSeparator, + tokenComment, + tokenError + }; + + class Token { + public: + TokenType type_; + Location start_; + Location end_; + }; + + class ErrorInfo { + public: + Token token_; + String message_; + Location extra_; + }; + + using Errors = std::deque; + + bool readToken(Token & token); + void skipSpaces(); + bool match(const Char * pattern, int patternLength); + bool readComment(); + bool readCStyleComment(); + bool readCppStyleComment(); + bool readString(); + void readNumber(); + bool readValue(); + bool readObject(Token & token); + bool readArray(Token & token); + bool decodeNumber(Token & token); + bool decodeNumber(Token & token, Value & decoded); + bool decodeString(Token & token); + bool decodeString(Token & token, String & decoded); + bool decodeDouble(Token & token); + bool decodeDouble(Token & token, Value & decoded); + bool decodeUnicodeCodePoint(Token & token, Location & current, Location end, + unsigned int& unicode); + bool decodeUnicodeEscapeSequence(Token & token, Location & current, + Location end, unsigned int& unicode); + bool addError(const String & message, Token & token, Location extra = nullptr); + bool recoverFromError(TokenType skipUntilToken); + bool addErrorAndRecover(const String & message, Token & token, + TokenType skipUntilToken); + void skipUntilSpace(); + Value& currentValue(); + Char getNextChar(); + void getLocationLineAndColumn(Location location, int& line, + int& column) const; + String getLocationLineAndColumn(Location location) const; + void addComment(Location begin, Location end, CommentPlacement placement); + void skipCommentTokens(Token & token); + + static bool containsNewLine(Location begin, Location end); + static String normalizeEOL(Location begin, Location end); + + using Nodes = std::stack; + Nodes nodes_; + Errors errors_; + String document_; + Location begin_{}; + Location end_{}; + Location current_{}; + Location lastValueEnd_{}; + Value* lastValue_{}; + String commentsBefore_; + Features features_; + bool collectComments_{}; + }; // Reader + + /** Interface for reading JSON from a char array. + */ + class JSON_API CharReader { + public: + virtual ~CharReader() = default; + /** \brief Read a Value from a JSON + * document. The document must be a UTF-8 encoded string containing the + * document to read. + * + * \param beginDoc Pointer on the beginning of the UTF-8 encoded string + * of the document to read. + * \param endDoc Pointer on the end of the UTF-8 encoded string of the + * document to read. Must be >= beginDoc. + * \param[out] root Contains the root value of the document if it was + * successfully parsed. + * \param[out] errs Formatted error messages (if not NULL) a user + * friendly string that lists errors in the parsed + * document. + * \return \c true if the document was successfully parsed, \c false if an + * error occurred. + */ + virtual bool parse(char const* beginDoc, char const* endDoc, Value* root, + String* errs) = 0; + + class JSON_API Factory { + public: + virtual ~Factory() = default; + /** \brief Allocate a CharReader via operator new(). + * \throw std::exception if something goes wrong (e.g. invalid settings) + */ + virtual CharReader* newCharReader() const = 0; + }; // Factory + }; // CharReader + + /** \brief Build a CharReader implementation. + * + * Usage: + * \code + * using namespace Json; + * CharReaderBuilder builder; + * builder["collectComments"] = false; + * Value value; + * String errs; + * bool ok = parseFromStream(builder, std::cin, &value, &errs); + * \endcode + */ + class JSON_API CharReaderBuilder : public CharReader::Factory { + public: + // Note: We use a Json::Value so that we can add data-members to this class + // without a major version bump. + /** Configuration of this builder. + * These are case-sensitive. + * Available settings (case-sensitive): + * - `"collectComments": false or true` + * - true to collect comment and allow writing them back during + * serialization, false to discard comments. This parameter is ignored + * if allowComments is false. + * - `"allowComments": false or true` + * - true if comments are allowed. + * - `"allowTrailingCommas": false or true` + * - true if trailing commas in objects and arrays are allowed. + * - `"strictRoot": false or true` + * - true if root must be either an array or an object value + * - `"allowDroppedNullPlaceholders": false or true` + * - true if dropped null placeholders are allowed. (See + * StreamWriterBuilder.) + * - `"allowNumericKeys": false or true` + * - true if numeric object keys are allowed. + * - `"allowSingleQuotes": false or true` + * - true if '' are allowed for strings (both keys and values) + * - `"stackLimit": integer` + * - Exceeding stackLimit (recursive depth of `readValue()`) will cause an + * exception. + * - This is a security issue (seg-faults caused by deeply nested JSON), so + * the default is low. + * - `"failIfExtra": false or true` + * - If true, `parse()` returns false when extra non-whitespace trails the + * JSON value in the input string. + * - `"rejectDupKeys": false or true` + * - If true, `parse()` returns false when a key is duplicated within an + * object. + * - `"allowSpecialFloats": false or true` + * - If true, special float values (NaNs and infinities) are allowed and + * their values are lossfree restorable. + * + * You can examine 'settings_` yourself to see the defaults. You can also + * write and read them just like any JSON Value. + * \sa setDefaults() + */ + Json::Value settings_; + + CharReaderBuilder(); + ~CharReaderBuilder() override; + + CharReader* newCharReader() const override; + + /** \return true if 'settings' are legal and consistent; + * otherwise, indicate bad settings via 'invalid'. + */ + bool validate(Json::Value* invalid) const; + + /** A simple way to update a specific setting. + */ + Value& operator[](const String& key); + + /** Called by ctor, but you can use this to reset settings_. + * \pre 'settings' != NULL (but Json::null is fine) + * \remark Defaults: + * \snippet src/lib_json/json_reader.cpp CharReaderBuilderDefaults + */ + static void setDefaults(Json::Value* settings); + /** Same as old Features::strictMode(). + * \pre 'settings' != NULL (but Json::null is fine) + * \remark Defaults: + * \snippet src/lib_json/json_reader.cpp CharReaderBuilderStrictMode + */ + static void strictMode(Json::Value* settings); + }; + + /** Consume entire stream and use its begin/end. + * Someday we might have a real StreamReader, but for now this + * is convenient. + */ + bool JSON_API parseFromStream(CharReader::Factory const&, IStream&, Value* root, + String* errs); + + /** \brief Read from 'sin' into 'root'. + * + * Always keep comments from the input JSON. + * + * This can be used to read a file into a particular sub-object. + * For example: + * \code + * Json::Value root; + * cin >> root["dir"]["file"]; + * cout << root; + * \endcode + * Result: + * \verbatim + * { + * "dir": { + * "file": { + * // The input stream JSON would be nested here. + * } + * } + * } + * \endverbatim + * \throw std::exception on parse error. + * \see Json::operator<<() + */ + JSON_API IStream& operator>>(IStream&, Value&); + +} // namespace Json + +#pragma pack(pop) + +#if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) +#pragma warning(pop) +#endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) + +#endif // JSON_READER_H_INCLUDED + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: include/json/reader.h +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: include/json/writer.h +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#ifndef JSON_WRITER_H_INCLUDED +#define JSON_WRITER_H_INCLUDED + +#if !defined(JSON_IS_AMALGAMATION) +#include "value.h" +#endif // if !defined(JSON_IS_AMALGAMATION) +#include +#include +#include + +// Disable warning C4251: : needs to have dll-interface to +// be used by... +#if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) && defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable : 4251) +#endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) + +#pragma pack(push, 8) + +namespace Json { + + class Value; + + /** + * + * Usage: + * \code + * using namespace Json; + * void writeToStdout(StreamWriter::Factory const& factory, Value const& value) + * { std::unique_ptr const writer( factory.newStreamWriter()); + * writer->write(value, &std::cout); + * std::cout << std::endl; // add lf and flush + * } + * \endcode + */ + class JSON_API StreamWriter { + protected: + OStream* sout_; // not owned; will not delete + public: + StreamWriter(); + virtual ~StreamWriter(); + /** Write Value into document as configured in sub-class. + * Do not take ownership of sout, but maintain a reference during function. + * \pre sout != NULL + * \return zero on success (For now, we always return zero, so check the + * stream instead.) \throw std::exception possibly, depending on + * configuration + */ + virtual int write(Value const& root, OStream* sout) = 0; + + /** \brief A simple abstract factory. + */ + class JSON_API Factory { + public: + virtual ~Factory(); + /** \brief Allocate a CharReader via operator new(). + * \throw std::exception if something goes wrong (e.g. invalid settings) + */ + virtual StreamWriter* newStreamWriter() const = 0; + }; // Factory + }; // StreamWriter + + /** \brief Write into stringstream, then return string, for convenience. + * A StreamWriter will be created from the factory, used, and then deleted. + */ + String JSON_API writeString(StreamWriter::Factory const& factory, + Value const& root); + + /** \brief Build a StreamWriter implementation. + + * Usage: + * \code + * using namespace Json; + * Value value = ...; + * StreamWriterBuilder builder; + * builder["commentStyle"] = "None"; + * builder["indentation"] = " "; // or whatever you like + * std::unique_ptr writer( + * builder.newStreamWriter()); + * writer->write(value, &std::cout); + * std::cout << std::endl; // add lf and flush + * \endcode + */ + class JSON_API StreamWriterBuilder : public StreamWriter::Factory { + public: + // Note: We use a Json::Value so that we can add data-members to this class + // without a major version bump. + /** Configuration of this builder. + * Available settings (case-sensitive): + * - "commentStyle": "None" or "All" + * - "indentation": "". + * - Setting this to an empty string also omits newline characters. + * - "enableYAMLCompatibility": false or true + * - slightly change the whitespace around colons + * - "dropNullPlaceholders": false or true + * - Drop the "null" string from the writer's output for nullValues. + * Strictly speaking, this is not valid JSON. But when the output is being + * fed to a browser's JavaScript, it makes for smaller output and the + * browser can handle the output just fine. + * - "useSpecialFloats": false or true + * - If true, outputs non-finite floating point values in the following way: + * NaN values as "NaN", positive infinity as "Infinity", and negative + * infinity as "-Infinity". + * - "precision": int + * - Number of precision digits for formatting of real values. + * - "precisionType": "significant"(default) or "decimal" + * - Type of precision for formatting of real values. + + * You can examine 'settings_` yourself + * to see the defaults. You can also write and read them just like any + * JSON Value. + * \sa setDefaults() + */ + Json::Value settings_; + + StreamWriterBuilder(); + ~StreamWriterBuilder() override; + + /** + * \throw std::exception if something goes wrong (e.g. invalid settings) + */ + StreamWriter* newStreamWriter() const override; + + /** \return true if 'settings' are legal and consistent; + * otherwise, indicate bad settings via 'invalid'. + */ + bool validate(Json::Value* invalid) const; + /** A simple way to update a specific setting. + */ + Value& operator[](const String& key); + + /** Called by ctor, but you can use this to reset settings_. + * \pre 'settings' != NULL (but Json::null is fine) + * \remark Defaults: + * \snippet src/lib_json/json_writer.cpp StreamWriterBuilderDefaults + */ + static void setDefaults(Json::Value* settings); + }; + + /** \brief Abstract class for writers. + * \deprecated Use StreamWriter. (And really, this is an implementation detail.) + */ + class JSONCPP_DEPRECATED("Use StreamWriter instead") JSON_API Writer { + public: + virtual ~Writer(); + + virtual String write(const Value & root) = 0; + }; + + /** \brief Outputs a Value in JSON format + *without formatting (not human friendly). + * + * The JSON document is written in a single line. It is not intended for 'human' + *consumption, + * but may be useful to support feature such as RPC where bandwidth is limited. + * \sa Reader, Value + * \deprecated Use StreamWriterBuilder. + */ +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable : 4996) // Deriving from deprecated class +#endif + class JSONCPP_DEPRECATED("Use StreamWriterBuilder instead") JSON_API FastWriter + : public Writer{ + public: + FastWriter(); + ~FastWriter() override = default; + + void enableYAMLCompatibility(); + + /** \brief Drop the "null" string from the writer's output for nullValues. + * Strictly speaking, this is not valid JSON. But when the output is being + * fed to a browser's JavaScript, it makes for smaller output and the + * browser can handle the output just fine. + */ + void dropNullPlaceholders(); + + void omitEndingLineFeed(); + + public: // overridden from Writer + String write(const Value& root) override; + + private: + void writeValue(const Value& value); + + String document_; + bool yamlCompatibilityEnabled_{false}; + bool dropNullPlaceholders_{false}; + bool omitEndingLineFeed_{false}; + }; +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + + /** \brief Writes a Value in JSON format in a + *human friendly way. + * + * The rules for line break and indent are as follow: + * - Object value: + * - if empty then print {} without indent and line break + * - if not empty the print '{', line break & indent, print one value per + *line + * and then unindent and line break and print '}'. + * - Array value: + * - if empty then print [] without indent and line break + * - if the array contains no object value, empty array or some other value + *types, + * and all the values fit on one lines, then print the array on a single + *line. + * - otherwise, it the values do not fit on one line, or the array contains + * object or non empty array, then print one value per line. + * + * If the Value have comments then they are outputed according to their + *#CommentPlacement. + * + * \sa Reader, Value, Value::setComment() + * \deprecated Use StreamWriterBuilder. + */ +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable : 4996) // Deriving from deprecated class +#endif + class JSONCPP_DEPRECATED("Use StreamWriterBuilder instead") JSON_API + StyledWriter : public Writer{ + public: + StyledWriter(); + ~StyledWriter() override = default; + + public: // overridden from Writer + /** \brief Serialize a Value in JSON format. + * \param root Value to serialize. + * \return String containing the JSON document that represents the root value. + */ + String write(const Value & root) override; + + private: + void writeValue(const Value & value); + void writeArrayValue(const Value & value); + bool isMultilineArray(const Value & value); + void pushValue(const String & value); + void writeIndent(); + void writeWithIndent(const String & value); + void indent(); + void unindent(); + void writeCommentBeforeValue(const Value & root); + void writeCommentAfterValueOnSameLine(const Value & root); + static bool hasCommentForValue(const Value & value); + static String normalizeEOL(const String & text); + + using ChildValues = std::vector; + + ChildValues childValues_; + String document_; + String indentString_; + unsigned int rightMargin_{74}; + unsigned int indentSize_{3}; + bool addChildValues_{false}; + }; +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + + /** \brief Writes a Value in JSON format in a + human friendly way, + to a stream rather than to a string. + * + * The rules for line break and indent are as follow: + * - Object value: + * - if empty then print {} without indent and line break + * - if not empty the print '{', line break & indent, print one value per + line + * and then unindent and line break and print '}'. + * - Array value: + * - if empty then print [] without indent and line break + * - if the array contains no object value, empty array or some other value + types, + * and all the values fit on one lines, then print the array on a single + line. + * - otherwise, it the values do not fit on one line, or the array contains + * object or non empty array, then print one value per line. + * + * If the Value have comments then they are outputed according to their + #CommentPlacement. + * + * \sa Reader, Value, Value::setComment() + * \deprecated Use StreamWriterBuilder. + */ +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable : 4996) // Deriving from deprecated class +#endif + class JSONCPP_DEPRECATED("Use StreamWriterBuilder instead") JSON_API + StyledStreamWriter { + public: + /** + * \param indentation Each level will be indented by this amount extra. + */ + StyledStreamWriter(String indentation = "\t"); + ~StyledStreamWriter() = default; + + public: + /** \brief Serialize a Value in JSON format. + * \param out Stream to write to. (Can be ostringstream, e.g.) + * \param root Value to serialize. + * \note There is no point in deriving from Writer, since write() should not + * return a value. + */ + void write(OStream & out, const Value & root); + + private: + void writeValue(const Value & value); + void writeArrayValue(const Value & value); + bool isMultilineArray(const Value & value); + void pushValue(const String & value); + void writeIndent(); + void writeWithIndent(const String & value); + void indent(); + void unindent(); + void writeCommentBeforeValue(const Value & root); + void writeCommentAfterValueOnSameLine(const Value & root); + static bool hasCommentForValue(const Value & value); + static String normalizeEOL(const String & text); + + using ChildValues = std::vector; + + ChildValues childValues_; + OStream* document_; + String indentString_; + unsigned int rightMargin_{ 74 }; + String indentation_; + bool addChildValues_ : 1; + bool indented_ : 1; + }; +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + +#if defined(JSON_HAS_INT64) + String JSON_API valueToString(Int value); + String JSON_API valueToString(UInt value); +#endif // if defined(JSON_HAS_INT64) + String JSON_API valueToString(LargestInt value); + String JSON_API valueToString(LargestUInt value); + String JSON_API valueToString( + double value, unsigned int precision = Value::defaultRealPrecision, + PrecisionType precisionType = PrecisionType::significantDigits); + String JSON_API valueToString(bool value); + String JSON_API valueToQuotedString(const char* value); + + /// \brief Output using the StyledStreamWriter. + /// \see Json::operator>>() + JSON_API OStream& operator<<(OStream&, const Value& root); + +} // namespace Json + +#pragma pack(pop) + +#if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) +#pragma warning(pop) +#endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) + +#endif // JSON_WRITER_H_INCLUDED + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: include/json/writer.h +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: include/json/assertions.h +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#ifndef JSON_ASSERTIONS_H_INCLUDED +#define JSON_ASSERTIONS_H_INCLUDED + +#include +#include + +#if !defined(JSON_IS_AMALGAMATION) +#include "config.h" +#endif // if !defined(JSON_IS_AMALGAMATION) + +/** It should not be possible for a maliciously designed file to + * cause an abort() or seg-fault, so these macros are used only + * for pre-condition violations and internal logic errors. + */ +#if JSON_USE_EXCEPTION + + // @todo <= add detail about condition in exception +#define JSON_ASSERT(condition) \ + do { \ + if (!(condition)) { \ + Json::throwLogicError("assert json failed"); \ + } \ + } while (0) + +#define JSON_FAIL_MESSAGE(message) \ + do { \ + OStringStream oss; \ + oss << message; \ + Json::throwLogicError(oss.str()); \ + abort(); \ + } while (0) + +#else // JSON_USE_EXCEPTION + +#define JSON_ASSERT(condition) assert(condition) + + // The call to assert() will show the failure message in debug builds. In + // release builds we abort, for a core-dump or debugger. +#define JSON_FAIL_MESSAGE(message) \ + { \ + OStringStream oss; \ + oss << message; \ + assert(false && oss.str().c_str()); \ + abort(); \ + } + +#endif + +#define JSON_ASSERT_MESSAGE(condition, message) \ + do { \ + if (!(condition)) { \ + JSON_FAIL_MESSAGE(message); \ + } \ + } while (0) + +#endif // JSON_ASSERTIONS_H_INCLUDED + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: include/json/assertions.h +// ////////////////////////////////////////////////////////////////////// + + + + + +#endif //ifndef JSON_AMALGAMATED_H_INCLUDED diff --git a/src/JsonCppLib/jsoncpp.cpp b/src/JsonCppLib/jsoncpp.cpp new file mode 100644 index 0000000000..c8ca8a4d09 --- /dev/null +++ b/src/JsonCppLib/jsoncpp.cpp @@ -0,0 +1,5380 @@ +/// Json-cpp amalgamated source (http://jsoncpp.sourceforge.net/). +/// It is intended to be used with #include "json/json.h" + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: LICENSE +// ////////////////////////////////////////////////////////////////////// + +/* +The JsonCpp library's source code, including accompanying documentation, +tests and demonstration applications, are licensed under the following +conditions... + +Baptiste Lepilleur and The JsonCpp Authors explicitly disclaim copyright in all +jurisdictions which recognize such a disclaimer. In such jurisdictions, +this software is released into the Public Domain. + +In jurisdictions which do not recognize Public Domain property (e.g. Germany as of +2010), this software is Copyright (c) 2007-2010 by Baptiste Lepilleur and +The JsonCpp Authors, and is released under the terms of the MIT License (see below). + +In jurisdictions which recognize Public Domain property, the user of this +software may choose to accept it either as 1) Public Domain, 2) under the +conditions of the MIT License (see below), or 3) under the terms of dual +Public Domain/MIT License conditions described here, as they choose. + +The MIT License is about as close to Public Domain as a license can get, and is +described in clear, concise terms at: + + http://en.wikipedia.org/wiki/MIT_License + +The full text of the MIT License follows: + +======================================================================== +Copyright (c) 2007-2010 Baptiste Lepilleur and The JsonCpp Authors + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, +modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +======================================================================== +(END LICENSE TEXT) + +The MIT license is compatible with both the GPL and commercial +software, affording one all of the rights of Public Domain with the +minor nuisance of being required to keep the above copyright notice +and license text in the source code. Note also that by accepting the +Public Domain "license" you can re-license your copy using whatever +license you like. + +*/ + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: LICENSE +// ////////////////////////////////////////////////////////////////////// + + + + + + +#include "json/json.h" + +#ifndef JSON_IS_AMALGAMATION +#error "Compile with -I PATH_TO_JSON_DIRECTORY" +#endif + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: src/lib_json/json_tool.h +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#ifndef LIB_JSONCPP_JSON_TOOL_H_INCLUDED +#define LIB_JSONCPP_JSON_TOOL_H_INCLUDED + +#if !defined(JSON_IS_AMALGAMATION) +#include +#endif + +// Also support old flag NO_LOCALE_SUPPORT +#ifdef NO_LOCALE_SUPPORT +#define JSONCPP_NO_LOCALE_SUPPORT +#endif + +#ifndef JSONCPP_NO_LOCALE_SUPPORT +#include +#endif + +/* This header provides common string manipulation support, such as UTF-8, + * portable conversion from/to string... + * + * It is an internal header that must not be exposed. + */ + +namespace Json { + static inline char getDecimalPoint() { +#ifdef JSONCPP_NO_LOCALE_SUPPORT + return '\0'; +#else + struct lconv* lc = localeconv(); + return lc ? *(lc->decimal_point) : '\0'; +#endif + } + + /// Converts a unicode code-point to UTF-8. + static inline String codePointToUTF8(unsigned int cp) { + String result; + + // based on description from http://en.wikipedia.org/wiki/UTF-8 + + if (cp <= 0x7f) { + result.resize(1); + result[0] = static_cast(cp); + } + else if (cp <= 0x7FF) { + result.resize(2); + result[1] = static_cast(0x80 | (0x3f & cp)); + result[0] = static_cast(0xC0 | (0x1f & (cp >> 6))); + } + else if (cp <= 0xFFFF) { + result.resize(3); + result[2] = static_cast(0x80 | (0x3f & cp)); + result[1] = static_cast(0x80 | (0x3f & (cp >> 6))); + result[0] = static_cast(0xE0 | (0xf & (cp >> 12))); + } + else if (cp <= 0x10FFFF) { + result.resize(4); + result[3] = static_cast(0x80 | (0x3f & cp)); + result[2] = static_cast(0x80 | (0x3f & (cp >> 6))); + result[1] = static_cast(0x80 | (0x3f & (cp >> 12))); + result[0] = static_cast(0xF0 | (0x7 & (cp >> 18))); + } + + return result; + } + + enum { + /// Constant that specify the size of the buffer that must be passed to + /// uintToString. + uintToStringBufferSize = 3 * sizeof(LargestUInt) + 1 + }; + + // Defines a char buffer for use with uintToString(). + using UIntToStringBuffer = char[uintToStringBufferSize]; + + /** Converts an unsigned integer to string. + * @param value Unsigned integer to convert to string + * @param current Input/Output string buffer. + * Must have at least uintToStringBufferSize chars free. + */ + static inline void uintToString(LargestUInt value, char*& current) { + *--current = 0; + do { + *--current = static_cast(value % 10U + static_cast('0')); + value /= 10; + } while (value != 0); + } + + /** Change ',' to '.' everywhere in buffer. + * + * We had a sophisticated way, but it did not work in WinCE. + * @see https://github.com/open-source-parsers/jsoncpp/pull/9 + */ + template Iter fixNumericLocale(Iter begin, Iter end) { + for (; begin != end; ++begin) { + if (*begin == ',') { + *begin = '.'; + } + } + return begin; + } + + template void fixNumericLocaleInput(Iter begin, Iter end) { + char decimalPoint = getDecimalPoint(); + if (decimalPoint == '\0' || decimalPoint == '.') { + return; + } + for (; begin != end; ++begin) { + if (*begin == '.') { + *begin = decimalPoint; + } + } + } + + /** + * Return iterator that would be the new end of the range [begin,end), if we + * were to delete zeros in the end of string, but not the last zero before '.'. + */ + template Iter fixZerosInTheEnd(Iter begin, Iter end) { + for (; begin != end; --end) { + if (*(end - 1) != '0') { + return end; + } + // Don't delete the last zero before the decimal point. + if (begin != (end - 1) && *(end - 2) == '.') { + return end; + } + } + return end; + } + +} // namespace Json + +#endif // LIB_JSONCPP_JSON_TOOL_H_INCLUDED + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: src/lib_json/json_tool.h +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: src/lib_json/json_reader.cpp +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2011 Baptiste Lepilleur and The JsonCpp Authors +// Copyright (C) 2016 InfoTeCS JSC. All rights reserved. +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#if !defined(JSON_IS_AMALGAMATION) +#include "json_tool.h" +#include +#include +#include +#endif // if !defined(JSON_IS_AMALGAMATION) +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#if __cplusplus >= 201103L + +#if !defined(sscanf) +#define sscanf std::sscanf +#endif + +#endif //__cplusplus + +#if defined(_MSC_VER) +#if !defined(_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES) +#define _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES 1 +#endif //_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES +#endif //_MSC_VER + +#if defined(_MSC_VER) +// Disable warning about strdup being deprecated. +#pragma warning(disable : 4996) +#endif + +// Define JSONCPP_DEPRECATED_STACK_LIMIT as an appropriate integer at compile +// time to change the stack limit +#if !defined(JSONCPP_DEPRECATED_STACK_LIMIT) +#define JSONCPP_DEPRECATED_STACK_LIMIT 1000 +#endif + +static size_t const stackLimit_g = +JSONCPP_DEPRECATED_STACK_LIMIT; // see readValue() + +namespace Json { + +#if __cplusplus >= 201103L || (defined(_CPPLIB_VER) && _CPPLIB_VER >= 520) + using CharReaderPtr = std::unique_ptr; +#else + using CharReaderPtr = std::auto_ptr; +#endif + + // Implementation of class Features + // //////////////////////////////// + + Features::Features() = default; + + Features Features::all() { return {}; } + + Features Features::strictMode() { + Features features; + features.allowComments_ = false; + features.strictRoot_ = true; + features.allowDroppedNullPlaceholders_ = false; + features.allowNumericKeys_ = false; + return features; + } + + // Implementation of class Reader + // //////////////////////////////// + + bool Reader::containsNewLine(Reader::Location begin, Reader::Location end) { + return std::any_of(begin, end, [](char b) { return b == '\n' || b == '\r'; }); + } + + // Class Reader + // ////////////////////////////////////////////////////////////////// + + Reader::Reader() : features_(Features::all()) {} + + Reader::Reader(const Features& features) : features_(features) {} + + bool Reader::parse(const std::string& document, Value& root, + bool collectComments) { + document_.assign(document.begin(), document.end()); + const char* begin = document_.c_str(); + const char* end = begin + document_.length(); + return parse(begin, end, root, collectComments); + } + + bool Reader::parse(std::istream& is, Value& root, bool collectComments) { + // std::istream_iterator begin(is); + // std::istream_iterator end; + // Those would allow streamed input from a file, if parse() were a + // template function. + + // Since String is reference-counted, this at least does not + // create an extra copy. + String doc; + std::getline(is, doc, static_cast EOF); + return parse(doc.data(), doc.data() + doc.size(), root, collectComments); + } + + bool Reader::parse(const char* beginDoc, const char* endDoc, Value& root, + bool collectComments) { + if (!features_.allowComments_) { + collectComments = false; + } + + begin_ = beginDoc; + end_ = endDoc; + collectComments_ = collectComments; + current_ = begin_; + lastValueEnd_ = nullptr; + lastValue_ = nullptr; + commentsBefore_.clear(); + errors_.clear(); + while (!nodes_.empty()) + nodes_.pop(); + nodes_.push(&root); + + bool successful = readValue(); + Token token; + skipCommentTokens(token); + if (collectComments_ && !commentsBefore_.empty()) + root.setComment(commentsBefore_, commentAfter); + if (features_.strictRoot_) { + if (!root.isArray() && !root.isObject()) { + // Set error location to start of doc, ideally should be first token found + // in doc + token.type_ = tokenError; + token.start_ = beginDoc; + token.end_ = endDoc; + addError( + "A valid JSON document must be either an array or an object value.", + token); + return false; + } + } + return successful; + } + + bool Reader::readValue() { + // readValue() may call itself only if it calls readObject() or ReadArray(). + // These methods execute nodes_.push() just before and nodes_.pop)() just + // after calling readValue(). parse() executes one nodes_.push(), so > instead + // of >=. + if (nodes_.size() > stackLimit_g) + throwRuntimeError("Exceeded stackLimit in readValue()."); + + Token token; + skipCommentTokens(token); + bool successful = true; + + if (collectComments_ && !commentsBefore_.empty()) { + currentValue().setComment(commentsBefore_, commentBefore); + commentsBefore_.clear(); + } + + switch (token.type_) { + case tokenObjectBegin: + successful = readObject(token); + currentValue().setOffsetLimit(current_ - begin_); + break; + case tokenArrayBegin: + successful = readArray(token); + currentValue().setOffsetLimit(current_ - begin_); + break; + case tokenNumber: + successful = decodeNumber(token); + break; + case tokenString: + successful = decodeString(token); + break; + case tokenTrue: { + Value v(true); + currentValue().swapPayload(v); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + } break; + case tokenFalse: { + Value v(false); + currentValue().swapPayload(v); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + } break; + case tokenNull: { + Value v; + currentValue().swapPayload(v); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + } break; + case tokenArraySeparator: + case tokenObjectEnd: + case tokenArrayEnd: + if (features_.allowDroppedNullPlaceholders_) { + // "Un-read" the current token and mark the current value as a null + // token. + current_--; + Value v; + currentValue().swapPayload(v); + currentValue().setOffsetStart(current_ - begin_ - 1); + currentValue().setOffsetLimit(current_ - begin_); + break; + } // Else, fall through... + default: + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + return addError("Syntax error: value, object or array expected.", token); + } + + if (collectComments_) { + lastValueEnd_ = current_; + lastValue_ = ¤tValue(); + } + + return successful; + } + + void Reader::skipCommentTokens(Token& token) { + if (features_.allowComments_) { + do { + readToken(token); + } while (token.type_ == tokenComment); + } + else { + readToken(token); + } + } + + bool Reader::readToken(Token& token) { + skipSpaces(); + token.start_ = current_; + Char c = getNextChar(); + bool ok = true; + switch (c) { + case '{': + token.type_ = tokenObjectBegin; + break; + case '}': + token.type_ = tokenObjectEnd; + break; + case '[': + token.type_ = tokenArrayBegin; + break; + case ']': + token.type_ = tokenArrayEnd; + break; + case '"': + token.type_ = tokenString; + ok = readString(); + break; + case '/': + token.type_ = tokenComment; + ok = readComment(); + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '-': + token.type_ = tokenNumber; + readNumber(); + break; + case 't': + token.type_ = tokenTrue; + ok = match("rue", 3); + break; + case 'f': + token.type_ = tokenFalse; + ok = match("alse", 4); + break; + case 'n': + token.type_ = tokenNull; + ok = match("ull", 3); + break; + case ',': + token.type_ = tokenArraySeparator; + break; + case ':': + token.type_ = tokenMemberSeparator; + break; + case 0: + token.type_ = tokenEndOfStream; + break; + default: + ok = false; + break; + } + if (!ok) + token.type_ = tokenError; + token.end_ = current_; + return ok; + } + + void Reader::skipSpaces() { + while (current_ != end_) { + Char c = *current_; + if (c == ' ' || c == '\t' || c == '\r' || c == '\n') + ++current_; + else + break; + } + } + + bool Reader::match(const Char* pattern, int patternLength) { + if (end_ - current_ < patternLength) + return false; + int index = patternLength; + while (index--) + if (current_[index] != pattern[index]) + return false; + current_ += patternLength; + return true; + } + + bool Reader::readComment() { + Location commentBegin = current_ - 1; + Char c = getNextChar(); + bool successful = false; + if (c == '*') + successful = readCStyleComment(); + else if (c == '/') + successful = readCppStyleComment(); + if (!successful) + return false; + + if (collectComments_) { + CommentPlacement placement = commentBefore; + if (lastValueEnd_ && !containsNewLine(lastValueEnd_, commentBegin)) { + if (c != '*' || !containsNewLine(commentBegin, current_)) + placement = commentAfterOnSameLine; + } + + addComment(commentBegin, current_, placement); + } + return true; + } + + String Reader::normalizeEOL(Reader::Location begin, Reader::Location end) { + String normalized; + normalized.reserve(static_cast(end - begin)); + Reader::Location current = begin; + while (current != end) { + char c = *current++; + if (c == '\r') { + if (current != end && *current == '\n') + // convert dos EOL + ++current; + // convert Mac EOL + normalized += '\n'; + } + else { + normalized += c; + } + } + return normalized; + } + + void Reader::addComment(Location begin, Location end, + CommentPlacement placement) { + assert(collectComments_); + const String& normalized = normalizeEOL(begin, end); + if (placement == commentAfterOnSameLine) { + assert(lastValue_ != nullptr); + lastValue_->setComment(normalized, placement); + } + else { + commentsBefore_ += normalized; + } + } + + bool Reader::readCStyleComment() { + while ((current_ + 1) < end_) { + Char c = getNextChar(); + if (c == '*' && *current_ == '/') + break; + } + return getNextChar() == '/'; + } + + bool Reader::readCppStyleComment() { + while (current_ != end_) { + Char c = getNextChar(); + if (c == '\n') + break; + if (c == '\r') { + // Consume DOS EOL. It will be normalized in addComment. + if (current_ != end_ && *current_ == '\n') + getNextChar(); + // Break on Moc OS 9 EOL. + break; + } + } + return true; + } + + void Reader::readNumber() { + Location p = current_; + char c = '0'; // stopgap for already consumed character + // integral part + while (c >= '0' && c <= '9') + c = (current_ = p) < end_ ? *p++ : '\0'; + // fractional part + if (c == '.') { + c = (current_ = p) < end_ ? *p++ : '\0'; + while (c >= '0' && c <= '9') + c = (current_ = p) < end_ ? *p++ : '\0'; + } + // exponential part + if (c == 'e' || c == 'E') { + c = (current_ = p) < end_ ? *p++ : '\0'; + if (c == '+' || c == '-') + c = (current_ = p) < end_ ? *p++ : '\0'; + while (c >= '0' && c <= '9') + c = (current_ = p) < end_ ? *p++ : '\0'; + } + } + + bool Reader::readString() { + Char c = '\0'; + while (current_ != end_) { + c = getNextChar(); + if (c == '\\') + getNextChar(); + else if (c == '"') + break; + } + return c == '"'; + } + + bool Reader::readObject(Token& token) { + Token tokenName; + String name; + Value init(objectValue); + currentValue().swapPayload(init); + currentValue().setOffsetStart(token.start_ - begin_); + while (readToken(tokenName)) { + bool initialTokenOk = true; + while (tokenName.type_ == tokenComment && initialTokenOk) + initialTokenOk = readToken(tokenName); + if (!initialTokenOk) + break; + if (tokenName.type_ == tokenObjectEnd && name.empty()) // empty object + return true; + name.clear(); + if (tokenName.type_ == tokenString) { + if (!decodeString(tokenName, name)) + return recoverFromError(tokenObjectEnd); + } + else if (tokenName.type_ == tokenNumber && features_.allowNumericKeys_) { + Value numberName; + if (!decodeNumber(tokenName, numberName)) + return recoverFromError(tokenObjectEnd); + name = numberName.asString(); + } + else { + break; + } + + Token colon; + if (!readToken(colon) || colon.type_ != tokenMemberSeparator) { + return addErrorAndRecover("Missing ':' after object member name", colon, + tokenObjectEnd); + } + Value& value = currentValue()[name]; + nodes_.push(&value); + bool ok = readValue(); + nodes_.pop(); + if (!ok) // error already set + return recoverFromError(tokenObjectEnd); + + Token comma; + if (!readToken(comma) || + (comma.type_ != tokenObjectEnd && comma.type_ != tokenArraySeparator && + comma.type_ != tokenComment)) { + return addErrorAndRecover("Missing ',' or '}' in object declaration", + comma, tokenObjectEnd); + } + bool finalizeTokenOk = true; + while (comma.type_ == tokenComment && finalizeTokenOk) + finalizeTokenOk = readToken(comma); + if (comma.type_ == tokenObjectEnd) + return true; + } + return addErrorAndRecover("Missing '}' or object member name", tokenName, + tokenObjectEnd); + } + + bool Reader::readArray(Token& token) { + Value init(arrayValue); + currentValue().swapPayload(init); + currentValue().setOffsetStart(token.start_ - begin_); + skipSpaces(); + if (current_ != end_ && *current_ == ']') // empty array + { + Token endArray; + readToken(endArray); + return true; + } + int index = 0; + for (;;) { + Value& value = currentValue()[index++]; + nodes_.push(&value); + bool ok = readValue(); + nodes_.pop(); + if (!ok) // error already set + return recoverFromError(tokenArrayEnd); + + Token currentToken; + // Accept Comment after last item in the array. + ok = readToken(currentToken); + while (currentToken.type_ == tokenComment && ok) { + ok = readToken(currentToken); + } + bool badTokenType = (currentToken.type_ != tokenArraySeparator && + currentToken.type_ != tokenArrayEnd); + if (!ok || badTokenType) { + return addErrorAndRecover("Missing ',' or ']' in array declaration", + currentToken, tokenArrayEnd); + } + if (currentToken.type_ == tokenArrayEnd) + break; + } + return true; + } + + bool Reader::decodeNumber(Token& token) { + Value decoded; + if (!decodeNumber(token, decoded)) + return false; + currentValue().swapPayload(decoded); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + return true; + } + + bool Reader::decodeNumber(Token& token, Value& decoded) { + // Attempts to parse the number as an integer. If the number is + // larger than the maximum supported value of an integer then + // we decode the number as a double. + Location current = token.start_; + bool isNegative = *current == '-'; + if (isNegative) + ++current; + // TODO: Help the compiler do the div and mod at compile time or get rid of + // them. + Value::LargestUInt maxIntegerValue = + isNegative ? Value::LargestUInt(Value::maxLargestInt) + 1 + : Value::maxLargestUInt; + Value::LargestUInt threshold = maxIntegerValue / 10; + Value::LargestUInt value = 0; + while (current < token.end_) { + Char c = *current++; + if (c < '0' || c > '9') + return decodeDouble(token, decoded); + auto digit(static_cast(c - '0')); + if (value >= threshold) { + // We've hit or exceeded the max value divided by 10 (rounded down). If + // a) we've only just touched the limit, b) this is the last digit, and + // c) it's small enough to fit in that rounding delta, we're okay. + // Otherwise treat this number as a double to avoid overflow. + if (value > threshold || current != token.end_ || + digit > maxIntegerValue % 10) { + return decodeDouble(token, decoded); + } + } + value = value * 10 + digit; + } + if (isNegative && value == maxIntegerValue) + decoded = Value::minLargestInt; + else if (isNegative) + decoded = -Value::LargestInt(value); + else if (value <= Value::LargestUInt(Value::maxInt)) + decoded = Value::LargestInt(value); + else + decoded = value; + return true; + } + + bool Reader::decodeDouble(Token& token) { + Value decoded; + if (!decodeDouble(token, decoded)) + return false; + currentValue().swapPayload(decoded); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + return true; + } + + bool Reader::decodeDouble(Token& token, Value& decoded) { + double value = 0; + String buffer(token.start_, token.end_); + IStringStream is(buffer); + if (!(is >> value)) + return addError( + "'" + String(token.start_, token.end_) + "' is not a number.", token); + decoded = value; + return true; + } + + bool Reader::decodeString(Token& token) { + String decoded_string; + if (!decodeString(token, decoded_string)) + return false; + Value decoded(decoded_string); + currentValue().swapPayload(decoded); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + return true; + } + + bool Reader::decodeString(Token& token, String& decoded) { + decoded.reserve(static_cast(token.end_ - token.start_ - 2)); + Location current = token.start_ + 1; // skip '"' + Location end = token.end_ - 1; // do not include '"' + while (current != end) { + Char c = *current++; + if (c == '"') + break; + if (c == '\\') { + if (current == end) + return addError("Empty escape sequence in string", token, current); + Char escape = *current++; + switch (escape) { + case '"': + decoded += '"'; + break; + case '/': + decoded += '/'; + break; + case '\\': + decoded += '\\'; + break; + case 'b': + decoded += '\b'; + break; + case 'f': + decoded += '\f'; + break; + case 'n': + decoded += '\n'; + break; + case 'r': + decoded += '\r'; + break; + case 't': + decoded += '\t'; + break; + case 'u': { + unsigned int unicode; + if (!decodeUnicodeCodePoint(token, current, end, unicode)) + return false; + decoded += codePointToUTF8(unicode); + } break; + default: + return addError("Bad escape sequence in string", token, current); + } + } + else { + decoded += c; + } + } + return true; + } + + bool Reader::decodeUnicodeCodePoint(Token& token, Location& current, + Location end, unsigned int& unicode) { + + if (!decodeUnicodeEscapeSequence(token, current, end, unicode)) + return false; + if (unicode >= 0xD800 && unicode <= 0xDBFF) { + // surrogate pairs + if (end - current < 6) + return addError( + "additional six characters expected to parse unicode surrogate pair.", + token, current); + if (*(current++) == '\\' && *(current++) == 'u') { + unsigned int surrogatePair; + if (decodeUnicodeEscapeSequence(token, current, end, surrogatePair)) { + unicode = 0x10000 + ((unicode & 0x3FF) << 10) + (surrogatePair & 0x3FF); + } + else + return false; + } + else + return addError("expecting another \\u token to begin the second half of " + "a unicode surrogate pair", + token, current); + } + return true; + } + + bool Reader::decodeUnicodeEscapeSequence(Token& token, Location& current, + Location end, + unsigned int& ret_unicode) { + if (end - current < 4) + return addError( + "Bad unicode escape sequence in string: four digits expected.", token, + current); + int unicode = 0; + for (int index = 0; index < 4; ++index) { + Char c = *current++; + unicode *= 16; + if (c >= '0' && c <= '9') + unicode += c - '0'; + else if (c >= 'a' && c <= 'f') + unicode += c - 'a' + 10; + else if (c >= 'A' && c <= 'F') + unicode += c - 'A' + 10; + else + return addError( + "Bad unicode escape sequence in string: hexadecimal digit expected.", + token, current); + } + ret_unicode = static_cast(unicode); + return true; + } + + bool Reader::addError(const String& message, Token& token, Location extra) { + ErrorInfo info; + info.token_ = token; + info.message_ = message; + info.extra_ = extra; + errors_.push_back(info); + return false; + } + + bool Reader::recoverFromError(TokenType skipUntilToken) { + size_t const errorCount = errors_.size(); + Token skip; + for (;;) { + if (!readToken(skip)) + errors_.resize(errorCount); // discard errors caused by recovery + if (skip.type_ == skipUntilToken || skip.type_ == tokenEndOfStream) + break; + } + errors_.resize(errorCount); + return false; + } + + bool Reader::addErrorAndRecover(const String& message, Token& token, + TokenType skipUntilToken) { + addError(message, token); + return recoverFromError(skipUntilToken); + } + + Value& Reader::currentValue() { return *(nodes_.top()); } + + Reader::Char Reader::getNextChar() { + if (current_ == end_) + return 0; + return *current_++; + } + + void Reader::getLocationLineAndColumn(Location location, int& line, + int& column) const { + Location current = begin_; + Location lastLineStart = current; + line = 0; + while (current < location && current != end_) { + Char c = *current++; + if (c == '\r') { + if (*current == '\n') + ++current; + lastLineStart = current; + ++line; + } + else if (c == '\n') { + lastLineStart = current; + ++line; + } + } + // column & line start at 1 + column = int(location - lastLineStart) + 1; + ++line; + } + + String Reader::getLocationLineAndColumn(Location location) const { + int line, column; + getLocationLineAndColumn(location, line, column); + char buffer[18 + 16 + 16 + 1]; + jsoncpp_snprintf(buffer, sizeof(buffer), "Line %d, Column %d", line, column); + return buffer; + } + + // Deprecated. Preserved for backward compatibility + String Reader::getFormatedErrorMessages() const { + return getFormattedErrorMessages(); + } + + String Reader::getFormattedErrorMessages() const { + String formattedMessage; + for (const auto& error : errors_) { + formattedMessage += + "* " + getLocationLineAndColumn(error.token_.start_) + "\n"; + formattedMessage += " " + error.message_ + "\n"; + if (error.extra_) + formattedMessage += + "See " + getLocationLineAndColumn(error.extra_) + " for detail.\n"; + } + return formattedMessage; + } + + std::vector Reader::getStructuredErrors() const { + std::vector allErrors; + for (const auto& error : errors_) { + Reader::StructuredError structured; + structured.offset_start = error.token_.start_ - begin_; + structured.offset_limit = error.token_.end_ - begin_; + structured.message = error.message_; + allErrors.push_back(structured); + } + return allErrors; + } + + bool Reader::pushError(const Value& value, const String& message) { + ptrdiff_t const length = end_ - begin_; + if (value.getOffsetStart() > length || value.getOffsetLimit() > length) + return false; + Token token; + token.type_ = tokenError; + token.start_ = begin_ + value.getOffsetStart(); + token.end_ = begin_ + value.getOffsetLimit(); + ErrorInfo info; + info.token_ = token; + info.message_ = message; + info.extra_ = nullptr; + errors_.push_back(info); + return true; + } + + bool Reader::pushError(const Value& value, const String& message, + const Value& extra) { + ptrdiff_t const length = end_ - begin_; + if (value.getOffsetStart() > length || value.getOffsetLimit() > length || + extra.getOffsetLimit() > length) + return false; + Token token; + token.type_ = tokenError; + token.start_ = begin_ + value.getOffsetStart(); + token.end_ = begin_ + value.getOffsetLimit(); + ErrorInfo info; + info.token_ = token; + info.message_ = message; + info.extra_ = begin_ + extra.getOffsetStart(); + errors_.push_back(info); + return true; + } + + bool Reader::good() const { return errors_.empty(); } + + // Originally copied from the Features class (now deprecated), used internally + // for features implementation. + class OurFeatures { + public: + static OurFeatures all(); + bool allowComments_; + bool allowTrailingCommas_; + bool strictRoot_; + bool allowDroppedNullPlaceholders_; + bool allowNumericKeys_; + bool allowSingleQuotes_; + bool failIfExtra_; + bool rejectDupKeys_; + bool allowSpecialFloats_; + bool skipBom_; + size_t stackLimit_; + }; // OurFeatures + + OurFeatures OurFeatures::all() { return {}; } + + // Implementation of class Reader + // //////////////////////////////// + + // Originally copied from the Reader class (now deprecated), used internally + // for implementing JSON reading. + class OurReader { + public: + using Char = char; + using Location = const Char*; + struct StructuredError { + ptrdiff_t offset_start; + ptrdiff_t offset_limit; + String message; + }; + + explicit OurReader(OurFeatures const& features); + bool parse(const char* beginDoc, const char* endDoc, Value& root, + bool collectComments = true); + String getFormattedErrorMessages() const; + std::vector getStructuredErrors() const; + + private: + OurReader(OurReader const&); // no impl + void operator=(OurReader const&); // no impl + + enum TokenType { + tokenEndOfStream = 0, + tokenObjectBegin, + tokenObjectEnd, + tokenArrayBegin, + tokenArrayEnd, + tokenString, + tokenNumber, + tokenTrue, + tokenFalse, + tokenNull, + tokenNaN, + tokenPosInf, + tokenNegInf, + tokenArraySeparator, + tokenMemberSeparator, + tokenComment, + tokenError + }; + + class Token { + public: + TokenType type_; + Location start_; + Location end_; + }; + + class ErrorInfo { + public: + Token token_; + String message_; + Location extra_; + }; + + using Errors = std::deque; + + bool readToken(Token& token); + void skipSpaces(); + void skipBom(bool skipBom); + bool match(const Char* pattern, int patternLength); + bool readComment(); + bool readCStyleComment(bool* containsNewLineResult); + bool readCppStyleComment(); + bool readString(); + bool readStringSingleQuote(); + bool readNumber(bool checkInf); + bool readValue(); + bool readObject(Token& token); + bool readArray(Token& token); + bool decodeNumber(Token& token); + bool decodeNumber(Token& token, Value& decoded); + bool decodeString(Token& token); + bool decodeString(Token& token, String& decoded); + bool decodeDouble(Token& token); + bool decodeDouble(Token& token, Value& decoded); + bool decodeUnicodeCodePoint(Token& token, Location& current, Location end, + unsigned int& unicode); + bool decodeUnicodeEscapeSequence(Token& token, Location& current, + Location end, unsigned int& unicode); + bool addError(const String& message, Token& token, Location extra = nullptr); + bool recoverFromError(TokenType skipUntilToken); + bool addErrorAndRecover(const String& message, Token& token, + TokenType skipUntilToken); + void skipUntilSpace(); + Value& currentValue(); + Char getNextChar(); + void getLocationLineAndColumn(Location location, int& line, + int& column) const; + String getLocationLineAndColumn(Location location) const; + void addComment(Location begin, Location end, CommentPlacement placement); + void skipCommentTokens(Token& token); + + static String normalizeEOL(Location begin, Location end); + static bool containsNewLine(Location begin, Location end); + + using Nodes = std::stack; + + Nodes nodes_{}; + Errors errors_{}; + String document_{}; + Location begin_ = nullptr; + Location end_ = nullptr; + Location current_ = nullptr; + Location lastValueEnd_ = nullptr; + Value* lastValue_ = nullptr; + bool lastValueHasAComment_ = false; + String commentsBefore_{}; + + OurFeatures const features_; + bool collectComments_ = false; + }; // OurReader + + // complete copy of Read impl, for OurReader + + bool OurReader::containsNewLine(OurReader::Location begin, + OurReader::Location end) { + return std::any_of(begin, end, [](char b) { return b == '\n' || b == '\r'; }); + } + + OurReader::OurReader(OurFeatures const& features) : features_(features) {} + + bool OurReader::parse(const char* beginDoc, const char* endDoc, Value& root, + bool collectComments) { + if (!features_.allowComments_) { + collectComments = false; + } + + begin_ = beginDoc; + end_ = endDoc; + collectComments_ = collectComments; + current_ = begin_; + lastValueEnd_ = nullptr; + lastValue_ = nullptr; + commentsBefore_.clear(); + errors_.clear(); + while (!nodes_.empty()) + nodes_.pop(); + nodes_.push(&root); + + // skip byte order mark if it exists at the beginning of the UTF-8 text. + skipBom(features_.skipBom_); + bool successful = readValue(); + nodes_.pop(); + Token token; + skipCommentTokens(token); + if (features_.failIfExtra_ && (token.type_ != tokenEndOfStream)) { + addError("Extra non-whitespace after JSON value.", token); + return false; + } + if (collectComments_ && !commentsBefore_.empty()) + root.setComment(commentsBefore_, commentAfter); + if (features_.strictRoot_) { + if (!root.isArray() && !root.isObject()) { + // Set error location to start of doc, ideally should be first token found + // in doc + token.type_ = tokenError; + token.start_ = beginDoc; + token.end_ = endDoc; + addError( + "A valid JSON document must be either an array or an object value.", + token); + return false; + } + } + return successful; + } + + bool OurReader::readValue() { + // To preserve the old behaviour we cast size_t to int. + if (nodes_.size() > features_.stackLimit_) + throwRuntimeError("Exceeded stackLimit in readValue()."); + Token token; + skipCommentTokens(token); + bool successful = true; + + if (collectComments_ && !commentsBefore_.empty()) { + currentValue().setComment(commentsBefore_, commentBefore); + commentsBefore_.clear(); + } + + switch (token.type_) { + case tokenObjectBegin: + successful = readObject(token); + currentValue().setOffsetLimit(current_ - begin_); + break; + case tokenArrayBegin: + successful = readArray(token); + currentValue().setOffsetLimit(current_ - begin_); + break; + case tokenNumber: + successful = decodeNumber(token); + break; + case tokenString: + successful = decodeString(token); + break; + case tokenTrue: { + Value v(true); + currentValue().swapPayload(v); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + } break; + case tokenFalse: { + Value v(false); + currentValue().swapPayload(v); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + } break; + case tokenNull: { + Value v; + currentValue().swapPayload(v); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + } break; + case tokenNaN: { + Value v(std::numeric_limits::quiet_NaN()); + currentValue().swapPayload(v); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + } break; + case tokenPosInf: { + Value v(std::numeric_limits::infinity()); + currentValue().swapPayload(v); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + } break; + case tokenNegInf: { + Value v(-std::numeric_limits::infinity()); + currentValue().swapPayload(v); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + } break; + case tokenArraySeparator: + case tokenObjectEnd: + case tokenArrayEnd: + if (features_.allowDroppedNullPlaceholders_) { + // "Un-read" the current token and mark the current value as a null + // token. + current_--; + Value v; + currentValue().swapPayload(v); + currentValue().setOffsetStart(current_ - begin_ - 1); + currentValue().setOffsetLimit(current_ - begin_); + break; + } // else, fall through ... + default: + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + return addError("Syntax error: value, object or array expected.", token); + } + + if (collectComments_) { + lastValueEnd_ = current_; + lastValueHasAComment_ = false; + lastValue_ = ¤tValue(); + } + + return successful; + } + + void OurReader::skipCommentTokens(Token& token) { + if (features_.allowComments_) { + do { + readToken(token); + } while (token.type_ == tokenComment); + } + else { + readToken(token); + } + } + + bool OurReader::readToken(Token& token) { + skipSpaces(); + token.start_ = current_; + Char c = getNextChar(); + bool ok = true; + switch (c) { + case '{': + token.type_ = tokenObjectBegin; + break; + case '}': + token.type_ = tokenObjectEnd; + break; + case '[': + token.type_ = tokenArrayBegin; + break; + case ']': + token.type_ = tokenArrayEnd; + break; + case '"': + token.type_ = tokenString; + ok = readString(); + break; + case '\'': + if (features_.allowSingleQuotes_) { + token.type_ = tokenString; + ok = readStringSingleQuote(); + } + else { + // If we don't allow single quotes, this is a failure case. + ok = false; + } + break; + case '/': + token.type_ = tokenComment; + ok = readComment(); + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + token.type_ = tokenNumber; + readNumber(false); + break; + case '-': + if (readNumber(true)) { + token.type_ = tokenNumber; + } + else { + token.type_ = tokenNegInf; + ok = features_.allowSpecialFloats_ && match("nfinity", 7); + } + break; + case '+': + if (readNumber(true)) { + token.type_ = tokenNumber; + } + else { + token.type_ = tokenPosInf; + ok = features_.allowSpecialFloats_ && match("nfinity", 7); + } + break; + case 't': + token.type_ = tokenTrue; + ok = match("rue", 3); + break; + case 'f': + token.type_ = tokenFalse; + ok = match("alse", 4); + break; + case 'n': + token.type_ = tokenNull; + ok = match("ull", 3); + break; + case 'N': + if (features_.allowSpecialFloats_) { + token.type_ = tokenNaN; + ok = match("aN", 2); + } + else { + ok = false; + } + break; + case 'I': + if (features_.allowSpecialFloats_) { + token.type_ = tokenPosInf; + ok = match("nfinity", 7); + } + else { + ok = false; + } + break; + case ',': + token.type_ = tokenArraySeparator; + break; + case ':': + token.type_ = tokenMemberSeparator; + break; + case 0: + token.type_ = tokenEndOfStream; + break; + default: + ok = false; + break; + } + if (!ok) + token.type_ = tokenError; + token.end_ = current_; + return ok; + } + + void OurReader::skipSpaces() { + while (current_ != end_) { + Char c = *current_; + if (c == ' ' || c == '\t' || c == '\r' || c == '\n') + ++current_; + else + break; + } + } + + void OurReader::skipBom(bool skipBom) { + // The default behavior is to skip BOM. + if (skipBom) { + if ((end_ - begin_) >= 3 && strncmp(begin_, "\xEF\xBB\xBF", 3) == 0) { + begin_ += 3; + current_ = begin_; + } + } + } + + bool OurReader::match(const Char* pattern, int patternLength) { + if (end_ - current_ < patternLength) + return false; + int index = patternLength; + while (index--) + if (current_[index] != pattern[index]) + return false; + current_ += patternLength; + return true; + } + + bool OurReader::readComment() { + const Location commentBegin = current_ - 1; + const Char c = getNextChar(); + bool successful = false; + bool cStyleWithEmbeddedNewline = false; + + const bool isCStyleComment = (c == '*'); + const bool isCppStyleComment = (c == '/'); + if (isCStyleComment) { + successful = readCStyleComment(&cStyleWithEmbeddedNewline); + } + else if (isCppStyleComment) { + successful = readCppStyleComment(); + } + + if (!successful) + return false; + + if (collectComments_) { + CommentPlacement placement = commentBefore; + + if (!lastValueHasAComment_) { + if (lastValueEnd_ && !containsNewLine(lastValueEnd_, commentBegin)) { + if (isCppStyleComment || !cStyleWithEmbeddedNewline) { + placement = commentAfterOnSameLine; + lastValueHasAComment_ = true; + } + } + } + + addComment(commentBegin, current_, placement); + } + return true; + } + + String OurReader::normalizeEOL(OurReader::Location begin, + OurReader::Location end) { + String normalized; + normalized.reserve(static_cast(end - begin)); + OurReader::Location current = begin; + while (current != end) { + char c = *current++; + if (c == '\r') { + if (current != end && *current == '\n') + // convert dos EOL + ++current; + // convert Mac EOL + normalized += '\n'; + } + else { + normalized += c; + } + } + return normalized; + } + + void OurReader::addComment(Location begin, Location end, + CommentPlacement placement) { + assert(collectComments_); + const String& normalized = normalizeEOL(begin, end); + if (placement == commentAfterOnSameLine) { + assert(lastValue_ != nullptr); + lastValue_->setComment(normalized, placement); + } + else { + commentsBefore_ += normalized; + } + } + + bool OurReader::readCStyleComment(bool* containsNewLineResult) { + *containsNewLineResult = false; + + while ((current_ + 1) < end_) { + Char c = getNextChar(); + if (c == '*' && *current_ == '/') + break; + if (c == '\n') + *containsNewLineResult = true; + } + + return getNextChar() == '/'; + } + + bool OurReader::readCppStyleComment() { + while (current_ != end_) { + Char c = getNextChar(); + if (c == '\n') + break; + if (c == '\r') { + // Consume DOS EOL. It will be normalized in addComment. + if (current_ != end_ && *current_ == '\n') + getNextChar(); + // Break on Moc OS 9 EOL. + break; + } + } + return true; + } + + bool OurReader::readNumber(bool checkInf) { + Location p = current_; + if (checkInf && p != end_ && *p == 'I') { + current_ = ++p; + return false; + } + char c = '0'; // stopgap for already consumed character + // integral part + while (c >= '0' && c <= '9') + c = (current_ = p) < end_ ? *p++ : '\0'; + // fractional part + if (c == '.') { + c = (current_ = p) < end_ ? *p++ : '\0'; + while (c >= '0' && c <= '9') + c = (current_ = p) < end_ ? *p++ : '\0'; + } + // exponential part + if (c == 'e' || c == 'E') { + c = (current_ = p) < end_ ? *p++ : '\0'; + if (c == '+' || c == '-') + c = (current_ = p) < end_ ? *p++ : '\0'; + while (c >= '0' && c <= '9') + c = (current_ = p) < end_ ? *p++ : '\0'; + } + return true; + } + bool OurReader::readString() { + Char c = 0; + while (current_ != end_) { + c = getNextChar(); + if (c == '\\') + getNextChar(); + else if (c == '"') + break; + } + return c == '"'; + } + + bool OurReader::readStringSingleQuote() { + Char c = 0; + while (current_ != end_) { + c = getNextChar(); + if (c == '\\') + getNextChar(); + else if (c == '\'') + break; + } + return c == '\''; + } + + bool OurReader::readObject(Token& token) { + Token tokenName; + String name; + Value init(objectValue); + currentValue().swapPayload(init); + currentValue().setOffsetStart(token.start_ - begin_); + while (readToken(tokenName)) { + bool initialTokenOk = true; + while (tokenName.type_ == tokenComment && initialTokenOk) + initialTokenOk = readToken(tokenName); + if (!initialTokenOk) + break; + if (tokenName.type_ == tokenObjectEnd && + (name.empty() || + features_.allowTrailingCommas_)) // empty object or trailing comma + return true; + name.clear(); + if (tokenName.type_ == tokenString) { + if (!decodeString(tokenName, name)) + return recoverFromError(tokenObjectEnd); + } + else if (tokenName.type_ == tokenNumber && features_.allowNumericKeys_) { + Value numberName; + if (!decodeNumber(tokenName, numberName)) + return recoverFromError(tokenObjectEnd); + name = numberName.asString(); + } + else { + break; + } + if (name.length() >= (1U << 30)) + throwRuntimeError("keylength >= 2^30"); + if (features_.rejectDupKeys_ && currentValue().isMember(name)) { + String msg = "Duplicate key: '" + name + "'"; + return addErrorAndRecover(msg, tokenName, tokenObjectEnd); + } + + Token colon; + if (!readToken(colon) || colon.type_ != tokenMemberSeparator) { + return addErrorAndRecover("Missing ':' after object member name", colon, + tokenObjectEnd); + } + Value& value = currentValue()[name]; + nodes_.push(&value); + bool ok = readValue(); + nodes_.pop(); + if (!ok) // error already set + return recoverFromError(tokenObjectEnd); + + Token comma; + if (!readToken(comma) || + (comma.type_ != tokenObjectEnd && comma.type_ != tokenArraySeparator && + comma.type_ != tokenComment)) { + return addErrorAndRecover("Missing ',' or '}' in object declaration", + comma, tokenObjectEnd); + } + bool finalizeTokenOk = true; + while (comma.type_ == tokenComment && finalizeTokenOk) + finalizeTokenOk = readToken(comma); + if (comma.type_ == tokenObjectEnd) + return true; + } + return addErrorAndRecover("Missing '}' or object member name", tokenName, + tokenObjectEnd); + } + + bool OurReader::readArray(Token& token) { + Value init(arrayValue); + currentValue().swapPayload(init); + currentValue().setOffsetStart(token.start_ - begin_); + int index = 0; + for (;;) { + skipSpaces(); + if (current_ != end_ && *current_ == ']' && + (index == 0 || + (features_.allowTrailingCommas_ && + !features_.allowDroppedNullPlaceholders_))) // empty array or trailing + // comma + { + Token endArray; + readToken(endArray); + return true; + } + Value& value = currentValue()[index++]; + nodes_.push(&value); + bool ok = readValue(); + nodes_.pop(); + if (!ok) // error already set + return recoverFromError(tokenArrayEnd); + + Token currentToken; + // Accept Comment after last item in the array. + ok = readToken(currentToken); + while (currentToken.type_ == tokenComment && ok) { + ok = readToken(currentToken); + } + bool badTokenType = (currentToken.type_ != tokenArraySeparator && + currentToken.type_ != tokenArrayEnd); + if (!ok || badTokenType) { + return addErrorAndRecover("Missing ',' or ']' in array declaration", + currentToken, tokenArrayEnd); + } + if (currentToken.type_ == tokenArrayEnd) + break; + } + return true; + } + + bool OurReader::decodeNumber(Token& token) { + Value decoded; + if (!decodeNumber(token, decoded)) + return false; + currentValue().swapPayload(decoded); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + return true; + } + + bool OurReader::decodeNumber(Token& token, Value& decoded) { + // Attempts to parse the number as an integer. If the number is + // larger than the maximum supported value of an integer then + // we decode the number as a double. + Location current = token.start_; + const bool isNegative = *current == '-'; + if (isNegative) { + ++current; + } + + // We assume we can represent the largest and smallest integer types as + // unsigned integers with separate sign. This is only true if they can fit + // into an unsigned integer. + static_assert(Value::maxLargestInt <= Value::maxLargestUInt, + "Int must be smaller than UInt"); + + // We need to convert minLargestInt into a positive number. The easiest way + // to do this conversion is to assume our "threshold" value of minLargestInt + // divided by 10 can fit in maxLargestInt when absolute valued. This should + // be a safe assumption. + static_assert(Value::minLargestInt <= -Value::maxLargestInt, + "The absolute value of minLargestInt must be greater than or " + "equal to maxLargestInt"); + static_assert(Value::minLargestInt / 10 >= -Value::maxLargestInt, + "The absolute value of minLargestInt must be only 1 magnitude " + "larger than maxLargest Int"); + + static constexpr Value::LargestUInt positive_threshold = + Value::maxLargestUInt / 10; + static constexpr Value::UInt positive_last_digit = Value::maxLargestUInt % 10; + + // For the negative values, we have to be more careful. Since typically + // -Value::minLargestInt will cause an overflow, we first divide by 10 and + // then take the inverse. This assumes that minLargestInt is only a single + // power of 10 different in magnitude, which we check above. For the last + // digit, we take the modulus before negating for the same reason. + static constexpr auto negative_threshold = + Value::LargestUInt(-(Value::minLargestInt / 10)); + static constexpr auto negative_last_digit = + Value::UInt(-(Value::minLargestInt % 10)); + + const Value::LargestUInt threshold = + isNegative ? negative_threshold : positive_threshold; + const Value::UInt max_last_digit = + isNegative ? negative_last_digit : positive_last_digit; + + Value::LargestUInt value = 0; + while (current < token.end_) { + Char c = *current++; + if (c < '0' || c > '9') + return decodeDouble(token, decoded); + + const auto digit(static_cast(c - '0')); + if (value >= threshold) { + // We've hit or exceeded the max value divided by 10 (rounded down). If + // a) we've only just touched the limit, meaing value == threshold, + // b) this is the last digit, or + // c) it's small enough to fit in that rounding delta, we're okay. + // Otherwise treat this number as a double to avoid overflow. + if (value > threshold || current != token.end_ || + digit > max_last_digit) { + return decodeDouble(token, decoded); + } + } + value = value * 10 + digit; + } + + if (isNegative) { + // We use the same magnitude assumption here, just in case. + const auto last_digit = static_cast(value % 10); + decoded = -Value::LargestInt(value / 10) * 10 - last_digit; + } + else if (value <= Value::LargestUInt(Value::maxLargestInt)) { + decoded = Value::LargestInt(value); + } + else { + decoded = value; + } + + return true; + } + + bool OurReader::decodeDouble(Token& token) { + Value decoded; + if (!decodeDouble(token, decoded)) + return false; + currentValue().swapPayload(decoded); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + return true; + } + + bool OurReader::decodeDouble(Token& token, Value& decoded) { + double value = 0; + const String buffer(token.start_, token.end_); + IStringStream is(buffer); + if (!(is >> value)) { + return addError( + "'" + String(token.start_, token.end_) + "' is not a number.", token); + } + decoded = value; + return true; + } + + bool OurReader::decodeString(Token& token) { + String decoded_string; + if (!decodeString(token, decoded_string)) + return false; + Value decoded(decoded_string); + currentValue().swapPayload(decoded); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + return true; + } + + bool OurReader::decodeString(Token& token, String& decoded) { + decoded.reserve(static_cast(token.end_ - token.start_ - 2)); + Location current = token.start_ + 1; // skip '"' + Location end = token.end_ - 1; // do not include '"' + while (current != end) { + Char c = *current++; + if (c == '"') + break; + if (c == '\\') { + if (current == end) + return addError("Empty escape sequence in string", token, current); + Char escape = *current++; + switch (escape) { + case '"': + decoded += '"'; + break; + case '/': + decoded += '/'; + break; + case '\\': + decoded += '\\'; + break; + case 'b': + decoded += '\b'; + break; + case 'f': + decoded += '\f'; + break; + case 'n': + decoded += '\n'; + break; + case 'r': + decoded += '\r'; + break; + case 't': + decoded += '\t'; + break; + case 'u': { + unsigned int unicode; + if (!decodeUnicodeCodePoint(token, current, end, unicode)) + return false; + decoded += codePointToUTF8(unicode); + } break; + default: + return addError("Bad escape sequence in string", token, current); + } + } + else { + decoded += c; + } + } + return true; + } + + bool OurReader::decodeUnicodeCodePoint(Token& token, Location& current, + Location end, unsigned int& unicode) { + + if (!decodeUnicodeEscapeSequence(token, current, end, unicode)) + return false; + if (unicode >= 0xD800 && unicode <= 0xDBFF) { + // surrogate pairs + if (end - current < 6) + return addError( + "additional six characters expected to parse unicode surrogate pair.", + token, current); + if (*(current++) == '\\' && *(current++) == 'u') { + unsigned int surrogatePair; + if (decodeUnicodeEscapeSequence(token, current, end, surrogatePair)) { + unicode = 0x10000 + ((unicode & 0x3FF) << 10) + (surrogatePair & 0x3FF); + } + else + return false; + } + else + return addError("expecting another \\u token to begin the second half of " + "a unicode surrogate pair", + token, current); + } + return true; + } + + bool OurReader::decodeUnicodeEscapeSequence(Token& token, Location& current, + Location end, + unsigned int& ret_unicode) { + if (end - current < 4) + return addError( + "Bad unicode escape sequence in string: four digits expected.", token, + current); + int unicode = 0; + for (int index = 0; index < 4; ++index) { + Char c = *current++; + unicode *= 16; + if (c >= '0' && c <= '9') + unicode += c - '0'; + else if (c >= 'a' && c <= 'f') + unicode += c - 'a' + 10; + else if (c >= 'A' && c <= 'F') + unicode += c - 'A' + 10; + else + return addError( + "Bad unicode escape sequence in string: hexadecimal digit expected.", + token, current); + } + ret_unicode = static_cast(unicode); + return true; + } + + bool OurReader::addError(const String& message, Token& token, Location extra) { + ErrorInfo info; + info.token_ = token; + info.message_ = message; + info.extra_ = extra; + errors_.push_back(info); + return false; + } + + bool OurReader::recoverFromError(TokenType skipUntilToken) { + size_t errorCount = errors_.size(); + Token skip; + for (;;) { + if (!readToken(skip)) + errors_.resize(errorCount); // discard errors caused by recovery + if (skip.type_ == skipUntilToken || skip.type_ == tokenEndOfStream) + break; + } + errors_.resize(errorCount); + return false; + } + + bool OurReader::addErrorAndRecover(const String& message, Token& token, + TokenType skipUntilToken) { + addError(message, token); + return recoverFromError(skipUntilToken); + } + + Value& OurReader::currentValue() { return *(nodes_.top()); } + + OurReader::Char OurReader::getNextChar() { + if (current_ == end_) + return 0; + return *current_++; + } + + void OurReader::getLocationLineAndColumn(Location location, int& line, + int& column) const { + Location current = begin_; + Location lastLineStart = current; + line = 0; + while (current < location && current != end_) { + Char c = *current++; + if (c == '\r') { + if (*current == '\n') + ++current; + lastLineStart = current; + ++line; + } + else if (c == '\n') { + lastLineStart = current; + ++line; + } + } + // column & line start at 1 + column = int(location - lastLineStart) + 1; + ++line; + } + + String OurReader::getLocationLineAndColumn(Location location) const { + int line, column; + getLocationLineAndColumn(location, line, column); + char buffer[18 + 16 + 16 + 1]; + jsoncpp_snprintf(buffer, sizeof(buffer), "Line %d, Column %d", line, column); + return buffer; + } + + String OurReader::getFormattedErrorMessages() const { + String formattedMessage; + for (const auto& error : errors_) { + formattedMessage += + "* " + getLocationLineAndColumn(error.token_.start_) + "\n"; + formattedMessage += " " + error.message_ + "\n"; + if (error.extra_) + formattedMessage += + "See " + getLocationLineAndColumn(error.extra_) + " for detail.\n"; + } + return formattedMessage; + } + + std::vector OurReader::getStructuredErrors() const { + std::vector allErrors; + for (const auto& error : errors_) { + OurReader::StructuredError structured; + structured.offset_start = error.token_.start_ - begin_; + structured.offset_limit = error.token_.end_ - begin_; + structured.message = error.message_; + allErrors.push_back(structured); + } + return allErrors; + } + + class OurCharReader : public CharReader { + bool const collectComments_; + OurReader reader_; + + public: + OurCharReader(bool collectComments, OurFeatures const& features) + : collectComments_(collectComments), reader_(features) {} + bool parse(char const* beginDoc, char const* endDoc, Value* root, + String* errs) override { + bool ok = reader_.parse(beginDoc, endDoc, *root, collectComments_); + if (errs) { + *errs = reader_.getFormattedErrorMessages(); + } + return ok; + } + }; + + CharReaderBuilder::CharReaderBuilder() { setDefaults(&settings_); } + CharReaderBuilder::~CharReaderBuilder() = default; + CharReader* CharReaderBuilder::newCharReader() const { + bool collectComments = settings_["collectComments"].asBool(); + OurFeatures features = OurFeatures::all(); + features.allowComments_ = settings_["allowComments"].asBool(); + features.allowTrailingCommas_ = settings_["allowTrailingCommas"].asBool(); + features.strictRoot_ = settings_["strictRoot"].asBool(); + features.allowDroppedNullPlaceholders_ = + settings_["allowDroppedNullPlaceholders"].asBool(); + features.allowNumericKeys_ = settings_["allowNumericKeys"].asBool(); + features.allowSingleQuotes_ = settings_["allowSingleQuotes"].asBool(); + + // Stack limit is always a size_t, so we get this as an unsigned int + // regardless of it we have 64-bit integer support enabled. + features.stackLimit_ = static_cast(settings_["stackLimit"].asUInt()); + features.failIfExtra_ = settings_["failIfExtra"].asBool(); + features.rejectDupKeys_ = settings_["rejectDupKeys"].asBool(); + features.allowSpecialFloats_ = settings_["allowSpecialFloats"].asBool(); + features.skipBom_ = settings_["skipBom"].asBool(); + return new OurCharReader(collectComments, features); + } + + bool CharReaderBuilder::validate(Json::Value* invalid) const { + static const auto& valid_keys = *new std::set{ + "collectComments", + "allowComments", + "allowTrailingCommas", + "strictRoot", + "allowDroppedNullPlaceholders", + "allowNumericKeys", + "allowSingleQuotes", + "stackLimit", + "failIfExtra", + "rejectDupKeys", + "allowSpecialFloats", + "skipBom", + }; + for (auto si = settings_.begin(); si != settings_.end(); ++si) { + auto key = si.name(); + if (valid_keys.count(key)) + continue; + if (invalid) + (*invalid)[std::move(key)] = *si; + else + return false; + } + return invalid ? invalid->empty() : true; + } + + Value& CharReaderBuilder::operator[](const String& key) { + return settings_[key]; + } + // static + void CharReaderBuilder::strictMode(Json::Value* settings) { + //! [CharReaderBuilderStrictMode] + (*settings)["allowComments"] = false; + (*settings)["allowTrailingCommas"] = false; + (*settings)["strictRoot"] = true; + (*settings)["allowDroppedNullPlaceholders"] = false; + (*settings)["allowNumericKeys"] = false; + (*settings)["allowSingleQuotes"] = false; + (*settings)["stackLimit"] = 1000; + (*settings)["failIfExtra"] = true; + (*settings)["rejectDupKeys"] = true; + (*settings)["allowSpecialFloats"] = false; + (*settings)["skipBom"] = true; + //! [CharReaderBuilderStrictMode] + } + // static + void CharReaderBuilder::setDefaults(Json::Value* settings) { + //! [CharReaderBuilderDefaults] + (*settings)["collectComments"] = true; + (*settings)["allowComments"] = true; + (*settings)["allowTrailingCommas"] = true; + (*settings)["strictRoot"] = false; + (*settings)["allowDroppedNullPlaceholders"] = false; + (*settings)["allowNumericKeys"] = false; + (*settings)["allowSingleQuotes"] = false; + (*settings)["stackLimit"] = 1000; + (*settings)["failIfExtra"] = false; + (*settings)["rejectDupKeys"] = false; + (*settings)["allowSpecialFloats"] = false; + (*settings)["skipBom"] = true; + //! [CharReaderBuilderDefaults] + } + + ////////////////////////////////// + // global functions + + bool parseFromStream(CharReader::Factory const& fact, IStream& sin, Value* root, + String* errs) { + OStringStream ssin; + ssin << sin.rdbuf(); + String doc = ssin.str(); + char const* begin = doc.data(); + char const* end = begin + doc.size(); + // Note that we do not actually need a null-terminator. + CharReaderPtr const reader(fact.newCharReader()); + return reader->parse(begin, end, root, errs); + } + + IStream& operator>>(IStream& sin, Value& root) { + CharReaderBuilder b; + String errs; + bool ok = parseFromStream(b, sin, &root, &errs); + if (!ok) { + throwRuntimeError(errs); + } + return sin; + } + +} // namespace Json + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: src/lib_json/json_reader.cpp +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: src/lib_json/json_valueiterator.inl +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +// included by json_value.cpp + +namespace Json { + + // ////////////////////////////////////////////////////////////////// + // ////////////////////////////////////////////////////////////////// + // ////////////////////////////////////////////////////////////////// + // class ValueIteratorBase + // ////////////////////////////////////////////////////////////////// + // ////////////////////////////////////////////////////////////////// + // ////////////////////////////////////////////////////////////////// + + ValueIteratorBase::ValueIteratorBase() : current_() {} + + ValueIteratorBase::ValueIteratorBase( + const Value::ObjectValues::iterator& current) + : current_(current), isNull_(false) {} + + Value& ValueIteratorBase::deref() { return current_->second; } + const Value& ValueIteratorBase::deref() const { return current_->second; } + + void ValueIteratorBase::increment() { ++current_; } + + void ValueIteratorBase::decrement() { --current_; } + + ValueIteratorBase::difference_type + ValueIteratorBase::computeDistance(const SelfType& other) const { + // Iterator for null value are initialized using the default + // constructor, which initialize current_ to the default + // std::map::iterator. As begin() and end() are two instance + // of the default std::map::iterator, they can not be compared. + // To allow this, we handle this comparison specifically. + if (isNull_ && other.isNull_) { + return 0; + } + + // Usage of std::distance is not portable (does not compile with Sun Studio 12 + // RogueWave STL, + // which is the one used by default). + // Using a portable hand-made version for non random iterator instead: + // return difference_type( std::distance( current_, other.current_ ) ); + difference_type myDistance = 0; + for (Value::ObjectValues::iterator it = current_; it != other.current_; + ++it) { + ++myDistance; + } + return myDistance; + } + + bool ValueIteratorBase::isEqual(const SelfType& other) const { + if (isNull_) { + return other.isNull_; + } + return current_ == other.current_; + } + + void ValueIteratorBase::copy(const SelfType& other) { + current_ = other.current_; + isNull_ = other.isNull_; + } + + Value ValueIteratorBase::key() const { + const Value::CZString czstring = (*current_).first; + if (czstring.data()) { + if (czstring.isStaticString()) + return Value(StaticString(czstring.data())); + return Value(czstring.data(), czstring.data() + czstring.length()); + } + return Value(czstring.index()); + } + + UInt ValueIteratorBase::index() const { + const Value::CZString czstring = (*current_).first; + if (!czstring.data()) + return czstring.index(); + return Value::UInt(-1); + } + + String ValueIteratorBase::name() const { + char const* keey; + char const* end; + keey = memberName(&end); + if (!keey) + return String(); + return String(keey, end); + } + + char const* ValueIteratorBase::memberName() const { + const char* cname = (*current_).first.data(); + return cname ? cname : ""; + } + + char const* ValueIteratorBase::memberName(char const** end) const { + const char* cname = (*current_).first.data(); + if (!cname) { + *end = nullptr; + return nullptr; + } + *end = cname + (*current_).first.length(); + return cname; + } + + // ////////////////////////////////////////////////////////////////// + // ////////////////////////////////////////////////////////////////// + // ////////////////////////////////////////////////////////////////// + // class ValueConstIterator + // ////////////////////////////////////////////////////////////////// + // ////////////////////////////////////////////////////////////////// + // ////////////////////////////////////////////////////////////////// + + ValueConstIterator::ValueConstIterator() = default; + + ValueConstIterator::ValueConstIterator( + const Value::ObjectValues::iterator& current) + : ValueIteratorBase(current) {} + + ValueConstIterator::ValueConstIterator(ValueIterator const& other) + : ValueIteratorBase(other) {} + + ValueConstIterator& ValueConstIterator:: + operator=(const ValueIteratorBase& other) { + copy(other); + return *this; + } + + // ////////////////////////////////////////////////////////////////// + // ////////////////////////////////////////////////////////////////// + // ////////////////////////////////////////////////////////////////// + // class ValueIterator + // ////////////////////////////////////////////////////////////////// + // ////////////////////////////////////////////////////////////////// + // ////////////////////////////////////////////////////////////////// + + ValueIterator::ValueIterator() = default; + + ValueIterator::ValueIterator(const Value::ObjectValues::iterator& current) + : ValueIteratorBase(current) {} + + ValueIterator::ValueIterator(const ValueConstIterator& other) + : ValueIteratorBase(other) { + throwRuntimeError("ConstIterator to Iterator should never be allowed."); + } + + ValueIterator::ValueIterator(const ValueIterator& other) = default; + + ValueIterator& ValueIterator::operator=(const SelfType& other) { + copy(other); + return *this; + } + +} // namespace Json + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: src/lib_json/json_valueiterator.inl +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: src/lib_json/json_value.cpp +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2011 Baptiste Lepilleur and The JsonCpp Authors +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#if !defined(JSON_IS_AMALGAMATION) +#include +#include +#include +#endif // if !defined(JSON_IS_AMALGAMATION) +#include +#include +#include +#include +#include +#include +#include +#include + +// Provide implementation equivalent of std::snprintf for older _MSC compilers +#if defined(_MSC_VER) && _MSC_VER < 1900 +#include +static int msvc_pre1900_c99_vsnprintf(char* outBuf, size_t size, + const char* format, va_list ap) { + int count = -1; + if (size != 0) + count = _vsnprintf_s(outBuf, size, _TRUNCATE, format, ap); + if (count == -1) + count = _vscprintf(format, ap); + return count; +} + +int JSON_API msvc_pre1900_c99_snprintf(char* outBuf, size_t size, + const char* format, ...) { + va_list ap; + va_start(ap, format); + const int count = msvc_pre1900_c99_vsnprintf(outBuf, size, format, ap); + va_end(ap); + return count; +} +#endif + +// Disable warning C4702 : unreachable code +#if defined(_MSC_VER) +#pragma warning(disable : 4702) +#endif + +#define JSON_ASSERT_UNREACHABLE assert(false) + +namespace Json { + template + static std::unique_ptr cloneUnique(const std::unique_ptr& p) { + std::unique_ptr r; + if (p) { + r = std::unique_ptr(new T(*p)); + } + return r; + } + + // This is a walkaround to avoid the static initialization of Value::null. + // kNull must be word-aligned to avoid crashing on ARM. We use an alignment of + // 8 (instead of 4) as a bit of future-proofing. +#if defined(__ARMEL__) +#define ALIGNAS(byte_alignment) __attribute__((aligned(byte_alignment))) +#else +#define ALIGNAS(byte_alignment) +#endif + +// static + Value const& Value::nullSingleton() { + static Value const nullStatic; + return nullStatic; + } + +#if JSON_USE_NULLREF + // for backwards compatibility, we'll leave these global references around, but + // DO NOT use them in JSONCPP library code any more! + // static + Value const& Value::null = Value::nullSingleton(); + + // static + Value const& Value::nullRef = Value::nullSingleton(); +#endif + +#if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) + template + static inline bool InRange(double d, T min, U max) { + // The casts can lose precision, but we are looking only for + // an approximate range. Might fail on edge cases though. ~cdunn + return d >= static_cast(min) && d <= static_cast(max); + } +#else // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) + static inline double integerToDouble(Json::UInt64 value) { + return static_cast(Int64(value / 2)) * 2.0 + + static_cast(Int64(value & 1)); + } + + template static inline double integerToDouble(T value) { + return static_cast(value); + } + + template + static inline bool InRange(double d, T min, U max) { + return d >= integerToDouble(min) && d <= integerToDouble(max); + } +#endif // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) + + /** Duplicates the specified string value. + * @param value Pointer to the string to duplicate. Must be zero-terminated if + * length is "unknown". + * @param length Length of the value. if equals to unknown, then it will be + * computed using strlen(value). + * @return Pointer on the duplicate instance of string. + */ + static inline char* duplicateStringValue(const char* value, size_t length) { + // Avoid an integer overflow in the call to malloc below by limiting length + // to a sane value. + if (length >= static_cast(Value::maxInt)) + length = Value::maxInt - 1; + + auto newString = static_cast(malloc(length + 1)); + if (newString == nullptr) { + throwRuntimeError("in Json::Value::duplicateStringValue(): " + "Failed to allocate string value buffer"); + } + memcpy(newString, value, length); + newString[length] = 0; + return newString; + } + + /* Record the length as a prefix. + */ + static inline char* duplicateAndPrefixStringValue(const char* value, + unsigned int length) { + // Avoid an integer overflow in the call to malloc below by limiting length + // to a sane value. + JSON_ASSERT_MESSAGE(length <= static_cast(Value::maxInt) - + sizeof(unsigned) - 1U, + "in Json::Value::duplicateAndPrefixStringValue(): " + "length too big for prefixing"); + size_t actualLength = sizeof(length) + length + 1; + auto newString = static_cast(malloc(actualLength)); + if (newString == nullptr) { + throwRuntimeError("in Json::Value::duplicateAndPrefixStringValue(): " + "Failed to allocate string value buffer"); + } + *reinterpret_cast(newString) = length; + memcpy(newString + sizeof(unsigned), value, length); + newString[actualLength - 1U] = + 0; // to avoid buffer over-run accidents by users later + return newString; + } + inline static void decodePrefixedString(bool isPrefixed, char const* prefixed, + unsigned* length, char const** value) { + if (!isPrefixed) { + *length = static_cast(strlen(prefixed)); + *value = prefixed; + } + else { + *length = *reinterpret_cast(prefixed); + *value = prefixed + sizeof(unsigned); + } + } + /** Free the string duplicated by + * duplicateStringValue()/duplicateAndPrefixStringValue(). + */ +#if JSONCPP_USING_SECURE_MEMORY + static inline void releasePrefixedStringValue(char* value) { + unsigned length = 0; + char const* valueDecoded; + decodePrefixedString(true, value, &length, &valueDecoded); + size_t const size = sizeof(unsigned) + length + 1U; + memset(value, 0, size); + free(value); + } + static inline void releaseStringValue(char* value, unsigned length) { + // length==0 => we allocated the strings memory + size_t size = (length == 0) ? strlen(value) : length; + memset(value, 0, size); + free(value); + } +#else // !JSONCPP_USING_SECURE_MEMORY + static inline void releasePrefixedStringValue(char* value) { free(value); } + static inline void releaseStringValue(char* value, unsigned) { free(value); } +#endif // JSONCPP_USING_SECURE_MEMORY + +} // namespace Json + +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ValueInternals... +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +#if !defined(JSON_IS_AMALGAMATION) + +#include "json_valueiterator.inl" +#endif // if !defined(JSON_IS_AMALGAMATION) + +namespace Json { + +#if JSON_USE_EXCEPTION + Exception::Exception(String msg) : msg_(std::move(msg)) {} + Exception::~Exception() noexcept = default; + char const* Exception::what() const noexcept { return msg_.c_str(); } + RuntimeError::RuntimeError(String const& msg) : Exception(msg) {} + LogicError::LogicError(String const& msg) : Exception(msg) {} + JSONCPP_NORETURN void throwRuntimeError(String const& msg) { + throw RuntimeError(msg); + } + JSONCPP_NORETURN void throwLogicError(String const& msg) { + throw LogicError(msg); + } +#else // !JSON_USE_EXCEPTION + JSONCPP_NORETURN void throwRuntimeError(String const& msg) { + std::cerr << msg << std::endl; + abort(); + } + JSONCPP_NORETURN void throwLogicError(String const& msg) { + std::cerr << msg << std::endl; + abort(); + } +#endif + + // ////////////////////////////////////////////////////////////////// + // ////////////////////////////////////////////////////////////////// + // ////////////////////////////////////////////////////////////////// + // class Value::CZString + // ////////////////////////////////////////////////////////////////// + // ////////////////////////////////////////////////////////////////// + // ////////////////////////////////////////////////////////////////// + + // Notes: policy_ indicates if the string was allocated when + // a string is stored. + + Value::CZString::CZString(ArrayIndex index) : cstr_(nullptr), index_(index) {} + + Value::CZString::CZString(char const* str, unsigned length, + DuplicationPolicy allocate) + : cstr_(str) { + // allocate != duplicate + storage_.policy_ = allocate & 0x3; + storage_.length_ = length & 0x3FFFFFFF; + } + + Value::CZString::CZString(const CZString& other) { + cstr_ = (other.storage_.policy_ != noDuplication && other.cstr_ != nullptr + ? duplicateStringValue(other.cstr_, other.storage_.length_) + : other.cstr_); + storage_.policy_ = + static_cast( + other.cstr_ + ? (static_cast(other.storage_.policy_) == + noDuplication + ? noDuplication + : duplicate) + : static_cast(other.storage_.policy_)) & + 3U; + storage_.length_ = other.storage_.length_; + } + + Value::CZString::CZString(CZString&& other) + : cstr_(other.cstr_), index_(other.index_) { + other.cstr_ = nullptr; + } + + Value::CZString::~CZString() { + if (cstr_ && storage_.policy_ == duplicate) { + releaseStringValue(const_cast(cstr_), + storage_.length_ + 1U); // +1 for null terminating + // character for sake of + // completeness but not actually + // necessary + } + } + + void Value::CZString::swap(CZString& other) { + std::swap(cstr_, other.cstr_); + std::swap(index_, other.index_); + } + + Value::CZString& Value::CZString::operator=(const CZString& other) { + cstr_ = other.cstr_; + index_ = other.index_; + return *this; + } + + Value::CZString& Value::CZString::operator=(CZString&& other) { + cstr_ = other.cstr_; + index_ = other.index_; + other.cstr_ = nullptr; + return *this; + } + + bool Value::CZString::operator<(const CZString& other) const { + if (!cstr_) + return index_ < other.index_; + // return strcmp(cstr_, other.cstr_) < 0; + // Assume both are strings. + unsigned this_len = this->storage_.length_; + unsigned other_len = other.storage_.length_; + unsigned min_len = std::min(this_len, other_len); + JSON_ASSERT(this->cstr_ && other.cstr_); + int comp = memcmp(this->cstr_, other.cstr_, min_len); + if (comp < 0) + return true; + if (comp > 0) + return false; + return (this_len < other_len); + } + + bool Value::CZString::operator==(const CZString& other) const { + if (!cstr_) + return index_ == other.index_; + // return strcmp(cstr_, other.cstr_) == 0; + // Assume both are strings. + unsigned this_len = this->storage_.length_; + unsigned other_len = other.storage_.length_; + if (this_len != other_len) + return false; + JSON_ASSERT(this->cstr_ && other.cstr_); + int comp = memcmp(this->cstr_, other.cstr_, this_len); + return comp == 0; + } + + ArrayIndex Value::CZString::index() const { return index_; } + + // const char* Value::CZString::c_str() const { return cstr_; } + const char* Value::CZString::data() const { return cstr_; } + unsigned Value::CZString::length() const { return storage_.length_; } + bool Value::CZString::isStaticString() const { + return storage_.policy_ == noDuplication; + } + + // ////////////////////////////////////////////////////////////////// + // ////////////////////////////////////////////////////////////////// + // ////////////////////////////////////////////////////////////////// + // class Value::Value + // ////////////////////////////////////////////////////////////////// + // ////////////////////////////////////////////////////////////////// + // ////////////////////////////////////////////////////////////////// + + /*! \internal Default constructor initialization must be equivalent to: + * memset( this, 0, sizeof(Value) ) + * This optimization is used in ValueInternalMap fast allocator. + */ + Value::Value(ValueType type) { + static char const emptyString[] = ""; + initBasic(type); + switch (type) { + case nullValue: + break; + case intValue: + case uintValue: + value_.int_ = 0; + break; + case realValue: + value_.real_ = 0.0; + break; + case stringValue: + // allocated_ == false, so this is safe. + value_.string_ = const_cast(static_cast(emptyString)); + break; + case arrayValue: + case objectValue: + value_.map_ = new ObjectValues(); + break; + case booleanValue: + value_.bool_ = false; + break; + default: + JSON_ASSERT_UNREACHABLE; + } + } + + Value::Value(Int value) { + initBasic(intValue); + value_.int_ = value; + } + + Value::Value(UInt value) { + initBasic(uintValue); + value_.uint_ = value; + } +#if defined(JSON_HAS_INT64) + Value::Value(Int64 value) { + initBasic(intValue); + value_.int_ = value; + } + Value::Value(UInt64 value) { + initBasic(uintValue); + value_.uint_ = value; + } +#endif // defined(JSON_HAS_INT64) + + Value::Value(double value) { + initBasic(realValue); + value_.real_ = value; + } + + Value::Value(const char* value) { + initBasic(stringValue, true); + JSON_ASSERT_MESSAGE(value != nullptr, + "Null Value Passed to Value Constructor"); + value_.string_ = duplicateAndPrefixStringValue( + value, static_cast(strlen(value))); + } + + Value::Value(const char* begin, const char* end) { + initBasic(stringValue, true); + value_.string_ = + duplicateAndPrefixStringValue(begin, static_cast(end - begin)); + } + + Value::Value(const String& value) { + initBasic(stringValue, true); + value_.string_ = duplicateAndPrefixStringValue( + value.data(), static_cast(value.length())); + } + + Value::Value(const StaticString& value) { + initBasic(stringValue); + value_.string_ = const_cast(value.c_str()); + } + + Value::Value(bool value) { + initBasic(booleanValue); + value_.bool_ = value; + } + + Value::Value(const Value& other) { + dupPayload(other); + dupMeta(other); + } + + Value::Value(Value&& other) { + initBasic(nullValue); + swap(other); + } + + Value::~Value() { + releasePayload(); + value_.uint_ = 0; + } + + Value& Value::operator=(const Value& other) { + Value(other).swap(*this); + return *this; + } + + Value& Value::operator=(Value&& other) { + other.swap(*this); + return *this; + } + + void Value::swapPayload(Value& other) { + std::swap(bits_, other.bits_); + std::swap(value_, other.value_); + } + + void Value::copyPayload(const Value& other) { + releasePayload(); + dupPayload(other); + } + + void Value::swap(Value& other) { + swapPayload(other); + std::swap(comments_, other.comments_); + std::swap(start_, other.start_); + std::swap(limit_, other.limit_); + } + + void Value::copy(const Value& other) { + copyPayload(other); + dupMeta(other); + } + + ValueType Value::type() const { + return static_cast(bits_.value_type_); + } + + int Value::compare(const Value& other) const { + if (*this < other) + return -1; + if (*this > other) + return 1; + return 0; + } + + bool Value::operator<(const Value& other) const { + int typeDelta = type() - other.type(); + if (typeDelta) + return typeDelta < 0; + switch (type()) { + case nullValue: + return false; + case intValue: + return value_.int_ < other.value_.int_; + case uintValue: + return value_.uint_ < other.value_.uint_; + case realValue: + return value_.real_ < other.value_.real_; + case booleanValue: + return value_.bool_ < other.value_.bool_; + case stringValue: { + if ((value_.string_ == nullptr) || (other.value_.string_ == nullptr)) { + return other.value_.string_ != nullptr; + } + unsigned this_len; + unsigned other_len; + char const* this_str; + char const* other_str; + decodePrefixedString(this->isAllocated(), this->value_.string_, &this_len, + &this_str); + decodePrefixedString(other.isAllocated(), other.value_.string_, &other_len, + &other_str); + unsigned min_len = std::min(this_len, other_len); + JSON_ASSERT(this_str && other_str); + int comp = memcmp(this_str, other_str, min_len); + if (comp < 0) + return true; + if (comp > 0) + return false; + return (this_len < other_len); + } + case arrayValue: + case objectValue: { + auto thisSize = value_.map_->size(); + auto otherSize = other.value_.map_->size(); + if (thisSize != otherSize) + return thisSize < otherSize; + return (*value_.map_) < (*other.value_.map_); + } + default: + JSON_ASSERT_UNREACHABLE; + } + return false; // unreachable + } + + bool Value::operator<=(const Value& other) const { return !(other < *this); } + + bool Value::operator>=(const Value& other) const { return !(*this < other); } + + bool Value::operator>(const Value& other) const { return other < *this; } + + bool Value::operator==(const Value& other) const { + if (type() != other.type()) + return false; + switch (type()) { + case nullValue: + return true; + case intValue: + return value_.int_ == other.value_.int_; + case uintValue: + return value_.uint_ == other.value_.uint_; + case realValue: + return value_.real_ == other.value_.real_; + case booleanValue: + return value_.bool_ == other.value_.bool_; + case stringValue: { + if ((value_.string_ == nullptr) || (other.value_.string_ == nullptr)) { + return (value_.string_ == other.value_.string_); + } + unsigned this_len; + unsigned other_len; + char const* this_str; + char const* other_str; + decodePrefixedString(this->isAllocated(), this->value_.string_, &this_len, + &this_str); + decodePrefixedString(other.isAllocated(), other.value_.string_, &other_len, + &other_str); + if (this_len != other_len) + return false; + JSON_ASSERT(this_str && other_str); + int comp = memcmp(this_str, other_str, this_len); + return comp == 0; + } + case arrayValue: + case objectValue: + return value_.map_->size() == other.value_.map_->size() && + (*value_.map_) == (*other.value_.map_); + default: + JSON_ASSERT_UNREACHABLE; + } + return false; // unreachable + } + + bool Value::operator!=(const Value& other) const { return !(*this == other); } + + const char* Value::asCString() const { + JSON_ASSERT_MESSAGE(type() == stringValue, + "in Json::Value::asCString(): requires stringValue"); + if (value_.string_ == nullptr) + return nullptr; + unsigned this_len; + char const* this_str; + decodePrefixedString(this->isAllocated(), this->value_.string_, &this_len, + &this_str); + return this_str; + } + +#if JSONCPP_USING_SECURE_MEMORY + unsigned Value::getCStringLength() const { + JSON_ASSERT_MESSAGE(type() == stringValue, + "in Json::Value::asCString(): requires stringValue"); + if (value_.string_ == 0) + return 0; + unsigned this_len; + char const* this_str; + decodePrefixedString(this->isAllocated(), this->value_.string_, &this_len, + &this_str); + return this_len; + } +#endif + + bool Value::getString(char const** begin, char const** end) const { + if (type() != stringValue) + return false; + if (value_.string_ == nullptr) + return false; + unsigned length; + decodePrefixedString(this->isAllocated(), this->value_.string_, &length, + begin); + *end = *begin + length; + return true; + } + + String Value::asString() const { + switch (type()) { + case nullValue: + return ""; + case stringValue: { + if (value_.string_ == nullptr) + return ""; + unsigned this_len; + char const* this_str; + decodePrefixedString(this->isAllocated(), this->value_.string_, &this_len, + &this_str); + return String(this_str, this_len); + } + case booleanValue: + return value_.bool_ ? "true" : "false"; + case intValue: + return valueToString(value_.int_); + case uintValue: + return valueToString(value_.uint_); + case realValue: + return valueToString(value_.real_); + default: + JSON_FAIL_MESSAGE("Type is not convertible to string"); + } + } + + Value::Int Value::asInt() const { + switch (type()) { + case intValue: + JSON_ASSERT_MESSAGE(isInt(), "LargestInt out of Int range"); + return Int(value_.int_); + case uintValue: + JSON_ASSERT_MESSAGE(isInt(), "LargestUInt out of Int range"); + return Int(value_.uint_); + case realValue: + JSON_ASSERT_MESSAGE(InRange(value_.real_, minInt, maxInt), + "double out of Int range"); + return Int(value_.real_); + case nullValue: + return 0; + case booleanValue: + return value_.bool_ ? 1 : 0; + default: + break; + } + JSON_FAIL_MESSAGE("Value is not convertible to Int."); + } + + Value::UInt Value::asUInt() const { + switch (type()) { + case intValue: + JSON_ASSERT_MESSAGE(isUInt(), "LargestInt out of UInt range"); + return UInt(value_.int_); + case uintValue: + JSON_ASSERT_MESSAGE(isUInt(), "LargestUInt out of UInt range"); + return UInt(value_.uint_); + case realValue: + JSON_ASSERT_MESSAGE(InRange(value_.real_, 0, maxUInt), + "double out of UInt range"); + return UInt(value_.real_); + case nullValue: + return 0; + case booleanValue: + return value_.bool_ ? 1 : 0; + default: + break; + } + JSON_FAIL_MESSAGE("Value is not convertible to UInt."); + } + +#if defined(JSON_HAS_INT64) + + Value::Int64 Value::asInt64() const { + switch (type()) { + case intValue: + return Int64(value_.int_); + case uintValue: + JSON_ASSERT_MESSAGE(isInt64(), "LargestUInt out of Int64 range"); + return Int64(value_.uint_); + case realValue: + JSON_ASSERT_MESSAGE(InRange(value_.real_, minInt64, maxInt64), + "double out of Int64 range"); + return Int64(value_.real_); + case nullValue: + return 0; + case booleanValue: + return value_.bool_ ? 1 : 0; + default: + break; + } + JSON_FAIL_MESSAGE("Value is not convertible to Int64."); + } + + Value::UInt64 Value::asUInt64() const { + switch (type()) { + case intValue: + JSON_ASSERT_MESSAGE(isUInt64(), "LargestInt out of UInt64 range"); + return UInt64(value_.int_); + case uintValue: + return UInt64(value_.uint_); + case realValue: + JSON_ASSERT_MESSAGE(InRange(value_.real_, 0, maxUInt64), + "double out of UInt64 range"); + return UInt64(value_.real_); + case nullValue: + return 0; + case booleanValue: + return value_.bool_ ? 1 : 0; + default: + break; + } + JSON_FAIL_MESSAGE("Value is not convertible to UInt64."); + } +#endif // if defined(JSON_HAS_INT64) + + LargestInt Value::asLargestInt() const { +#if defined(JSON_NO_INT64) + return asInt(); +#else + return asInt64(); +#endif + } + + LargestUInt Value::asLargestUInt() const { +#if defined(JSON_NO_INT64) + return asUInt(); +#else + return asUInt64(); +#endif + } + + double Value::asDouble() const { + switch (type()) { + case intValue: + return static_cast(value_.int_); + case uintValue: +#if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) + return static_cast(value_.uint_); +#else // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) + return integerToDouble(value_.uint_); +#endif // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) + case realValue: + return value_.real_; + case nullValue: + return 0.0; + case booleanValue: + return value_.bool_ ? 1.0 : 0.0; + default: + break; + } + JSON_FAIL_MESSAGE("Value is not convertible to double."); + } + + float Value::asFloat() const { + switch (type()) { + case intValue: + return static_cast(value_.int_); + case uintValue: +#if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) + return static_cast(value_.uint_); +#else // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) + // This can fail (silently?) if the value is bigger than MAX_FLOAT. + return static_cast(integerToDouble(value_.uint_)); +#endif // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) + case realValue: + return static_cast(value_.real_); + case nullValue: + return 0.0; + case booleanValue: + return value_.bool_ ? 1.0F : 0.0F; + default: + break; + } + JSON_FAIL_MESSAGE("Value is not convertible to float."); + } + + bool Value::asBool() const { + switch (type()) { + case booleanValue: + return value_.bool_; + case nullValue: + return false; + case intValue: + return value_.int_ != 0; + case uintValue: + return value_.uint_ != 0; + case realValue: { + // According to JavaScript language zero or NaN is regarded as false + const auto value_classification = std::fpclassify(value_.real_); + return value_classification != FP_ZERO && value_classification != FP_NAN; + } + default: + break; + } + JSON_FAIL_MESSAGE("Value is not convertible to bool."); + } + + bool Value::isConvertibleTo(ValueType other) const { + switch (other) { + case nullValue: + return (isNumeric() && asDouble() == 0.0) || + (type() == booleanValue && !value_.bool_) || + (type() == stringValue && asString().empty()) || + (type() == arrayValue && value_.map_->empty()) || + (type() == objectValue && value_.map_->empty()) || + type() == nullValue; + case intValue: + return isInt() || + (type() == realValue && InRange(value_.real_, minInt, maxInt)) || + type() == booleanValue || type() == nullValue; + case uintValue: + return isUInt() || + (type() == realValue && InRange(value_.real_, 0, maxUInt)) || + type() == booleanValue || type() == nullValue; + case realValue: + return isNumeric() || type() == booleanValue || type() == nullValue; + case booleanValue: + return isNumeric() || type() == booleanValue || type() == nullValue; + case stringValue: + return isNumeric() || type() == booleanValue || type() == stringValue || + type() == nullValue; + case arrayValue: + return type() == arrayValue || type() == nullValue; + case objectValue: + return type() == objectValue || type() == nullValue; + } + JSON_ASSERT_UNREACHABLE; + return false; + } + + /// Number of values in array or object + ArrayIndex Value::size() const { + switch (type()) { + case nullValue: + case intValue: + case uintValue: + case realValue: + case booleanValue: + case stringValue: + return 0; + case arrayValue: // size of the array is highest index + 1 + if (!value_.map_->empty()) { + ObjectValues::const_iterator itLast = value_.map_->end(); + --itLast; + return (*itLast).first.index() + 1; + } + return 0; + case objectValue: + return ArrayIndex(value_.map_->size()); + } + JSON_ASSERT_UNREACHABLE; + return 0; // unreachable; + } + + bool Value::empty() const { + if (isNull() || isArray() || isObject()) + return size() == 0U; + return false; + } + + Value::operator bool() const { return !isNull(); } + + void Value::clear() { + JSON_ASSERT_MESSAGE(type() == nullValue || type() == arrayValue || + type() == objectValue, + "in Json::Value::clear(): requires complex value"); + start_ = 0; + limit_ = 0; + switch (type()) { + case arrayValue: + case objectValue: + value_.map_->clear(); + break; + default: + break; + } + } + + void Value::resize(ArrayIndex newSize) { + JSON_ASSERT_MESSAGE(type() == nullValue || type() == arrayValue, + "in Json::Value::resize(): requires arrayValue"); + if (type() == nullValue) + *this = Value(arrayValue); + ArrayIndex oldSize = size(); + if (newSize == 0) + clear(); + else if (newSize > oldSize) + this->operator[](newSize - 1); + else { + for (ArrayIndex index = newSize; index < oldSize; ++index) { + value_.map_->erase(index); + } + JSON_ASSERT(size() == newSize); + } + } + + Value& Value::operator[](ArrayIndex index) { + JSON_ASSERT_MESSAGE( + type() == nullValue || type() == arrayValue, + "in Json::Value::operator[](ArrayIndex): requires arrayValue"); + if (type() == nullValue) + *this = Value(arrayValue); + CZString key(index); + auto it = value_.map_->lower_bound(key); + if (it != value_.map_->end() && (*it).first == key) + return (*it).second; + + ObjectValues::value_type defaultValue(key, nullSingleton()); + it = value_.map_->insert(it, defaultValue); + return (*it).second; + } + + Value& Value::operator[](int index) { + JSON_ASSERT_MESSAGE( + index >= 0, + "in Json::Value::operator[](int index): index cannot be negative"); + return (*this)[ArrayIndex(index)]; + } + + const Value& Value::operator[](ArrayIndex index) const { + JSON_ASSERT_MESSAGE( + type() == nullValue || type() == arrayValue, + "in Json::Value::operator[](ArrayIndex)const: requires arrayValue"); + if (type() == nullValue) + return nullSingleton(); + CZString key(index); + ObjectValues::const_iterator it = value_.map_->find(key); + if (it == value_.map_->end()) + return nullSingleton(); + return (*it).second; + } + + const Value& Value::operator[](int index) const { + JSON_ASSERT_MESSAGE( + index >= 0, + "in Json::Value::operator[](int index) const: index cannot be negative"); + return (*this)[ArrayIndex(index)]; + } + + void Value::initBasic(ValueType type, bool allocated) { + setType(type); + setIsAllocated(allocated); + comments_ = Comments{}; + start_ = 0; + limit_ = 0; + } + + void Value::dupPayload(const Value& other) { + setType(other.type()); + setIsAllocated(false); + switch (type()) { + case nullValue: + case intValue: + case uintValue: + case realValue: + case booleanValue: + value_ = other.value_; + break; + case stringValue: + if (other.value_.string_ && other.isAllocated()) { + unsigned len; + char const* str; + decodePrefixedString(other.isAllocated(), other.value_.string_, &len, + &str); + value_.string_ = duplicateAndPrefixStringValue(str, len); + setIsAllocated(true); + } + else { + value_.string_ = other.value_.string_; + } + break; + case arrayValue: + case objectValue: + value_.map_ = new ObjectValues(*other.value_.map_); + break; + default: + JSON_ASSERT_UNREACHABLE; + } + } + + void Value::releasePayload() { + switch (type()) { + case nullValue: + case intValue: + case uintValue: + case realValue: + case booleanValue: + break; + case stringValue: + if (isAllocated()) + releasePrefixedStringValue(value_.string_); + break; + case arrayValue: + case objectValue: + delete value_.map_; + break; + default: + JSON_ASSERT_UNREACHABLE; + } + } + + void Value::dupMeta(const Value& other) { + comments_ = other.comments_; + start_ = other.start_; + limit_ = other.limit_; + } + + // Access an object value by name, create a null member if it does not exist. + // @pre Type of '*this' is object or null. + // @param key is null-terminated. + Value& Value::resolveReference(const char* key) { + JSON_ASSERT_MESSAGE( + type() == nullValue || type() == objectValue, + "in Json::Value::resolveReference(): requires objectValue"); + if (type() == nullValue) + *this = Value(objectValue); + CZString actualKey(key, static_cast(strlen(key)), + CZString::noDuplication); // NOTE! + auto it = value_.map_->lower_bound(actualKey); + if (it != value_.map_->end() && (*it).first == actualKey) + return (*it).second; + + ObjectValues::value_type defaultValue(actualKey, nullSingleton()); + it = value_.map_->insert(it, defaultValue); + Value& value = (*it).second; + return value; + } + + // @param key is not null-terminated. + Value& Value::resolveReference(char const* key, char const* end) { + JSON_ASSERT_MESSAGE( + type() == nullValue || type() == objectValue, + "in Json::Value::resolveReference(key, end): requires objectValue"); + if (type() == nullValue) + *this = Value(objectValue); + CZString actualKey(key, static_cast(end - key), + CZString::duplicateOnCopy); + auto it = value_.map_->lower_bound(actualKey); + if (it != value_.map_->end() && (*it).first == actualKey) + return (*it).second; + + ObjectValues::value_type defaultValue(actualKey, nullSingleton()); + it = value_.map_->insert(it, defaultValue); + Value& value = (*it).second; + return value; + } + + Value Value::get(ArrayIndex index, const Value& defaultValue) const { + const Value* value = &((*this)[index]); + return value == &nullSingleton() ? defaultValue : *value; + } + + bool Value::isValidIndex(ArrayIndex index) const { return index < size(); } + + Value const* Value::find(char const* begin, char const* end) const { + JSON_ASSERT_MESSAGE(type() == nullValue || type() == objectValue, + "in Json::Value::find(begin, end): requires " + "objectValue or nullValue"); + if (type() == nullValue) + return nullptr; + CZString actualKey(begin, static_cast(end - begin), + CZString::noDuplication); + ObjectValues::const_iterator it = value_.map_->find(actualKey); + if (it == value_.map_->end()) + return nullptr; + return &(*it).second; + } + Value* Value::demand(char const* begin, char const* end) { + JSON_ASSERT_MESSAGE(type() == nullValue || type() == objectValue, + "in Json::Value::demand(begin, end): requires " + "objectValue or nullValue"); + return &resolveReference(begin, end); + } + const Value& Value::operator[](const char* key) const { + Value const* found = find(key, key + strlen(key)); + if (!found) + return nullSingleton(); + return *found; + } + Value const& Value::operator[](const String& key) const { + Value const* found = find(key.data(), key.data() + key.length()); + if (!found) + return nullSingleton(); + return *found; + } + + Value& Value::operator[](const char* key) { + return resolveReference(key, key + strlen(key)); + } + + Value& Value::operator[](const String& key) { + return resolveReference(key.data(), key.data() + key.length()); + } + + Value& Value::operator[](const StaticString& key) { + return resolveReference(key.c_str()); + } + + Value& Value::append(const Value& value) { return append(Value(value)); } + + Value& Value::append(Value&& value) { + JSON_ASSERT_MESSAGE(type() == nullValue || type() == arrayValue, + "in Json::Value::append: requires arrayValue"); + if (type() == nullValue) { + *this = Value(arrayValue); + } + return this->value_.map_->emplace(size(), std::move(value)).first->second; + } + + bool Value::insert(ArrayIndex index, const Value& newValue) { + return insert(index, Value(newValue)); + } + + bool Value::insert(ArrayIndex index, Value&& newValue) { + JSON_ASSERT_MESSAGE(type() == nullValue || type() == arrayValue, + "in Json::Value::insert: requires arrayValue"); + ArrayIndex length = size(); + if (index > length) { + return false; + } + for (ArrayIndex i = length; i > index; i--) { + (*this)[i] = std::move((*this)[i - 1]); + } + (*this)[index] = std::move(newValue); + return true; + } + + Value Value::get(char const* begin, char const* end, + Value const& defaultValue) const { + Value const* found = find(begin, end); + return !found ? defaultValue : *found; + } + Value Value::get(char const* key, Value const& defaultValue) const { + return get(key, key + strlen(key), defaultValue); + } + Value Value::get(String const& key, Value const& defaultValue) const { + return get(key.data(), key.data() + key.length(), defaultValue); + } + + bool Value::removeMember(const char* begin, const char* end, Value* removed) { + if (type() != objectValue) { + return false; + } + CZString actualKey(begin, static_cast(end - begin), + CZString::noDuplication); + auto it = value_.map_->find(actualKey); + if (it == value_.map_->end()) + return false; + if (removed) + *removed = std::move(it->second); + value_.map_->erase(it); + return true; + } + bool Value::removeMember(const char* key, Value* removed) { + return removeMember(key, key + strlen(key), removed); + } + bool Value::removeMember(String const& key, Value* removed) { + return removeMember(key.data(), key.data() + key.length(), removed); + } + void Value::removeMember(const char* key) { + JSON_ASSERT_MESSAGE(type() == nullValue || type() == objectValue, + "in Json::Value::removeMember(): requires objectValue"); + if (type() == nullValue) + return; + + CZString actualKey(key, unsigned(strlen(key)), CZString::noDuplication); + value_.map_->erase(actualKey); + } + void Value::removeMember(const String& key) { removeMember(key.c_str()); } + + bool Value::removeIndex(ArrayIndex index, Value* removed) { + if (type() != arrayValue) { + return false; + } + CZString key(index); + auto it = value_.map_->find(key); + if (it == value_.map_->end()) { + return false; + } + if (removed) + *removed = it->second; + ArrayIndex oldSize = size(); + // shift left all items left, into the place of the "removed" + for (ArrayIndex i = index; i < (oldSize - 1); ++i) { + CZString keey(i); + (*value_.map_)[keey] = (*this)[i + 1]; + } + // erase the last one ("leftover") + CZString keyLast(oldSize - 1); + auto itLast = value_.map_->find(keyLast); + value_.map_->erase(itLast); + return true; + } + + bool Value::isMember(char const* begin, char const* end) const { + Value const* value = find(begin, end); + return nullptr != value; + } + bool Value::isMember(char const* key) const { + return isMember(key, key + strlen(key)); + } + bool Value::isMember(String const& key) const { + return isMember(key.data(), key.data() + key.length()); + } + + Value::Members Value::getMemberNames() const { + JSON_ASSERT_MESSAGE( + type() == nullValue || type() == objectValue, + "in Json::Value::getMemberNames(), value must be objectValue"); + if (type() == nullValue) + return Value::Members(); + Members members; + members.reserve(value_.map_->size()); + ObjectValues::const_iterator it = value_.map_->begin(); + ObjectValues::const_iterator itEnd = value_.map_->end(); + for (; it != itEnd; ++it) { + members.push_back(String((*it).first.data(), (*it).first.length())); + } + return members; + } + + static bool IsIntegral(double d) { + double integral_part; + return modf(d, &integral_part) == 0.0; + } + + bool Value::isNull() const { return type() == nullValue; } + + bool Value::isBool() const { return type() == booleanValue; } + + bool Value::isInt() const { + switch (type()) { + case intValue: +#if defined(JSON_HAS_INT64) + return value_.int_ >= minInt && value_.int_ <= maxInt; +#else + return true; +#endif + case uintValue: + return value_.uint_ <= UInt(maxInt); + case realValue: + return value_.real_ >= minInt && value_.real_ <= maxInt && + IsIntegral(value_.real_); + default: + break; + } + return false; + } + + bool Value::isUInt() const { + switch (type()) { + case intValue: +#if defined(JSON_HAS_INT64) + return value_.int_ >= 0 && LargestUInt(value_.int_) <= LargestUInt(maxUInt); +#else + return value_.int_ >= 0; +#endif + case uintValue: +#if defined(JSON_HAS_INT64) + return value_.uint_ <= maxUInt; +#else + return true; +#endif + case realValue: + return value_.real_ >= 0 && value_.real_ <= maxUInt && + IsIntegral(value_.real_); + default: + break; + } + return false; + } + + bool Value::isInt64() const { +#if defined(JSON_HAS_INT64) + switch (type()) { + case intValue: + return true; + case uintValue: + return value_.uint_ <= UInt64(maxInt64); + case realValue: + // Note that maxInt64 (= 2^63 - 1) is not exactly representable as a + // double, so double(maxInt64) will be rounded up to 2^63. Therefore we + // require the value to be strictly less than the limit. + return value_.real_ >= double(minInt64) && + value_.real_ < double(maxInt64) && IsIntegral(value_.real_); + default: + break; + } +#endif // JSON_HAS_INT64 + return false; + } + + bool Value::isUInt64() const { +#if defined(JSON_HAS_INT64) + switch (type()) { + case intValue: + return value_.int_ >= 0; + case uintValue: + return true; + case realValue: + // Note that maxUInt64 (= 2^64 - 1) is not exactly representable as a + // double, so double(maxUInt64) will be rounded up to 2^64. Therefore we + // require the value to be strictly less than the limit. + return value_.real_ >= 0 && value_.real_ < maxUInt64AsDouble&& + IsIntegral(value_.real_); + default: + break; + } +#endif // JSON_HAS_INT64 + return false; + } + + bool Value::isIntegral() const { + switch (type()) { + case intValue: + case uintValue: + return true; + case realValue: +#if defined(JSON_HAS_INT64) + // Note that maxUInt64 (= 2^64 - 1) is not exactly representable as a + // double, so double(maxUInt64) will be rounded up to 2^64. Therefore we + // require the value to be strictly less than the limit. + return value_.real_ >= double(minInt64) && + value_.real_ < maxUInt64AsDouble&& IsIntegral(value_.real_); +#else + return value_.real_ >= minInt && value_.real_ <= maxUInt && + IsIntegral(value_.real_); +#endif // JSON_HAS_INT64 + default: + break; + } + return false; + } + + bool Value::isDouble() const { + return type() == intValue || type() == uintValue || type() == realValue; + } + + bool Value::isNumeric() const { return isDouble(); } + + bool Value::isString() const { return type() == stringValue; } + + bool Value::isArray() const { return type() == arrayValue; } + + bool Value::isObject() const { return type() == objectValue; } + + Value::Comments::Comments(const Comments& that) + : ptr_{ cloneUnique(that.ptr_) } {} + + Value::Comments::Comments(Comments&& that) : ptr_{ std::move(that.ptr_) } {} + + Value::Comments& Value::Comments::operator=(const Comments& that) { + ptr_ = cloneUnique(that.ptr_); + return *this; + } + + Value::Comments& Value::Comments::operator=(Comments&& that) { + ptr_ = std::move(that.ptr_); + return *this; + } + + bool Value::Comments::has(CommentPlacement slot) const { + return ptr_ && !(*ptr_)[slot].empty(); + } + + String Value::Comments::get(CommentPlacement slot) const { + if (!ptr_) + return {}; + return (*ptr_)[slot]; + } + + void Value::Comments::set(CommentPlacement slot, String comment) { + if (!ptr_) { + ptr_ = std::unique_ptr(new Array()); + } + // check comments array boundry. + if (slot < CommentPlacement::numberOfCommentPlacement) { + (*ptr_)[slot] = std::move(comment); + } + } + + void Value::setComment(String comment, CommentPlacement placement) { + if (!comment.empty() && (comment.back() == '\n')) { + // Always discard trailing newline, to aid indentation. + comment.pop_back(); + } + JSON_ASSERT(!comment.empty()); + JSON_ASSERT_MESSAGE( + comment[0] == '\0' || comment[0] == '/', + "in Json::Value::setComment(): Comments must start with /"); + comments_.set(placement, std::move(comment)); + } + + bool Value::hasComment(CommentPlacement placement) const { + return comments_.has(placement); + } + + String Value::getComment(CommentPlacement placement) const { + return comments_.get(placement); + } + + void Value::setOffsetStart(ptrdiff_t start) { start_ = start; } + + void Value::setOffsetLimit(ptrdiff_t limit) { limit_ = limit; } + + ptrdiff_t Value::getOffsetStart() const { return start_; } + + ptrdiff_t Value::getOffsetLimit() const { return limit_; } + + String Value::toStyledString() const { + StreamWriterBuilder builder; + + String out = this->hasComment(commentBefore) ? "\n" : ""; + out += Json::writeString(builder, *this); + out += '\n'; + + return out; + } + + Value::const_iterator Value::begin() const { + switch (type()) { + case arrayValue: + case objectValue: + if (value_.map_) + return const_iterator(value_.map_->begin()); + break; + default: + break; + } + return {}; + } + + Value::const_iterator Value::end() const { + switch (type()) { + case arrayValue: + case objectValue: + if (value_.map_) + return const_iterator(value_.map_->end()); + break; + default: + break; + } + return {}; + } + + Value::iterator Value::begin() { + switch (type()) { + case arrayValue: + case objectValue: + if (value_.map_) + return iterator(value_.map_->begin()); + break; + default: + break; + } + return iterator(); + } + + Value::iterator Value::end() { + switch (type()) { + case arrayValue: + case objectValue: + if (value_.map_) + return iterator(value_.map_->end()); + break; + default: + break; + } + return iterator(); + } + + // class PathArgument + // ////////////////////////////////////////////////////////////////// + + PathArgument::PathArgument() = default; + + PathArgument::PathArgument(ArrayIndex index) + : index_(index), kind_(kindIndex) {} + + PathArgument::PathArgument(const char* key) : key_(key), kind_(kindKey) {} + + PathArgument::PathArgument(String key) : key_(std::move(key)), kind_(kindKey) {} + + // class Path + // ////////////////////////////////////////////////////////////////// + + Path::Path(const String& path, const PathArgument& a1, const PathArgument& a2, + const PathArgument& a3, const PathArgument& a4, + const PathArgument& a5) { + InArgs in; + in.reserve(5); + in.push_back(&a1); + in.push_back(&a2); + in.push_back(&a3); + in.push_back(&a4); + in.push_back(&a5); + makePath(path, in); + } + + void Path::makePath(const String& path, const InArgs& in) { + const char* current = path.c_str(); + const char* end = current + path.length(); + auto itInArg = in.begin(); + while (current != end) { + if (*current == '[') { + ++current; + if (*current == '%') + addPathInArg(path, in, itInArg, PathArgument::kindIndex); + else { + ArrayIndex index = 0; + for (; current != end && *current >= '0' && *current <= '9'; ++current) + index = index * 10 + ArrayIndex(*current - '0'); + args_.push_back(index); + } + if (current == end || *++current != ']') + invalidPath(path, int(current - path.c_str())); + } + else if (*current == '%') { + addPathInArg(path, in, itInArg, PathArgument::kindKey); + ++current; + } + else if (*current == '.' || *current == ']') { + ++current; + } + else { + const char* beginName = current; + while (current != end && !strchr("[.", *current)) + ++current; + args_.push_back(String(beginName, current)); + } + } + } + + void Path::addPathInArg(const String& /*path*/, const InArgs& in, + InArgs::const_iterator& itInArg, + PathArgument::Kind kind) { + if (itInArg == in.end()) { + // Error: missing argument %d + } + else if ((*itInArg)->kind_ != kind) { + // Error: bad argument type + } + else { + args_.push_back(**itInArg++); + } + } + + void Path::invalidPath(const String& /*path*/, int /*location*/) { + // Error: invalid path. + } + + const Value& Path::resolve(const Value& root) const { + const Value* node = &root; + for (const auto& arg : args_) { + if (arg.kind_ == PathArgument::kindIndex) { + if (!node->isArray() || !node->isValidIndex(arg.index_)) { + // Error: unable to resolve path (array value expected at position... ) + return Value::nullSingleton(); + } + node = &((*node)[arg.index_]); + } + else if (arg.kind_ == PathArgument::kindKey) { + if (!node->isObject()) { + // Error: unable to resolve path (object value expected at position...) + return Value::nullSingleton(); + } + node = &((*node)[arg.key_]); + if (node == &Value::nullSingleton()) { + // Error: unable to resolve path (object has no member named '' at + // position...) + return Value::nullSingleton(); + } + } + } + return *node; + } + + Value Path::resolve(const Value& root, const Value& defaultValue) const { + const Value* node = &root; + for (const auto& arg : args_) { + if (arg.kind_ == PathArgument::kindIndex) { + if (!node->isArray() || !node->isValidIndex(arg.index_)) + return defaultValue; + node = &((*node)[arg.index_]); + } + else if (arg.kind_ == PathArgument::kindKey) { + if (!node->isObject()) + return defaultValue; + node = &((*node)[arg.key_]); + if (node == &Value::nullSingleton()) + return defaultValue; + } + } + return *node; + } + + Value& Path::make(Value& root) const { + Value* node = &root; + for (const auto& arg : args_) { + if (arg.kind_ == PathArgument::kindIndex) { + if (!node->isArray()) { + // Error: node is not an array at position ... + } + node = &((*node)[arg.index_]); + } + else if (arg.kind_ == PathArgument::kindKey) { + if (!node->isObject()) { + // Error: node is not an object at position... + } + node = &((*node)[arg.key_]); + } + } + return *node; + } + +} // namespace Json + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: src/lib_json/json_value.cpp +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: src/lib_json/json_writer.cpp +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2011 Baptiste Lepilleur and The JsonCpp Authors +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#if !defined(JSON_IS_AMALGAMATION) +#include "json_tool.h" +#include +#endif // if !defined(JSON_IS_AMALGAMATION) +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if __cplusplus >= 201103L +#include +#include + +#if !defined(isnan) +#define isnan std::isnan +#endif + +#if !defined(isfinite) +#define isfinite std::isfinite +#endif + +#else +#include +#include + +#if defined(_MSC_VER) +#if !defined(isnan) +#include +#define isnan _isnan +#endif + +#if !defined(isfinite) +#include +#define isfinite _finite +#endif + +#if !defined(_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES) +#define _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES 1 +#endif //_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES + +#endif //_MSC_VER + +#if defined(__sun) && defined(__SVR4) // Solaris +#if !defined(isfinite) +#include +#define isfinite finite +#endif +#endif + +#if defined(__hpux) +#if !defined(isfinite) +#if defined(__ia64) && !defined(finite) +#define isfinite(x) \ + ((sizeof(x) == sizeof(float) ? _Isfinitef(x) : _IsFinite(x))) +#endif +#endif +#endif + +#if !defined(isnan) +// IEEE standard states that NaN values will not compare to themselves +#define isnan(x) (x != x) +#endif + +#if !defined(__APPLE__) +#if !defined(isfinite) +#define isfinite finite +#endif +#endif +#endif + +#if defined(_MSC_VER) +// Disable warning about strdup being deprecated. +#pragma warning(disable : 4996) +#endif + +namespace Json { + +#if __cplusplus >= 201103L || (defined(_CPPLIB_VER) && _CPPLIB_VER >= 520) + using StreamWriterPtr = std::unique_ptr; +#else + using StreamWriterPtr = std::auto_ptr; +#endif + + String valueToString(LargestInt value) { + UIntToStringBuffer buffer; + char* current = buffer + sizeof(buffer); + if (value == Value::minLargestInt) { + uintToString(LargestUInt(Value::maxLargestInt) + 1, current); + *--current = '-'; + } + else if (value < 0) { + uintToString(LargestUInt(-value), current); + *--current = '-'; + } + else { + uintToString(LargestUInt(value), current); + } + assert(current >= buffer); + return current; + } + + String valueToString(LargestUInt value) { + UIntToStringBuffer buffer; + char* current = buffer + sizeof(buffer); + uintToString(value, current); + assert(current >= buffer); + return current; + } + +#if defined(JSON_HAS_INT64) + + String valueToString(Int value) { return valueToString(LargestInt(value)); } + + String valueToString(UInt value) { return valueToString(LargestUInt(value)); } + +#endif // # if defined(JSON_HAS_INT64) + + namespace { + String valueToString(double value, bool useSpecialFloats, + unsigned int precision, PrecisionType precisionType) { + // Print into the buffer. We need not request the alternative representation + // that always has a decimal point because JSON doesn't distinguish the + // concepts of reals and integers. + if (!isfinite(value)) { + static const char* const reps[2][3] = { {"NaN", "-Infinity", "Infinity"}, + {"null", "-1e+9999", "1e+9999"} }; + return reps[useSpecialFloats ? 0 : 1] + [isnan(value) ? 0 : (value < 0) ? 1 : 2]; + } + + String buffer(size_t(36), '\0'); + while (true) { + int len = jsoncpp_snprintf( + &*buffer.begin(), buffer.size(), + (precisionType == PrecisionType::significantDigits) ? "%.*g" : "%.*f", + precision, value); + assert(len >= 0); + auto wouldPrint = static_cast(len); + if (wouldPrint >= buffer.size()) { + buffer.resize(wouldPrint + 1); + continue; + } + buffer.resize(wouldPrint); + break; + } + + buffer.erase(fixNumericLocale(buffer.begin(), buffer.end()), buffer.end()); + + // strip the zero padding from the right + if (precisionType == PrecisionType::decimalPlaces) { + buffer.erase(fixZerosInTheEnd(buffer.begin(), buffer.end()), buffer.end()); + } + + // try to ensure we preserve the fact that this was given to us as a double on + // input + if (buffer.find('.') == buffer.npos && buffer.find('e') == buffer.npos) { + buffer += ".0"; + } + return buffer; + } + } // namespace + + String valueToString(double value, unsigned int precision, + PrecisionType precisionType) { + return valueToString(value, false, precision, precisionType); + } + + String valueToString(bool value) { return value ? "true" : "false"; } + + static bool isAnyCharRequiredQuoting(char const* s, size_t n) { + assert(s || !n); + + return std::any_of(s, s + n, [](unsigned char c) { + return c == '\\' || c == '"' || !std::isprint(c); + }); + } + + static unsigned int utf8ToCodepoint(const char*& s, const char* e) { + const unsigned int REPLACEMENT_CHARACTER = 0xFFFD; + + unsigned int firstByte = static_cast(*s); + + if (firstByte < 0x80) + return firstByte; + + if (firstByte < 0xE0) { + if (e - s < 2) + return REPLACEMENT_CHARACTER; + + unsigned int calculated = + ((firstByte & 0x1F) << 6) | (static_cast(s[1]) & 0x3F); + s += 1; + // oversized encoded characters are invalid + return calculated < 0x80 ? REPLACEMENT_CHARACTER : calculated; + } + + if (firstByte < 0xF0) { + if (e - s < 3) + return REPLACEMENT_CHARACTER; + + unsigned int calculated = ((firstByte & 0x0F) << 12) | + ((static_cast(s[1]) & 0x3F) << 6) | + (static_cast(s[2]) & 0x3F); + s += 2; + // surrogates aren't valid codepoints itself + // shouldn't be UTF-8 encoded + if (calculated >= 0xD800 && calculated <= 0xDFFF) + return REPLACEMENT_CHARACTER; + // oversized encoded characters are invalid + return calculated < 0x800 ? REPLACEMENT_CHARACTER : calculated; + } + + if (firstByte < 0xF8) { + if (e - s < 4) + return REPLACEMENT_CHARACTER; + + unsigned int calculated = ((firstByte & 0x07) << 18) | + ((static_cast(s[1]) & 0x3F) << 12) | + ((static_cast(s[2]) & 0x3F) << 6) | + (static_cast(s[3]) & 0x3F); + s += 3; + // oversized encoded characters are invalid + return calculated < 0x10000 ? REPLACEMENT_CHARACTER : calculated; + } + + return REPLACEMENT_CHARACTER; + } + + static const char hex2[] = "000102030405060708090a0b0c0d0e0f" + "101112131415161718191a1b1c1d1e1f" + "202122232425262728292a2b2c2d2e2f" + "303132333435363738393a3b3c3d3e3f" + "404142434445464748494a4b4c4d4e4f" + "505152535455565758595a5b5c5d5e5f" + "606162636465666768696a6b6c6d6e6f" + "707172737475767778797a7b7c7d7e7f" + "808182838485868788898a8b8c8d8e8f" + "909192939495969798999a9b9c9d9e9f" + "a0a1a2a3a4a5a6a7a8a9aaabacadaeaf" + "b0b1b2b3b4b5b6b7b8b9babbbcbdbebf" + "c0c1c2c3c4c5c6c7c8c9cacbcccdcecf" + "d0d1d2d3d4d5d6d7d8d9dadbdcdddedf" + "e0e1e2e3e4e5e6e7e8e9eaebecedeeef" + "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"; + + static String toHex16Bit(unsigned int x) { + const unsigned int hi = (x >> 8) & 0xff; + const unsigned int lo = x & 0xff; + String result(4, ' '); + result[0] = hex2[2 * hi]; + result[1] = hex2[2 * hi + 1]; + result[2] = hex2[2 * lo]; + result[3] = hex2[2 * lo + 1]; + return result; + } + + static void appendRaw(String& result, unsigned ch) { + result += static_cast(ch); + } + + static void appendHex(String& result, unsigned ch) { + result.append("\\u").append(toHex16Bit(ch)); + } + + static String valueToQuotedStringN(const char* value, unsigned length, + bool emitUTF8 = false) { + if (value == nullptr) + return ""; + + if (!isAnyCharRequiredQuoting(value, length)) + return String("\"") + value + "\""; + // We have to walk value and escape any special characters. + // Appending to String is not efficient, but this should be rare. + // (Note: forward slashes are *not* rare, but I am not escaping them.) + String::size_type maxsize = length * 2 + 3; // allescaped+quotes+NULL + String result; + result.reserve(maxsize); // to avoid lots of mallocs + result += "\""; + char const* end = value + length; + for (const char* c = value; c != end; ++c) { + switch (*c) { + case '\"': + result += "\\\""; + break; + case '\\': + result += "\\\\"; + break; + case '\b': + result += "\\b"; + break; + case '\f': + result += "\\f"; + break; + case '\n': + result += "\\n"; + break; + case '\r': + result += "\\r"; + break; + case '\t': + result += "\\t"; + break; + // case '/': + // Even though \/ is considered a legal escape in JSON, a bare + // slash is also legal, so I see no reason to escape it. + // (I hope I am not misunderstanding something.) + // blep notes: actually escaping \/ may be useful in javascript to avoid (*c); + if (codepoint < 0x20) { + appendHex(result, codepoint); + } + else { + appendRaw(result, codepoint); + } + } + else { + unsigned codepoint = utf8ToCodepoint(c, end); // modifies `c` + if (codepoint < 0x20) { + appendHex(result, codepoint); + } + else if (codepoint < 0x80) { + appendRaw(result, codepoint); + } + else if (codepoint < 0x10000) { + // Basic Multilingual Plane + appendHex(result, codepoint); + } + else { + // Extended Unicode. Encode 20 bits as a surrogate pair. + codepoint -= 0x10000; + appendHex(result, 0xd800 + ((codepoint >> 10) & 0x3ff)); + appendHex(result, 0xdc00 + (codepoint & 0x3ff)); + } + } + } break; + } + } + result += "\""; + return result; + } + + String valueToQuotedString(const char* value) { + return valueToQuotedStringN(value, static_cast(strlen(value))); + } + + // Class Writer + // ////////////////////////////////////////////////////////////////// + Writer::~Writer() = default; + + // Class FastWriter + // ////////////////////////////////////////////////////////////////// + + FastWriter::FastWriter() + + = default; + + void FastWriter::enableYAMLCompatibility() { yamlCompatibilityEnabled_ = true; } + + void FastWriter::dropNullPlaceholders() { dropNullPlaceholders_ = true; } + + void FastWriter::omitEndingLineFeed() { omitEndingLineFeed_ = true; } + + String FastWriter::write(const Value& root) { + document_.clear(); + writeValue(root); + if (!omitEndingLineFeed_) + document_ += '\n'; + return document_; + } + + void FastWriter::writeValue(const Value& value) { + switch (value.type()) { + case nullValue: + if (!dropNullPlaceholders_) + document_ += "null"; + break; + case intValue: + document_ += valueToString(value.asLargestInt()); + break; + case uintValue: + document_ += valueToString(value.asLargestUInt()); + break; + case realValue: + document_ += valueToString(value.asDouble()); + break; + case stringValue: { + // Is NULL possible for value.string_? No. + char const* str; + char const* end; + bool ok = value.getString(&str, &end); + if (ok) + document_ += valueToQuotedStringN(str, static_cast(end - str)); + break; + } + case booleanValue: + document_ += valueToString(value.asBool()); + break; + case arrayValue: { + document_ += '['; + ArrayIndex size = value.size(); + for (ArrayIndex index = 0; index < size; ++index) { + if (index > 0) + document_ += ','; + writeValue(value[index]); + } + document_ += ']'; + } break; + case objectValue: { + Value::Members members(value.getMemberNames()); + document_ += '{'; + for (auto it = members.begin(); it != members.end(); ++it) { + const String& name = *it; + if (it != members.begin()) + document_ += ','; + document_ += valueToQuotedStringN(name.data(), + static_cast(name.length())); + document_ += yamlCompatibilityEnabled_ ? ": " : ":"; + writeValue(value[name]); + } + document_ += '}'; + } break; + } + } + + // Class StyledWriter + // ////////////////////////////////////////////////////////////////// + + StyledWriter::StyledWriter() = default; + + String StyledWriter::write(const Value& root) { + document_.clear(); + addChildValues_ = false; + indentString_.clear(); + writeCommentBeforeValue(root); + writeValue(root); + writeCommentAfterValueOnSameLine(root); + document_ += '\n'; + return document_; + } + + void StyledWriter::writeValue(const Value& value) { + switch (value.type()) { + case nullValue: + pushValue("null"); + break; + case intValue: + pushValue(valueToString(value.asLargestInt())); + break; + case uintValue: + pushValue(valueToString(value.asLargestUInt())); + break; + case realValue: + pushValue(valueToString(value.asDouble())); + break; + case stringValue: { + // Is NULL possible for value.string_? No. + char const* str; + char const* end; + bool ok = value.getString(&str, &end); + if (ok) + pushValue(valueToQuotedStringN(str, static_cast(end - str))); + else + pushValue(""); + break; + } + case booleanValue: + pushValue(valueToString(value.asBool())); + break; + case arrayValue: + writeArrayValue(value); + break; + case objectValue: { + Value::Members members(value.getMemberNames()); + if (members.empty()) + pushValue("{}"); + else { + writeWithIndent("{"); + indent(); + auto it = members.begin(); + for (;;) { + const String& name = *it; + const Value& childValue = value[name]; + writeCommentBeforeValue(childValue); + writeWithIndent(valueToQuotedString(name.c_str())); + document_ += " : "; + writeValue(childValue); + if (++it == members.end()) { + writeCommentAfterValueOnSameLine(childValue); + break; + } + document_ += ','; + writeCommentAfterValueOnSameLine(childValue); + } + unindent(); + writeWithIndent("}"); + } + } break; + } + } + + void StyledWriter::writeArrayValue(const Value& value) { + unsigned size = value.size(); + if (size == 0) + pushValue("[]"); + else { + bool isArrayMultiLine = isMultilineArray(value); + if (isArrayMultiLine) { + writeWithIndent("["); + indent(); + bool hasChildValue = !childValues_.empty(); + unsigned index = 0; + for (;;) { + const Value& childValue = value[index]; + writeCommentBeforeValue(childValue); + if (hasChildValue) + writeWithIndent(childValues_[index]); + else { + writeIndent(); + writeValue(childValue); + } + if (++index == size) { + writeCommentAfterValueOnSameLine(childValue); + break; + } + document_ += ','; + writeCommentAfterValueOnSameLine(childValue); + } + unindent(); + writeWithIndent("]"); + } + else // output on a single line + { + assert(childValues_.size() == size); + document_ += "[ "; + for (unsigned index = 0; index < size; ++index) { + if (index > 0) + document_ += ", "; + document_ += childValues_[index]; + } + document_ += " ]"; + } + } + } + + bool StyledWriter::isMultilineArray(const Value& value) { + ArrayIndex const size = value.size(); + bool isMultiLine = size * 3 >= rightMargin_; + childValues_.clear(); + for (ArrayIndex index = 0; index < size && !isMultiLine; ++index) { + const Value& childValue = value[index]; + isMultiLine = ((childValue.isArray() || childValue.isObject()) && + !childValue.empty()); + } + if (!isMultiLine) // check if line length > max line length + { + childValues_.reserve(size); + addChildValues_ = true; + ArrayIndex lineLength = 4 + (size - 1) * 2; // '[ ' + ', '*n + ' ]' + for (ArrayIndex index = 0; index < size; ++index) { + if (hasCommentForValue(value[index])) { + isMultiLine = true; + } + writeValue(value[index]); + lineLength += static_cast(childValues_[index].length()); + } + addChildValues_ = false; + isMultiLine = isMultiLine || lineLength >= rightMargin_; + } + return isMultiLine; + } + + void StyledWriter::pushValue(const String& value) { + if (addChildValues_) + childValues_.push_back(value); + else + document_ += value; + } + + void StyledWriter::writeIndent() { + if (!document_.empty()) { + char last = document_[document_.length() - 1]; + if (last == ' ') // already indented + return; + if (last != '\n') // Comments may add new-line + document_ += '\n'; + } + document_ += indentString_; + } + + void StyledWriter::writeWithIndent(const String& value) { + writeIndent(); + document_ += value; + } + + void StyledWriter::indent() { indentString_ += String(indentSize_, ' '); } + + void StyledWriter::unindent() { + assert(indentString_.size() >= indentSize_); + indentString_.resize(indentString_.size() - indentSize_); + } + + void StyledWriter::writeCommentBeforeValue(const Value& root) { + if (!root.hasComment(commentBefore)) + return; + + document_ += '\n'; + writeIndent(); + const String& comment = root.getComment(commentBefore); + String::const_iterator iter = comment.begin(); + while (iter != comment.end()) { + document_ += *iter; + if (*iter == '\n' && ((iter + 1) != comment.end() && *(iter + 1) == '/')) + writeIndent(); + ++iter; + } + + // Comments are stripped of trailing newlines, so add one here + document_ += '\n'; + } + + void StyledWriter::writeCommentAfterValueOnSameLine(const Value& root) { + if (root.hasComment(commentAfterOnSameLine)) + document_ += " " + root.getComment(commentAfterOnSameLine); + + if (root.hasComment(commentAfter)) { + document_ += '\n'; + document_ += root.getComment(commentAfter); + document_ += '\n'; + } + } + + bool StyledWriter::hasCommentForValue(const Value& value) { + return value.hasComment(commentBefore) || + value.hasComment(commentAfterOnSameLine) || + value.hasComment(commentAfter); + } + + // Class StyledStreamWriter + // ////////////////////////////////////////////////////////////////// + + StyledStreamWriter::StyledStreamWriter(String indentation) + : document_(nullptr), indentation_(std::move(indentation)), + addChildValues_(), indented_(false) {} + + void StyledStreamWriter::write(OStream& out, const Value& root) { + document_ = &out; + addChildValues_ = false; + indentString_.clear(); + indented_ = true; + writeCommentBeforeValue(root); + if (!indented_) + writeIndent(); + indented_ = true; + writeValue(root); + writeCommentAfterValueOnSameLine(root); + *document_ << "\n"; + document_ = nullptr; // Forget the stream, for safety. + } + + void StyledStreamWriter::writeValue(const Value& value) { + switch (value.type()) { + case nullValue: + pushValue("null"); + break; + case intValue: + pushValue(valueToString(value.asLargestInt())); + break; + case uintValue: + pushValue(valueToString(value.asLargestUInt())); + break; + case realValue: + pushValue(valueToString(value.asDouble())); + break; + case stringValue: { + // Is NULL possible for value.string_? No. + char const* str; + char const* end; + bool ok = value.getString(&str, &end); + if (ok) + pushValue(valueToQuotedStringN(str, static_cast(end - str))); + else + pushValue(""); + break; + } + case booleanValue: + pushValue(valueToString(value.asBool())); + break; + case arrayValue: + writeArrayValue(value); + break; + case objectValue: { + Value::Members members(value.getMemberNames()); + if (members.empty()) + pushValue("{}"); + else { + writeWithIndent("{"); + indent(); + auto it = members.begin(); + for (;;) { + const String& name = *it; + const Value& childValue = value[name]; + writeCommentBeforeValue(childValue); + writeWithIndent(valueToQuotedString(name.c_str())); + *document_ << " : "; + writeValue(childValue); + if (++it == members.end()) { + writeCommentAfterValueOnSameLine(childValue); + break; + } + *document_ << ","; + writeCommentAfterValueOnSameLine(childValue); + } + unindent(); + writeWithIndent("}"); + } + } break; + } + } + + void StyledStreamWriter::writeArrayValue(const Value& value) { + unsigned size = value.size(); + if (size == 0) + pushValue("[]"); + else { + bool isArrayMultiLine = isMultilineArray(value); + if (isArrayMultiLine) { + writeWithIndent("["); + indent(); + bool hasChildValue = !childValues_.empty(); + unsigned index = 0; + for (;;) { + const Value& childValue = value[index]; + writeCommentBeforeValue(childValue); + if (hasChildValue) + writeWithIndent(childValues_[index]); + else { + if (!indented_) + writeIndent(); + indented_ = true; + writeValue(childValue); + indented_ = false; + } + if (++index == size) { + writeCommentAfterValueOnSameLine(childValue); + break; + } + *document_ << ","; + writeCommentAfterValueOnSameLine(childValue); + } + unindent(); + writeWithIndent("]"); + } + else // output on a single line + { + assert(childValues_.size() == size); + *document_ << "[ "; + for (unsigned index = 0; index < size; ++index) { + if (index > 0) + *document_ << ", "; + *document_ << childValues_[index]; + } + *document_ << " ]"; + } + } + } + + bool StyledStreamWriter::isMultilineArray(const Value& value) { + ArrayIndex const size = value.size(); + bool isMultiLine = size * 3 >= rightMargin_; + childValues_.clear(); + for (ArrayIndex index = 0; index < size && !isMultiLine; ++index) { + const Value& childValue = value[index]; + isMultiLine = ((childValue.isArray() || childValue.isObject()) && + !childValue.empty()); + } + if (!isMultiLine) // check if line length > max line length + { + childValues_.reserve(size); + addChildValues_ = true; + ArrayIndex lineLength = 4 + (size - 1) * 2; // '[ ' + ', '*n + ' ]' + for (ArrayIndex index = 0; index < size; ++index) { + if (hasCommentForValue(value[index])) { + isMultiLine = true; + } + writeValue(value[index]); + lineLength += static_cast(childValues_[index].length()); + } + addChildValues_ = false; + isMultiLine = isMultiLine || lineLength >= rightMargin_; + } + return isMultiLine; + } + + void StyledStreamWriter::pushValue(const String& value) { + if (addChildValues_) + childValues_.push_back(value); + else + *document_ << value; + } + + void StyledStreamWriter::writeIndent() { + // blep intended this to look at the so-far-written string + // to determine whether we are already indented, but + // with a stream we cannot do that. So we rely on some saved state. + // The caller checks indented_. + *document_ << '\n' << indentString_; + } + + void StyledStreamWriter::writeWithIndent(const String& value) { + if (!indented_) + writeIndent(); + *document_ << value; + indented_ = false; + } + + void StyledStreamWriter::indent() { indentString_ += indentation_; } + + void StyledStreamWriter::unindent() { + assert(indentString_.size() >= indentation_.size()); + indentString_.resize(indentString_.size() - indentation_.size()); + } + + void StyledStreamWriter::writeCommentBeforeValue(const Value& root) { + if (!root.hasComment(commentBefore)) + return; + + if (!indented_) + writeIndent(); + const String& comment = root.getComment(commentBefore); + String::const_iterator iter = comment.begin(); + while (iter != comment.end()) { + *document_ << *iter; + if (*iter == '\n' && ((iter + 1) != comment.end() && *(iter + 1) == '/')) + // writeIndent(); // would include newline + *document_ << indentString_; + ++iter; + } + indented_ = false; + } + + void StyledStreamWriter::writeCommentAfterValueOnSameLine(const Value& root) { + if (root.hasComment(commentAfterOnSameLine)) + *document_ << ' ' << root.getComment(commentAfterOnSameLine); + + if (root.hasComment(commentAfter)) { + writeIndent(); + *document_ << root.getComment(commentAfter); + } + indented_ = false; + } + + bool StyledStreamWriter::hasCommentForValue(const Value& value) { + return value.hasComment(commentBefore) || + value.hasComment(commentAfterOnSameLine) || + value.hasComment(commentAfter); + } + + ////////////////////////// + // BuiltStyledStreamWriter + + /// Scoped enums are not available until C++11. + struct CommentStyle { + /// Decide whether to write comments. + enum Enum { + None, ///< Drop all comments. + Most, ///< Recover odd behavior of previous versions (not implemented yet). + All ///< Keep all comments. + }; + }; + + struct BuiltStyledStreamWriter : public StreamWriter { + BuiltStyledStreamWriter(String indentation, CommentStyle::Enum cs, + String colonSymbol, String nullSymbol, + String endingLineFeedSymbol, bool useSpecialFloats, + bool emitUTF8, unsigned int precision, + PrecisionType precisionType); + int write(Value const& root, OStream* sout) override; + + private: + void writeValue(Value const& value); + void writeArrayValue(Value const& value); + bool isMultilineArray(Value const& value); + void pushValue(String const& value); + void writeIndent(); + void writeWithIndent(String const& value); + void indent(); + void unindent(); + void writeCommentBeforeValue(Value const& root); + void writeCommentAfterValueOnSameLine(Value const& root); + static bool hasCommentForValue(const Value& value); + + using ChildValues = std::vector; + + ChildValues childValues_; + String indentString_; + unsigned int rightMargin_; + String indentation_; + CommentStyle::Enum cs_; + String colonSymbol_; + String nullSymbol_; + String endingLineFeedSymbol_; + bool addChildValues_ : 1; + bool indented_ : 1; + bool useSpecialFloats_ : 1; + bool emitUTF8_ : 1; + unsigned int precision_; + PrecisionType precisionType_; + }; + BuiltStyledStreamWriter::BuiltStyledStreamWriter( + String indentation, CommentStyle::Enum cs, String colonSymbol, + String nullSymbol, String endingLineFeedSymbol, bool useSpecialFloats, + bool emitUTF8, unsigned int precision, PrecisionType precisionType) + : rightMargin_(74), indentation_(std::move(indentation)), cs_(cs), + colonSymbol_(std::move(colonSymbol)), nullSymbol_(std::move(nullSymbol)), + endingLineFeedSymbol_(std::move(endingLineFeedSymbol)), + addChildValues_(false), indented_(false), + useSpecialFloats_(useSpecialFloats), emitUTF8_(emitUTF8), + precision_(precision), precisionType_(precisionType) {} + int BuiltStyledStreamWriter::write(Value const& root, OStream* sout) { + sout_ = sout; + addChildValues_ = false; + indented_ = true; + indentString_.clear(); + writeCommentBeforeValue(root); + if (!indented_) + writeIndent(); + indented_ = true; + writeValue(root); + writeCommentAfterValueOnSameLine(root); + *sout_ << endingLineFeedSymbol_; + sout_ = nullptr; + return 0; + } + void BuiltStyledStreamWriter::writeValue(Value const& value) { + switch (value.type()) { + case nullValue: + pushValue(nullSymbol_); + break; + case intValue: + pushValue(valueToString(value.asLargestInt())); + break; + case uintValue: + pushValue(valueToString(value.asLargestUInt())); + break; + case realValue: + pushValue(valueToString(value.asDouble(), useSpecialFloats_, precision_, + precisionType_)); + break; + case stringValue: { + // Is NULL is possible for value.string_? No. + char const* str; + char const* end; + bool ok = value.getString(&str, &end); + if (ok) + pushValue(valueToQuotedStringN(str, static_cast(end - str), + emitUTF8_)); + else + pushValue(""); + break; + } + case booleanValue: + pushValue(valueToString(value.asBool())); + break; + case arrayValue: + writeArrayValue(value); + break; + case objectValue: { + Value::Members members(value.getMemberNames()); + if (members.empty()) + pushValue("{}"); + else { + writeWithIndent("{"); + indent(); + auto it = members.begin(); + for (;;) { + String const& name = *it; + Value const& childValue = value[name]; + writeCommentBeforeValue(childValue); + writeWithIndent(valueToQuotedStringN( + name.data(), static_cast(name.length()), emitUTF8_)); + *sout_ << colonSymbol_; + writeValue(childValue); + if (++it == members.end()) { + writeCommentAfterValueOnSameLine(childValue); + break; + } + *sout_ << ","; + writeCommentAfterValueOnSameLine(childValue); + } + unindent(); + writeWithIndent("}"); + } + } break; + } + } + + void BuiltStyledStreamWriter::writeArrayValue(Value const& value) { + unsigned size = value.size(); + if (size == 0) + pushValue("[]"); + else { + bool isMultiLine = (cs_ == CommentStyle::All) || isMultilineArray(value); + if (isMultiLine) { + writeWithIndent("["); + indent(); + bool hasChildValue = !childValues_.empty(); + unsigned index = 0; + for (;;) { + Value const& childValue = value[index]; + writeCommentBeforeValue(childValue); + if (hasChildValue) + writeWithIndent(childValues_[index]); + else { + if (!indented_) + writeIndent(); + indented_ = true; + writeValue(childValue); + indented_ = false; + } + if (++index == size) { + writeCommentAfterValueOnSameLine(childValue); + break; + } + *sout_ << ","; + writeCommentAfterValueOnSameLine(childValue); + } + unindent(); + writeWithIndent("]"); + } + else // output on a single line + { + assert(childValues_.size() == size); + *sout_ << "["; + if (!indentation_.empty()) + *sout_ << " "; + for (unsigned index = 0; index < size; ++index) { + if (index > 0) + *sout_ << ((!indentation_.empty()) ? ", " : ","); + *sout_ << childValues_[index]; + } + if (!indentation_.empty()) + *sout_ << " "; + *sout_ << "]"; + } + } + } + + bool BuiltStyledStreamWriter::isMultilineArray(Value const& value) { + ArrayIndex const size = value.size(); + bool isMultiLine = size * 3 >= rightMargin_; + childValues_.clear(); + for (ArrayIndex index = 0; index < size && !isMultiLine; ++index) { + Value const& childValue = value[index]; + isMultiLine = ((childValue.isArray() || childValue.isObject()) && + !childValue.empty()); + } + if (!isMultiLine) // check if line length > max line length + { + childValues_.reserve(size); + addChildValues_ = true; + ArrayIndex lineLength = 4 + (size - 1) * 2; // '[ ' + ', '*n + ' ]' + for (ArrayIndex index = 0; index < size; ++index) { + if (hasCommentForValue(value[index])) { + isMultiLine = true; + } + writeValue(value[index]); + lineLength += static_cast(childValues_[index].length()); + } + addChildValues_ = false; + isMultiLine = isMultiLine || lineLength >= rightMargin_; + } + return isMultiLine; + } + + void BuiltStyledStreamWriter::pushValue(String const& value) { + if (addChildValues_) + childValues_.push_back(value); + else + *sout_ << value; + } + + void BuiltStyledStreamWriter::writeIndent() { + // blep intended this to look at the so-far-written string + // to determine whether we are already indented, but + // with a stream we cannot do that. So we rely on some saved state. + // The caller checks indented_. + + if (!indentation_.empty()) { + // In this case, drop newlines too. + *sout_ << '\n' << indentString_; + } + } + + void BuiltStyledStreamWriter::writeWithIndent(String const& value) { + if (!indented_) + writeIndent(); + *sout_ << value; + indented_ = false; + } + + void BuiltStyledStreamWriter::indent() { indentString_ += indentation_; } + + void BuiltStyledStreamWriter::unindent() { + assert(indentString_.size() >= indentation_.size()); + indentString_.resize(indentString_.size() - indentation_.size()); + } + + void BuiltStyledStreamWriter::writeCommentBeforeValue(Value const& root) { + if (cs_ == CommentStyle::None) + return; + if (!root.hasComment(commentBefore)) + return; + + if (!indented_) + writeIndent(); + const String& comment = root.getComment(commentBefore); + String::const_iterator iter = comment.begin(); + while (iter != comment.end()) { + *sout_ << *iter; + if (*iter == '\n' && ((iter + 1) != comment.end() && *(iter + 1) == '/')) + // writeIndent(); // would write extra newline + *sout_ << indentString_; + ++iter; + } + indented_ = false; + } + + void BuiltStyledStreamWriter::writeCommentAfterValueOnSameLine( + Value const& root) { + if (cs_ == CommentStyle::None) + return; + if (root.hasComment(commentAfterOnSameLine)) + *sout_ << " " + root.getComment(commentAfterOnSameLine); + + if (root.hasComment(commentAfter)) { + writeIndent(); + *sout_ << root.getComment(commentAfter); + } + } + + // static + bool BuiltStyledStreamWriter::hasCommentForValue(const Value& value) { + return value.hasComment(commentBefore) || + value.hasComment(commentAfterOnSameLine) || + value.hasComment(commentAfter); + } + + /////////////// + // StreamWriter + + StreamWriter::StreamWriter() : sout_(nullptr) {} + StreamWriter::~StreamWriter() = default; + StreamWriter::Factory::~Factory() = default; + StreamWriterBuilder::StreamWriterBuilder() { setDefaults(&settings_); } + StreamWriterBuilder::~StreamWriterBuilder() = default; + StreamWriter* StreamWriterBuilder::newStreamWriter() const { + const String indentation = settings_["indentation"].asString(); + const String cs_str = settings_["commentStyle"].asString(); + const String pt_str = settings_["precisionType"].asString(); + const bool eyc = settings_["enableYAMLCompatibility"].asBool(); + const bool dnp = settings_["dropNullPlaceholders"].asBool(); + const bool usf = settings_["useSpecialFloats"].asBool(); + const bool emitUTF8 = settings_["emitUTF8"].asBool(); + unsigned int pre = settings_["precision"].asUInt(); + CommentStyle::Enum cs = CommentStyle::All; + if (cs_str == "All") { + cs = CommentStyle::All; + } + else if (cs_str == "None") { + cs = CommentStyle::None; + } + else { + throwRuntimeError("commentStyle must be 'All' or 'None'"); + } + PrecisionType precisionType(significantDigits); + if (pt_str == "significant") { + precisionType = PrecisionType::significantDigits; + } + else if (pt_str == "decimal") { + precisionType = PrecisionType::decimalPlaces; + } + else { + throwRuntimeError("precisionType must be 'significant' or 'decimal'"); + } + String colonSymbol = " : "; + if (eyc) { + colonSymbol = ": "; + } + else if (indentation.empty()) { + colonSymbol = ":"; + } + String nullSymbol = "null"; + if (dnp) { + nullSymbol.clear(); + } + if (pre > 17) + pre = 17; + String endingLineFeedSymbol; + return new BuiltStyledStreamWriter(indentation, cs, colonSymbol, nullSymbol, + endingLineFeedSymbol, usf, emitUTF8, pre, + precisionType); + } + + bool StreamWriterBuilder::validate(Json::Value* invalid) const { + static const auto& valid_keys = *new std::set{ + "indentation", + "commentStyle", + "enableYAMLCompatibility", + "dropNullPlaceholders", + "useSpecialFloats", + "emitUTF8", + "precision", + "precisionType", + }; + for (auto si = settings_.begin(); si != settings_.end(); ++si) { + auto key = si.name(); + if (valid_keys.count(key)) + continue; + if (invalid) + (*invalid)[std::move(key)] = *si; + else + return false; + } + return invalid ? invalid->empty() : true; + } + + Value& StreamWriterBuilder::operator[](const String& key) { + return settings_[key]; + } + // static + void StreamWriterBuilder::setDefaults(Json::Value* settings) { + //! [StreamWriterBuilderDefaults] + (*settings)["commentStyle"] = "All"; + (*settings)["indentation"] = "\t"; + (*settings)["enableYAMLCompatibility"] = false; + (*settings)["dropNullPlaceholders"] = false; + (*settings)["useSpecialFloats"] = false; + (*settings)["emitUTF8"] = false; + (*settings)["precision"] = 17; + (*settings)["precisionType"] = "significant"; + //! [StreamWriterBuilderDefaults] + } + + String writeString(StreamWriter::Factory const& factory, Value const& root) { + OStringStream sout; + StreamWriterPtr const writer(factory.newStreamWriter()); + writer->write(root, &sout); + return sout.str(); + } + + OStream& operator<<(OStream& sout, Value const& root) { + StreamWriterBuilder builder; + StreamWriterPtr const writer(builder.newStreamWriter()); + writer->write(root, &sout); + return sout; + } + +} // namespace Json + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: src/lib_json/json_writer.cpp +// ////////////////////////////////////////////////////////////////////// + + + + + diff --git a/src/WinGetUtil/WinGetUtil.vcxproj b/src/WinGetUtil/WinGetUtil.vcxproj index d34a514ea7..f12d45877e 100644 --- a/src/WinGetUtil/WinGetUtil.vcxproj +++ b/src/WinGetUtil/WinGetUtil.vcxproj @@ -124,9 +124,9 @@ Disabled _DEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD - $(ProjectDir);$(ProjectDir)..\AppInstallerCommonCore;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)..\AppInstallerCommonCore;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)..\AppInstallerCommonCore;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)..\AppInstallerCommonCore;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)..\AppInstallerCommonCore;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)..\AppInstallerCommonCore;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories) true true true @@ -156,7 +156,7 @@ WIN32;%(PreprocessorDefinitions);CLICOREDLLBUILD - $(ProjectDir);$(ProjectDir)..\AppInstallerCommonCore;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)..\AppInstallerCommonCore;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories) true @@ -235,6 +235,9 @@ {5eb88068-5fb9-4e69-89b2-72dbc5e068f9} + + {82b39fda-e86b-4713-a873-9d56de00247a} + {8bb94bb8-374f-4294-bca1-c7811514a6b7} From 3b09d2b81b7b6ece8c4511a7ab13997789c65cb2 Mon Sep 17 00:00:00 2001 From: Ruben Guerrero Samaniego Date: Tue, 16 Jun 2020 18:26:18 -0700 Subject: [PATCH 02/13] Fix build breaks --- .../Commands/SettingsCommand.cpp | 4 +- .../Public/AppInstallerUserSettings.h | 7 +- .../UserSettings/UserSettings.cpp | 110 ++++++++---------- 3 files changed, 55 insertions(+), 66 deletions(-) diff --git a/src/AppInstallerCLICore/Commands/SettingsCommand.cpp b/src/AppInstallerCLICore/Commands/SettingsCommand.cpp index 1160bc3545..f5825db870 100644 --- a/src/AppInstallerCLICore/Commands/SettingsCommand.cpp +++ b/src/AppInstallerCLICore/Commands/SettingsCommand.cpp @@ -44,12 +44,12 @@ namespace AppInstaller::CLI if (userSettingType == UserSettingsType::Default) { // Create settings file if it doesn't exist. - UserSettings::Instance().CreateSettingsFile(); + UserSettings::Instance().CreateFileIfNeeded(); } else if (userSettingType == UserSettingsType::Standard) { // Settings file was loaded correctly, create backup. - UserSettings::Instance().CreateBackupFile(); + UserSettings::Instance().CreateBackup(); } std::string filePathUTF8Str = UserSettings::Instance().SettingsFilePath().u8string(); diff --git a/src/AppInstallerCommonCore/Public/AppInstallerUserSettings.h b/src/AppInstallerCommonCore/Public/AppInstallerUserSettings.h index 22587a6a66..b4be160a25 100644 --- a/src/AppInstallerCommonCore/Public/AppInstallerUserSettings.h +++ b/src/AppInstallerCommonCore/Public/AppInstallerUserSettings.h @@ -40,10 +40,9 @@ namespace AppInstaller::Settings UserSettingsType GetType() const { return m_type; } std::vector GetWarnings() const { return m_warnings; } + void CreateFileIfNeeded(); + void CreateBackup(); std::filesystem::path SettingsFilePath(); - std::filesystem::path SettingsBackupPath(); - void CreateSettingsFile(); - void CreateBackupFile(); // Settings inline const Source& GetSource() const { return *m_source; } @@ -53,7 +52,7 @@ namespace AppInstaller::Settings UserSettings(); ~UserSettings() {}; - std::optional ParseFile(const std::filesystem::path& path); + std::optional ParseFile(const std::string_view& fileName); UserSettingsType m_type = UserSettingsType::Default; std::vector m_warnings; diff --git a/src/AppInstallerCommonCore/UserSettings/UserSettings.cpp b/src/AppInstallerCommonCore/UserSettings/UserSettings.cpp index 6284369f8b..56c43ac234 100644 --- a/src/AppInstallerCommonCore/UserSettings/UserSettings.cpp +++ b/src/AppInstallerCommonCore/UserSettings/UserSettings.cpp @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #include "pch.h" +#include "winget/Settings.h" #include "AppInstallerUserSettings.h" #include @@ -12,19 +13,18 @@ namespace AppInstaller::Settings { using namespace std::string_view_literals; + using namespace Runtime; using namespace Utility; static constexpr std::string_view s_SettingFileName = "settings.json"sv; static constexpr std::string_view s_SettingBackupFileName = "settings.json.backup"sv; + // TODO: add message and link to aka.ms. The message will need to be localized? static constexpr std::string_view s_SettingEmpty = R"""({ - "source": { - "autoUpdateIntervalInMinutes": 15 - }, - "visual": { - "progressBar": "rainbow" - } + // "source": { + // "autoUpdateIntervalInMinutes": 5 + // } })"""sv; UserSettings::UserSettings() : m_type(UserSettingsType::Default) @@ -35,33 +35,24 @@ namespace AppInstaller::Settings // 1 - Use settings.json if exists and passes parsing. // 2 - Use settings.backup.json if settings.json fails to parse. // 3 - Use default (empty) if both settings files fail to load. - auto settingsPath = SettingsFilePath(); - if (std::filesystem::exists(settingsPath)) + + auto settingsJson = ParseFile(s_SettingFileName); + if (settingsJson.has_value()) { - auto settingsJson = ParseFile(settingsPath); - if (settingsJson.has_value()) - { - m_type = UserSettingsType::Standard; - settingsRoot = settingsJson.value(); - } + m_type = UserSettingsType::Standard; + settingsRoot = settingsJson.value(); } // Settings didn't parse or doesn't exist, try with backup. if (settingsRoot.isNull()) { - auto settingsBackup = SettingsBackupPath(); - if (std::filesystem::exists(settingsBackup)) + auto settingsBackupJson = ParseFile(s_SettingBackupFileName); + if (settingsBackupJson.has_value()) { - auto settingsBackupJson = ParseFile(settingsBackup); - - // We don't expect users to modify manually this file so don't warn. - if (settingsBackupJson.has_value()) - { - // TODO: Localize - m_warnings.emplace_back("Loaded settings from backup file."); - m_type = UserSettingsType::Backup; - settingsRoot = settingsBackupJson.value(); - } + // TODO: Localize + m_warnings.emplace_back("Loaded settings from backup file."); + m_type = UserSettingsType::Backup; + settingsRoot = settingsBackupJson.value(); } } @@ -77,57 +68,56 @@ namespace AppInstaller::Settings std::move(visualWarnings.begin(), visualWarnings.end(), std::inserter(m_warnings, m_warnings.end())); } - std::optional UserSettings::ParseFile(const std::filesystem::path& path) + std::optional UserSettings::ParseFile(const std::string_view& fileName) { - Json::Value root; - Json::CharReaderBuilder builder; - const std::unique_ptr reader(builder.newCharReader()); + auto stream = GetSettingStream(Type::UserFile, fileName); + if (stream) + { + Json::Value root; + Json::CharReaderBuilder builder; + const std::unique_ptr reader(builder.newCharReader()); - auto settingsStream = std::make_unique(path); - std::stringstream settingsContent; - settingsContent << settingsStream->rdbuf(); - std::string settingsContentStr = settingsContent.str(); - std::string error; + std::stringstream settingsContent; + settingsContent << stream->rdbuf(); + std::string settingsContentStr = settingsContent.str(); + std::string error; - if (reader->parse(settingsContentStr.c_str(), settingsContentStr.c_str() + settingsContentStr.size(), &root, &error)) - { - return root; - } + if (reader->parse(settingsContentStr.c_str(), settingsContentStr.c_str() + settingsContentStr.size(), &root, &error)) + { + return root; + } - std::string warning = path.filename().u8string() + " : " + error; - m_warnings.push_back(warning); + m_warnings.emplace_back(fileName); + m_warnings.emplace_back(error); + } return {}; } std::filesystem::path UserSettings::SettingsFilePath() { - return Runtime::GetPathToLocalState() / s_SettingFileName; + return GetPathTo(PathName::UserFileSettings) / s_SettingFileName; } - std::filesystem::path UserSettings::SettingsBackupPath() + void UserSettings::CreateFileIfNeeded() { - return Runtime::GetPathToLocalState() / s_SettingBackupFileName; - } - - void UserSettings::CreateSettingsFile() - { - std::filesystem::path path = SettingsFilePath(); - - // Create settings file if it doesn't exist. - if (!std::filesystem::exists(path)) + if (!std::filesystem::exists(SettingsFilePath())) { - std::ofstream stream(path); - stream << s_SettingEmpty << std::endl; - stream.flush(); + SetSetting(Type::UserFile, s_SettingFileName, s_SettingEmpty); } } - void UserSettings::CreateBackupFile() + void UserSettings::CreateBackup() { - std::filesystem::path from = SettingsFilePath(); - std::filesystem::path to = SettingsBackupPath(); - - std::filesystem::copy_file(from, to, std::filesystem::copy_options::overwrite_existing); + if (std::filesystem::exists(SettingsFilePath())) + { + auto from = SettingsFilePath(); + auto to = GetPathTo(PathName::UserFileSettings) / s_SettingBackupFileName; + std::filesystem::copy_file(from, to, std::filesystem::copy_options::overwrite_existing); + } + else + { + THROW_HR(ERROR_FILE_NOT_FOUND); + } } } From 7710575080ca5d4dc18d6652eb089817cd91764e Mon Sep 17 00:00:00 2001 From: Ruben Guerrero Samaniego Date: Wed, 17 Jun 2020 12:32:54 -0700 Subject: [PATCH 03/13] Delete accidental sln --- src/JsonCppLib/JsonCppLib.sln | 31 ------------------------------- 1 file changed, 31 deletions(-) delete mode 100644 src/JsonCppLib/JsonCppLib.sln diff --git a/src/JsonCppLib/JsonCppLib.sln b/src/JsonCppLib/JsonCppLib.sln deleted file mode 100644 index 6be6574b8a..0000000000 --- a/src/JsonCppLib/JsonCppLib.sln +++ /dev/null @@ -1,31 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.30011.22 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "JsonCppLib", "JsonCppLib.vcxproj", "{82B39FDA-E86B-4713-A873-9D56DE00247A}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|x64 = Debug|x64 - Debug|x86 = Debug|x86 - Release|x64 = Release|x64 - Release|x86 = Release|x86 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {82B39FDA-E86B-4713-A873-9D56DE00247A}.Debug|x64.ActiveCfg = Debug|x64 - {82B39FDA-E86B-4713-A873-9D56DE00247A}.Debug|x64.Build.0 = Debug|x64 - {82B39FDA-E86B-4713-A873-9D56DE00247A}.Debug|x86.ActiveCfg = Debug|Win32 - {82B39FDA-E86B-4713-A873-9D56DE00247A}.Debug|x86.Build.0 = Debug|Win32 - {82B39FDA-E86B-4713-A873-9D56DE00247A}.Release|x64.ActiveCfg = Release|x64 - {82B39FDA-E86B-4713-A873-9D56DE00247A}.Release|x64.Build.0 = Release|x64 - {82B39FDA-E86B-4713-A873-9D56DE00247A}.Release|x86.ActiveCfg = Release|Win32 - {82B39FDA-E86B-4713-A873-9D56DE00247A}.Release|x86.Build.0 = Release|Win32 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {E3C30FD7-B1CD-42D7-BA8E-1B3A6C225F56} - EndGlobalSection -EndGlobal From 8971717f5391d11d8600af9e09f8b57c44fbc111 Mon Sep 17 00:00:00 2001 From: Ruben Guerrero Samaniego Date: Wed, 17 Jun 2020 12:54:18 -0700 Subject: [PATCH 04/13] Rename header file --- src/AppInstallerCLICore/Commands/SettingsCommand.cpp | 5 ++--- src/AppInstallerCLICore/Core.cpp | 2 +- src/AppInstallerCLICore/ExecutionContext.cpp | 2 +- src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj | 2 +- .../AppInstallerCommonCore.vcxproj.filters | 6 +++--- .../{AppInstallerUserSettings.h => winget/UserSettings.h} | 1 - src/AppInstallerCommonCore/UserSettings/UserSettings.cpp | 2 +- src/AppInstallerCommonCore/UserSettings/Visual.cpp | 2 +- src/AppInstallerRepositoryCore/RepositorySource.cpp | 2 +- 9 files changed, 11 insertions(+), 13 deletions(-) rename src/AppInstallerCommonCore/Public/{AppInstallerUserSettings.h => winget/UserSettings.h} (99%) diff --git a/src/AppInstallerCLICore/Commands/SettingsCommand.cpp b/src/AppInstallerCLICore/Commands/SettingsCommand.cpp index f5825db870..940c449599 100644 --- a/src/AppInstallerCLICore/Commands/SettingsCommand.cpp +++ b/src/AppInstallerCLICore/Commands/SettingsCommand.cpp @@ -4,8 +4,7 @@ #include "SettingsCommand.h" #include "Workflows/WorkflowBase.h" #include "Resources.h" -#include - +#include namespace AppInstaller::CLI { @@ -62,4 +61,4 @@ namespace AppInstaller::CLI ShellExecuteA(nullptr, nullptr, "notepad", filePathUTF8Str.c_str(), nullptr, SW_SHOW); } } -} \ No newline at end of file +} diff --git a/src/AppInstallerCLICore/Core.cpp b/src/AppInstallerCLICore/Core.cpp index 1079208488..170a29fc74 100644 --- a/src/AppInstallerCLICore/Core.cpp +++ b/src/AppInstallerCLICore/Core.cpp @@ -4,7 +4,7 @@ #include "Public/AppInstallerCLICore.h" #include "Commands/RootCommand.h" #include "ExecutionContext.h" -#include +#include using namespace winrt; using namespace winrt::Windows::Foundation; diff --git a/src/AppInstallerCLICore/ExecutionContext.cpp b/src/AppInstallerCLICore/ExecutionContext.cpp index ee4f75e0e8..0583861286 100644 --- a/src/AppInstallerCLICore/ExecutionContext.cpp +++ b/src/AppInstallerCLICore/ExecutionContext.cpp @@ -2,7 +2,7 @@ // Licensed under the MIT License. #include "pch.h" #include "ExecutionContext.h" -#include "AppInstallerUserSettings.h" +#include "winget/UserSettings.h" namespace AppInstaller::CLI::Execution { diff --git a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj index c14674d39b..ea4ea4fc76 100644 --- a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj +++ b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj @@ -191,7 +191,6 @@ - @@ -199,6 +198,7 @@ + diff --git a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters index 51d73662d5..20cb1a08f0 100644 --- a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters +++ b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters @@ -108,9 +108,6 @@ Public\winget - - Public - Public\winget\settings @@ -126,6 +123,9 @@ Public\winget + + Public\winget + diff --git a/src/AppInstallerCommonCore/Public/AppInstallerUserSettings.h b/src/AppInstallerCommonCore/Public/winget/UserSettings.h similarity index 99% rename from src/AppInstallerCommonCore/Public/AppInstallerUserSettings.h rename to src/AppInstallerCommonCore/Public/winget/UserSettings.h index b4be160a25..0b365df9df 100644 --- a/src/AppInstallerCommonCore/Public/AppInstallerUserSettings.h +++ b/src/AppInstallerCommonCore/Public/winget/UserSettings.h @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #pragma once - #include "AppInstallerStrings.h" #include "winget/settings/Source.h" #include "winget/settings/Visual.h" diff --git a/src/AppInstallerCommonCore/UserSettings/UserSettings.cpp b/src/AppInstallerCommonCore/UserSettings/UserSettings.cpp index 56c43ac234..e11925cb2b 100644 --- a/src/AppInstallerCommonCore/UserSettings/UserSettings.cpp +++ b/src/AppInstallerCommonCore/UserSettings/UserSettings.cpp @@ -2,7 +2,7 @@ // Licensed under the MIT License. #include "pch.h" #include "winget/Settings.h" -#include "AppInstallerUserSettings.h" +#include "winget/UserSettings.h" #include #include "winget/settings/Source.h" diff --git a/src/AppInstallerCommonCore/UserSettings/Visual.cpp b/src/AppInstallerCommonCore/UserSettings/Visual.cpp index dcb3bc2fa0..f2d93981c0 100644 --- a/src/AppInstallerCommonCore/UserSettings/Visual.cpp +++ b/src/AppInstallerCommonCore/UserSettings/Visual.cpp @@ -15,7 +15,7 @@ namespace AppInstaller::Settings // Visual root static constexpr std::string_view s_VisualKey = "visual"sv; - // progressBar property + // progressBar property possible values static constexpr std::string_view s_progressBar_Accent = "accent"; static constexpr std::string_view s_progressBar_Rainbow = "rainbow"; static constexpr std::string_view s_progressBar_Retro = "retro"; diff --git a/src/AppInstallerRepositoryCore/RepositorySource.cpp b/src/AppInstallerRepositoryCore/RepositorySource.cpp index 172c98486d..8833de4b5c 100644 --- a/src/AppInstallerRepositoryCore/RepositorySource.cpp +++ b/src/AppInstallerRepositoryCore/RepositorySource.cpp @@ -2,7 +2,7 @@ // Licensed under the MIT License. #include "pch.h" #include "Public/AppInstallerRepositorySource.h" -#include +#include #include "SourceFactory.h" #include "Microsoft/PreIndexedPackageSourceFactory.h" From 9ae69a7ec8499cdd420cb5eafb5a28c0c5a9870e Mon Sep 17 00:00:00 2001 From: Ruben Guerrero Samaniego Date: Wed, 17 Jun 2020 14:22:29 -0700 Subject: [PATCH 05/13] Add localization string for settings command --- .../Commands/SettingsCommand.cpp | 7 ++----- src/AppInstallerCLICore/Resources.h | 2 ++ .../Shared/Strings/en-us/winget.resw | 6 ++++++ .../UserSettings/SettingValidation.h | 20 +++++++++++++++++++ .../UserSettings/SettingsValidation.cpp | 6 ------ .../UserSettings/Source.cpp | 4 +--- .../UserSettings/UserSettings.cpp | 8 ++++---- .../UserSettings/Visual.cpp | 8 ++------ 8 files changed, 37 insertions(+), 24 deletions(-) diff --git a/src/AppInstallerCLICore/Commands/SettingsCommand.cpp b/src/AppInstallerCLICore/Commands/SettingsCommand.cpp index 940c449599..ee6e0b42be 100644 --- a/src/AppInstallerCLICore/Commands/SettingsCommand.cpp +++ b/src/AppInstallerCLICore/Commands/SettingsCommand.cpp @@ -20,19 +20,16 @@ namespace AppInstaller::CLI Resource::LocString SettingsCommand::ShortDescription() const { - // TODO: Create a string and localize. - return { Resource::String::HashCommandShortDescription }; + return { Resource::String::SettingsCommandShortDescription }; } Resource::LocString SettingsCommand::LongDescription() const { - // TODO: Create a string and localize. - return { Resource::String::HashCommandLongDescription }; + return { Resource::String::SettingsCommandLongDescription }; } std::string SettingsCommand::HelpLink() const { - // TODO: Create and point to readme return "https://aka.ms/winget-settings"; } diff --git a/src/AppInstallerCLICore/Resources.h b/src/AppInstallerCLICore/Resources.h index c87c3433f1..e870a1e419 100644 --- a/src/AppInstallerCLICore/Resources.h +++ b/src/AppInstallerCLICore/Resources.h @@ -87,6 +87,8 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(RequiredArgError); WINGET_DEFINE_RESOURCE_STRINGID(SearchCommandLongDescription); WINGET_DEFINE_RESOURCE_STRINGID(SearchCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SettingsCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SettingsCommandShortDescription); WINGET_DEFINE_RESOURCE_STRINGID(ShowCommandLongDescription); WINGET_DEFINE_RESOURCE_STRINGID(ShowCommandShortDescription); WINGET_DEFINE_RESOURCE_STRINGID(SilentArgumentDescription); diff --git a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw index cb41df768e..22f26bedb5 100644 --- a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw +++ b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw @@ -305,6 +305,12 @@ Find and show basic info of apps + + Open settings in the default json text editor. If no editor is configured, opens settings in notepad. For available settings see https://aka.ms/winget-settings + + + Open settings + Shows information on a specific application. diff --git a/src/AppInstallerCommonCore/UserSettings/SettingValidation.h b/src/AppInstallerCommonCore/UserSettings/SettingValidation.h index 37b6e61d4c..da98bac613 100644 --- a/src/AppInstallerCommonCore/UserSettings/SettingValidation.h +++ b/src/AppInstallerCommonCore/UserSettings/SettingValidation.h @@ -7,6 +7,26 @@ namespace AppInstaller::Settings { + namespace SettingsWarnings + { + const char* const Field = " Field: "; + const char* const Value = " Value: "; + const char* const InvalidFieldValue = "Invalid field value."; + const char* const InvalidFieldFormat = "Invalid field format."; + const char* const LoadedBackupSettings = "Loaded settings from backup file."; + } + + // Jsoncpp doesn't provide line number and column for an individual Json::Value node. + inline std::string GetSettingsMessage(const std::string& message, const std::string& field) + { + return message + SettingsWarnings::Field + field; + } + + inline std::string GetSettingsMessage(const std::string& message, const std::string& field, const std::string& value) + { + return GetSettingsMessage(message, field) + SettingsWarnings::Value + value; + } + struct SettingFieldInfo { std::string Name; diff --git a/src/AppInstallerCommonCore/UserSettings/SettingsValidation.cpp b/src/AppInstallerCommonCore/UserSettings/SettingsValidation.cpp index ef89027b3f..5500da33bf 100644 --- a/src/AppInstallerCommonCore/UserSettings/SettingsValidation.cpp +++ b/src/AppInstallerCommonCore/UserSettings/SettingsValidation.cpp @@ -23,12 +23,6 @@ namespace AppInstaller::Settings { fieldIter->ProcessFunc(key, root); } - else - { - // TODO: Localize - std::string warning = "Unrecognized field " + key; - warnings.emplace_back(warning); - } } return warnings; diff --git a/src/AppInstallerCommonCore/UserSettings/Source.cpp b/src/AppInstallerCommonCore/UserSettings/Source.cpp index 69ac972420..0557127216 100644 --- a/src/AppInstallerCommonCore/UserSettings/Source.cpp +++ b/src/AppInstallerCommonCore/UserSettings/Source.cpp @@ -37,9 +37,7 @@ namespace AppInstaller::Settings } else { - // TODO: Localize - std::string warning = "Invalid format for: " + key; - m_warnings.emplace_back(warning); + m_warnings.push_back(GetSettingsMessage(SettingsWarnings::InvalidFieldFormat, key)); } } } diff --git a/src/AppInstallerCommonCore/UserSettings/UserSettings.cpp b/src/AppInstallerCommonCore/UserSettings/UserSettings.cpp index e11925cb2b..cb292b5b25 100644 --- a/src/AppInstallerCommonCore/UserSettings/UserSettings.cpp +++ b/src/AppInstallerCommonCore/UserSettings/UserSettings.cpp @@ -1,9 +1,10 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #include "pch.h" +#include +#include "SettingValidation.h" #include "winget/Settings.h" #include "winget/UserSettings.h" -#include #include "winget/settings/Source.h" #include "winget/settings/Visual.h" @@ -19,9 +20,9 @@ namespace AppInstaller::Settings static constexpr std::string_view s_SettingFileName = "settings.json"sv; static constexpr std::string_view s_SettingBackupFileName = "settings.json.backup"sv; - // TODO: add message and link to aka.ms. The message will need to be localized? static constexpr std::string_view s_SettingEmpty = R"""({ + // For documentation on these settings, see: https://aka.ms/winget-settings // "source": { // "autoUpdateIntervalInMinutes": 5 // } @@ -49,8 +50,7 @@ namespace AppInstaller::Settings auto settingsBackupJson = ParseFile(s_SettingBackupFileName); if (settingsBackupJson.has_value()) { - // TODO: Localize - m_warnings.emplace_back("Loaded settings from backup file."); + m_warnings.emplace_back(SettingsWarnings::LoadedBackupSettings); m_type = UserSettingsType::Backup; settingsRoot = settingsBackupJson.value(); } diff --git a/src/AppInstallerCommonCore/UserSettings/Visual.cpp b/src/AppInstallerCommonCore/UserSettings/Visual.cpp index f2d93981c0..bb5b1cb01a 100644 --- a/src/AppInstallerCommonCore/UserSettings/Visual.cpp +++ b/src/AppInstallerCommonCore/UserSettings/Visual.cpp @@ -47,16 +47,12 @@ namespace AppInstaller::Settings } else { - // TODO: Localize - std::string warning = "Invalid value for " + key + ": " + value.value(); - m_warnings.emplace_back(warning); + m_warnings.push_back(GetSettingsMessage(SettingsWarnings::InvalidFieldValue, key, value.value())); } } else { - // TODO: Localize. - std::string warning = "Invalid format for " + key; - m_warnings.emplace_back(warning); + m_warnings.emplace_back(GetSettingsMessage(SettingsWarnings::InvalidFieldFormat, key)); } } } From 98f6688caf34aef2375a2158ce615b7326b76cc7 Mon Sep 17 00:00:00 2001 From: Ruben Guerrero Samaniego Date: Wed, 17 Jun 2020 16:43:28 -0700 Subject: [PATCH 06/13] add settings md --- doc/Settings.md | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 doc/Settings.md diff --git a/doc/Settings.md b/doc/Settings.md new file mode 100644 index 0000000000..205cac68eb --- /dev/null +++ b/doc/Settings.md @@ -0,0 +1,46 @@ +# WinGet CLI Settings + +You can configure WinGet by editing the `settings.json` file. The file can be open with the default json editor by running `winget settings`. If no editor is configure, notepad.exe will be used. + +## File Location + +Settings file is located in %LOCALAPPDATA%\Packages\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe\LocalState\settings.json + +If you are using the non-packaged winget version by building it from source code the file will %LOCALAPPDATA%\Microsoft\WinGet\Settings\settings.json + +## Source + +These settings involve configuration to the WinGet source. + +``` + "source": { + "autoUpdateIntervalInMinutes": 3 + }, +``` + +### autoUpdateIntervalInMinutes + +Positive integer that represents the interval in minutes on how often to update the WinGet source automatically. For no updates use 0. + +- Minimum: 0 +- Default: 5 + +To manually update the source use `winget source update` + +## Visual + +These settings involve visual elements that are displayed by WinGet + +``` + "visual": { + "progressBar": "accent" + } +``` + +### progressBar + +Color of the progress bar that WinGet displays when not specified by arguments. + +- accent (default) +- retro +- rainbow \ No newline at end of file From e4dfd72604e4c55bf830cc52cdc18dc670b71a44 Mon Sep 17 00:00:00 2001 From: Ruben Guerrero Samaniego Date: Wed, 17 Jun 2020 18:19:44 -0700 Subject: [PATCH 07/13] Source and Visual settings unittests --- .../AppInstallerCLITests.vcxproj | 4 +- .../AppInstallerCLITests.vcxproj.filters | 6 ++ src/AppInstallerCLITests/SourceSetting.cpp | 54 ++++++++++++++++++ src/AppInstallerCLITests/VisualSettings.cpp | 56 +++++++++++++++++++ 4 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 src/AppInstallerCLITests/SourceSetting.cpp create mode 100644 src/AppInstallerCLITests/VisualSettings.cpp diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj index 80dd461be7..e373c11854 100644 --- a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj +++ b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj @@ -182,9 +182,11 @@ + + @@ -413,4 +415,4 @@ - + \ No newline at end of file diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters index a4dbd348e8..7c53d51884 100644 --- a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters +++ b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters @@ -86,6 +86,12 @@ Source Files + + Source Files + + + Source Files + diff --git a/src/AppInstallerCLITests/SourceSetting.cpp b/src/AppInstallerCLITests/SourceSetting.cpp new file mode 100644 index 0000000000..817c0cd3bc --- /dev/null +++ b/src/AppInstallerCLITests/SourceSetting.cpp @@ -0,0 +1,54 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" +#include +#include + +using namespace AppInstaller::Settings; + +/* Source property example + "source": { + "autoUpdateIntervalInMinutes": -1 + }, +*/ + +namespace +{ + template + void TestAutoUpdateIntervalInMinutes(T value, uint32_t expected, int warnings) + { + Json::Value property; + property["autoUpdateIntervalInMinutes"] = value; + Source source(property); + REQUIRE(source.Warnings().size() == warnings); + REQUIRE(source.GetAutoUpdateTimeInMinutes() == expected); + } +} + +TEST_CASE("SourceSetting", "[settings]") +{ + Source source(Json::Value::nullSingleton()); + REQUIRE(source.GetPropertyName() == "source"); +} + +TEST_CASE("SourceSettingAutoUpdateIntervalInMinutes", "[settings]") +{ + const std::string progressBar = "autoUpdateIntervalInMinutes"; + + Source sourceNull(Json::Value::nullSingleton()); + REQUIRE(sourceNull.Warnings().size() == 0); + REQUIRE(sourceNull.GetAutoUpdateTimeInMinutes() == 5); + + TestAutoUpdateIntervalInMinutes(300, 300, 0); + + TestAutoUpdateIntervalInMinutes(-20, 5, 1); + + TestAutoUpdateIntervalInMinutes("not a number", 5, 1); + + Json::Value otherProperty; + otherProperty["other"] = "property"; + Source sourceOther(otherProperty); + REQUIRE(sourceOther.Warnings().size() == 0); + REQUIRE(sourceOther.GetAutoUpdateTimeInMinutes() == 5); +} diff --git a/src/AppInstallerCLITests/VisualSettings.cpp b/src/AppInstallerCLITests/VisualSettings.cpp new file mode 100644 index 0000000000..4807d00d52 --- /dev/null +++ b/src/AppInstallerCLITests/VisualSettings.cpp @@ -0,0 +1,56 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" +#include +#include + +using namespace AppInstaller::Settings; + +/* Visual property example + "visual": { + "progressBar": "accent" + } +*/ + +namespace +{ + template + void TestProgressBar(T value, VisualStyle expected, int warnings) + { + Json::Value property; + property["progressBar"] = value; + Visual visual(property); + REQUIRE(visual.Warnings().size() == warnings); + REQUIRE(visual.GetProgressBarVisualStyle() == expected); + } +} + +TEST_CASE("VisualSetting", "[settings]") +{ + Visual visual(Json::Value::nullSingleton()); + REQUIRE(visual.GetPropertyName() == "visual"); +} + +TEST_CASE("VisualSettingProgressBar", "[settings]") +{ + Visual visualNull(Json::Value::nullSingleton()); + REQUIRE(visualNull.Warnings().size() == 0); + REQUIRE(visualNull.GetProgressBarVisualStyle() == VisualStyle::Accent); + + TestProgressBar("accent", VisualStyle::Accent, 0); + + TestProgressBar("rainbow", VisualStyle::Rainbow, 0); + + TestProgressBar("retro", VisualStyle::Retro, 0); + + TestProgressBar("fake", VisualStyle::Accent, 1); + + TestProgressBar(5, VisualStyle::Accent, 1); + + Json::Value otherProperty; + otherProperty["other"] = "property"; + Visual visualOther(otherProperty); + REQUIRE(visualOther.Warnings().size() == 0); + REQUIRE(visualOther.GetProgressBarVisualStyle() == VisualStyle::Accent); +} From c6d298ff5f400169134691a885b389771f386ab6 Mon Sep 17 00:00:00 2001 From: Ruben Guerrero Samaniego Date: Wed, 17 Jun 2020 19:00:09 -0700 Subject: [PATCH 08/13] Fix x86 --- src/AppInstallerCLITests/SourceSetting.cpp | 2 +- src/AppInstallerCLITests/VisualSettings.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/AppInstallerCLITests/SourceSetting.cpp b/src/AppInstallerCLITests/SourceSetting.cpp index 817c0cd3bc..9c2f464959 100644 --- a/src/AppInstallerCLITests/SourceSetting.cpp +++ b/src/AppInstallerCLITests/SourceSetting.cpp @@ -16,7 +16,7 @@ using namespace AppInstaller::Settings; namespace { template - void TestAutoUpdateIntervalInMinutes(T value, uint32_t expected, int warnings) + void TestAutoUpdateIntervalInMinutes(T value, uint32_t expected, size_t warnings) { Json::Value property; property["autoUpdateIntervalInMinutes"] = value; diff --git a/src/AppInstallerCLITests/VisualSettings.cpp b/src/AppInstallerCLITests/VisualSettings.cpp index 4807d00d52..8b1ce64e94 100644 --- a/src/AppInstallerCLITests/VisualSettings.cpp +++ b/src/AppInstallerCLITests/VisualSettings.cpp @@ -16,7 +16,7 @@ using namespace AppInstaller::Settings; namespace { template - void TestProgressBar(T value, VisualStyle expected, int warnings) + void TestProgressBar(T value, VisualStyle expected, size_t warnings) { Json::Value property; property["progressBar"] = value; From 0c29cc32b4466848efc65c7513e34a029cc5c7e5 Mon Sep 17 00:00:00 2001 From: Ruben Guerrero Samaniego Date: Wed, 17 Jun 2020 20:55:14 -0700 Subject: [PATCH 09/13] UserSetting unittests --- .../AppInstallerCLITests.vcxproj | 1 + .../AppInstallerCLITests.vcxproj.filters | 3 + src/AppInstallerCLITests/SourceSetting.cpp | 3 +- src/AppInstallerCLITests/UserSettings.cpp | 146 ++++++++++++++++++ src/AppInstallerCLITests/VisualSettings.cpp | 3 +- .../Public/winget/UserSettings.h | 3 + .../UserSettings/UserSettings.cpp | 37 +++-- 7 files changed, 178 insertions(+), 18 deletions(-) create mode 100644 src/AppInstallerCLITests/UserSettings.cpp diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj index e373c11854..66e7dee05c 100644 --- a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj +++ b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj @@ -185,6 +185,7 @@ + diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters index 7c53d51884..3db2aee595 100644 --- a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters +++ b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters @@ -92,6 +92,9 @@ Source Files + + Source Files + diff --git a/src/AppInstallerCLITests/SourceSetting.cpp b/src/AppInstallerCLITests/SourceSetting.cpp index 9c2f464959..a73cf5b96b 100644 --- a/src/AppInstallerCLITests/SourceSetting.cpp +++ b/src/AppInstallerCLITests/SourceSetting.cpp @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #include "pch.h" -#include "TestCommon.h" #include #include @@ -16,7 +15,7 @@ using namespace AppInstaller::Settings; namespace { template - void TestAutoUpdateIntervalInMinutes(T value, uint32_t expected, size_t warnings) + void TestAutoUpdateIntervalInMinutes(const T& value, uint32_t expected, size_t warnings) { Json::Value property; property["autoUpdateIntervalInMinutes"] = value; diff --git a/src/AppInstallerCLITests/UserSettings.cpp b/src/AppInstallerCLITests/UserSettings.cpp new file mode 100644 index 0000000000..68e01157b9 --- /dev/null +++ b/src/AppInstallerCLITests/UserSettings.cpp @@ -0,0 +1,146 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" +#include +#include +#include + +#include + +#include +#include + +using namespace AppInstaller::Settings; +using namespace AppInstaller::Runtime; +using namespace std::string_view_literals; + +namespace +{ + void DeleteUserSettingsFiles() + { + auto settingsPath = UserSettings::Instance().SettingsFilePath(); + if (std::filesystem::exists(settingsPath)) + { + std::filesystem::remove(settingsPath); + } + + auto settingsBackupPath = UserSettings::Instance().SettingsBackupFilePath(); + if (std::filesystem::exists(settingsBackupPath)) + { + std::filesystem::remove(settingsBackupPath); + } + } +} + +TEST_CASE("UserSettingsFilePaths", "[settings]") +{ + auto settingsPath = UserSettings::Instance().SettingsFilePath(); + auto expectedPath = GetPathTo(PathName::UserFileSettings) / "settings.json" ; + REQUIRE(settingsPath == expectedPath); + + auto settingsBackupPath = UserSettings::Instance().SettingsBackupFilePath(); + auto expectedBackupPath = GetPathTo(PathName::UserFileSettings) / "settings.json.backup"; + REQUIRE(expectedBackupPath == expectedBackupPath); +} + +TEST_CASE("UserSettingsType", "[settings]") +{ + static constexpr std::string_view goodJson = "{}"; + static constexpr std::string_view badJson = "{"; + static constexpr std::string_view settings = "settings.json"sv; + static constexpr std::string_view settingsBackup = "settings.json.backup"sv; + + // These are all the possible combinations between (7 of them are impossible): + // 1 - No settings.json file exists + // 2 - Bad settings.json file + // 3 - No settings.json.backup file exists + // 4 - Bad settings.json.backup file exists. + DeleteUserSettingsFiles(); + + SECTION("No setting.json No setting.json.backup") + { + UserSettings::Instance().Reload(); + REQUIRE(UserSettings::Instance().GetType() == UserSettingsType::Default); + } + SECTION("No setting.json Bad setting.json.backup") + { + SetSetting(Type::UserFile, settingsBackup, badJson); + + UserSettings::Instance().Reload(); + REQUIRE(UserSettings::Instance().GetType() == UserSettingsType::Default); + } + SECTION("No setting.json Good setting.json.backup") + { + SetSetting(Type::UserFile, settingsBackup, goodJson); + + UserSettings::Instance().Reload(); + REQUIRE(UserSettings::Instance().GetType() == UserSettingsType::Backup); + } + SECTION("Bad setting.json No setting.json.backup") + { + SetSetting(Type::UserFile, settings, badJson); + + UserSettings::Instance().Reload(); + REQUIRE(UserSettings::Instance().GetType() == UserSettingsType::Default); + } + SECTION("Bad setting.json Bad setting.json.backup") + { + SetSetting(Type::UserFile, settings, badJson); + SetSetting(Type::UserFile, settingsBackup, badJson); + + UserSettings::Instance().Reload(); + REQUIRE(UserSettings::Instance().GetType() == UserSettingsType::Default); + } + SECTION("Bad setting.json Good setting.json.backup") + { + SetSetting(Type::UserFile, settings, badJson); + SetSetting(Type::UserFile, settingsBackup, goodJson); + UserSettings::Instance().Reload(); + + REQUIRE(UserSettings::Instance().GetType() == UserSettingsType::Backup); + } + SECTION("Good setting.json No setting.json.backup") + { + SetSetting(Type::UserFile, settings, goodJson); + UserSettings::Instance().Reload(); + + REQUIRE(UserSettings::Instance().GetType() == UserSettingsType::Standard); + } + SECTION("Good setting.json Bad setting.json.backup") + { + SetSetting(Type::UserFile, settings, goodJson); + SetSetting(Type::UserFile, settingsBackup, badJson); + UserSettings::Instance().Reload(); + + REQUIRE(UserSettings::Instance().GetType() == UserSettingsType::Standard); + } + SECTION("Good setting.json Good setting.json.backup") + { + SetSetting(Type::UserFile, settings, goodJson); + SetSetting(Type::UserFile, settingsBackup, goodJson); + UserSettings::Instance().Reload(); + + REQUIRE(UserSettings::Instance().GetType() == UserSettingsType::Standard); + } +} + +TEST_CASE("UserSettingsCreateFiles", "[settings]") +{ + DeleteUserSettingsFiles(); + + auto settingsPath = UserSettings::Instance().SettingsFilePath(); + auto settingsBackupPath = UserSettings::Instance().SettingsBackupFilePath(); + + REQUIRE(!std::filesystem::exists(settingsPath)); + REQUIRE(!std::filesystem::exists(settingsBackupPath)); + + REQUIRE_THROWS_HR(UserSettings::Instance().CreateBackup(), HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)); + REQUIRE(!std::filesystem::exists(settingsBackupPath)); + + UserSettings::Instance().CreateFileIfNeeded(); + REQUIRE(std::filesystem::exists(settingsPath)); + + UserSettings::Instance().CreateBackup(); + REQUIRE(std::filesystem::exists(settingsBackupPath)); +} diff --git a/src/AppInstallerCLITests/VisualSettings.cpp b/src/AppInstallerCLITests/VisualSettings.cpp index 8b1ce64e94..d01d00b5de 100644 --- a/src/AppInstallerCLITests/VisualSettings.cpp +++ b/src/AppInstallerCLITests/VisualSettings.cpp @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #include "pch.h" -#include "TestCommon.h" #include #include @@ -16,7 +15,7 @@ using namespace AppInstaller::Settings; namespace { template - void TestProgressBar(T value, VisualStyle expected, size_t warnings) + void TestProgressBar(const T& value, VisualStyle expected, size_t warnings) { Json::Value property; property["progressBar"] = value; diff --git a/src/AppInstallerCommonCore/Public/winget/UserSettings.h b/src/AppInstallerCommonCore/Public/winget/UserSettings.h index 0b365df9df..457908262c 100644 --- a/src/AppInstallerCommonCore/Public/winget/UserSettings.h +++ b/src/AppInstallerCommonCore/Public/winget/UserSettings.h @@ -39,9 +39,12 @@ namespace AppInstaller::Settings UserSettingsType GetType() const { return m_type; } std::vector GetWarnings() const { return m_warnings; } + void Reload(); void CreateFileIfNeeded(); void CreateBackup(); + std::filesystem::path SettingsFilePath(); + std::filesystem::path SettingsBackupFilePath(); // Settings inline const Source& GetSource() const { return *m_source; } diff --git a/src/AppInstallerCommonCore/UserSettings/UserSettings.cpp b/src/AppInstallerCommonCore/UserSettings/UserSettings.cpp index cb292b5b25..d6b21f8622 100644 --- a/src/AppInstallerCommonCore/UserSettings/UserSettings.cpp +++ b/src/AppInstallerCommonCore/UserSettings/UserSettings.cpp @@ -29,8 +29,14 @@ namespace AppInstaller::Settings })"""sv; UserSettings::UserSettings() : m_type(UserSettingsType::Default) + { + Reload(); + } + + void UserSettings::Reload() { Json::Value settingsRoot = Json::Value::nullSingleton(); + m_type = UserSettingsType::Default; // Settings can be loaded from settings.json or settings.json.backup files. // 1 - Use settings.json if exists and passes parsing. @@ -58,14 +64,17 @@ namespace AppInstaller::Settings // Populate the settings. auto sourceStr = Utility::ToString(Source::GetPropertyName()); - m_source = std::make_unique(settingsRoot[sourceStr]); - auto sourceWarnings = m_source->Warnings(); + auto source = std::make_unique(settingsRoot[sourceStr]); + auto sourceWarnings = source->Warnings(); std::move(sourceWarnings.begin(), sourceWarnings.end(), std::inserter(m_warnings, m_warnings.end())); auto visualStr = Utility::ToString(Visual::GetPropertyName()); - m_visual = std::make_unique(settingsRoot[visualStr]); - auto visualWarnings = m_visual->Warnings(); + auto visual = std::make_unique(settingsRoot[visualStr]); + auto visualWarnings = visual->Warnings(); std::move(visualWarnings.begin(), visualWarnings.end(), std::inserter(m_warnings, m_warnings.end())); + + m_source = std::move(source); + m_visual = std::move(visual); } std::optional UserSettings::ParseFile(const std::string_view& fileName) @@ -99,6 +108,11 @@ namespace AppInstaller::Settings return GetPathTo(PathName::UserFileSettings) / s_SettingFileName; } + std::filesystem::path UserSettings::SettingsBackupFilePath() + { + return GetPathTo(PathName::UserFileSettings) / s_SettingBackupFileName; + } + void UserSettings::CreateFileIfNeeded() { if (!std::filesystem::exists(SettingsFilePath())) @@ -109,15 +123,10 @@ namespace AppInstaller::Settings void UserSettings::CreateBackup() { - if (std::filesystem::exists(SettingsFilePath())) - { - auto from = SettingsFilePath(); - auto to = GetPathTo(PathName::UserFileSettings) / s_SettingBackupFileName; - std::filesystem::copy_file(from, to, std::filesystem::copy_options::overwrite_existing); - } - else - { - THROW_HR(ERROR_FILE_NOT_FOUND); - } + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND), !std::filesystem::exists(SettingsFilePath())); + + auto from = SettingsFilePath(); + auto to = GetPathTo(PathName::UserFileSettings) / s_SettingBackupFileName; + std::filesystem::copy_file(from, to, std::filesystem::copy_options::overwrite_existing); } } From db2d024623d1540fbd1b1f5c1e45c4158c626b2b Mon Sep 17 00:00:00 2001 From: Ruben Guerrero Samaniego Date: Thu, 18 Jun 2020 19:04:15 -0700 Subject: [PATCH 10/13] PR comments --- doc/Settings.md | 2 +- src/AppInstallerCLICore/Argument.cpp | 2 +- .../Commands/SettingsCommand.cpp | 17 +-- src/AppInstallerCLICore/Core.cpp | 3 +- src/AppInstallerCLICore/ExecutionArgs.h | 2 +- src/AppInstallerCLICore/ExecutionContext.cpp | 4 +- src/AppInstallerCLICore/ExecutionProgress.cpp | 11 +- src/AppInstallerCLICore/ExecutionProgress.h | 10 +- src/AppInstallerCLICore/ExecutionReporter.cpp | 1 + src/AppInstallerCLICore/ExecutionReporter.h | 2 +- src/AppInstallerCLICore/Resources.h | 2 +- .../Shared/Strings/en-us/winget.resw | 6 +- src/AppInstallerCLITests/UserSettings.cpp | 101 ++++++++++------- .../AppInstallerCommonCore.vcxproj | 2 +- .../AppInstallerCommonCore.vcxproj.filters | 6 +- .../AppInstallerStrings.cpp | 5 - src/AppInstallerCommonCore/JsonUtil.cpp | 6 +- .../{Public/winget => }/JsonUtil.h | 1 + .../Public/AppInstallerStrings.h | 3 - .../Public/winget/UserSettings.h | 31 +++--- .../UserSettings/SettingValidation.h | 2 +- .../UserSettings/Source.cpp | 2 +- .../UserSettings/UserSettings.cpp | 102 +++++++++--------- .../UserSettings/Visual.cpp | 2 +- .../RepositorySource.cpp | 2 +- 25 files changed, 166 insertions(+), 161 deletions(-) rename src/AppInstallerCommonCore/{Public/winget => }/JsonUtil.h (96%) diff --git a/doc/Settings.md b/doc/Settings.md index 205cac68eb..9b25e3a8e2 100644 --- a/doc/Settings.md +++ b/doc/Settings.md @@ -20,7 +20,7 @@ These settings involve configuration to the WinGet source. ### autoUpdateIntervalInMinutes -Positive integer that represents the interval in minutes on how often to update the WinGet source automatically. For no updates use 0. +Positive integer that represents the interval in minutes of how often to automatically check for updates to a WinGet source. The check for updates only happens when a source is used, and if no update is available the interval will be reset. - Minimum: 0 - Default: 5 diff --git a/src/AppInstallerCLICore/Argument.cpp b/src/AppInstallerCLICore/Argument.cpp index 494b3fa3a8..c51aa16105 100644 --- a/src/AppInstallerCLICore/Argument.cpp +++ b/src/AppInstallerCLICore/Argument.cpp @@ -73,7 +73,7 @@ namespace AppInstaller::CLI case Args::Type::RainbowStyle: return Argument{ "rainbow", None, Args::Type::RainbowStyle, Resource::String::RainbowArgumentDescription, ArgumentType::Flag, Visibility::Hidden }; case Args::Type::RetroStyle: - return Argument{ "retro", None, Args::Type::RetroStyle, Resource::String::PlainArgumentDescription, ArgumentType::Flag, Visibility::Hidden }; + return Argument{ "retro", None, Args::Type::RetroStyle, Resource::String::RetroArgumentDescription, ArgumentType::Flag, Visibility::Hidden }; case Args::Type::Force: return Argument{ "force", None, Args::Type::Force, Resource::String::ForceArgumentDescription, ArgumentType::Flag }; case Args::Type::VerboseLogs: diff --git a/src/AppInstallerCLICore/Commands/SettingsCommand.cpp b/src/AppInstallerCLICore/Commands/SettingsCommand.cpp index ee6e0b42be..210302b988 100644 --- a/src/AppInstallerCLICore/Commands/SettingsCommand.cpp +++ b/src/AppInstallerCLICore/Commands/SettingsCommand.cpp @@ -35,21 +35,12 @@ namespace AppInstaller::CLI void SettingsCommand::ExecuteInternal(Execution::Context& ) const { - UserSettingsType userSettingType = UserSettings::Instance().GetType(); + User().PrepareToShellExecuteFile(); - if (userSettingType == UserSettingsType::Default) - { - // Create settings file if it doesn't exist. - UserSettings::Instance().CreateFileIfNeeded(); - } - else if (userSettingType == UserSettingsType::Standard) - { - // Settings file was loaded correctly, create backup. - UserSettings::Instance().CreateBackup(); - } - - std::string filePathUTF8Str = UserSettings::Instance().SettingsFilePath().u8string(); + std::string filePathUTF8Str = UserSettings::SettingsFilePath().u8string(); + // Some versions of windows will fail if no file extension association exists, other will pop up the dialog + // to make the user pick their default. // Kudos to the terminal team for this work around. HINSTANCE res = ShellExecuteA(nullptr, nullptr, filePathUTF8Str.c_str(), nullptr, nullptr, SW_SHOW); if (static_cast(reinterpret_cast(res)) <= 32) diff --git a/src/AppInstallerCLICore/Core.cpp b/src/AppInstallerCLICore/Core.cpp index 170a29fc74..46280c1731 100644 --- a/src/AppInstallerCLICore/Core.cpp +++ b/src/AppInstallerCLICore/Core.cpp @@ -80,8 +80,7 @@ namespace AppInstaller::CLI std::unique_ptr command = std::make_unique(); // Load the settings file and warn if necessary. - auto warnings = Settings::UserSettings::Instance().GetWarnings(); - for (const auto& warning : warnings) + for (const auto& warning : Settings::User().GetWarnings()) { context.Reporter.Warn() << warning << std::endl; } diff --git a/src/AppInstallerCLICore/ExecutionArgs.h b/src/AppInstallerCLICore/ExecutionArgs.h index d9a9efdc77..58fa72f845 100644 --- a/src/AppInstallerCLICore/ExecutionArgs.h +++ b/src/AppInstallerCLICore/ExecutionArgs.h @@ -54,7 +54,7 @@ namespace AppInstaller::CLI::Execution Force, // Generic flag to enable a command to skip some check ListVersions, // Used in Show command to list all available versions of an app NoVT, // Disable VirtualTerminal outputs - RetroStyle, // Makes progress display as plain + RetroStyle, // Makes progress display as retro RainbowStyle, // Makes progress display as a rainbow Help, // Show command usage Info, // Show general info about WinGet diff --git a/src/AppInstallerCLICore/ExecutionContext.cpp b/src/AppInstallerCLICore/ExecutionContext.cpp index 0583861286..706a45a55b 100644 --- a/src/AppInstallerCLICore/ExecutionContext.cpp +++ b/src/AppInstallerCLICore/ExecutionContext.cpp @@ -6,6 +6,8 @@ namespace AppInstaller::CLI::Execution { + using namespace Settings; + namespace { // The context that will receive CTRL signals @@ -90,7 +92,7 @@ namespace AppInstaller::CLI::Execution } else { - Reporter.SetStyle(UserSettings::Instance().GetVisual().GetProgressBarVisualStyle()); + Reporter.SetStyle(User().GetVisual().GetProgressBarVisualStyle()); } } diff --git a/src/AppInstallerCLICore/ExecutionProgress.cpp b/src/AppInstallerCLICore/ExecutionProgress.cpp index 5daf30c27a..c8ae5f7959 100644 --- a/src/AppInstallerCLICore/ExecutionProgress.cpp +++ b/src/AppInstallerCLICore/ExecutionProgress.cpp @@ -2,10 +2,11 @@ // Licensed under the MIT License. #include "pch.h" #include "ExecutionProgress.h" - +#include namespace AppInstaller::CLI::Execution { + using namespace Settings; using namespace VirtualTerminal; using namespace std::string_view_literals; @@ -135,10 +136,10 @@ namespace AppInstaller::CLI::Execution { switch (m_style) { - case AppInstaller::CLI::Execution::VisualStyle::NoVT: + case VisualStyle::NoVT: // No VT means no style set break; - case AppInstaller::CLI::Execution::VisualStyle::Retro: + case VisualStyle::Retro: if (enabled) { m_out << TextFormat::Default; @@ -148,10 +149,10 @@ namespace AppInstaller::CLI::Execution m_out << TextFormat::Negative; } break; - case AppInstaller::CLI::Execution::VisualStyle::Accent: + case VisualStyle::Accent: SetColor(m_out, TextFormat::Color::GetAccentColor(), enabled); break; - case AppInstaller::CLI::Execution::VisualStyle::Rainbow: + case VisualStyle::Rainbow: SetRainbowColor(m_out, i, max, enabled); break; default: diff --git a/src/AppInstallerCLICore/ExecutionProgress.h b/src/AppInstallerCLICore/ExecutionProgress.h index 2fa2b0fa0e..24eb80d827 100644 --- a/src/AppInstallerCLICore/ExecutionProgress.h +++ b/src/AppInstallerCLICore/ExecutionProgress.h @@ -16,8 +16,6 @@ namespace AppInstaller::CLI::Execution { - using namespace Settings; - namespace details { // Shared functionality for progress visualizers. @@ -26,13 +24,13 @@ namespace AppInstaller::CLI::Execution ProgressVisualizerBase(std::ostream& stream, bool enableVT) : m_out(stream), m_enableVT(enableVT) {} - void SetStyle(VisualStyle style) { m_style = style; } + void SetStyle(AppInstaller::Settings::VisualStyle style) { m_style = style; } protected: std::ostream& m_out; - VisualStyle m_style = VisualStyle::Accent; + Settings::VisualStyle m_style = AppInstaller::Settings::VisualStyle::Accent; - bool UseVT() const { return m_enableVT && m_style != VisualStyle::NoVT; } + bool UseVT() const { return m_enableVT && m_style != AppInstaller::Settings::VisualStyle::NoVT; } // Applies the selected visual style. void ApplyStyle(size_t i, size_t max, bool enabled); @@ -73,7 +71,7 @@ namespace AppInstaller::CLI::Execution void EndProgress(bool hideProgressWhenDone); - void SetStyle(VisualStyle style) { m_style = style; } + void SetStyle(AppInstaller::Settings::VisualStyle style) { m_style = style; } private: std::atomic m_isVisible = false; diff --git a/src/AppInstallerCLICore/ExecutionReporter.cpp b/src/AppInstallerCLICore/ExecutionReporter.cpp index 0c980fd2a1..b6e3fae8e4 100644 --- a/src/AppInstallerCLICore/ExecutionReporter.cpp +++ b/src/AppInstallerCLICore/ExecutionReporter.cpp @@ -6,6 +6,7 @@ namespace AppInstaller::CLI::Execution { + using namespace Settings; using namespace VirtualTerminal; const Sequence& HelpCommandEmphasis = TextFormat::Foreground::BrightWhite; diff --git a/src/AppInstallerCLICore/ExecutionReporter.h b/src/AppInstallerCLICore/ExecutionReporter.h index 8febfc33c4..7b37d4ba25 100644 --- a/src/AppInstallerCLICore/ExecutionReporter.h +++ b/src/AppInstallerCLICore/ExecutionReporter.h @@ -124,7 +124,7 @@ namespace AppInstaller::CLI::Execution void EmptyLine() { m_out << std::endl; } // Sets the visual style (mostly for progress currently) - void SetStyle(VisualStyle style); + void SetStyle(AppInstaller::Settings::VisualStyle style); // Prompts the user, return true if they consented. bool PromptForBoolResponse(const std::string& msg, Level level = Level::Info); diff --git a/src/AppInstallerCLICore/Resources.h b/src/AppInstallerCLICore/Resources.h index e870a1e419..59d1bc4eea 100644 --- a/src/AppInstallerCLICore/Resources.h +++ b/src/AppInstallerCLICore/Resources.h @@ -79,12 +79,12 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(OverrideArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(Package); WINGET_DEFINE_RESOURCE_STRINGID(PendingWorkError); - WINGET_DEFINE_RESOURCE_STRINGID(PlainArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(PreviewVersion); WINGET_DEFINE_RESOURCE_STRINGID(PrivacyStatement); WINGET_DEFINE_RESOURCE_STRINGID(QueryArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(RainbowArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(RequiredArgError); + WINGET_DEFINE_RESOURCE_STRINGID(RetroArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(SearchCommandLongDescription); WINGET_DEFINE_RESOURCE_STRINGID(SearchCommandShortDescription); WINGET_DEFINE_RESOURCE_STRINGID(SettingsCommandLongDescription); diff --git a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw index 22f26bedb5..174b2f9a10 100644 --- a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw +++ b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw @@ -281,9 +281,6 @@ Oops, we forgot to do this... - - Progress display as the default color - Preview @@ -299,6 +296,9 @@ Required argument not provided + + Progress display as the default color + Searches for applications from configured sources. diff --git a/src/AppInstallerCLITests/UserSettings.cpp b/src/AppInstallerCLITests/UserSettings.cpp index 68e01157b9..f4733921a4 100644 --- a/src/AppInstallerCLITests/UserSettings.cpp +++ b/src/AppInstallerCLITests/UserSettings.cpp @@ -17,40 +17,45 @@ using namespace std::string_view_literals; namespace { + static constexpr std::string_view goodJson = "{}"; + static constexpr std::string_view badJson = "{"; + static constexpr std::string_view settings = "settings.json"sv; + static constexpr std::string_view settingsBackup = "settings.json.backup"sv; + + std::filesystem::path GetBackupPath() + { + return GetPathTo(PathName::UserFileSettings) / settingsBackup; + } + void DeleteUserSettingsFiles() { - auto settingsPath = UserSettings::Instance().SettingsFilePath(); + auto settingsPath = UserSettings::SettingsFilePath(); if (std::filesystem::exists(settingsPath)) { std::filesystem::remove(settingsPath); } - auto settingsBackupPath = UserSettings::Instance().SettingsBackupFilePath(); + auto settingsBackupPath = GetBackupPath(); if (std::filesystem::exists(settingsBackupPath)) { std::filesystem::remove(settingsBackupPath); } } + + struct UserSettingsTest : UserSettings + { + }; } TEST_CASE("UserSettingsFilePaths", "[settings]") { - auto settingsPath = UserSettings::Instance().SettingsFilePath(); - auto expectedPath = GetPathTo(PathName::UserFileSettings) / "settings.json" ; + auto settingsPath = UserSettings::SettingsFilePath(); + auto expectedPath = GetPathTo(PathName::UserFileSettings) / "settings.json"; REQUIRE(settingsPath == expectedPath); - - auto settingsBackupPath = UserSettings::Instance().SettingsBackupFilePath(); - auto expectedBackupPath = GetPathTo(PathName::UserFileSettings) / "settings.json.backup"; - REQUIRE(expectedBackupPath == expectedBackupPath); } TEST_CASE("UserSettingsType", "[settings]") { - static constexpr std::string_view goodJson = "{}"; - static constexpr std::string_view badJson = "{"; - static constexpr std::string_view settings = "settings.json"sv; - static constexpr std::string_view settingsBackup = "settings.json.backup"sv; - // These are all the possible combinations between (7 of them are impossible): // 1 - No settings.json file exists // 2 - Bad settings.json file @@ -60,68 +65,68 @@ TEST_CASE("UserSettingsType", "[settings]") SECTION("No setting.json No setting.json.backup") { - UserSettings::Instance().Reload(); - REQUIRE(UserSettings::Instance().GetType() == UserSettingsType::Default); + UserSettingsTest userSettingTest; + REQUIRE(userSettingTest.GetType() == UserSettingsType::Default); } SECTION("No setting.json Bad setting.json.backup") { SetSetting(Type::UserFile, settingsBackup, badJson); - UserSettings::Instance().Reload(); - REQUIRE(UserSettings::Instance().GetType() == UserSettingsType::Default); + UserSettingsTest userSettingTest; + REQUIRE(userSettingTest.GetType() == UserSettingsType::Default); } SECTION("No setting.json Good setting.json.backup") { SetSetting(Type::UserFile, settingsBackup, goodJson); - UserSettings::Instance().Reload(); - REQUIRE(UserSettings::Instance().GetType() == UserSettingsType::Backup); + UserSettingsTest userSettingTest; + REQUIRE(userSettingTest.GetType() == UserSettingsType::Backup); } SECTION("Bad setting.json No setting.json.backup") { SetSetting(Type::UserFile, settings, badJson); - UserSettings::Instance().Reload(); - REQUIRE(UserSettings::Instance().GetType() == UserSettingsType::Default); + UserSettingsTest userSettingTest; + REQUIRE(userSettingTest.GetType() == UserSettingsType::Default); } SECTION("Bad setting.json Bad setting.json.backup") { SetSetting(Type::UserFile, settings, badJson); SetSetting(Type::UserFile, settingsBackup, badJson); - UserSettings::Instance().Reload(); - REQUIRE(UserSettings::Instance().GetType() == UserSettingsType::Default); + UserSettingsTest userSettingTest; + REQUIRE(userSettingTest.GetType() == UserSettingsType::Default); } SECTION("Bad setting.json Good setting.json.backup") { SetSetting(Type::UserFile, settings, badJson); SetSetting(Type::UserFile, settingsBackup, goodJson); - UserSettings::Instance().Reload(); - REQUIRE(UserSettings::Instance().GetType() == UserSettingsType::Backup); + UserSettingsTest userSettingTest; + REQUIRE(userSettingTest.GetType() == UserSettingsType::Backup); } SECTION("Good setting.json No setting.json.backup") { SetSetting(Type::UserFile, settings, goodJson); - UserSettings::Instance().Reload(); - REQUIRE(UserSettings::Instance().GetType() == UserSettingsType::Standard); + UserSettingsTest userSettingTest; + REQUIRE(userSettingTest.GetType() == UserSettingsType::Standard); } SECTION("Good setting.json Bad setting.json.backup") { SetSetting(Type::UserFile, settings, goodJson); SetSetting(Type::UserFile, settingsBackup, badJson); - UserSettings::Instance().Reload(); - REQUIRE(UserSettings::Instance().GetType() == UserSettingsType::Standard); + UserSettingsTest userSettingTest; + REQUIRE(userSettingTest.GetType() == UserSettingsType::Standard); } SECTION("Good setting.json Good setting.json.backup") { SetSetting(Type::UserFile, settings, goodJson); SetSetting(Type::UserFile, settingsBackup, goodJson); - UserSettings::Instance().Reload(); - REQUIRE(UserSettings::Instance().GetType() == UserSettingsType::Standard); + UserSettingsTest userSettingTest; + REQUIRE(userSettingTest.GetType() == UserSettingsType::Standard); } } @@ -129,18 +134,32 @@ TEST_CASE("UserSettingsCreateFiles", "[settings]") { DeleteUserSettingsFiles(); - auto settingsPath = UserSettings::Instance().SettingsFilePath(); - auto settingsBackupPath = UserSettings::Instance().SettingsBackupFilePath(); + auto settingsPath = UserSettings::SettingsFilePath(); + auto settingsBackupPath = GetBackupPath(); - REQUIRE(!std::filesystem::exists(settingsPath)); - REQUIRE(!std::filesystem::exists(settingsBackupPath)); + SECTION("No settings.json create new") + { + REQUIRE(!std::filesystem::exists(settingsPath)); + REQUIRE(!std::filesystem::exists(settingsBackupPath)); - REQUIRE_THROWS_HR(UserSettings::Instance().CreateBackup(), HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)); - REQUIRE(!std::filesystem::exists(settingsBackupPath)); + UserSettingsTest userSettingTest; + REQUIRE(userSettingTest.GetType() == UserSettingsType::Default); + userSettingTest.PrepareToShellExecuteFile(); - UserSettings::Instance().CreateFileIfNeeded(); - REQUIRE(std::filesystem::exists(settingsPath)); + REQUIRE(std::filesystem::exists(settingsPath)); + REQUIRE(!std::filesystem::exists(settingsBackupPath)); + } + SECTION("Good settings.json create new backup") + { + SetSetting(Type::UserFile, settings, goodJson); + REQUIRE(std::filesystem::exists(settingsPath)); + REQUIRE(!std::filesystem::exists(settingsBackupPath)); - UserSettings::Instance().CreateBackup(); - REQUIRE(std::filesystem::exists(settingsBackupPath)); + UserSettingsTest userSettingTest; + REQUIRE(userSettingTest.GetType() == UserSettingsType::Standard); + userSettingTest.PrepareToShellExecuteFile(); + + REQUIRE(std::filesystem::exists(settingsPath)); + REQUIRE(std::filesystem::exists(settingsBackupPath)); + } } diff --git a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj index ea4ea4fc76..4c9bf60afb 100644 --- a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj +++ b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj @@ -175,6 +175,7 @@ + @@ -193,7 +194,6 @@ - diff --git a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters index 20cb1a08f0..47d4b44b12 100644 --- a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters +++ b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters @@ -114,9 +114,6 @@ Public\winget\settings - - Public\winget - UserSettings @@ -126,6 +123,9 @@ Public\winget + + Header Files + diff --git a/src/AppInstallerCommonCore/AppInstallerStrings.cpp b/src/AppInstallerCommonCore/AppInstallerStrings.cpp index af67285bb6..29b7e4ef48 100644 --- a/src/AppInstallerCommonCore/AppInstallerStrings.cpp +++ b/src/AppInstallerCommonCore/AppInstallerStrings.cpp @@ -268,9 +268,4 @@ namespace AppInstaller::Utility return result; } - - std::string ToString(const std::string_view key) - { - return static_cast(key); - } } diff --git a/src/AppInstallerCommonCore/JsonUtil.cpp b/src/AppInstallerCommonCore/JsonUtil.cpp index c51d61070d..017d939291 100644 --- a/src/AppInstallerCommonCore/JsonUtil.cpp +++ b/src/AppInstallerCommonCore/JsonUtil.cpp @@ -1,7 +1,7 @@ // Copyright(c) Microsoft Corporation. // Licensed under the MIT License. #include "pch.h" -#include "Public/winget/JsonUtil.h" +#include "JsonUtil.h" #include "AppInstallerStrings.h" namespace AppInstaller::Utility @@ -11,7 +11,7 @@ namespace AppInstaller::Utility std::optional value = std::nullopt; // jsoncpp doesn't support std::string_view yet. - auto keyStr = Utility::ToString(key); + auto keyStr = std::string(key); if (node.isMember(keyStr)) { @@ -31,7 +31,7 @@ namespace AppInstaller::Utility std::optional value = std::nullopt; // jsoncpp doesn't support std::string_view yet. - auto keyStr = Utility::ToString(key); + auto keyStr = std::string(key); if (node.isMember(keyStr)) { diff --git a/src/AppInstallerCommonCore/Public/winget/JsonUtil.h b/src/AppInstallerCommonCore/JsonUtil.h similarity index 96% rename from src/AppInstallerCommonCore/Public/winget/JsonUtil.h rename to src/AppInstallerCommonCore/JsonUtil.h index 477c05df6a..8e31b91d8d 100644 --- a/src/AppInstallerCommonCore/Public/winget/JsonUtil.h +++ b/src/AppInstallerCommonCore/JsonUtil.h @@ -1,3 +1,4 @@ +#pragma once // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #pragma once diff --git a/src/AppInstallerCommonCore/Public/AppInstallerStrings.h b/src/AppInstallerCommonCore/Public/AppInstallerStrings.h index 36f22f8313..e0b5cf637b 100644 --- a/src/AppInstallerCommonCore/Public/AppInstallerStrings.h +++ b/src/AppInstallerCommonCore/Public/AppInstallerStrings.h @@ -97,7 +97,4 @@ namespace AppInstaller::Utility // Reads the entire stream into a string. std::string ReadEntireStream(std::istream& stream); - - // Converts string view to string - std::string ToString(const std::string_view key); } diff --git a/src/AppInstallerCommonCore/Public/winget/UserSettings.h b/src/AppInstallerCommonCore/Public/winget/UserSettings.h index 457908262c..4cafc0b3e2 100644 --- a/src/AppInstallerCommonCore/Public/winget/UserSettings.h +++ b/src/AppInstallerCommonCore/Public/winget/UserSettings.h @@ -4,7 +4,6 @@ #include "AppInstallerStrings.h" #include "winget/settings/Source.h" #include "winget/settings/Visual.h" -#include #include #include @@ -26,41 +25,45 @@ namespace AppInstaller::Settings // Representation of the parsed settings file. struct UserSettings { - static UserSettings& Instance() + static UserSettings const& Instance() { - // If we go multi-threaded secure this. static UserSettings userSettings; return userSettings; } + static std::filesystem::path SettingsFilePath(); + UserSettings(const UserSettings&) = delete; UserSettings& operator=(const UserSettings&) = delete; + UserSettings(UserSettings&&) = delete; + UserSettings& operator=(UserSettings&&) = delete; + UserSettingsType GetType() const { return m_type; } - std::vector GetWarnings() const { return m_warnings; } + std::vector const& GetWarnings() const { return m_warnings; } void Reload(); - void CreateFileIfNeeded(); - void CreateBackup(); - - std::filesystem::path SettingsFilePath(); - std::filesystem::path SettingsBackupFilePath(); + void PrepareToShellExecuteFile() const; // Settings inline const Source& GetSource() const { return *m_source; } inline const Visual& GetVisual() const { return *m_visual; } private: - UserSettings(); - ~UserSettings() {}; - - std::optional ParseFile(const std::string_view& fileName); - UserSettingsType m_type = UserSettingsType::Default; std::vector m_warnings; std::unique_ptr m_source; std::unique_ptr m_visual; + protected: + UserSettings(); + ~UserSettings() = default; + }; + + inline UserSettings const& User() + { + return UserSettings::Instance(); + } } diff --git a/src/AppInstallerCommonCore/UserSettings/SettingValidation.h b/src/AppInstallerCommonCore/UserSettings/SettingValidation.h index da98bac613..0d3fa17956 100644 --- a/src/AppInstallerCommonCore/UserSettings/SettingValidation.h +++ b/src/AppInstallerCommonCore/UserSettings/SettingValidation.h @@ -13,7 +13,7 @@ namespace AppInstaller::Settings const char* const Value = " Value: "; const char* const InvalidFieldValue = "Invalid field value."; const char* const InvalidFieldFormat = "Invalid field format."; - const char* const LoadedBackupSettings = "Loaded settings from backup file."; + constexpr std::string_view LoadedBackupSettings = "Loaded settings from backup file."; } // Jsoncpp doesn't provide line number and column for an individual Json::Value node. diff --git a/src/AppInstallerCommonCore/UserSettings/Source.cpp b/src/AppInstallerCommonCore/UserSettings/Source.cpp index 0557127216..d4199661bd 100644 --- a/src/AppInstallerCommonCore/UserSettings/Source.cpp +++ b/src/AppInstallerCommonCore/UserSettings/Source.cpp @@ -2,8 +2,8 @@ // Licensed under the MIT License. #include "pch.h" #include "AppInstallerStrings.h" +#include "JsonUtil.h" #include "SettingValidation.h" -#include "winget\JsonUtil.h" #include "winget\settings\Source.h" #include diff --git a/src/AppInstallerCommonCore/UserSettings/UserSettings.cpp b/src/AppInstallerCommonCore/UserSettings/UserSettings.cpp index d6b21f8622..83a24b4ab5 100644 --- a/src/AppInstallerCommonCore/UserSettings/UserSettings.cpp +++ b/src/AppInstallerCommonCore/UserSettings/UserSettings.cpp @@ -9,8 +9,6 @@ #include "winget/settings/Source.h" #include "winget/settings/Visual.h" -#include - namespace AppInstaller::Settings { using namespace std::string_view_literals; @@ -21,19 +19,46 @@ namespace AppInstaller::Settings static constexpr std::string_view s_SettingBackupFileName = "settings.json.backup"sv; static constexpr std::string_view s_SettingEmpty = - R"""({ + R"("{ // For documentation on these settings, see: https://aka.ms/winget-settings // "source": { // "autoUpdateIntervalInMinutes": 5 // } -})"""sv; +}")"sv; - UserSettings::UserSettings() : m_type(UserSettingsType::Default) + namespace { - Reload(); + std::filesystem::path SettingsBackupFilePath() + { + return GetPathTo(PathName::UserFileSettings) / s_SettingBackupFileName; + } + + std::optional ParseFile(const std::filesystem::path& fileName, std::vector& warnings) + { + auto stream = GetSettingStream(Type::UserFile, fileName); + if (stream) + { + Json::Value root; + Json::CharReaderBuilder builder; + const std::unique_ptr reader(builder.newCharReader()); + + std::string settingsContentStr = Utility::ReadEntireStream(*stream); + std::string error; + + if (reader->parse(settingsContentStr.c_str(), settingsContentStr.c_str() + settingsContentStr.size(), &root, &error)) + { + return root; + } + + warnings.emplace_back(fileName.u8string()); + warnings.emplace_back(error); + } + + return {}; + } } - void UserSettings::Reload() + UserSettings::UserSettings() : m_type(UserSettingsType::Default) { Json::Value settingsRoot = Json::Value::nullSingleton(); m_type = UserSettingsType::Default; @@ -43,7 +68,7 @@ namespace AppInstaller::Settings // 2 - Use settings.backup.json if settings.json fails to parse. // 3 - Use default (empty) if both settings files fail to load. - auto settingsJson = ParseFile(s_SettingFileName); + auto settingsJson = ParseFile(s_SettingFileName, m_warnings); if (settingsJson.has_value()) { m_type = UserSettingsType::Standard; @@ -53,7 +78,7 @@ namespace AppInstaller::Settings // Settings didn't parse or doesn't exist, try with backup. if (settingsRoot.isNull()) { - auto settingsBackupJson = ParseFile(s_SettingBackupFileName); + auto settingsBackupJson = ParseFile(s_SettingBackupFileName, m_warnings); if (settingsBackupJson.has_value()) { m_warnings.emplace_back(SettingsWarnings::LoadedBackupSettings); @@ -63,12 +88,12 @@ namespace AppInstaller::Settings } // Populate the settings. - auto sourceStr = Utility::ToString(Source::GetPropertyName()); + auto sourceStr = std::string(Source::GetPropertyName()); auto source = std::make_unique(settingsRoot[sourceStr]); auto sourceWarnings = source->Warnings(); std::move(sourceWarnings.begin(), sourceWarnings.end(), std::inserter(m_warnings, m_warnings.end())); - auto visualStr = Utility::ToString(Visual::GetPropertyName()); + auto visualStr = std::string(Visual::GetPropertyName()); auto visual = std::make_unique(settingsRoot[visualStr]); auto visualWarnings = visual->Warnings(); std::move(visualWarnings.begin(), visualWarnings.end(), std::inserter(m_warnings, m_warnings.end())); @@ -77,56 +102,29 @@ namespace AppInstaller::Settings m_visual = std::move(visual); } - std::optional UserSettings::ParseFile(const std::string_view& fileName) + void UserSettings::PrepareToShellExecuteFile() const { - auto stream = GetSettingStream(Type::UserFile, fileName); - if (stream) - { - Json::Value root; - Json::CharReaderBuilder builder; - const std::unique_ptr reader(builder.newCharReader()); + UserSettingsType userSettingType = GetType(); - std::stringstream settingsContent; - settingsContent << stream->rdbuf(); - std::string settingsContentStr = settingsContent.str(); - std::string error; - - if (reader->parse(settingsContentStr.c_str(), settingsContentStr.c_str() + settingsContentStr.size(), &root, &error)) + if (userSettingType == UserSettingsType::Default) + { + // Create settings file if it doesn't exist. + if (!std::filesystem::exists(UserSettings::SettingsFilePath())) { - return root; + SetSetting(Type::UserFile, s_SettingFileName, s_SettingEmpty); } - - m_warnings.emplace_back(fileName); - m_warnings.emplace_back(error); } - - return {}; - } - - std::filesystem::path UserSettings::SettingsFilePath() - { - return GetPathTo(PathName::UserFileSettings) / s_SettingFileName; - } - - std::filesystem::path UserSettings::SettingsBackupFilePath() - { - return GetPathTo(PathName::UserFileSettings) / s_SettingBackupFileName; - } - - void UserSettings::CreateFileIfNeeded() - { - if (!std::filesystem::exists(SettingsFilePath())) + else if (userSettingType == UserSettingsType::Standard) { - SetSetting(Type::UserFile, s_SettingFileName, s_SettingEmpty); + // Settings file was loaded correctly, create backup. + auto from = SettingsFilePath(); + auto to = SettingsBackupFilePath(); + std::filesystem::copy_file(from, to, std::filesystem::copy_options::overwrite_existing); } } - void UserSettings::CreateBackup() + std::filesystem::path UserSettings::SettingsFilePath() { - THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND), !std::filesystem::exists(SettingsFilePath())); - - auto from = SettingsFilePath(); - auto to = GetPathTo(PathName::UserFileSettings) / s_SettingBackupFileName; - std::filesystem::copy_file(from, to, std::filesystem::copy_options::overwrite_existing); + return GetPathTo(PathName::UserFileSettings) / s_SettingFileName; } } diff --git a/src/AppInstallerCommonCore/UserSettings/Visual.cpp b/src/AppInstallerCommonCore/UserSettings/Visual.cpp index bb5b1cb01a..7312c120a8 100644 --- a/src/AppInstallerCommonCore/UserSettings/Visual.cpp +++ b/src/AppInstallerCommonCore/UserSettings/Visual.cpp @@ -2,8 +2,8 @@ // Licensed under the MIT License. #include "pch.h" #include "AppInstallerStrings.h" +#include "JsonUtil.h" #include "SettingValidation.h" -#include "winget\JsonUtil.h" #include "winget\settings\Visual.h" #include diff --git a/src/AppInstallerRepositoryCore/RepositorySource.cpp b/src/AppInstallerRepositoryCore/RepositorySource.cpp index 8833de4b5c..8ad05e9ce1 100644 --- a/src/AppInstallerRepositoryCore/RepositorySource.cpp +++ b/src/AppInstallerRepositoryCore/RepositorySource.cpp @@ -259,7 +259,7 @@ namespace AppInstaller::Repository return true; } - auto autoUpdateTime = Settings::UserSettings::Instance().GetSource().GetAutoUpdateTimeInMinutes(); + auto autoUpdateTime = Settings::User().GetSource().GetAutoUpdateTimeInMinutes(); // A value of zero means no auto update, to get update the source run `winget update` if (autoUpdateTime != 0) From d80334bb046f110a4b121a61f6cc5dc2a2ab9b83 Mon Sep 17 00:00:00 2001 From: Ruben Guerrero Samaniego Date: Fri, 19 Jun 2020 12:59:50 -0700 Subject: [PATCH 11/13] Change implementation of UserContext to follow Execution::Context model --- .../Commands/SettingsCommand.cpp | 8 +- src/AppInstallerCLICore/Core.cpp | 11 +- src/AppInstallerCLICore/ExecutionContext.cpp | 2 +- src/AppInstallerCLICore/ExecutionContext.h | 2 +- src/AppInstallerCLICore/ExecutionProgress.cpp | 1 - src/AppInstallerCLICore/ExecutionProgress.h | 2 +- src/AppInstallerCLICore/Resources.h | 1 + .../Shared/Strings/en-us/winget.resw | 3 + .../AppInstallerCLITests.vcxproj | 2 - .../AppInstallerCLITests.vcxproj.filters | 6 - src/AppInstallerCLITests/SourceSetting.cpp | 53 ---- src/AppInstallerCLITests/UserSettings.cpp | 134 ++++++++-- src/AppInstallerCLITests/VisualSettings.cpp | 55 ----- .../AppInstallerCommonCore.vcxproj | 8 +- .../AppInstallerCommonCore.vcxproj.filters | 33 +-- src/AppInstallerCommonCore/JsonUtil.cpp | 30 +-- src/AppInstallerCommonCore/JsonUtil.h | 9 +- .../Public/winget/UserSettings.h | 90 ++++++- .../Public/winget/settings/Source.h | 35 --- .../Public/winget/settings/Visual.h | 39 --- src/AppInstallerCommonCore/UserSettings.cpp | 229 ++++++++++++++++++ .../UserSettings/SettingValidation.h | 39 --- .../UserSettings/SettingsValidation.cpp | 30 --- .../UserSettings/Source.cpp | 55 ----- .../UserSettings/UserSettings.cpp | 130 ---------- .../UserSettings/Visual.cpp | 70 ------ .../RepositorySource.cpp | 4 +- 27 files changed, 471 insertions(+), 610 deletions(-) delete mode 100644 src/AppInstallerCLITests/SourceSetting.cpp delete mode 100644 src/AppInstallerCLITests/VisualSettings.cpp delete mode 100644 src/AppInstallerCommonCore/Public/winget/settings/Source.h delete mode 100644 src/AppInstallerCommonCore/Public/winget/settings/Visual.h create mode 100644 src/AppInstallerCommonCore/UserSettings.cpp delete mode 100644 src/AppInstallerCommonCore/UserSettings/SettingValidation.h delete mode 100644 src/AppInstallerCommonCore/UserSettings/SettingsValidation.cpp delete mode 100644 src/AppInstallerCommonCore/UserSettings/Source.cpp delete mode 100644 src/AppInstallerCommonCore/UserSettings/UserSettings.cpp delete mode 100644 src/AppInstallerCommonCore/UserSettings/Visual.cpp diff --git a/src/AppInstallerCLICore/Commands/SettingsCommand.cpp b/src/AppInstallerCLICore/Commands/SettingsCommand.cpp index 210302b988..a568209af6 100644 --- a/src/AppInstallerCLICore/Commands/SettingsCommand.cpp +++ b/src/AppInstallerCLICore/Commands/SettingsCommand.cpp @@ -33,8 +33,14 @@ namespace AppInstaller::CLI return "https://aka.ms/winget-settings"; } - void SettingsCommand::ExecuteInternal(Execution::Context& ) const + void SettingsCommand::ExecuteInternal(Execution::Context& context) const { + // Show warnings only when the setting command is executed. + for (const auto& warning : Settings::User().GetWarnings()) + { + context.Reporter.Warn() << warning << std::endl; + } + User().PrepareToShellExecuteFile(); std::string filePathUTF8Str = UserSettings::SettingsFilePath().u8string(); diff --git a/src/AppInstallerCLICore/Core.cpp b/src/AppInstallerCLICore/Core.cpp index 46280c1731..519309fef9 100644 --- a/src/AppInstallerCLICore/Core.cpp +++ b/src/AppInstallerCLICore/Core.cpp @@ -79,12 +79,6 @@ namespace AppInstaller::CLI // The root command is our fallback in the event of very bad or very little input std::unique_ptr command = std::make_unique(); - // Load the settings file and warn if necessary. - for (const auto& warning : Settings::User().GetWarnings()) - { - context.Reporter.Warn() << warning << std::endl; - } - try { std::unique_ptr subCommand = command->FindSubCommand(invocation); @@ -117,6 +111,11 @@ namespace AppInstaller::CLI try { + if (!Settings::User().GetWarnings().empty()) + { + context.Reporter.Warn() << Resource::String::SettingsWarnings << std::endl; + } + command->Execute(context); } // Exceptions that may occur in the process of executing an arbitrary command diff --git a/src/AppInstallerCLICore/ExecutionContext.cpp b/src/AppInstallerCLICore/ExecutionContext.cpp index 706a45a55b..4d6558d6b7 100644 --- a/src/AppInstallerCLICore/ExecutionContext.cpp +++ b/src/AppInstallerCLICore/ExecutionContext.cpp @@ -92,7 +92,7 @@ namespace AppInstaller::CLI::Execution } else { - Reporter.SetStyle(User().GetVisual().GetProgressBarVisualStyle()); + Reporter.SetStyle(User().Get()); } } diff --git a/src/AppInstallerCLICore/ExecutionContext.h b/src/AppInstallerCLICore/ExecutionContext.h index 157dc53887..3912f5ec71 100644 --- a/src/AppInstallerCLICore/ExecutionContext.h +++ b/src/AppInstallerCLICore/ExecutionContext.h @@ -37,7 +37,7 @@ namespace AppInstaller::CLI::Workflow namespace AppInstaller::CLI::Execution { - // Names a peice of data stored in the context by a workflow step. + // Names a piece of data stored in the context by a workflow step. // Must start at 0 to enable direct access to variant in Context. // Max must be last and unused. enum class Data : size_t diff --git a/src/AppInstallerCLICore/ExecutionProgress.cpp b/src/AppInstallerCLICore/ExecutionProgress.cpp index c8ae5f7959..609f04db54 100644 --- a/src/AppInstallerCLICore/ExecutionProgress.cpp +++ b/src/AppInstallerCLICore/ExecutionProgress.cpp @@ -2,7 +2,6 @@ // Licensed under the MIT License. #include "pch.h" #include "ExecutionProgress.h" -#include namespace AppInstaller::CLI::Execution { diff --git a/src/AppInstallerCLICore/ExecutionProgress.h b/src/AppInstallerCLICore/ExecutionProgress.h index 24eb80d827..f3eec53623 100644 --- a/src/AppInstallerCLICore/ExecutionProgress.h +++ b/src/AppInstallerCLICore/ExecutionProgress.h @@ -3,7 +3,7 @@ #pragma once #include "VTSupport.h" #include -#include +#include #include diff --git a/src/AppInstallerCLICore/Resources.h b/src/AppInstallerCLICore/Resources.h index 59d1bc4eea..8ba0662dde 100644 --- a/src/AppInstallerCLICore/Resources.h +++ b/src/AppInstallerCLICore/Resources.h @@ -89,6 +89,7 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(SearchCommandShortDescription); WINGET_DEFINE_RESOURCE_STRINGID(SettingsCommandLongDescription); WINGET_DEFINE_RESOURCE_STRINGID(SettingsCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SettingsWarnings); WINGET_DEFINE_RESOURCE_STRINGID(ShowCommandLongDescription); WINGET_DEFINE_RESOURCE_STRINGID(ShowCommandShortDescription); WINGET_DEFINE_RESOURCE_STRINGID(SilentArgumentDescription); diff --git a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw index 174b2f9a10..61afdb7e7e 100644 --- a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw +++ b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw @@ -311,6 +311,9 @@ Open settings + + Unexpected error while loading settings. Please verify your settings by running settings command + Shows information on a specific application. diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj index 66e7dee05c..62fc8f0cc5 100644 --- a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj +++ b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj @@ -182,12 +182,10 @@ - - diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters index 3db2aee595..d348d410a9 100644 --- a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters +++ b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters @@ -86,12 +86,6 @@ Source Files - - Source Files - - - Source Files - Source Files diff --git a/src/AppInstallerCLITests/SourceSetting.cpp b/src/AppInstallerCLITests/SourceSetting.cpp deleted file mode 100644 index a73cf5b96b..0000000000 --- a/src/AppInstallerCLITests/SourceSetting.cpp +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include -#include - -using namespace AppInstaller::Settings; - -/* Source property example - "source": { - "autoUpdateIntervalInMinutes": -1 - }, -*/ - -namespace -{ - template - void TestAutoUpdateIntervalInMinutes(const T& value, uint32_t expected, size_t warnings) - { - Json::Value property; - property["autoUpdateIntervalInMinutes"] = value; - Source source(property); - REQUIRE(source.Warnings().size() == warnings); - REQUIRE(source.GetAutoUpdateTimeInMinutes() == expected); - } -} - -TEST_CASE("SourceSetting", "[settings]") -{ - Source source(Json::Value::nullSingleton()); - REQUIRE(source.GetPropertyName() == "source"); -} - -TEST_CASE("SourceSettingAutoUpdateIntervalInMinutes", "[settings]") -{ - const std::string progressBar = "autoUpdateIntervalInMinutes"; - - Source sourceNull(Json::Value::nullSingleton()); - REQUIRE(sourceNull.Warnings().size() == 0); - REQUIRE(sourceNull.GetAutoUpdateTimeInMinutes() == 5); - - TestAutoUpdateIntervalInMinutes(300, 300, 0); - - TestAutoUpdateIntervalInMinutes(-20, 5, 1); - - TestAutoUpdateIntervalInMinutes("not a number", 5, 1); - - Json::Value otherProperty; - otherProperty["other"] = "property"; - Source sourceOther(otherProperty); - REQUIRE(sourceOther.Warnings().size() == 0); - REQUIRE(sourceOther.GetAutoUpdateTimeInMinutes() == 5); -} diff --git a/src/AppInstallerCLITests/UserSettings.cpp b/src/AppInstallerCLITests/UserSettings.cpp index f4733921a4..9d2dc35692 100644 --- a/src/AppInstallerCLITests/UserSettings.cpp +++ b/src/AppInstallerCLITests/UserSettings.cpp @@ -17,14 +17,14 @@ using namespace std::string_view_literals; namespace { - static constexpr std::string_view goodJson = "{}"; - static constexpr std::string_view badJson = "{"; - static constexpr std::string_view settings = "settings.json"sv; - static constexpr std::string_view settingsBackup = "settings.json.backup"sv; + static constexpr std::string_view s_goodJson = "{}"; + static constexpr std::string_view s_badJson = "{"; + static constexpr std::string_view s_settings = "settings.json"sv; + static constexpr std::string_view s_settingsBackup = "settings.json.backup"sv; std::filesystem::path GetBackupPath() { - return GetPathTo(PathName::UserFileSettings) / settingsBackup; + return GetPathTo(PathName::UserFileSettings) / s_settingsBackup; } void DeleteUserSettingsFiles() @@ -70,60 +70,60 @@ TEST_CASE("UserSettingsType", "[settings]") } SECTION("No setting.json Bad setting.json.backup") { - SetSetting(Type::UserFile, settingsBackup, badJson); + SetSetting(Type::UserFile, s_settingsBackup, s_badJson); UserSettingsTest userSettingTest; REQUIRE(userSettingTest.GetType() == UserSettingsType::Default); } SECTION("No setting.json Good setting.json.backup") { - SetSetting(Type::UserFile, settingsBackup, goodJson); + SetSetting(Type::UserFile, s_settingsBackup, s_goodJson); UserSettingsTest userSettingTest; REQUIRE(userSettingTest.GetType() == UserSettingsType::Backup); } SECTION("Bad setting.json No setting.json.backup") { - SetSetting(Type::UserFile, settings, badJson); + SetSetting(Type::UserFile, s_settings, s_badJson); UserSettingsTest userSettingTest; REQUIRE(userSettingTest.GetType() == UserSettingsType::Default); } SECTION("Bad setting.json Bad setting.json.backup") { - SetSetting(Type::UserFile, settings, badJson); - SetSetting(Type::UserFile, settingsBackup, badJson); + SetSetting(Type::UserFile, s_settings, s_badJson); + SetSetting(Type::UserFile, s_settingsBackup, s_badJson); UserSettingsTest userSettingTest; REQUIRE(userSettingTest.GetType() == UserSettingsType::Default); } SECTION("Bad setting.json Good setting.json.backup") { - SetSetting(Type::UserFile, settings, badJson); - SetSetting(Type::UserFile, settingsBackup, goodJson); + SetSetting(Type::UserFile, s_settings, s_badJson); + SetSetting(Type::UserFile, s_settingsBackup, s_goodJson); UserSettingsTest userSettingTest; REQUIRE(userSettingTest.GetType() == UserSettingsType::Backup); } SECTION("Good setting.json No setting.json.backup") { - SetSetting(Type::UserFile, settings, goodJson); + SetSetting(Type::UserFile, s_settings, s_goodJson); UserSettingsTest userSettingTest; REQUIRE(userSettingTest.GetType() == UserSettingsType::Standard); } SECTION("Good setting.json Bad setting.json.backup") { - SetSetting(Type::UserFile, settings, goodJson); - SetSetting(Type::UserFile, settingsBackup, badJson); + SetSetting(Type::UserFile, s_settings, s_goodJson); + SetSetting(Type::UserFile, s_settingsBackup, s_badJson); UserSettingsTest userSettingTest; REQUIRE(userSettingTest.GetType() == UserSettingsType::Standard); } SECTION("Good setting.json Good setting.json.backup") { - SetSetting(Type::UserFile, settings, goodJson); - SetSetting(Type::UserFile, settingsBackup, goodJson); + SetSetting(Type::UserFile, s_settings, s_goodJson); + SetSetting(Type::UserFile, s_settingsBackup, s_goodJson); UserSettingsTest userSettingTest; REQUIRE(userSettingTest.GetType() == UserSettingsType::Standard); @@ -151,7 +151,7 @@ TEST_CASE("UserSettingsCreateFiles", "[settings]") } SECTION("Good settings.json create new backup") { - SetSetting(Type::UserFile, settings, goodJson); + SetSetting(Type::UserFile, s_settings, s_goodJson); REQUIRE(std::filesystem::exists(settingsPath)); REQUIRE(!std::filesystem::exists(settingsBackupPath)); @@ -163,3 +163,101 @@ TEST_CASE("UserSettingsCreateFiles", "[settings]") REQUIRE(std::filesystem::exists(settingsBackupPath)); } } + +TEST_CASE("SettingProgressBar", "[settings]") +{ + DeleteUserSettingsFiles(); + + SECTION("Default value") + { + UserSettingsTest userSettingTest; + + REQUIRE(userSettingTest.Get() == VisualStyle::Accent); + REQUIRE(userSettingTest.GetWarnings().size() == 0); + } + SECTION("Accent") + { + std::string_view json = R"({ "visual": { "progressBar": "accent" } })"; + SetSetting(Type::UserFile, s_settings, json); + UserSettingsTest userSettingTest; + + REQUIRE(userSettingTest.Get() == VisualStyle::Accent); + REQUIRE(userSettingTest.GetWarnings().size() == 0); + } + SECTION("Rainbow") + { + std::string_view json = R"({ "visual": { "progressBar": "rainbow" } })"; + SetSetting(Type::UserFile, s_settings, json); + UserSettingsTest userSettingTest; + + REQUIRE(userSettingTest.Get() == VisualStyle::Rainbow); + REQUIRE(userSettingTest.GetWarnings().size() == 0); + } + SECTION("retro") + { + std::string_view json = R"({ "visual": { "progressBar": "retro" } })"; + SetSetting(Type::UserFile, s_settings, json); + UserSettingsTest userSettingTest; + + REQUIRE(userSettingTest.Get() == VisualStyle::Retro); + REQUIRE(userSettingTest.GetWarnings().size() == 0); + } + SECTION("Bad value") + { + std::string_view json = R"({ "visual": { "progressBar": "fake" } })"; + SetSetting(Type::UserFile, s_settings, json); + UserSettingsTest userSettingTest; + + REQUIRE(userSettingTest.Get() == VisualStyle::Accent); + REQUIRE(userSettingTest.GetWarnings().size() == 1); + } + SECTION("Bad value type") + { + std::string_view json = R"({ "visual": { "progressBar": 5 } })"; + SetSetting(Type::UserFile, s_settings, json); + UserSettingsTest userSettingTest; + + REQUIRE(userSettingTest.Get() == VisualStyle::Accent); + REQUIRE(userSettingTest.GetWarnings().size() == 1); + } +} + +TEST_CASE("SettingAutoUpdateIntervalInMinutes", "[settings]") +{ + DeleteUserSettingsFiles(); + + SECTION("Default value") + { + UserSettingsTest userSettingTest; + + REQUIRE(userSettingTest.Get() == 5); + REQUIRE(userSettingTest.GetWarnings().size() == 0); + } + SECTION("Valid value") + { + std::string_view json = R"({ "source": { "autoUpdateIntervalInMinutes": 300 } })"; + SetSetting(Type::UserFile, s_settings, json); + UserSettingsTest userSettingTest; + + REQUIRE(userSettingTest.Get() == 300); + REQUIRE(userSettingTest.GetWarnings().size() == 0); + } + SECTION("Invalid type negative integer") + { + std::string_view json = R"({ "source": { "autoUpdateIntervalInMinutes": -20 } })"; + SetSetting(Type::UserFile, s_settings, json); + UserSettingsTest userSettingTest; + + REQUIRE(userSettingTest.Get() == 5); + REQUIRE(userSettingTest.GetWarnings().size() == 1); + } + SECTION("Invalid type string") + { + std::string_view json = R"({ "source": { "autoUpdateIntervalInMinutes": "not a number" } })"; + SetSetting(Type::UserFile, s_settings, json); + UserSettingsTest userSettingTest; + + REQUIRE(userSettingTest.Get() == 5); + REQUIRE(userSettingTest.GetWarnings().size() == 1); + } +} diff --git a/src/AppInstallerCLITests/VisualSettings.cpp b/src/AppInstallerCLITests/VisualSettings.cpp deleted file mode 100644 index d01d00b5de..0000000000 --- a/src/AppInstallerCLITests/VisualSettings.cpp +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include -#include - -using namespace AppInstaller::Settings; - -/* Visual property example - "visual": { - "progressBar": "accent" - } -*/ - -namespace -{ - template - void TestProgressBar(const T& value, VisualStyle expected, size_t warnings) - { - Json::Value property; - property["progressBar"] = value; - Visual visual(property); - REQUIRE(visual.Warnings().size() == warnings); - REQUIRE(visual.GetProgressBarVisualStyle() == expected); - } -} - -TEST_CASE("VisualSetting", "[settings]") -{ - Visual visual(Json::Value::nullSingleton()); - REQUIRE(visual.GetPropertyName() == "visual"); -} - -TEST_CASE("VisualSettingProgressBar", "[settings]") -{ - Visual visualNull(Json::Value::nullSingleton()); - REQUIRE(visualNull.Warnings().size() == 0); - REQUIRE(visualNull.GetProgressBarVisualStyle() == VisualStyle::Accent); - - TestProgressBar("accent", VisualStyle::Accent, 0); - - TestProgressBar("rainbow", VisualStyle::Rainbow, 0); - - TestProgressBar("retro", VisualStyle::Retro, 0); - - TestProgressBar("fake", VisualStyle::Accent, 1); - - TestProgressBar(5, VisualStyle::Accent, 1); - - Json::Value otherProperty; - otherProperty["other"] = "property"; - Visual visualOther(otherProperty); - REQUIRE(visualOther.Warnings().size() == 0); - REQUIRE(visualOther.GetProgressBarVisualStyle() == VisualStyle::Accent); -} diff --git a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj index 4c9bf60afb..9ed88a5854 100644 --- a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj +++ b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj @@ -195,14 +195,11 @@ - - - @@ -229,10 +226,7 @@ - - - - + diff --git a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters index 47d4b44b12..458aa9d182 100644 --- a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters +++ b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters @@ -25,12 +25,6 @@ {41035fd6-dc74-4464-b9b1-4ffe95d6789c} - - {dd43556d-10de-433a-99f4-1282590e68a0} - - - {95046c4f-633b-4cf0-995a-430c41d1ab62} - @@ -108,21 +102,15 @@ Public\winget - - Public\winget\settings - - - Public\winget\settings - - - UserSettings - Public\winget Public\winget + + Header Files + Header Files @@ -188,24 +176,15 @@ Source Files - - UserSettings - - - UserSettings - - - UserSettings - Source Files - - UserSettings - Source Files + + Source Files + diff --git a/src/AppInstallerCommonCore/JsonUtil.cpp b/src/AppInstallerCommonCore/JsonUtil.cpp index 017d939291..ba4f41e82f 100644 --- a/src/AppInstallerCommonCore/JsonUtil.cpp +++ b/src/AppInstallerCommonCore/JsonUtil.cpp @@ -6,41 +6,27 @@ namespace AppInstaller::Utility { - std::optional GetValueString(const std::string_view& key, const Json::Value& node) + template<> + std::optional GetValue(const Json::Value& node) { std::optional value = std::nullopt; - // jsoncpp doesn't support std::string_view yet. - auto keyStr = std::string(key); - - if (node.isMember(keyStr)) + if (node.isString()) { - auto keyValue = node[keyStr]; - - if (keyValue.isString()) - { - value = keyValue.asString(); - } + value = node.asString(); } return value; } - std::optional GetValueUInt(const std::string_view& key, const Json::Value& node) + template<> + std::optional GetValue(const Json::Value& node) { std::optional value = std::nullopt; - // jsoncpp doesn't support std::string_view yet. - auto keyStr = std::string(key); - - if (node.isMember(keyStr)) + if (node.isUInt()) { - auto keyValue = node[keyStr]; - - if (keyValue.isUInt()) - { - value = keyValue.asUInt(); - } + value = node.asUInt(); } return value; diff --git a/src/AppInstallerCommonCore/JsonUtil.h b/src/AppInstallerCommonCore/JsonUtil.h index 8e31b91d8d..247c79af40 100644 --- a/src/AppInstallerCommonCore/JsonUtil.h +++ b/src/AppInstallerCommonCore/JsonUtil.h @@ -9,8 +9,13 @@ namespace AppInstaller::Utility { - std::optional GetValueString(const std::string_view& key, const Json::Value& node); + template + std::optional GetValue(const Json::Value& node); - std::optional GetValueUInt(const std::string_view& key, const Json::Value& node); + template<> + std::optional GetValue(const Json::Value& node); + + template<> + std::optional GetValue(const Json::Value& node); } diff --git a/src/AppInstallerCommonCore/Public/winget/UserSettings.h b/src/AppInstallerCommonCore/Public/winget/UserSettings.h index 4cafc0b3e2..eb2a28f28e 100644 --- a/src/AppInstallerCommonCore/Public/winget/UserSettings.h +++ b/src/AppInstallerCommonCore/Public/winget/UserSettings.h @@ -2,12 +2,11 @@ // Licensed under the MIT License. #pragma once #include "AppInstallerStrings.h" -#include "winget/settings/Source.h" -#include "winget/settings/Visual.h" #include #include #include +#include namespace AppInstaller::Settings { @@ -22,6 +21,74 @@ namespace AppInstaller::Settings Backup, }; + // The visual style of the progress bar. + enum class VisualStyle + { + NoVT, + Retro, + Accent, + Rainbow, + }; + + // Enum of settings. + // Must start at 0 to enable direct access to variant in UserSettings. + // Max must be last and unused. + enum class Setting : size_t + { + ProgressBarVisualStyle, + AutoUpdateTimeInMinutes, + Max + }; + + namespace details + { + template + struct SettingMapping + { + // json_t - type the setting in json. + // value_t - the type of this setting. + // DefaultValue - the value_t default value when setting is absent or semantically wrong. + // Path - json path to the property. See Json::Path in json.h for syntax. So far, this is sufficient + // but since is "brief" and "untested" we might implement our own if needed. + // Validate - Function that does semantic validation. + }; + + template <> + struct SettingMapping + { + using json_t = std::string; + using value_t = VisualStyle; + + static constexpr value_t DefaultValue = VisualStyle::Accent; + static constexpr std::string_view Path = ".visual.progressBar"; + + static std::optional Validate(json_t value); + }; + + template <> + struct SettingMapping + { + using json_t = uint32_t; + using value_t = uint32_t; + + static constexpr value_t DefaultValue = 5; + static constexpr std::string_view Path = ".source.autoUpdateIntervalInMinutes"; + + static std::optional Validate(json_t value); + }; + + // Used to deduce the SettingVariant type; making a variant that includes std::monostate and all SettingMapping types. + template + inline auto Deduce(std::index_sequence) { return std::variant(I)>::value_t...>{}; } + + // Holds data of any type listed in a SettingMapping. + using SettingVariant = decltype(Deduce(std::make_index_sequence(Setting::Max)>())); + + // Gets the index into the variant for the given Setting. + constexpr inline size_t SettingIndex(Setting s) { return static_cast(s) + 1; } + } + + // Representation of the parsed settings file. struct UserSettings { @@ -42,19 +109,26 @@ namespace AppInstaller::Settings UserSettingsType GetType() const { return m_type; } std::vector const& GetWarnings() const { return m_warnings; } - void Reload(); void PrepareToShellExecuteFile() const; - // Settings - inline const Source& GetSource() const { return *m_source; } - inline const Visual& GetVisual() const { return *m_visual; } + // Gets setting value, if its not in the map it returns the default value. + template + typename details::SettingMapping::value_t Get() const + { + auto itr = m_settings.find(S); + if (itr == m_settings.end()) + { + return details::SettingMapping::DefaultValue; + } + + return std::get(itr->second); + } private: UserSettingsType m_type = UserSettingsType::Default; std::vector m_warnings; - std::unique_ptr m_source; - std::unique_ptr m_visual; + std::map m_settings; protected: UserSettings(); diff --git a/src/AppInstallerCommonCore/Public/winget/settings/Source.h b/src/AppInstallerCommonCore/Public/winget/settings/Source.h deleted file mode 100644 index ca2dbc3fa8..0000000000 --- a/src/AppInstallerCommonCore/Public/winget/settings/Source.h +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include - -#include - -namespace AppInstaller::Settings -{ - using namespace std::chrono_literals; - - struct Source final - { - Source(const Json::Value& node); - virtual ~Source() = default; - - Source(const Source&) = default; - Source& operator=(const Source&) = default; - - Source(Source&&) = default; - Source& operator=(Source&&) = default; - - std::vector Warnings() const { return m_warnings; } - - uint32_t GetAutoUpdateTimeInMinutes() const noexcept { return m_autoUpdateIntervalInMinutes; } - - static std::string_view GetPropertyName(); - - private: - uint32_t m_autoUpdateIntervalInMinutes; - - std::vector m_warnings; - - }; -} diff --git a/src/AppInstallerCommonCore/Public/winget/settings/Visual.h b/src/AppInstallerCommonCore/Public/winget/settings/Visual.h deleted file mode 100644 index 5773f760d8..0000000000 --- a/src/AppInstallerCommonCore/Public/winget/settings/Visual.h +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include - -namespace AppInstaller::Settings -{ - // The visual style of the progress bar. - enum class VisualStyle - { - NoVT, - Retro, - Accent, - Rainbow, - }; - - struct Visual final - { - Visual(const Json::Value& node); - virtual ~Visual() = default; - - Visual(const Visual&) = default; - Visual& operator=(const Visual&) = default; - - Visual(Visual&&) = default; - Visual& operator=(Visual&&) = default; - - std::vector Warnings() const { return m_warnings; } - - VisualStyle GetProgressBarVisualStyle() const noexcept { return m_progressBar; } - - static std::string_view GetPropertyName(); - - private: - VisualStyle m_progressBar; - - std::vector m_warnings; - }; -} diff --git a/src/AppInstallerCommonCore/UserSettings.cpp b/src/AppInstallerCommonCore/UserSettings.cpp new file mode 100644 index 0000000000..0d1a87fea7 --- /dev/null +++ b/src/AppInstallerCommonCore/UserSettings.cpp @@ -0,0 +1,229 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include +#include "JsonUtil.h" +#include "winget/Settings.h" +#include "winget/UserSettings.h" + +namespace AppInstaller::Settings +{ + using namespace std::string_view_literals; + using namespace Runtime; + using namespace Utility; + + static constexpr std::string_view s_SettingFileName = "settings.json"sv; + static constexpr std::string_view s_SettingBackupFileName = "settings.json.backup"sv; + + static constexpr std::string_view s_SettingEmpty = + R"({ + // For documentation on these settings, see: https://aka.ms/winget-settings + // "source": { + // "autoUpdateIntervalInMinutes": 5 + // } +})"sv; + + namespace SettingsWarnings + { + const char* const Field = " Field: "; + const char* const Value = " Value: "; + const char* const InvalidFieldValue = "Invalid field value."; + const char* const InvalidFieldFormat = "Invalid field format."; + constexpr std::string_view LoadedBackupSettings = "Loaded settings from backup file."; + } + + namespace + { + // Jsoncpp doesn't provide line number and column for an individual Json::Value node. + inline std::string GetSettingsMessage(const std::string& message, const std::string& path) + { + return message + SettingsWarnings::Field + path; + } + + template + inline std::string GetSettingsMessage(const std::string& message, const std::string& path, T value) + { + std::string convertedValue; + + // This won't work when there's a json_t with bool. Change when this happens. + if constexpr (std::is_arithmetic_v) + { + convertedValue = std::to_string(value); + } + else + { + convertedValue = value; + } + + return GetSettingsMessage(message, path) + SettingsWarnings::Value + convertedValue; + } + + + std::filesystem::path SettingsBackupFilePath() + { + return GetPathTo(PathName::UserFileSettings) / s_SettingBackupFileName; + } + + std::optional ParseFile(const std::filesystem::path& fileName, std::vector& warnings) + { + auto stream = GetSettingStream(Type::UserFile, fileName); + if (stream) + { + Json::Value root; + Json::CharReaderBuilder builder; + const std::unique_ptr reader(builder.newCharReader()); + + std::string settingsContentStr = Utility::ReadEntireStream(*stream); + std::string error; + + if (reader->parse(settingsContentStr.c_str(), settingsContentStr.c_str() + settingsContentStr.size(), &root, &error)) + { + return root; + } + + warnings.emplace_back(fileName.u8string()); + warnings.emplace_back(error); + } + + return {}; + } + + template + void Validate( + Json::Value& root, + std::map& settings, + std::vector& warnings) + { + // jsoncpp doesn't support std::string_view yet. + auto path = std::string(details::SettingMapping::Path); + + const Json::Path jsonPath(path); + Json::Value result = jsonPath.resolve(root); + if (!result.isNull()) + { + auto jsonValue = GetValue::json_t>(result); + + if (jsonValue.has_value()) + { + auto validatedValue = details::SettingMapping::Validate(jsonValue.value()); + + if (validatedValue.has_value()) + { + // Finally add it to the map + settings[S0].emplace( + std::forward::value_t>(validatedValue.value())); + } + else + { + warnings.push_back(GetSettingsMessage(SettingsWarnings::InvalidFieldValue, path, jsonValue.value())); + } + } + else + { + warnings.emplace_back(GetSettingsMessage(SettingsWarnings::InvalidFieldFormat, path)); + } + } + + if constexpr (sizeof...(S) != 0) + { + Validate(root, settings, warnings); + } + } + } + + namespace details + { + std::optional::value_t> + SettingMapping::Validate(SettingMapping::json_t value) + { + return value; + } + + std::optional::value_t> + SettingMapping::Validate(SettingMapping::json_t value) + { + // progressBar property possible values + static constexpr std::string_view s_progressBar_Accent = "accent"; + static constexpr std::string_view s_progressBar_Rainbow = "rainbow"; + static constexpr std::string_view s_progressBar_Retro = "retro"; + + if (Utility::CaseInsensitiveEquals(value, s_progressBar_Accent)) + { + return VisualStyle::Accent; + } + else if (Utility::CaseInsensitiveEquals(value, s_progressBar_Rainbow)) + { + return VisualStyle::Rainbow; + } + else if (Utility::CaseInsensitiveEquals(value, s_progressBar_Retro)) + { + return VisualStyle::Retro; + } + + return {}; + } + } + + UserSettings::UserSettings() : m_type(UserSettingsType::Default) + { + Json::Value settingsRoot = Json::Value::nullSingleton(); + + // Settings can be loaded from settings.json or settings.json.backup files. + // 1 - Use settings.json if exists and passes parsing. + // 2 - Use settings.backup.json if settings.json fails to parse. + // 3 - Use default (empty) if both settings files fail to load. + + auto settingsJson = ParseFile(s_SettingFileName, m_warnings); + if (settingsJson.has_value()) + { + m_type = UserSettingsType::Standard; + settingsRoot = settingsJson.value(); + } + + // Settings didn't parse or doesn't exist, try with backup. + if (settingsRoot.isNull()) + { + auto settingsBackupJson = ParseFile(s_SettingBackupFileName, m_warnings); + if (settingsBackupJson.has_value()) + { + m_warnings.emplace_back(SettingsWarnings::LoadedBackupSettings); + m_type = UserSettingsType::Backup; + settingsRoot = settingsBackupJson.value(); + } + } + + if (!settingsRoot.isNull()) + { + Validate< + Setting::AutoUpdateTimeInMinutes, + Setting::ProgressBarVisualStyle + >(settingsRoot, m_settings, m_warnings); + } + } + + void UserSettings::PrepareToShellExecuteFile() const + { + UserSettingsType userSettingType = GetType(); + + if (userSettingType == UserSettingsType::Default) + { + // Create settings file if it doesn't exist. + if (!std::filesystem::exists(UserSettings::SettingsFilePath())) + { + SetSetting(Type::UserFile, s_SettingFileName, s_SettingEmpty); + } + } + else if (userSettingType == UserSettingsType::Standard) + { + // Settings file was loaded correctly, create backup. + auto from = SettingsFilePath(); + auto to = SettingsBackupFilePath(); + std::filesystem::copy_file(from, to, std::filesystem::copy_options::overwrite_existing); + } + } + + std::filesystem::path UserSettings::SettingsFilePath() + { + return GetPathTo(PathName::UserFileSettings) / s_SettingFileName; + } +} diff --git a/src/AppInstallerCommonCore/UserSettings/SettingValidation.h b/src/AppInstallerCommonCore/UserSettings/SettingValidation.h deleted file mode 100644 index 0d3fa17956..0000000000 --- a/src/AppInstallerCommonCore/UserSettings/SettingValidation.h +++ /dev/null @@ -1,39 +0,0 @@ -#pragma once -#include - -#include -#include -#include - -namespace AppInstaller::Settings -{ - namespace SettingsWarnings - { - const char* const Field = " Field: "; - const char* const Value = " Value: "; - const char* const InvalidFieldValue = "Invalid field value."; - const char* const InvalidFieldFormat = "Invalid field format."; - constexpr std::string_view LoadedBackupSettings = "Loaded settings from backup file."; - } - - // Jsoncpp doesn't provide line number and column for an individual Json::Value node. - inline std::string GetSettingsMessage(const std::string& message, const std::string& field) - { - return message + SettingsWarnings::Field + field; - } - - inline std::string GetSettingsMessage(const std::string& message, const std::string& field, const std::string& value) - { - return GetSettingsMessage(message, field) + SettingsWarnings::Value + value; - } - - struct SettingFieldInfo - { - std::string Name; - std::function ProcessFunc; - }; - - std::vector ValidateAndProcessFields( - const Json::Value& rootNode, - const std::vector fieldInfos); -} diff --git a/src/AppInstallerCommonCore/UserSettings/SettingsValidation.cpp b/src/AppInstallerCommonCore/UserSettings/SettingsValidation.cpp deleted file mode 100644 index 5500da33bf..0000000000 --- a/src/AppInstallerCommonCore/UserSettings/SettingsValidation.cpp +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once -#include "pch.h" -#include "SettingValidation.h" - -namespace AppInstaller::Settings -{ - std::vector ValidateAndProcessFields( - const Json::Value& root, - const std::vector fieldInfos) - { - if (root.isNull()) - { - return {}; - } - - std::vector warnings; - - for (const std::string& key : root.getMemberNames()) { - - auto fieldIter = std::find_if(fieldInfos.begin(), fieldInfos.end(), [&](auto const& s) { return s.Name == key; }); - - if (fieldIter != fieldInfos.end()) - { - fieldIter->ProcessFunc(key, root); - } - } - - return warnings; - } -} \ No newline at end of file diff --git a/src/AppInstallerCommonCore/UserSettings/Source.cpp b/src/AppInstallerCommonCore/UserSettings/Source.cpp deleted file mode 100644 index d4199661bd..0000000000 --- a/src/AppInstallerCommonCore/UserSettings/Source.cpp +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "AppInstallerStrings.h" -#include "JsonUtil.h" -#include "SettingValidation.h" -#include "winget\settings\Source.h" - -#include - -#include - -namespace AppInstaller::Settings -{ - using namespace std::string_view_literals; - - // Visual root - static constexpr std::string_view s_SourceKey = "source"sv; - - // Default values; - static constexpr uint32_t Five = 5; - - Source::Source(const Json::Value& node) : m_autoUpdateIntervalInMinutes(Five) - { - std::vector sourceSettings = - { - { - "autoUpdateIntervalInMinutes", - [this](const std::string& key, const Json::Value& node) - { - if (!node.isNull()) - { - auto autoUpdateInterval = Utility::GetValueUInt(key, node); - if (autoUpdateInterval.has_value()) - { - m_autoUpdateIntervalInMinutes = autoUpdateInterval.value(); - } - else - { - m_warnings.push_back(GetSettingsMessage(SettingsWarnings::InvalidFieldFormat, key)); - } - } - } - }, - }; - - auto warnings = ValidateAndProcessFields(node, sourceSettings); - std::move(warnings.begin(), warnings.end(), std::inserter(m_warnings, m_warnings.end())); - } - - std::string_view Source::GetPropertyName() - { - return s_SourceKey; - } -} diff --git a/src/AppInstallerCommonCore/UserSettings/UserSettings.cpp b/src/AppInstallerCommonCore/UserSettings/UserSettings.cpp deleted file mode 100644 index 83a24b4ab5..0000000000 --- a/src/AppInstallerCommonCore/UserSettings/UserSettings.cpp +++ /dev/null @@ -1,130 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include -#include "SettingValidation.h" -#include "winget/Settings.h" -#include "winget/UserSettings.h" - -#include "winget/settings/Source.h" -#include "winget/settings/Visual.h" - -namespace AppInstaller::Settings -{ - using namespace std::string_view_literals; - using namespace Runtime; - using namespace Utility; - - static constexpr std::string_view s_SettingFileName = "settings.json"sv; - static constexpr std::string_view s_SettingBackupFileName = "settings.json.backup"sv; - - static constexpr std::string_view s_SettingEmpty = - R"("{ - // For documentation on these settings, see: https://aka.ms/winget-settings - // "source": { - // "autoUpdateIntervalInMinutes": 5 - // } -}")"sv; - - namespace - { - std::filesystem::path SettingsBackupFilePath() - { - return GetPathTo(PathName::UserFileSettings) / s_SettingBackupFileName; - } - - std::optional ParseFile(const std::filesystem::path& fileName, std::vector& warnings) - { - auto stream = GetSettingStream(Type::UserFile, fileName); - if (stream) - { - Json::Value root; - Json::CharReaderBuilder builder; - const std::unique_ptr reader(builder.newCharReader()); - - std::string settingsContentStr = Utility::ReadEntireStream(*stream); - std::string error; - - if (reader->parse(settingsContentStr.c_str(), settingsContentStr.c_str() + settingsContentStr.size(), &root, &error)) - { - return root; - } - - warnings.emplace_back(fileName.u8string()); - warnings.emplace_back(error); - } - - return {}; - } - } - - UserSettings::UserSettings() : m_type(UserSettingsType::Default) - { - Json::Value settingsRoot = Json::Value::nullSingleton(); - m_type = UserSettingsType::Default; - - // Settings can be loaded from settings.json or settings.json.backup files. - // 1 - Use settings.json if exists and passes parsing. - // 2 - Use settings.backup.json if settings.json fails to parse. - // 3 - Use default (empty) if both settings files fail to load. - - auto settingsJson = ParseFile(s_SettingFileName, m_warnings); - if (settingsJson.has_value()) - { - m_type = UserSettingsType::Standard; - settingsRoot = settingsJson.value(); - } - - // Settings didn't parse or doesn't exist, try with backup. - if (settingsRoot.isNull()) - { - auto settingsBackupJson = ParseFile(s_SettingBackupFileName, m_warnings); - if (settingsBackupJson.has_value()) - { - m_warnings.emplace_back(SettingsWarnings::LoadedBackupSettings); - m_type = UserSettingsType::Backup; - settingsRoot = settingsBackupJson.value(); - } - } - - // Populate the settings. - auto sourceStr = std::string(Source::GetPropertyName()); - auto source = std::make_unique(settingsRoot[sourceStr]); - auto sourceWarnings = source->Warnings(); - std::move(sourceWarnings.begin(), sourceWarnings.end(), std::inserter(m_warnings, m_warnings.end())); - - auto visualStr = std::string(Visual::GetPropertyName()); - auto visual = std::make_unique(settingsRoot[visualStr]); - auto visualWarnings = visual->Warnings(); - std::move(visualWarnings.begin(), visualWarnings.end(), std::inserter(m_warnings, m_warnings.end())); - - m_source = std::move(source); - m_visual = std::move(visual); - } - - void UserSettings::PrepareToShellExecuteFile() const - { - UserSettingsType userSettingType = GetType(); - - if (userSettingType == UserSettingsType::Default) - { - // Create settings file if it doesn't exist. - if (!std::filesystem::exists(UserSettings::SettingsFilePath())) - { - SetSetting(Type::UserFile, s_SettingFileName, s_SettingEmpty); - } - } - else if (userSettingType == UserSettingsType::Standard) - { - // Settings file was loaded correctly, create backup. - auto from = SettingsFilePath(); - auto to = SettingsBackupFilePath(); - std::filesystem::copy_file(from, to, std::filesystem::copy_options::overwrite_existing); - } - } - - std::filesystem::path UserSettings::SettingsFilePath() - { - return GetPathTo(PathName::UserFileSettings) / s_SettingFileName; - } -} diff --git a/src/AppInstallerCommonCore/UserSettings/Visual.cpp b/src/AppInstallerCommonCore/UserSettings/Visual.cpp deleted file mode 100644 index 7312c120a8..0000000000 --- a/src/AppInstallerCommonCore/UserSettings/Visual.cpp +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "AppInstallerStrings.h" -#include "JsonUtil.h" -#include "SettingValidation.h" -#include "winget\settings\Visual.h" - -#include - -namespace AppInstaller::Settings -{ - using namespace std::string_view_literals; - - // Visual root - static constexpr std::string_view s_VisualKey = "visual"sv; - - // progressBar property possible values - static constexpr std::string_view s_progressBar_Accent = "accent"; - static constexpr std::string_view s_progressBar_Rainbow = "rainbow"; - static constexpr std::string_view s_progressBar_Retro = "retro"; - - Visual::Visual(const Json::Value& node) : m_progressBar(VisualStyle::Accent) - { - std::vector visualSettings = - { - { - "progressBar", - [this](const std::string& key, const Json::Value& node) - { - if (!node.isNull()) - { - auto value = Utility::GetValueString(key, node); - if (value.has_value()) - { - if (Utility::CaseInsensitiveEquals(value.value(), s_progressBar_Accent)) - { - m_progressBar = VisualStyle::Accent; - } - else if (Utility::CaseInsensitiveEquals(value.value(), s_progressBar_Rainbow)) - { - m_progressBar = VisualStyle::Rainbow; - } - else if (Utility::CaseInsensitiveEquals(value.value(), s_progressBar_Retro)) - { - m_progressBar = VisualStyle::Retro; - } - else - { - m_warnings.push_back(GetSettingsMessage(SettingsWarnings::InvalidFieldValue, key, value.value())); - } - } - else - { - m_warnings.emplace_back(GetSettingsMessage(SettingsWarnings::InvalidFieldFormat, key)); - } - } - } - }, - }; - - auto warnings = ValidateAndProcessFields(node, visualSettings); - std::move(warnings.begin(), warnings.end(), std::inserter(m_warnings, m_warnings.end())); - } - - std::string_view Visual::GetPropertyName() - { - return s_VisualKey; - } -} \ No newline at end of file diff --git a/src/AppInstallerRepositoryCore/RepositorySource.cpp b/src/AppInstallerRepositoryCore/RepositorySource.cpp index 8ad05e9ce1..05144fc198 100644 --- a/src/AppInstallerRepositoryCore/RepositorySource.cpp +++ b/src/AppInstallerRepositoryCore/RepositorySource.cpp @@ -9,6 +9,8 @@ namespace AppInstaller::Repository { + using namespace Settings; + using namespace std::chrono_literals; using namespace std::string_view_literals; constexpr std::string_view s_RepositorySettings_UserSources = "usersources"sv; @@ -259,7 +261,7 @@ namespace AppInstaller::Repository return true; } - auto autoUpdateTime = Settings::User().GetSource().GetAutoUpdateTimeInMinutes(); + auto autoUpdateTime = User().Get(); // A value of zero means no auto update, to get update the source run `winget update` if (autoUpdateTime != 0) From 2e9d526c8cbccb66a2b5cd83a2bad480db84e11b Mon Sep 17 00:00:00 2001 From: Ruben Guerrero Samaniego Date: Fri, 19 Jun 2020 21:06:17 -0700 Subject: [PATCH 12/13] Black Magic --- .../Commands/SettingsCommand.cpp | 16 ++++--- src/AppInstallerCLICore/Resources.h | 1 + .../Shared/Strings/en-us/winget.resw | 5 ++- src/AppInstallerCLITests/UserSettings.cpp | 23 ++++++++-- .../Public/winget/UserSettings.h | 19 ++++++--- src/AppInstallerCommonCore/UserSettings.cpp | 42 +++++++++++-------- .../RepositorySource.cpp | 3 +- 7 files changed, 74 insertions(+), 35 deletions(-) diff --git a/src/AppInstallerCLICore/Commands/SettingsCommand.cpp b/src/AppInstallerCLICore/Commands/SettingsCommand.cpp index a568209af6..75310bde36 100644 --- a/src/AppInstallerCLICore/Commands/SettingsCommand.cpp +++ b/src/AppInstallerCLICore/Commands/SettingsCommand.cpp @@ -10,6 +10,7 @@ namespace AppInstaller::CLI { using namespace Utility::literals; using namespace AppInstaller::Settings; + using namespace AppInstaller::Utility; using namespace std::string_view_literals; @@ -36,23 +37,28 @@ namespace AppInstaller::CLI void SettingsCommand::ExecuteInternal(Execution::Context& context) const { // Show warnings only when the setting command is executed. - for (const auto& warning : Settings::User().GetWarnings()) + if (!User().GetWarnings().empty()) { - context.Reporter.Warn() << warning << std::endl; + context.Reporter.Warn() << Resource::String::SettingLoadFailure << std::endl; + for (const auto& warning : User().GetWarnings()) + { + context.Reporter.Warn() << warning << std::endl; + } } User().PrepareToShellExecuteFile(); - std::string filePathUTF8Str = UserSettings::SettingsFilePath().u8string(); + + auto filePathUTF16 = ConvertToUTF16(UserSettings::SettingsFilePath().u8string()); // Some versions of windows will fail if no file extension association exists, other will pop up the dialog // to make the user pick their default. // Kudos to the terminal team for this work around. - HINSTANCE res = ShellExecuteA(nullptr, nullptr, filePathUTF8Str.c_str(), nullptr, nullptr, SW_SHOW); + HINSTANCE res = ShellExecuteW(nullptr, nullptr, filePathUTF16.c_str(), nullptr, nullptr, SW_SHOW); if (static_cast(reinterpret_cast(res)) <= 32) { // User doesn't have file type association. Default to notepad - ShellExecuteA(nullptr, nullptr, "notepad", filePathUTF8Str.c_str(), nullptr, SW_SHOW); + ShellExecuteW(nullptr, nullptr, L"notepad", filePathUTF16.c_str(), nullptr, SW_SHOW); } } } diff --git a/src/AppInstallerCLICore/Resources.h b/src/AppInstallerCLICore/Resources.h index 8ba0662dde..a4fedb61ae 100644 --- a/src/AppInstallerCLICore/Resources.h +++ b/src/AppInstallerCLICore/Resources.h @@ -89,6 +89,7 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(SearchCommandShortDescription); WINGET_DEFINE_RESOURCE_STRINGID(SettingsCommandLongDescription); WINGET_DEFINE_RESOURCE_STRINGID(SettingsCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SettingLoadFailure); WINGET_DEFINE_RESOURCE_STRINGID(SettingsWarnings); WINGET_DEFINE_RESOURCE_STRINGID(ShowCommandLongDescription); WINGET_DEFINE_RESOURCE_STRINGID(ShowCommandShortDescription); diff --git a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw index 61afdb7e7e..cddc07cf40 100644 --- a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw +++ b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw @@ -305,6 +305,9 @@ Find and show basic info of apps + + The following failures were found validating the settings: + Open settings in the default json text editor. If no editor is configured, opens settings in notepad. For available settings see https://aka.ms/winget-settings @@ -312,7 +315,7 @@ Open settings - Unexpected error while loading settings. Please verify your settings by running settings command + Unexpected error while loading settings. Please verify your settings by running the settings command. Shows information on a specific application. diff --git a/src/AppInstallerCLITests/UserSettings.cpp b/src/AppInstallerCLITests/UserSettings.cpp index 9d2dc35692..74d8851f06 100644 --- a/src/AppInstallerCLITests/UserSettings.cpp +++ b/src/AppInstallerCLITests/UserSettings.cpp @@ -10,10 +10,12 @@ #include #include +#include using namespace AppInstaller::Settings; using namespace AppInstaller::Runtime; using namespace std::string_view_literals; +using namespace std::chrono_literals; namespace { @@ -226,20 +228,33 @@ TEST_CASE("SettingAutoUpdateIntervalInMinutes", "[settings]") { DeleteUserSettingsFiles(); + constexpr static auto cinq = 5min; + constexpr static auto cero = 0min; + constexpr static auto threehundred = 300min; + SECTION("Default value") { UserSettingsTest userSettingTest; - REQUIRE(userSettingTest.Get() == 5); + REQUIRE(userSettingTest.Get() == cinq); REQUIRE(userSettingTest.GetWarnings().size() == 0); } SECTION("Valid value") + { + std::string_view json = R"({ "source": { "autoUpdateIntervalInMinutes": 0 } })"; + SetSetting(Type::UserFile, s_settings, json); + UserSettingsTest userSettingTest; + + REQUIRE(userSettingTest.Get() == cero); + REQUIRE(userSettingTest.GetWarnings().size() == 0); + } + SECTION("Valid value 0") { std::string_view json = R"({ "source": { "autoUpdateIntervalInMinutes": 300 } })"; SetSetting(Type::UserFile, s_settings, json); UserSettingsTest userSettingTest; - REQUIRE(userSettingTest.Get() == 300); + REQUIRE(userSettingTest.Get() == threehundred); REQUIRE(userSettingTest.GetWarnings().size() == 0); } SECTION("Invalid type negative integer") @@ -248,7 +263,7 @@ TEST_CASE("SettingAutoUpdateIntervalInMinutes", "[settings]") SetSetting(Type::UserFile, s_settings, json); UserSettingsTest userSettingTest; - REQUIRE(userSettingTest.Get() == 5); + REQUIRE(userSettingTest.Get() == cinq); REQUIRE(userSettingTest.GetWarnings().size() == 1); } SECTION("Invalid type string") @@ -257,7 +272,7 @@ TEST_CASE("SettingAutoUpdateIntervalInMinutes", "[settings]") SetSetting(Type::UserFile, s_settings, json); UserSettingsTest userSettingTest; - REQUIRE(userSettingTest.Get() == 5); + REQUIRE(userSettingTest.Get() == cinq); REQUIRE(userSettingTest.GetWarnings().size() == 1); } } diff --git a/src/AppInstallerCommonCore/Public/winget/UserSettings.h b/src/AppInstallerCommonCore/Public/winget/UserSettings.h index eb2a28f28e..a7356dc7cc 100644 --- a/src/AppInstallerCommonCore/Public/winget/UserSettings.h +++ b/src/AppInstallerCommonCore/Public/winget/UserSettings.h @@ -10,6 +10,9 @@ namespace AppInstaller::Settings { + using namespace std::chrono_literals; + using namespace std::string_view_literals; + // The type of argument. enum class UserSettingsType { @@ -33,6 +36,10 @@ namespace AppInstaller::Settings // Enum of settings. // Must start at 0 to enable direct access to variant in UserSettings. // Max must be last and unused. + // How to add a setting + // 1 - Add to enum. + // 2 - Implement SettingMap specialization + // Validate will be called by ValidateAll without any more changes. enum class Setting : size_t { ProgressBarVisualStyle, @@ -60,21 +67,21 @@ namespace AppInstaller::Settings using value_t = VisualStyle; static constexpr value_t DefaultValue = VisualStyle::Accent; - static constexpr std::string_view Path = ".visual.progressBar"; + static constexpr std::string_view Path = ".visual.progressBar"sv; - static std::optional Validate(json_t value); + static std::optional Validate(const json_t& value); }; template <> struct SettingMapping { using json_t = uint32_t; - using value_t = uint32_t; + using value_t = std::chrono::minutes; - static constexpr value_t DefaultValue = 5; - static constexpr std::string_view Path = ".source.autoUpdateIntervalInMinutes"; + static constexpr std::chrono::minutes DefaultValue = 5min; + static constexpr std::string_view Path = ".source.autoUpdateIntervalInMinutes"sv; - static std::optional Validate(json_t value); + static std::optional Validate(const json_t& value); }; // Used to deduce the SettingVariant type; making a variant that includes std::monostate and all SettingMapping types. diff --git a/src/AppInstallerCommonCore/UserSettings.cpp b/src/AppInstallerCommonCore/UserSettings.cpp index 0d1a87fea7..597766a949 100644 --- a/src/AppInstallerCommonCore/UserSettings.cpp +++ b/src/AppInstallerCommonCore/UserSettings.cpp @@ -2,6 +2,7 @@ // Licensed under the MIT License. #include "pch.h" #include +#include "AppInstallerLanguageUtilities.h" #include "JsonUtil.h" #include "winget/Settings.h" #include "winget/UserSettings.h" @@ -29,7 +30,7 @@ namespace AppInstaller::Settings const char* const Value = " Value: "; const char* const InvalidFieldValue = "Invalid field value."; const char* const InvalidFieldFormat = "Invalid field format."; - constexpr std::string_view LoadedBackupSettings = "Loaded settings from backup file."; + constexpr std::string_view LoadedBackupSettings = "Loaded settings from backup file."sv; } namespace @@ -88,30 +89,30 @@ namespace AppInstaller::Settings return {}; } - template + template void Validate( Json::Value& root, std::map& settings, std::vector& warnings) { // jsoncpp doesn't support std::string_view yet. - auto path = std::string(details::SettingMapping::Path); + auto path = std::string(details::SettingMapping::Path); const Json::Path jsonPath(path); Json::Value result = jsonPath.resolve(root); if (!result.isNull()) { - auto jsonValue = GetValue::json_t>(result); + auto jsonValue = GetValue::json_t>(result); if (jsonValue.has_value()) { - auto validatedValue = details::SettingMapping::Validate(jsonValue.value()); + auto validatedValue = details::SettingMapping::Validate(jsonValue.value()); if (validatedValue.has_value()) { // Finally add it to the map - settings[S0].emplace( - std::forward::value_t>(validatedValue.value())); + settings[S].emplace( + std::forward::value_t>(validatedValue.value())); } else { @@ -123,24 +124,32 @@ namespace AppInstaller::Settings warnings.emplace_back(GetSettingsMessage(SettingsWarnings::InvalidFieldFormat, path)); } } + } - if constexpr (sizeof...(S) != 0) - { - Validate(root, settings, warnings); - } + template + void ValidateAll( + Json::Value& root, + std::map& settings, + std::vector& warnings, + std::index_sequence) + { + // Use folding to call each setting validate function. + // Do not change this expression without understanding the implications to the bind order. + // See: https://en.cppreference.com/w/cpp/language/fold for more details. + (FoldHelper{}, ..., Validate(S)>(root, settings, warnings)); } } namespace details { std::optional::value_t> - SettingMapping::Validate(SettingMapping::json_t value) + SettingMapping::Validate(const SettingMapping::json_t& value) { - return value; + return std::chrono::minutes(value); } std::optional::value_t> - SettingMapping::Validate(SettingMapping::json_t value) + SettingMapping::Validate(const SettingMapping::json_t& value) { // progressBar property possible values static constexpr std::string_view s_progressBar_Accent = "accent"; @@ -194,10 +203,7 @@ namespace AppInstaller::Settings if (!settingsRoot.isNull()) { - Validate< - Setting::AutoUpdateTimeInMinutes, - Setting::ProgressBarVisualStyle - >(settingsRoot, m_settings, m_warnings); + ValidateAll(settingsRoot, m_settings, m_warnings, std::make_index_sequence(Setting::Max)>()); } } diff --git a/src/AppInstallerRepositoryCore/RepositorySource.cpp b/src/AppInstallerRepositoryCore/RepositorySource.cpp index 05144fc198..d899931741 100644 --- a/src/AppInstallerRepositoryCore/RepositorySource.cpp +++ b/src/AppInstallerRepositoryCore/RepositorySource.cpp @@ -261,10 +261,11 @@ namespace AppInstaller::Repository return true; } + constexpr static auto s_ZeroMins = 0min; auto autoUpdateTime = User().Get(); // A value of zero means no auto update, to get update the source run `winget update` - if (autoUpdateTime != 0) + if (autoUpdateTime != s_ZeroMins) { auto autoUpdateTimeMins = std::chrono::minutes(autoUpdateTime); auto timeSinceLastUpdate = std::chrono::system_clock::now() - details.LastUpdateTime; From 100c5c7c91450e8ad67a2acffff3d0cbc6fccd94 Mon Sep 17 00:00:00 2001 From: Ruben Guerrero Samaniego Date: Mon, 22 Jun 2020 12:14:04 -0700 Subject: [PATCH 13/13] PR comments --- src/AppInstallerCLICore/Commands/SettingsCommand.cpp | 4 +--- src/AppInstallerCommonCore/UserSettings.cpp | 2 -- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/AppInstallerCLICore/Commands/SettingsCommand.cpp b/src/AppInstallerCLICore/Commands/SettingsCommand.cpp index 75310bde36..ddf3dc4310 100644 --- a/src/AppInstallerCLICore/Commands/SettingsCommand.cpp +++ b/src/AppInstallerCLICore/Commands/SettingsCommand.cpp @@ -10,7 +10,6 @@ namespace AppInstaller::CLI { using namespace Utility::literals; using namespace AppInstaller::Settings; - using namespace AppInstaller::Utility; using namespace std::string_view_literals; @@ -48,8 +47,7 @@ namespace AppInstaller::CLI User().PrepareToShellExecuteFile(); - - auto filePathUTF16 = ConvertToUTF16(UserSettings::SettingsFilePath().u8string()); + auto filePathUTF16 = UserSettings::SettingsFilePath().wstring(); // Some versions of windows will fail if no file extension association exists, other will pop up the dialog // to make the user pick their default. diff --git a/src/AppInstallerCommonCore/UserSettings.cpp b/src/AppInstallerCommonCore/UserSettings.cpp index 597766a949..6564d02274 100644 --- a/src/AppInstallerCommonCore/UserSettings.cpp +++ b/src/AppInstallerCommonCore/UserSettings.cpp @@ -134,8 +134,6 @@ namespace AppInstaller::Settings std::index_sequence) { // Use folding to call each setting validate function. - // Do not change this expression without understanding the implications to the bind order. - // See: https://en.cppreference.com/w/cpp/language/fold for more details. (FoldHelper{}, ..., Validate(S)>(root, settings, warnings)); } }