Add commands::CommandSwitches class

This class is used to model the command-line switches
that appear in various GN commands, as a set of boolean,
enum or string values, which can be swapped as a whole
globally (which might be useful when implementing a REPL
in GN in the future, since each command evaluation may
need a different set of switches then).

A future CL will modify the command implementation functions
to use the methods on this class, instead of peeking directly
into the current process' base::CommandLine value.

Bug: None
Change-Id: I240778abf9a5e4fba4760f5ca81eb84cebf2c1c8
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/12862
Reviewed-by: Brett Wilson <brettw@chromium.org>
Reviewed-by: David Turner <digit@google.com>
Commit-Queue: David Turner <digit@google.com>
diff --git a/src/gn/commands.cc b/src/gn/commands.cc
index 24a16b1..a5f2ca9 100644
--- a/src/gn/commands.cc
+++ b/src/gn/commands.cc
@@ -19,6 +19,7 @@
 #include "gn/label_pattern.h"
 #include "gn/setup.h"
 #include "gn/standard_out.h"
+#include "gn/switches.h"
 #include "gn/target.h"
 #include "util/build_config.h"
 
@@ -127,45 +128,12 @@
   return true;
 }
 
-enum TargetPrintingMode {
-  TARGET_PRINT_BUILDFILE,
-  TARGET_PRINT_LABEL,
-  TARGET_PRINT_OUTPUT,
-};
-
 // Retrieves the target printing mode based on the command line flags for the
 // current process. Returns true on success. On error, prints a message to the
 // console and returns false.
-bool GetTargetPrintingMode(TargetPrintingMode* mode) {
-  std::string switch_key = "as";
-  const base::CommandLine* cmdline = base::CommandLine::ForCurrentProcess();
-
-  if (!cmdline->HasSwitch(switch_key)) {
-    // Default to labels.
-    *mode = TARGET_PRINT_LABEL;
-    return true;
-  }
-
-  std::string value = cmdline->GetSwitchValueASCII(switch_key);
-  if (value == "buildfile") {
-    *mode = TARGET_PRINT_BUILDFILE;
-    return true;
-  }
-  if (value == "label") {
-    *mode = TARGET_PRINT_LABEL;
-    return true;
-  }
-  if (value == "output") {
-    *mode = TARGET_PRINT_OUTPUT;
-    return true;
-  }
-
-  Err(Location(), "Invalid value for \"--as\".",
-      "I was expecting \"buildfile\", \"label\", or \"output\" but you\n"
-      "said \"" +
-          value + "\".")
-      .PrintToStdout();
-  return false;
+bool GetTargetPrintingMode(CommandSwitches::TargetPrintMode* mode) {
+  *mode = CommandSwitches::Get().target_print_mode();
+  return true;
 }
 
 // Returns the target type filter based on the command line flags for the
@@ -176,72 +144,20 @@
 // will never be returned. Code applying the filters should apply Target::ACTION
 // to both ACTION and ACTION_FOREACH.
 bool GetTargetTypeFilter(Target::OutputType* type) {
-  std::string switch_key = "type";
-  const base::CommandLine* cmdline = base::CommandLine::ForCurrentProcess();
-
-  if (!cmdline->HasSwitch(switch_key)) {
-    // Default to unknown -> no filtering.
-    *type = Target::UNKNOWN;
-    return true;
-  }
-
-  std::string value = cmdline->GetSwitchValueASCII(switch_key);
-  if (value == "group") {
-    *type = Target::GROUP;
-    return true;
-  }
-  if (value == "executable") {
-    *type = Target::EXECUTABLE;
-    return true;
-  }
-  if (value == "shared_library") {
-    *type = Target::SHARED_LIBRARY;
-    return true;
-  }
-  if (value == "loadable_module") {
-    *type = Target::LOADABLE_MODULE;
-    return true;
-  }
-  if (value == "static_library") {
-    *type = Target::STATIC_LIBRARY;
-    return true;
-  }
-  if (value == "source_set") {
-    *type = Target::SOURCE_SET;
-    return true;
-  }
-  if (value == "copy") {
-    *type = Target::COPY_FILES;
-    return true;
-  }
-  if (value == "action") {
-    *type = Target::ACTION;
-    return true;
-  }
-
-  Err(Location(), "Invalid value for \"--type\".").PrintToStdout();
-  return false;
+  *type = CommandSwitches::Get().target_type();
+  return true;
 }
 
 // Applies any testonly filtering specified on the command line to the given
 // target set. On failure, prints an error and returns false.
 bool ApplyTestonlyFilter(std::vector<const Target*>* targets) {
-  const base::CommandLine* cmdline = base::CommandLine::ForCurrentProcess();
-  std::string testonly_key = "testonly";
+  CommandSwitches::TestonlyMode testonly_mode =
+      CommandSwitches::Get().testonly_mode();
 
-  if (targets->empty() || !cmdline->HasSwitch(testonly_key))
+  if (targets->empty() || testonly_mode == CommandSwitches::TESTONLY_NONE)
     return true;
 
-  std::string testonly_value = cmdline->GetSwitchValueASCII(testonly_key);
-  bool testonly = false;
-  if (testonly_value == "true") {
-    testonly = true;
-  } else if (testonly_value != "false") {
-    Err(Location(), "Bad value for --testonly.",
-        "I was expecting --testonly=true or --testonly=false.")
-        .PrintToStdout();
-    return false;
-  }
+  bool testonly = (testonly_mode == CommandSwitches::TESTONLY_TRUE);
 
   // Filter into a copy of the vector, then replace the output.
   std::vector<const Target*> result;
@@ -438,13 +354,119 @@
     INSERT_COMMAND(Outputs)
     INSERT_COMMAND(Path)
     INSERT_COMMAND(Refs)
-    INSERT_COMMAND(CleanStale);
+    INSERT_COMMAND(CleanStale)
 
 #undef INSERT_COMMAND
   }
   return info_map;
 }
 
+// static
+CommandSwitches CommandSwitches::s_global_switches_ = {};
+
+// static
+bool CommandSwitches::Init(const base::CommandLine& cmdline) {
+  return s_global_switches_.InitFrom(cmdline);
+}
+
+// static
+const CommandSwitches& CommandSwitches::Get() {
+  return s_global_switches_;
+}
+
+// static
+CommandSwitches CommandSwitches::Set(CommandSwitches new_switches) {
+  CommandSwitches result = std::move(s_global_switches_);
+  s_global_switches_ = std::move(new_switches);
+  return result;
+}
+
+bool CommandSwitches::InitFrom(const base::CommandLine& cmdline) {
+  CommandSwitches result;
+  result.has_quiet_ = cmdline.HasSwitch("a");
+  result.has_force_ = cmdline.HasSwitch("force");
+  result.has_all_ = cmdline.HasSwitch("all");
+  result.has_blame_ = cmdline.HasSwitch("blame");
+  result.has_tree_ = cmdline.HasSwitch("tree");
+  result.has_format_json_ = cmdline.GetSwitchValueASCII("format") == "json";
+  result.has_default_toolchain_ =
+      cmdline.HasSwitch(switches::kDefaultToolchain);
+
+  result.has_check_generated_ = cmdline.HasSwitch("check-generated");
+  result.has_check_system_ = cmdline.HasSwitch("check-system");
+  result.has_public_ = cmdline.HasSwitch("public");
+  result.has_with_data_ = cmdline.HasSwitch("with-data");
+
+  std::string_view target_print_switch = "as";
+  if (cmdline.HasSwitch(target_print_switch)) {
+    std::string value = cmdline.GetSwitchValueASCII(target_print_switch);
+    if (value == "buildfile") {
+      result.target_print_mode_ = TARGET_PRINT_BUILDFILE;
+    } else if (value == "label") {
+      result.target_print_mode_ = TARGET_PRINT_LABEL;
+    } else if (value == "output") {
+      result.target_print_mode_ = TARGET_PRINT_OUTPUT;
+    } else {
+      Err(Location(), "Invalid value for \"--as\".",
+          "I was expecting \"buildfile\", \"label\", or \"output\" but you\n"
+          "said \"" +
+              value + "\".")
+          .PrintToStdout();
+      return false;
+    }
+  }
+
+  std::string_view target_type_switch = "type";
+  if (cmdline.HasSwitch(target_type_switch)) {
+    std::string value = cmdline.GetSwitchValueASCII(target_type_switch);
+    static const struct {
+      const char* name;
+      Target::OutputType type;
+    } kTypes[] = {
+        {"group", Target::GROUP},
+        {"executable", Target::EXECUTABLE},
+        {"shared_library", Target::SHARED_LIBRARY},
+        {"loadable_module", Target::LOADABLE_MODULE},
+        {"static_library", Target::STATIC_LIBRARY},
+        {"source_set", Target::SOURCE_SET},
+        {"copy", Target::COPY_FILES},
+        {"action", Target::ACTION},
+    };
+    bool found = false;
+    for (const auto& type : kTypes) {
+      if (value == type.name) {
+        result.target_type_ = type.type;
+        found = true;
+        break;
+      }
+    }
+    if (!found) {
+      Err(Location(), "Invalid value for \"--type\".").PrintToStdout();
+      return false;
+    }
+  }
+  std::string_view testonly_switch = "testonly";
+  if (cmdline.HasSwitch(testonly_switch)) {
+    std::string value = cmdline.GetSwitchValueASCII(testonly_switch);
+    if (value == "true") {
+      testonly_mode_ = TESTONLY_TRUE;
+    } else if (value == "false") {
+      testonly_mode_ = TESTONLY_FALSE;
+    } else {
+      Err(Location(), "Bad value for --testonly.",
+          "I was expecting --testonly=true or --testonly=false.")
+          .PrintToStdout();
+      return false;
+    }
+  }
+
+  result.meta_rebase_dir_ = cmdline.GetSwitchValueASCII("rebase");
+  result.meta_data_keys_ = cmdline.GetSwitchValueASCII("data");
+  result.meta_walk_keys_ = cmdline.GetSwitchValueASCII("walk");
+  *this = result;
+  return true;
+}
+
 const Target* ResolveTargetFromCommandLineString(
     Setup* setup,
     const std::string& label_string) {
@@ -583,17 +605,18 @@
   if (!ApplyTypeFilter(targets))
     return;
 
-  TargetPrintingMode printing_mode = TARGET_PRINT_LABEL;
+  CommandSwitches::TargetPrintMode printing_mode =
+      CommandSwitches::TARGET_PRINT_LABEL;
   if (targets->empty() || !GetTargetPrintingMode(&printing_mode))
     return;
   switch (printing_mode) {
-    case TARGET_PRINT_BUILDFILE:
+    case CommandSwitches::TARGET_PRINT_BUILDFILE:
       PrintTargetsAsBuildfiles(*targets, out);
       break;
-    case TARGET_PRINT_LABEL:
+    case CommandSwitches::TARGET_PRINT_LABEL:
       PrintTargetsAsLabels(*targets, out);
       break;
-    case TARGET_PRINT_OUTPUT:
+    case CommandSwitches::TARGET_PRINT_OUTPUT:
       PrintTargetsAsOutputs(*targets, out);
       break;
   }
diff --git a/src/gn/commands.h b/src/gn/commands.h
index 4824b79..049ff85 100644
--- a/src/gn/commands.h
+++ b/src/gn/commands.h
@@ -23,6 +23,10 @@
 class Target;
 class Toolchain;
 
+namespace base {
+class CommandLine;
+}  // namespace base
+
 // Each "Run" command returns the value we should return from main().
 
 namespace commands {
@@ -116,6 +120,120 @@
 
 const CommandInfoMap& GetCommands();
 
+// Command switches as flags and enums -----------------------------------------
+
+// A class that models a set of command-line flags and values that
+// can affect the output of various GN commands. For example --tree
+// can be used with `gn desc <out_dir> <label> deps --tree`.
+//
+// Each flag or value is checked by an accessor method which returns
+// a boolean or an enum.
+//
+// Use CommandSwitches::Get() to get a reference to the current
+// global set of switches for the process.
+//
+// Use CommandSwitches::Set() to update its value. This may be
+// useful when implementing a REPL in GN, where each evaluation
+// might need a different set of command switches.
+class CommandSwitches {
+ public:
+  // Default constructor.
+  CommandSwitches() = default;
+
+  // For --quiet, used by `refs`.
+  bool has_quiet() const { return has_quiet_; }
+
+  // For --force, used by `check`.
+  bool has_force() const { return has_force_; }
+
+  // For --all, used by `desc` and `refs`.
+  bool has_all() const { return has_all_; }
+
+  // For --blame used by `desc`.
+  bool has_blame() const { return has_blame_; }
+
+  // For --tree used by `desc` and `refs`.
+  bool has_tree() const { return has_tree_; }
+
+  // For --format=json used by `desc`.
+  bool has_format_json() const { return has_format_json_; }
+
+  // For --default-toolchain used by `desc`, `refs`.
+  bool has_default_toolchain() const { return has_default_toolchain_; }
+
+  // For --check-generated
+  bool has_check_generated() const { return has_check_generated_; }
+
+  // For --check-system
+  bool has_check_system() const { return has_check_system_; }
+
+  // For --public
+  bool has_public() const { return has_public_; }
+
+  // For --with-data
+  bool has_with_data() const { return has_with_data_; }
+
+  // For --as=(buildtype|label|output).
+  enum TargetPrintMode {
+    TARGET_PRINT_BUILDFILE,
+    TARGET_PRINT_LABEL,
+    TARGET_PRINT_OUTPUT,
+  };
+  TargetPrintMode target_print_mode() const { return target_print_mode_; }
+
+  // For --type=TARGET_TYPE
+  Target::OutputType target_type() const { return target_type_; }
+
+  enum TestonlyMode {
+    TESTONLY_NONE,   // no --testonly used.
+    TESTONLY_FALSE,  // --testonly=false
+    TESTONLY_TRUE,   // --testonly=true
+  };
+  TestonlyMode testonly_mode() const { return testonly_mode_; }
+
+  // For --rebase, --walk and --data in `gn meta`
+  std::string meta_rebase_dir() const { return meta_rebase_dir_; }
+  std::string meta_data_keys() const { return meta_data_keys_; }
+  std::string meta_walk_keys() const { return meta_walk_keys_; }
+
+  // Initialize the global set from a given command line.
+  // Must be called early from main(). On success return true,
+  // on failure return false after printing an error message.
+  static bool Init(const base::CommandLine& cmdline);
+
+  // Retrieve a reference to the current global set of command switches.
+  static const CommandSwitches& Get();
+
+  // Change the current global set of command switches, and return
+  // the previous value.
+  static CommandSwitches Set(CommandSwitches new_switches);
+
+ private:
+  bool InitFrom(const base::CommandLine&);
+
+  static CommandSwitches s_global_switches_;
+
+  bool has_quiet_ = false;
+  bool has_force_ = false;
+  bool has_all_ = false;
+  bool has_blame_ = false;
+  bool has_tree_ = false;
+  bool has_format_json_ = false;
+  bool has_default_toolchain_ = false;
+  bool has_check_generated_ = false;
+  bool has_check_system_ = false;
+  bool has_public_ = false;
+  bool has_with_data_ = false;
+
+  TargetPrintMode target_print_mode_ = TARGET_PRINT_LABEL;
+  Target::OutputType target_type_ = Target::UNKNOWN;
+  TestonlyMode testonly_mode_ = TESTONLY_NONE;
+
+  std::string meta_rebase_dir_;
+  std::string meta_data_keys_;
+  std::string meta_walk_keys_;
+};
+
 // Helper functions for some commands ------------------------------------------
 
 // Given a setup that has already been run and some command-line input,