Browse Source

Merge topic 'stdio-streams'

d6a1ff59f1 StdIo: Provide metadata about stdin, stdout, stderr streams

Acked-by: Kitware Robot <[email protected]>
Merge-request: !10759
Brad King 5 months ago
parent
commit
4802077fb9
6 changed files with 323 additions and 1 deletions
  1. 2 0
      Source/CMakeLists.txt
  2. 22 0
      Source/cmStdIoInit.cxx
  3. 161 0
      Source/cmStdIoStream.cxx
  4. 103 0
      Source/cmStdIoStream.h
  5. 34 1
      Tests/CMakeLib/testStdIo.cxx
  6. 1 0
      bootstrap

+ 2 - 0
Source/CMakeLists.txt

@@ -470,6 +470,8 @@ add_library(
   cmStateTypes.h
   cmStdIoInit.h
   cmStdIoInit.cxx
+  cmStdIoStream.h
+  cmStdIoStream.cxx
   cmStringAlgorithms.cxx
   cmStringAlgorithms.h
   cmSyntheticTargetCache.h

+ 22 - 0
Source/cmStdIoInit.cxx

@@ -5,6 +5,7 @@
 #include <cerrno>
 #include <cstdio>
 #include <cstdlib>
+#include <iostream>
 
 #include <fcntl.h>
 
@@ -18,6 +19,8 @@
 #  include <unistd.h>
 #endif
 
+#include "cmStdIoStream.h"
+
 namespace cm {
 namespace StdIo {
 
@@ -82,7 +85,11 @@ struct InitStdPipes
 class Globals
 {
 public:
+  std::ios::Init InitIos;
   InitStdPipes InitPipes;
+  IStream StdIn{ std::cin, stdin };
+  OStream StdOut{ std::cout, stdout };
+  OStream StdErr{ std::cerr, stderr };
 
   static Globals& Get();
 };
@@ -98,5 +105,20 @@ Init::Init()
   Globals::Get();
 }
 
+IStream& In()
+{
+  return Globals::Get().StdIn;
+}
+
+OStream& Out()
+{
+  return Globals::Get().StdOut;
+}
+
+OStream& Err()
+{
+  return Globals::Get().StdErr;
+}
+
 }
 }

+ 161 - 0
Source/cmStdIoStream.cxx

@@ -0,0 +1,161 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file LICENSE.rst or https://cmake.org/licensing for details.  */
+#include "cmStdIoStream.h"
+
+#include <algorithm>
+#include <array>
+#include <cstdio>
+#include <istream> // IWYU pragma: keep
+#include <ostream> // IWYU pragma: keep
+
+#ifdef _WIN32
+#  include <windows.h>
+
+#  include <io.h> // for _get_osfhandle
+#else
+#  include <string>
+
+#  include <cm/optional>
+#  include <cm/string_view>
+#  include <cmext/string_view>
+
+#  include <unistd.h>
+#endif
+
+#include "cm_fileno.hxx"
+
+#ifndef _WIN32
+#  include "cmSystemTools.h"
+#endif
+
+namespace cm {
+namespace StdIo {
+
+namespace {
+
+#ifndef _WIN32
+// List of known `TERM` names that support VT100 escape sequences.
+// Order by `LC_COLLATE=C sort` to search using `std::lower_bound`.
+std::array<cm::string_view, 56> const kVT100Names{ {
+  "Eterm"_s,
+  "alacritty"_s,
+  "alacritty-direct"_s,
+  "ansi"_s,
+  "color-xterm"_s,
+  "con132x25"_s,
+  "con132x30"_s,
+  "con132x43"_s,
+  "con132x60"_s,
+  "con80x25"_s,
+  "con80x28"_s,
+  "con80x30"_s,
+  "con80x43"_s,
+  "con80x50"_s,
+  "con80x60"_s,
+  "cons25"_s,
+  "console"_s,
+  "cygwin"_s,
+  "dtterm"_s,
+  "eterm-color"_s,
+  "gnome"_s,
+  "gnome-256color"_s,
+  "konsole"_s,
+  "konsole-256color"_s,
+  "kterm"_s,
+  "linux"_s,
+  "linux-c"_s,
+  "mach-color"_s,
+  "mlterm"_s,
+  "msys"_s,
+  "putty"_s,
+  "putty-256color"_s,
+  "rxvt"_s,
+  "rxvt-256color"_s,
+  "rxvt-cygwin"_s,
+  "rxvt-cygwin-native"_s,
+  "rxvt-unicode"_s,
+  "rxvt-unicode-256color"_s,
+  "screen"_s,
+  "screen-256color"_s,
+  "screen-256color-bce"_s,
+  "screen-bce"_s,
+  "screen-w"_s,
+  "screen.linux"_s,
+  "st-256color"_s,
+  "tmux"_s,
+  "tmux-256color"_s,
+  "vt100"_s,
+  "xterm"_s,
+  "xterm-16color"_s,
+  "xterm-256color"_s,
+  "xterm-88color"_s,
+  "xterm-color"_s,
+  "xterm-debian"_s,
+  "xterm-kitty"_s,
+  "xterm-termite"_s,
+} };
+
+bool TermIsVT100()
+{
+  if (cm::optional<std::string> term = cmSystemTools::GetEnvVar("TERM")) {
+    // NOLINTNEXTLINE(readability-qualified-auto)
+    auto i = std::lower_bound(kVT100Names.begin(), kVT100Names.end(), *term);
+    if (i != kVT100Names.end() && *i == *term) {
+      return true;
+    }
+  }
+  return false;
+}
+#endif
+
+} // anonymous namespace
+
+Stream::Stream(std::ios& s, FILE* file, Direction direction)
+  : IOS_(s)
+  , FD_(cm_fileno(file))
+{
+#ifdef _WIN32
+  DWORD mode;
+  auto h = reinterpret_cast<HANDLE>(_get_osfhandle(this->FD_));
+  if (GetConsoleMode(h, &mode)) {
+    this->Console_ = h;
+    DWORD vtMode = mode |
+      (direction == Direction::In ? ENABLE_VIRTUAL_TERMINAL_INPUT
+                                  : ENABLE_VIRTUAL_TERMINAL_PROCESSING);
+    if (SetConsoleMode(this->Console_, vtMode)) {
+      this->Kind_ = TermKind::VT100;
+    } else {
+      SetConsoleMode(this->Console_, mode);
+      this->Kind_ = TermKind::Console;
+    }
+  }
+#else
+  static_cast<void>(direction);
+  if (isatty(this->FD_) && TermIsVT100()) {
+    this->Kind_ = TermKind::VT100;
+  }
+#endif
+}
+
+IStream::IStream(std::istream& is, FILE* file)
+  : Stream(is, file, Direction::In)
+{
+}
+
+std::istream& IStream::IOS() const
+{
+  return dynamic_cast<std::istream&>(this->Stream::IOS());
+}
+
+OStream::OStream(std::ostream& os, FILE* file)
+  : Stream(os, file, Direction::Out)
+{
+}
+
+std::ostream& OStream::IOS() const
+{
+  return dynamic_cast<std::ostream&>(this->Stream::IOS());
+}
+
+}
+}

+ 103 - 0
Source/cmStdIoStream.h

@@ -0,0 +1,103 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file LICENSE.rst or https://cmake.org/licensing for details.  */
+#pragma once
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include <cstdio>
+#include <iosfwd>
+
+namespace cm {
+namespace StdIo {
+
+/**
+ * Identify the kind of terminal to which a stream is attached, if any.
+ */
+enum class TermKind
+{
+  /** Not an interactive terminal.  */
+  None,
+  /** A VT100 terminal.  */
+  VT100,
+#ifdef _WIN32
+  /** A Windows Console that does not support VT100 sequences.  */
+  Console,
+#endif
+};
+
+/**
+ * Represent stdin, stdout, or stderr stream metadata.
+ */
+class Stream
+{
+public:
+  /** The kind of terminal to which the stream is attached, if any.  */
+  TermKind Kind() const { return this->Kind_; }
+
+  /** The underlying C++ stream.  */
+  std::ios& IOS() const { return this->IOS_; }
+
+  /** The underlying file descriptor.  */
+  int FD() const { return this->FD_; }
+
+#ifdef _WIN32
+  /** The underlying HANDLE of an attached Windows Console, if any.  */
+  void* Console() const { return this->Console_; }
+#endif
+
+protected:
+  enum class Direction
+  {
+    In,
+    Out,
+  };
+
+  Stream(std::ios& s, FILE* file, Direction direction);
+
+private:
+  std::ios& IOS_;
+  int FD_ = -1;
+  TermKind Kind_ = TermKind::None;
+
+#ifdef _WIN32
+  void* Console_ = nullptr;
+#endif
+};
+
+/**
+ * Represent stdin metadata.
+ */
+class IStream : public Stream
+{
+  friend class Globals;
+  IStream(std::istream& is, FILE* file);
+
+public:
+  /** The underlying C++ stream.  */
+  std::istream& IOS() const;
+};
+
+/**
+ * Represent stdout or stderr metadata.
+ */
+class OStream : public Stream
+{
+  friend class Globals;
+  OStream(std::ostream& os, FILE* file);
+
+public:
+  /** The underlying C++ stream.  */
+  std::ostream& IOS() const;
+};
+
+/** Metadata for stdin.  */
+IStream& In();
+
+/** Metadata for stdout.  */
+OStream& Out();
+
+/** Metadata for stderr.  */
+OStream& Err();
+
+}
+}

+ 34 - 1
Tests/CMakeLib/testStdIo.cxx

@@ -1,15 +1,48 @@
 /* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
    file LICENSE.rst or https://cmake.org/licensing for details.  */
 
+#include <cm/string_view>
+#include <cmext/string_view>
+
 #include "cmStdIoInit.h"
+#include "cmStdIoStream.h"
 
 #include "testCommon.h"
 
 namespace {
+
+void printTermKind(cm::string_view t, cm::StdIo::Stream& s)
+{
+  switch (s.Kind()) {
+    case cm::StdIo::TermKind::None:
+      std::cout << "  " << t << " is not a terminal.\n";
+      break;
+    case cm::StdIo::TermKind::VT100:
+      std::cout << "  " << t << " is a VT100 terminal.\n";
+      break;
+#ifdef _WIN32
+    case cm::StdIo::TermKind::Console:
+      std::cout << "  " << t << " is a Windows Console.\n";
+      break;
+#endif
+  };
+}
+
+bool testStream()
+{
+  std::cout << "testStream()\n";
+  printTermKind("stdin"_s, cm::StdIo::In());
+  printTermKind("stdout"_s, cm::StdIo::Out());
+  printTermKind("stderr"_s, cm::StdIo::Err());
+  return true;
+}
+
 }
 
 int testStdIo(int /*unused*/, char* /*unused*/[])
 {
   cm::StdIo::Init();
-  return runTests({});
+  return runTests({
+    testStream,
+  });
 }

+ 1 - 0
bootstrap

@@ -490,6 +490,7 @@ CMAKE_CXX_SOURCES="\
   cmStateDirectory \
   cmStateSnapshot \
   cmStdIoInit \
+  cmStdIoStream \
   cmString \
   cmStringAlgorithms \
   cmStringReplaceHelper \