From b9cc5a8adecb6590628ad7523990cb1699319473 Mon Sep 17 00:00:00 2001 From: Akinwale Alagbe Date: Tue, 28 Sep 2021 19:33:12 -0700 Subject: [PATCH 1/5] stash --- src/AppInstallerCLICore/ChannelStreams.cpp | 15 ++++++++++----- src/AppInstallerCLICore/ChannelStreams.h | 12 +++++++----- src/AppInstallerCLICore/ExecutionProgress.cpp | 6 +++--- src/AppInstallerCLICore/ExecutionProgress.h | 9 +++++---- src/AppInstallerCLICore/ExecutionReporter.cpp | 14 ++++++++++---- src/AppInstallerCLICore/ExecutionReporter.h | 5 +++-- 6 files changed, 38 insertions(+), 23 deletions(-) diff --git a/src/AppInstallerCLICore/ChannelStreams.cpp b/src/AppInstallerCLICore/ChannelStreams.cpp index 9dc2013a80..fb369fa214 100644 --- a/src/AppInstallerCLICore/ChannelStreams.cpp +++ b/src/AppInstallerCLICore/ChannelStreams.cpp @@ -39,8 +39,11 @@ namespace AppInstaller::CLI::Execution return *this; } - OutputStream::OutputStream(std::ostream& out, bool enabled, bool VTEnabled) : - m_out(out, enabled, VTEnabled) {} + OutputStream::OutputStream(BaseStream& out, bool enabled, bool VTEnabled) : + m_out(out), + m_enabled(enabled), + m_VTEnabled(VTEnabled) + {} void OutputStream::AddFormat(const Sequence& sequence) { @@ -92,12 +95,14 @@ namespace AppInstaller::CLI::Execution return *this; } - NoVTStream::NoVTStream(std::ostream& out, bool enabled) : - m_out(out, enabled, false) {} + NoVTStream::NoVTStream(BaseStream& out, bool enabled) : + m_out(out), + m_enabled(enabled) + {} NoVTStream& NoVTStream::operator<<(std::ostream& (__cdecl* f)(std::ostream&)) { m_out << f; return *this; } -} +} \ No newline at end of file diff --git a/src/AppInstallerCLICore/ChannelStreams.h b/src/AppInstallerCLICore/ChannelStreams.h index d3b7df2315..1472e97ece 100644 --- a/src/AppInstallerCLICore/ChannelStreams.h +++ b/src/AppInstallerCLICore/ChannelStreams.h @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #pragma once -#include "ExecutionProgress.h" #include "Resources.h" #include "VTSupport.h" #include @@ -70,7 +69,7 @@ namespace AppInstaller::CLI::Execution // Holds output formatting information. struct OutputStream { - OutputStream(std::ostream& out, bool enabled, bool VTEnabled); + OutputStream(BaseStream& out, bool enabled, bool VTEnabled); // Adds a format to the current value. void AddFormat(const VirtualTerminal::Sequence& sequence); @@ -106,7 +105,9 @@ namespace AppInstaller::CLI::Execution // Applies the format for the stream. void ApplyFormat(); - BaseStream m_out; + BaseStream& m_out; + bool m_enabled; + bool m_VTEnabled; size_t m_applyFormatAtOne = 1; VirtualTerminal::ConstructedSequence m_format; }; @@ -114,7 +115,7 @@ namespace AppInstaller::CLI::Execution // Does not allow VT at all. struct NoVTStream { - NoVTStream(std::ostream& out, bool enabled); + NoVTStream(BaseStream& out, bool enabled); template NoVTStream& operator<<(const T& t) @@ -128,6 +129,7 @@ namespace AppInstaller::CLI::Execution NoVTStream& operator<<(const VirtualTerminal::ConstructedSequence& sequence) = delete; private: - BaseStream m_out; + bool m_enabled; + BaseStream& m_out; }; } diff --git a/src/AppInstallerCLICore/ExecutionProgress.cpp b/src/AppInstallerCLICore/ExecutionProgress.cpp index f2db300b6f..411597cd0a 100644 --- a/src/AppInstallerCLICore/ExecutionProgress.cpp +++ b/src/AppInstallerCLICore/ExecutionProgress.cpp @@ -41,7 +41,7 @@ namespace AppInstaller::CLI::Execution return s_bytesFormatData[ARRAYSIZE(s_bytesFormatData) - 1]; } - void OutputBytes(std::ostream& out, uint64_t byteCount) + void OutputBytes(BaseStream& out, uint64_t byteCount) { const BytesFormatData& bfd = GetFormatForSize(byteCount); @@ -76,7 +76,7 @@ namespace AppInstaller::CLI::Execution out << ' ' << bfd.Name; } - void SetColor(std::ostream& out, const TextFormat::Color& color, bool enabled) + void SetColor(BaseStream& out, const TextFormat::Color& color, bool enabled) { if (enabled) { @@ -95,7 +95,7 @@ namespace AppInstaller::CLI::Execution } } - void SetRainbowColor(std::ostream& out, size_t i, size_t max, bool enabled) + void SetRainbowColor(BaseStream& out, size_t i, size_t max, bool enabled) { TextFormat::Color rainbow[] = { diff --git a/src/AppInstallerCLICore/ExecutionProgress.h b/src/AppInstallerCLICore/ExecutionProgress.h index 9642ee258c..a3c46dd7f4 100644 --- a/src/AppInstallerCLICore/ExecutionProgress.h +++ b/src/AppInstallerCLICore/ExecutionProgress.h @@ -4,6 +4,7 @@ #include "VTSupport.h" #include #include +#include #include @@ -21,13 +22,13 @@ namespace AppInstaller::CLI::Execution // Shared functionality for progress visualizers. struct ProgressVisualizerBase { - ProgressVisualizerBase(std::ostream& stream, bool enableVT) : + ProgressVisualizerBase(BaseStream& stream, bool enableVT) : m_out(stream), m_enableVT(enableVT) {} void SetStyle(AppInstaller::Settings::VisualStyle style) { m_style = style; } protected: - std::ostream& m_out; + BaseStream& m_out; Settings::VisualStyle m_style = AppInstaller::Settings::VisualStyle::Accent; bool UseVT() const { return m_enableVT && m_style != AppInstaller::Settings::VisualStyle::NoVT; } @@ -43,7 +44,7 @@ namespace AppInstaller::CLI::Execution // Displays an indefinite spinner. struct IndefiniteSpinner : public details::ProgressVisualizerBase { - IndefiniteSpinner(std::ostream& stream, bool enableVT) : + IndefiniteSpinner(BaseStream& stream, bool enableVT) : details::ProgressVisualizerBase(stream, enableVT) {} void ShowSpinner(); @@ -62,7 +63,7 @@ namespace AppInstaller::CLI::Execution class ProgressBar : public details::ProgressVisualizerBase { public: - ProgressBar(std::ostream& stream, bool enableVT) : + ProgressBar(BaseStream& stream, bool enableVT) : details::ProgressVisualizerBase(stream, enableVT) {} void ShowProgress(uint64_t current, uint64_t maximum, ProgressType type); diff --git a/src/AppInstallerCLICore/ExecutionReporter.cpp b/src/AppInstallerCLICore/ExecutionReporter.cpp index cdf7e46a31..a15d627665 100644 --- a/src/AppInstallerCLICore/ExecutionReporter.cpp +++ b/src/AppInstallerCLICore/ExecutionReporter.cpp @@ -19,10 +19,16 @@ namespace AppInstaller::CLI::Execution const Sequence& PromptEmphasis = TextFormat::Foreground::Bright; Reporter::Reporter(std::ostream& outStream, std::istream& inStream) : + Reporter(std::make_shared(outStream, true, IsVTEnabled()), inStream) + { + SetProgressSink(this); + } + + Reporter::Reporter(std::shared_ptr outStream, std::istream& inStream) : m_out(outStream), m_in(inStream), - m_progressBar(std::in_place, outStream, IsVTEnabled()), - m_spinner(std::in_place, outStream, IsVTEnabled()) + m_progressBar(std::in_place, *m_out, IsVTEnabled()), + m_spinner(std::in_place, *m_out, IsVTEnabled()) { SetProgressSink(this); } @@ -70,7 +76,7 @@ namespace AppInstaller::CLI::Execution OutputStream Reporter::GetBasicOutputStream() { - return { m_out, m_channel == Channel::Output, IsVTEnabled() }; + return {*m_out, m_channel == Channel::Output, IsVTEnabled() }; } void Reporter::SetChannel(Channel channel) @@ -213,4 +219,4 @@ namespace AppInstaller::CLI::Execution { return m_isVTEnabled && ConsoleModeRestore::Instance().IsVTEnabled(); } -} +} \ No newline at end of file diff --git a/src/AppInstallerCLICore/ExecutionReporter.h b/src/AppInstallerCLICore/ExecutionReporter.h index f5e257f034..5254a42262 100644 --- a/src/AppInstallerCLICore/ExecutionReporter.h +++ b/src/AppInstallerCLICore/ExecutionReporter.h @@ -81,7 +81,7 @@ namespace AppInstaller::CLI::Execution OutputStream Error() { return GetOutputStream(Level::Error); } // Get a stream for outputting completion words. - NoVTStream Completion() { return NoVTStream(m_out, m_channel == Channel::Completion); } + NoVTStream Completion() { return NoVTStream(*m_out, m_channel == Channel::Completion); } // Gets a stream for output of the given level. OutputStream GetOutputStream(Level level); @@ -137,6 +137,7 @@ namespace AppInstaller::CLI::Execution } private: + Reporter(std::shared_ptr outStream, std::istream& inStream); // Gets whether VT is enabled for this reporter. bool IsVTEnabled() const; @@ -144,7 +145,7 @@ namespace AppInstaller::CLI::Execution OutputStream GetBasicOutputStream(); Channel m_channel = Channel::Output; - std::ostream& m_out; + std::shared_ptr m_out; std::istream& m_in; bool m_isVTEnabled = true; std::optional m_style; From f741fd7c5a558326144ef05245b85a7515b9efcb Mon Sep 17 00:00:00 2001 From: Akinwale Alagbe Date: Wed, 29 Sep 2021 14:27:47 -0700 Subject: [PATCH 2/5] color contamination fix --- src/AppInstallerCLICore/ChannelStreams.cpp | 55 +++++++++++-------- src/AppInstallerCLICore/ChannelStreams.h | 37 ++++--------- src/AppInstallerCLICore/ExecutionContext.cpp | 1 + src/AppInstallerCLICore/ExecutionReporter.cpp | 9 ++- src/AppInstallerCLICore/ExecutionReporter.h | 4 +- .../Workflows/CompletionFlow.cpp | 2 +- src/AppInstallerCommonCore/MsixInfo.cpp | 1 + 7 files changed, 57 insertions(+), 52 deletions(-) diff --git a/src/AppInstallerCLICore/ChannelStreams.cpp b/src/AppInstallerCLICore/ChannelStreams.cpp index fb369fa214..eeae0d5150 100644 --- a/src/AppInstallerCLICore/ChannelStreams.cpp +++ b/src/AppInstallerCLICore/ChannelStreams.cpp @@ -39,6 +39,15 @@ namespace AppInstaller::CLI::Execution return *this; } + void BaseStream::Close(bool withDefaultVTSequence) + { + m_enabled = false; + if (withDefaultVTSequence) + { + Write(TextFormat::Default, true); + } + } + OutputStream::OutputStream(BaseStream& out, bool enabled, bool VTEnabled) : m_out(out), m_enabled(enabled), @@ -64,27 +73,40 @@ namespace AppInstaller::CLI::Execution OutputStream& OutputStream::operator<<(std::ostream& (__cdecl* f)(std::ostream&)) { - m_out << f; + if (m_enabled) + { + m_out << f; + } + return *this; } OutputStream& OutputStream::operator<<(const Sequence& sequence) { - m_out << sequence; - // An incoming sequence will be valid for 1 "standard" output after this one. - // We set this to 2 to make that happen, because when it is 1, we will output - // the format for the current OutputStream. - m_applyFormatAtOne = 2; + if (m_enabled && m_VTEnabled) + { + m_out << sequence; + + // An incoming sequence will be valid for 1 "standard" output after this one. + // We set this to 2 to make that happen, because when it is 1, we will output + // the format for the current OutputStream. + m_applyFormatAtOne = 2; + } + return *this; } OutputStream& OutputStream::operator<<(const ConstructedSequence& sequence) { - m_out << sequence; - // An incoming sequence will be valid for 1 "standard" output after this one. - // We set this to 2 to make that happen, because when it is 1, we will output - // the format for the current OutputStream. - m_applyFormatAtOne = 2; + if (m_enabled && m_VTEnabled) + { + m_out << sequence; + // An incoming sequence will be valid for 1 "standard" output after this one. + // We set this to 2 to make that happen, because when it is 1, we will output + // the format for the current OutputStream. + m_applyFormatAtOne = 2; + } + return *this; } @@ -94,15 +116,4 @@ namespace AppInstaller::CLI::Execution m_out << path.u8string(); return *this; } - - NoVTStream::NoVTStream(BaseStream& out, bool enabled) : - m_out(out), - m_enabled(enabled) - {} - - NoVTStream& NoVTStream::operator<<(std::ostream& (__cdecl* f)(std::ostream&)) - { - m_out << f; - return *this; - } } \ No newline at end of file diff --git a/src/AppInstallerCLICore/ChannelStreams.h b/src/AppInstallerCLICore/ChannelStreams.h index 1472e97ece..195d0191d4 100644 --- a/src/AppInstallerCLICore/ChannelStreams.h +++ b/src/AppInstallerCLICore/ChannelStreams.h @@ -49,10 +49,7 @@ namespace AppInstaller::CLI::Execution template BaseStream& operator<<(const T& t) { - if (m_enabled) - { - m_out << t; - } + Write(t, false); return *this; } @@ -60,7 +57,18 @@ namespace AppInstaller::CLI::Execution BaseStream& operator<<(const VirtualTerminal::Sequence& sequence); BaseStream& operator<<(const VirtualTerminal::ConstructedSequence& sequence); + void Close(bool withDefaultVTSequence); + private: + template + void Write(const T& t, bool bypass) + { + if (bypass || m_enabled) + { + m_out << t; + } + }; + std::ostream& m_out; bool m_enabled; bool m_VTEnabled; @@ -111,25 +119,4 @@ namespace AppInstaller::CLI::Execution size_t m_applyFormatAtOne = 1; VirtualTerminal::ConstructedSequence m_format; }; - - // Does not allow VT at all. - struct NoVTStream - { - NoVTStream(BaseStream& out, bool enabled); - - template - NoVTStream& operator<<(const T& t) - { - m_out << t; - return *this; - } - - NoVTStream& operator<<(std::ostream& (__cdecl* f)(std::ostream&)); - NoVTStream& operator<<(const VirtualTerminal::Sequence& sequence) = delete; - NoVTStream& operator<<(const VirtualTerminal::ConstructedSequence& sequence) = delete; - - private: - bool m_enabled; - BaseStream& m_out; - }; } diff --git a/src/AppInstallerCLICore/ExecutionContext.cpp b/src/AppInstallerCLICore/ExecutionContext.cpp index 68878109c2..bfe3445748 100644 --- a/src/AppInstallerCLICore/ExecutionContext.cpp +++ b/src/AppInstallerCLICore/ExecutionContext.cpp @@ -169,6 +169,7 @@ namespace AppInstaller::CLI::Execution // Unless we want to spin a separate thread for all work, we have to just exit here. if (m_CtrlSignalCount >= 2) { + Reporter.CloseOutputStream(); Logging::Telemetry().LogCommandTermination(hr, file, line); std::exit(hr); } diff --git a/src/AppInstallerCLICore/ExecutionReporter.cpp b/src/AppInstallerCLICore/ExecutionReporter.cpp index a15d627665..fa538fc444 100644 --- a/src/AppInstallerCLICore/ExecutionReporter.cpp +++ b/src/AppInstallerCLICore/ExecutionReporter.cpp @@ -35,9 +35,7 @@ namespace AppInstaller::CLI::Execution Reporter::~Reporter() { - // The goal of this is to return output to its previous state. - // For now, we assume this means "default". - GetBasicOutputStream() << TextFormat::Default; + this->CloseOutputStream(); } Reporter::Reporter(const Reporter& other, clone_t) : @@ -219,4 +217,9 @@ namespace AppInstaller::CLI::Execution { return m_isVTEnabled && ConsoleModeRestore::Instance().IsVTEnabled(); } + + void Reporter::CloseOutputStream() + { + m_out->Close(m_channel == Channel::Output); + } } \ No newline at end of file diff --git a/src/AppInstallerCLICore/ExecutionReporter.h b/src/AppInstallerCLICore/ExecutionReporter.h index 5254a42262..667eec2291 100644 --- a/src/AppInstallerCLICore/ExecutionReporter.h +++ b/src/AppInstallerCLICore/ExecutionReporter.h @@ -81,7 +81,7 @@ namespace AppInstaller::CLI::Execution OutputStream Error() { return GetOutputStream(Level::Error); } // Get a stream for outputting completion words. - NoVTStream Completion() { return NoVTStream(*m_out, m_channel == Channel::Completion); } + OutputStream Completion() { return OutputStream(*m_out, m_channel == Channel::Completion, false); } // Gets a stream for output of the given level. OutputStream GetOutputStream(Level level); @@ -131,6 +131,8 @@ namespace AppInstaller::CLI::Execution // Cancels the in progress task. void CancelInProgressTask(bool force); + void CloseOutputStream(); + void SetProgressSink(IProgressSink* sink) { m_progressSink = sink; diff --git a/src/AppInstallerCLICore/Workflows/CompletionFlow.cpp b/src/AppInstallerCLICore/Workflows/CompletionFlow.cpp index c3dfed1518..b2cad09ec0 100644 --- a/src/AppInstallerCLICore/Workflows/CompletionFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/CompletionFlow.cpp @@ -11,7 +11,7 @@ namespace AppInstaller::CLI::Workflow namespace { // Outputs the completion string, wrapping it in quotes if needed. - void OutputCompletionString(Execution::NoVTStream& stream, std::string_view value) + void OutputCompletionString(Execution::OutputStream& stream, std::string_view value) { if (value.find_first_of(' ') != std::string_view::npos) { diff --git a/src/AppInstallerCommonCore/MsixInfo.cpp b/src/AppInstallerCommonCore/MsixInfo.cpp index 0f99ae75dc..f6cbbfe484 100644 --- a/src/AppInstallerCommonCore/MsixInfo.cpp +++ b/src/AppInstallerCommonCore/MsixInfo.cpp @@ -65,6 +65,7 @@ namespace AppInstaller::Msix file.write(buffer.get(), bytesRead); totalBytesRead += bytesRead; + Sleep(20000); progress.OnProgress(totalBytesRead, expectedSize, ProgressType::Bytes); } else From 123257dfea7a62bbd929af6f1ea59e6c7398bd2f Mon Sep 17 00:00:00 2001 From: Akinwale Alagbe Date: Wed, 29 Sep 2021 16:27:39 -0700 Subject: [PATCH 3/5] using mvt updated --- src/AppInstallerCLICore/ChannelStreams.cpp | 6 ++++-- src/AppInstallerCLICore/ChannelStreams.h | 5 +++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/AppInstallerCLICore/ChannelStreams.cpp b/src/AppInstallerCLICore/ChannelStreams.cpp index eeae0d5150..6a2200433d 100644 --- a/src/AppInstallerCLICore/ChannelStreams.cpp +++ b/src/AppInstallerCLICore/ChannelStreams.cpp @@ -26,6 +26,7 @@ namespace AppInstaller::CLI::Execution if (m_enabled && m_VTEnabled) { m_out << sequence; + m_VTUpdated = true; } return *this; } @@ -35,14 +36,15 @@ namespace AppInstaller::CLI::Execution if (m_enabled && m_VTEnabled) { m_out << sequence; + m_VTUpdated = true; } return *this; } - void BaseStream::Close(bool withDefaultVTSequence) + void BaseStream::Close() { m_enabled = false; - if (withDefaultVTSequence) + if (m_VTUpdated) { Write(TextFormat::Default, true); } diff --git a/src/AppInstallerCLICore/ChannelStreams.h b/src/AppInstallerCLICore/ChannelStreams.h index 195d0191d4..59ddf94a7d 100644 --- a/src/AppInstallerCLICore/ChannelStreams.h +++ b/src/AppInstallerCLICore/ChannelStreams.h @@ -57,7 +57,7 @@ namespace AppInstaller::CLI::Execution BaseStream& operator<<(const VirtualTerminal::Sequence& sequence); BaseStream& operator<<(const VirtualTerminal::ConstructedSequence& sequence); - void Close(bool withDefaultVTSequence); + void Close(); private: template @@ -70,7 +70,8 @@ namespace AppInstaller::CLI::Execution }; std::ostream& m_out; - bool m_enabled; + std::atomic_bool m_enabled; + bool m_VTUpdated; bool m_VTEnabled; }; From 4b612fb603cf0bb736e63f6618df3c6f5ecb0e95 Mon Sep 17 00:00:00 2001 From: Akinwale Alagbe Date: Wed, 29 Sep 2021 22:46:31 -0700 Subject: [PATCH 4/5] Removed sleep --- src/AppInstallerCLICore/ExecutionReporter.cpp | 2 +- src/AppInstallerCommonCore/MsixInfo.cpp | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/AppInstallerCLICore/ExecutionReporter.cpp b/src/AppInstallerCLICore/ExecutionReporter.cpp index fa538fc444..8b4b6833d7 100644 --- a/src/AppInstallerCLICore/ExecutionReporter.cpp +++ b/src/AppInstallerCLICore/ExecutionReporter.cpp @@ -220,6 +220,6 @@ namespace AppInstaller::CLI::Execution void Reporter::CloseOutputStream() { - m_out->Close(m_channel == Channel::Output); + m_out->Close(); } } \ No newline at end of file diff --git a/src/AppInstallerCommonCore/MsixInfo.cpp b/src/AppInstallerCommonCore/MsixInfo.cpp index f6cbbfe484..0f99ae75dc 100644 --- a/src/AppInstallerCommonCore/MsixInfo.cpp +++ b/src/AppInstallerCommonCore/MsixInfo.cpp @@ -65,7 +65,6 @@ namespace AppInstaller::Msix file.write(buffer.get(), bytesRead); totalBytesRead += bytesRead; - Sleep(20000); progress.OnProgress(totalBytesRead, expectedSize, ProgressType::Bytes); } else From 2136dd82fecf6a7cbd7b57a10d8e6f3941cec2df Mon Sep 17 00:00:00 2001 From: Akinwale Alagbe Date: Tue, 5 Oct 2021 11:38:30 -0700 Subject: [PATCH 5/5] Addressed PR comments --- src/AppInstallerCLICore/ChannelStreams.cpp | 4 ++-- src/AppInstallerCLICore/ChannelStreams.h | 7 +++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/AppInstallerCLICore/ChannelStreams.cpp b/src/AppInstallerCLICore/ChannelStreams.cpp index 6a2200433d..6b3852de50 100644 --- a/src/AppInstallerCLICore/ChannelStreams.cpp +++ b/src/AppInstallerCLICore/ChannelStreams.cpp @@ -25,8 +25,8 @@ namespace AppInstaller::CLI::Execution { if (m_enabled && m_VTEnabled) { - m_out << sequence; m_VTUpdated = true; + m_out << sequence; } return *this; } @@ -35,8 +35,8 @@ namespace AppInstaller::CLI::Execution { if (m_enabled && m_VTEnabled) { - m_out << sequence; m_VTUpdated = true; + m_out << sequence; } return *this; } diff --git a/src/AppInstallerCLICore/ChannelStreams.h b/src/AppInstallerCLICore/ChannelStreams.h index 59ddf94a7d..18dd0e2fb7 100644 --- a/src/AppInstallerCLICore/ChannelStreams.h +++ b/src/AppInstallerCLICore/ChannelStreams.h @@ -71,7 +71,7 @@ namespace AppInstaller::CLI::Execution std::ostream& m_out; std::atomic_bool m_enabled; - bool m_VTUpdated; + std::atomic_bool m_VTUpdated; bool m_VTEnabled; }; @@ -100,7 +100,10 @@ namespace AppInstaller::CLI::Execution // informs the output that there is no localized version to use. // TODO: Convert the rest of the code base and uncomment to enforce localization. //static_assert(details::IsApprovedForOutput>::value, "This type may not be localized, see comment for more information"); - ApplyFormat(); + if (m_VTEnabled) + { + ApplyFormat(); + } m_out << t; return *this; }