Snap for 10843824 from 9d830a88b4aa9e2a47b21b9c614f1b18e8c6800d to 24Q1-release

Change-Id: I9a901aa0427210c4bbdb7b176b1907affba51925
diff --git a/Android.bp b/Android.bp
index 5ebe66f..c4af5eb 100644
--- a/Android.bp
+++ b/Android.bp
@@ -42,6 +42,12 @@
 }
 
 cc_binary_host {
+    name: "sysprop_rust",
+    defaults: ["sysprop-defaults"],
+    srcs: ["RustGen.cpp", "RustMain.cpp"],
+}
+
+cc_binary_host {
     name: "sysprop_api_checker",
     defaults: ["sysprop-defaults"],
     srcs: ["ApiChecker.cpp", "ApiCheckerMain.cpp"],
@@ -65,6 +71,7 @@
     srcs: ["ApiChecker.cpp",
            "CppGen.cpp",
            "JavaGen.cpp",
+           "RustGen.cpp",
            "TypeChecker.cpp",
            "tests/*.cpp"],
     whole_static_libs: ["libcom.android.sysprop.tests"],
diff --git a/Common.cpp b/Common.cpp
index ec7d120..fc64099 100644
--- a/Common.cpp
+++ b/Common.cpp
@@ -360,3 +360,34 @@
   return (isdigit(name[0]) ? "_" : "") +
          std::regex_replace(name, kRegexAllowed, "_");
 }
+
+std::string SnakeCaseToCamelCase(const std::string& s) {
+  std::string result;
+  for (int i = 0; i < s.size(); ++i) {
+    if (s[i] == '_') continue;
+    char current = tolower(s[i]);  // Handle screaming snake case.
+    if (i == 0 || s[i - 1] == '_') {
+      current = toupper(current);
+    }
+    result += current;
+  }
+  return result;
+}
+
+std::string CamelCaseToSnakeCase(const std::string& s) {
+  // Desired behaviour: "CurrentAPIVersion" -> "current_api_version".
+  std::string result;
+  for (int i = 0; i < s.size(); ++i) {
+    char current = s[i];
+    char prev = (i > 0) ? s[i - 1] : 0;
+    char next = (i < s.size() - 1) ? s[i + 1] : 0;
+    if (prev && isupper(prev) && (!next || isupper(next) || !isalpha(next))) {
+      current = tolower(current);
+    }
+    if (i > 0 && isupper(current) && result[result.size() - 1] != '_') {
+      result += '_';
+    }
+    result += tolower(current);
+  }
+  return result;
+}
\ No newline at end of file
diff --git a/RustGen.cpp b/RustGen.cpp
new file mode 100644
index 0000000..a1e7314
--- /dev/null
+++ b/RustGen.cpp
@@ -0,0 +1,516 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "sysprop_rust_gen"
+
+#include "RustGen.h"
+
+#include <android-base/file.h>
+
+#include <regex>
+#include <string>
+
+#include "CodeWriter.h"
+#include "Common.h"
+#include "sysprop.pb.h"
+
+using android::base::Result;
+
+namespace {
+
+constexpr const char* kParsersAndFormatters = R"(//! Parsers and formatters.
+//!
+//! This code must only be used in the system properties generate code.
+//!
+//! This is autogenerated code. DO NOT EDIT!
+
+use std::str::FromStr;
+use std::string::ToString;
+
+#[allow(missing_docs)]
+pub type Result<T> = std::result::Result<T, String>;
+
+// Parsers.
+
+#[allow(dead_code,missing_docs)]
+pub fn parse<T: FromStr>(s: &str) -> Result<T> {
+    s.parse::<T>().map_err(|_| format!("Can't convert '{}' to '{}'.",
+                                       s, std::any::type_name::<T>()))
+}
+
+#[allow(dead_code,missing_docs)]
+pub fn parse_bool(s: &str) -> Result<bool> {
+    match s {
+        "1" | "true" => Ok(true),
+        "0" | "false" => Ok(false),
+        _ => Err(format!("Can't convert '{}' to 'bool'.", s)),
+    }
+}
+
+fn parse_list_with<T, F>(s: &str, f: F) -> Result<Vec<T>>
+where F: Fn(&str) -> Result<T> {
+
+    let mut result = Vec::new();
+    if s.is_empty() { return Ok(result); }
+
+    let mut chars = s.chars();
+    let mut current = chars.next();
+    while current.is_some() {
+        // Extract token.
+        let mut token = String::with_capacity(s.len());
+        while let Some(value) = current {
+            if value == ',' { break; }
+            if value == '\\' {
+                current = chars.next()
+            }
+            if let Some(value) = current {
+                token.push(value);
+            }
+            current = chars.next();
+        }
+        // Parse token.
+        result.push(f(token.as_str())?);
+        current = chars.next()
+    }
+
+    Ok(result)
+}
+
+#[allow(dead_code,missing_docs)]
+pub fn parse_list<T: FromStr>(s: &str) -> Result<Vec<T>> {
+    parse_list_with(s, parse)
+}
+
+#[allow(dead_code,missing_docs)]
+pub fn parse_bool_list(s: &str) -> Result<Vec<bool>> {
+    parse_list_with(s, parse_bool)
+}
+
+// Formatters.
+
+#[allow(dead_code,missing_docs)]
+pub fn format<T: ToString>(v: &T) -> String {
+    v.to_string()
+}
+
+#[allow(dead_code,missing_docs)]
+pub fn format_bool(v: &bool) -> String {
+    if *v {
+        return "true".into();
+    }
+    "false".into()
+}
+
+#[allow(dead_code,missing_docs)]
+pub fn format_bool_as_int(v: &bool) -> String {
+    if *v {
+        return "1".into();
+    }
+    "0".into()
+}
+
+fn format_list_with<T, F>(v: &[T], f: F) -> String
+where F: Fn(&T) -> String {
+    let mut result = String::new();
+    for item in v {
+        let formatted = f(item);
+        result.push_str(formatted.as_str());
+        result.push(',');
+    }
+    result.pop();
+    result
+}
+
+#[allow(dead_code,missing_docs)]
+pub fn format_list<T: ToString>(v: &[T]) -> String {
+    format_list_with(v, format)
+}
+
+#[allow(dead_code,missing_docs)]
+pub fn format_bool_list(v: &[bool]) -> String {
+    format_list_with(v, format_bool)
+}
+
+#[allow(dead_code,missing_docs)]
+pub fn format_bool_list_as_int(v: &[bool]) -> String {
+    format_list_with(v, format_bool_as_int)
+})";
+
+constexpr const char* kDocs = R"(//! Autogenerated system properties.
+//!
+//! This is autogenerated crate. The crate contains methods for easy access to
+//! the Android system properties.)";
+
+constexpr const char* kRustFileImports = R"(use std::fmt;
+use rustutils::system_properties;
+
+mod gen_parsers_and_formatters;)";
+
+constexpr const char* kIndent = "    ";
+
+constexpr const char* kDeprecated = "#[deprecated]";
+
+constexpr const char* kError = R"(/// Errors this crate could generate.
+#[derive(Debug)]
+pub enum SysPropError {
+    /// Failed to fetch the system property.
+    FetchError(system_properties::PropertyWatcherError),
+    /// Failed to set the system property.
+    SetError(system_properties::PropertyWatcherError),
+    /// Failed to parse the system property value.
+    ParseError(String),
+}
+
+impl fmt::Display for SysPropError {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match self {
+            SysPropError::FetchError(err) =>
+                write!(f, "failed to fetch the system property: {}", err),
+            SysPropError::SetError(err) =>
+                write!(f, "failed to set the system property: {}", err),
+            SysPropError::ParseError(err) =>
+                write!(f, "failed to parse the system property value: {}", err),
+        }
+    }
+})";
+
+constexpr const char* kResult = R"(/// Result type specific for this crate.
+pub type Result<T> = std::result::Result<T, SysPropError>;)";
+
+std::string GetRustEnumType(const sysprop::Property& prop) {
+  std::string result = ApiNameToIdentifier(prop.api_name());
+  return SnakeCaseToCamelCase(result) + "Values";
+}
+
+std::string GetRustReturnType(const sysprop::Property& prop) {
+  switch (prop.type()) {
+    case sysprop::Boolean:
+      return "bool";
+    case sysprop::Integer:
+      return "i32";
+    case sysprop::UInt:
+      return "u32";
+    case sysprop::Long:
+      return "i64";
+    case sysprop::ULong:
+      return "u64";
+    case sysprop::Double:
+      return "f64";
+    case sysprop::String:
+      return "String";
+    case sysprop::Enum:
+      return GetRustEnumType(prop);
+    case sysprop::BooleanList:
+      return "Vec<bool>";
+    case sysprop::IntegerList:
+      return "Vec<i32>";
+    case sysprop::UIntList:
+      return "Vec<u32>";
+    case sysprop::LongList:
+      return "Vec<i64>";
+    case sysprop::ULongList:
+      return "Vec<u64>";
+    case sysprop::DoubleList:
+      return "Vec<f64>";
+    case sysprop::StringList:
+      return "Vec<String>";
+    case sysprop::EnumList:
+      return "Vec<" + GetRustEnumType(prop) + ">";
+    default:
+      __builtin_unreachable();
+  }
+}
+
+std::string GetRustAcceptType(const sysprop::Property& prop) {
+  switch (prop.type()) {
+    case sysprop::Boolean:
+      return "bool";
+    case sysprop::Integer:
+      return "i32";
+    case sysprop::UInt:
+      return "u32";
+    case sysprop::Long:
+      return "i64";
+    case sysprop::ULong:
+      return "u64";
+    case sysprop::Double:
+      return "f64";
+    case sysprop::String:
+      return "&str";
+    case sysprop::Enum:
+      return GetRustEnumType(prop);
+    case sysprop::BooleanList:
+      return "&[bool]";
+    case sysprop::IntegerList:
+      return "&[i32]";
+    case sysprop::UIntList:
+      return "&[u32]";
+    case sysprop::LongList:
+      return "&[i64]";
+    case sysprop::ULongList:
+      return "&[u64]";
+    case sysprop::DoubleList:
+      return "&[f64]";
+    case sysprop::StringList:
+      return "&[String]";
+    case sysprop::EnumList:
+      return "&[" + GetRustEnumType(prop) + "]";
+    default:
+      __builtin_unreachable();
+  }
+}
+
+std::string GetTypeParser(const sysprop::Property& prop) {
+  switch (prop.type()) {
+    case sysprop::Boolean:
+      return "gen_parsers_and_formatters::parse_bool";
+    case sysprop::Integer:
+    case sysprop::UInt:
+    case sysprop::Long:
+    case sysprop::ULong:
+    case sysprop::Double:
+    case sysprop::String:
+    case sysprop::Enum:
+      return "gen_parsers_and_formatters::parse";
+    case sysprop::BooleanList:
+      return "gen_parsers_and_formatters::parse_bool_list";
+    case sysprop::IntegerList:
+    case sysprop::UIntList:
+    case sysprop::LongList:
+    case sysprop::ULongList:
+    case sysprop::DoubleList:
+    case sysprop::StringList:
+    case sysprop::EnumList:
+      return "gen_parsers_and_formatters::parse_list";
+    default:
+      __builtin_unreachable();
+  }
+}
+
+std::string GetTypeFormatter(const sysprop::Property& prop) {
+  switch (prop.type()) {
+    case sysprop::Boolean:
+      if (prop.integer_as_bool()) {
+        return "gen_parsers_and_formatters::format_bool_as_int";
+      }
+      return "gen_parsers_and_formatters::format_bool";
+    case sysprop::String:
+    case sysprop::Integer:
+    case sysprop::UInt:
+    case sysprop::Long:
+    case sysprop::ULong:
+    case sysprop::Double:
+    case sysprop::Enum:
+      return "gen_parsers_and_formatters::format";
+    case sysprop::BooleanList:
+      if (prop.integer_as_bool()) {
+        return "gen_parsers_and_formatters::format_bool_list_as_int";
+      }
+      return "gen_parsers_and_formatters::format_bool_list";
+    case sysprop::IntegerList:
+    case sysprop::UIntList:
+    case sysprop::LongList:
+    case sysprop::ULongList:
+    case sysprop::DoubleList:
+    case sysprop::StringList:
+    case sysprop::EnumList:
+      return "gen_parsers_and_formatters::format_list";
+    default:
+      __builtin_unreachable();
+  }
+}
+
+std::string GenerateRustSource(sysprop::Properties props, sysprop::Scope scope) {
+  CodeWriter writer(kIndent);
+  writer.Write("%s\n\n", kDocs);
+  writer.Write("%s", kGeneratedFileFooterComments);
+  writer.Write("%s\n\n", kRustFileImports);
+  writer.Write("%s\n\n", kError);
+  writer.Write("%s\n\n", kResult);
+
+  for (int i = 0; i < props.prop_size(); ++i) {
+    const sysprop::Property& prop = props.prop(i);
+    if (prop.scope() > scope) continue;
+
+    std::string prop_id =
+        CamelCaseToSnakeCase(ApiNameToIdentifier(prop.api_name()));
+
+    // Create enum.
+    if (prop.type() == sysprop::Enum || prop.type() == sysprop::EnumList) {
+      auto enum_type = GetRustEnumType(prop);
+      auto values = ParseEnumValues(prop.enum_values());
+
+      writer.Write(
+          "#[derive(Copy, Clone, Debug, Eq, "
+          "PartialEq, PartialOrd, Hash, Ord)]\n");
+      writer.Write("pub enum %s {\n", enum_type.c_str());
+      writer.Indent();
+      for (const std::string& value : values) {
+        writer.Write("%s,\n", SnakeCaseToCamelCase(value).c_str());
+      }
+      writer.Dedent();
+      writer.Write("}\n\n");
+
+      // Enum parser.
+      writer.Write("impl std::str::FromStr for %s {\n", enum_type.c_str());
+      writer.Indent();
+      writer.Write("type Err = String;\n\n");
+      writer.Write(
+          "fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {\n");
+      writer.Indent();
+      writer.Write("match s {\n");
+      writer.Indent();
+      for (const std::string& value : values) {
+        writer.Write("\"%s\" => Ok(%s::%s),\n", value.c_str(),
+                     enum_type.c_str(), SnakeCaseToCamelCase(value).c_str());
+      }
+      writer.Write("_ => Err(format!(\"'{}' cannot be parsed for %s\", s)),\n",
+                   enum_type.c_str());
+      writer.Dedent();
+      writer.Write("}\n");
+      writer.Dedent();
+      writer.Write("}\n");
+      writer.Dedent();
+      writer.Write("}\n\n");
+
+      // Enum formatter.
+      writer.Write("impl fmt::Display for %s {\n", enum_type.c_str());
+      writer.Indent();
+      writer.Write(
+          "fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n");
+      writer.Indent();
+      writer.Write("match self {\n");
+      writer.Indent();
+      for (const std::string& value : values) {
+        writer.Write("%s::%s => write!(f, \"%s\"),\n", enum_type.c_str(),
+                     SnakeCaseToCamelCase(value).c_str(), value.c_str());
+      }
+      writer.Write("_ => Err(fmt::Error),\n");
+      writer.Dedent();
+      writer.Write("}\n");
+      writer.Dedent();
+      writer.Write("}\n");
+      writer.Dedent();
+      writer.Write("}\n\n");
+    }
+
+    // Write getter.
+    std::string prop_return_type = GetRustReturnType(prop);
+    std::string parser = GetTypeParser(prop);
+    writer.Write("/// Returns the value of the property '%s' if set.\n",
+                 prop.prop_name().c_str());
+    if (prop.deprecated()) writer.Write("%s\n", kDeprecated);
+    // Escape prop id if it is similar to `type` keyword.
+    std::string identifier = (prop_id == "type") ? "r#" + prop_id : prop_id;
+    writer.Write("pub fn %s() -> Result<Option<%s>> {\n", identifier.c_str(),
+                 prop_return_type.c_str());
+    writer.Indent();
+    // Try original property.
+    writer.Write("let result = match system_properties::read(\"%s\") {\n",
+                 prop.prop_name().c_str());
+    writer.Indent();
+    writer.Write("Err(e) => Err(SysPropError::FetchError(e)),\n");
+    writer.Write(
+        "Ok(Some(val)) => "
+        "%s(val.as_str()).map_err(SysPropError::ParseError).map(Some),\n",
+        parser.c_str());
+    writer.Write("Ok(None) => Ok(None),\n");
+    writer.Dedent();
+    writer.Write("};\n");
+    // Try legacy property
+    if (!prop.legacy_prop_name().empty()) {
+      writer.Write("if result.is_ok() { return result; }\n");
+      // Avoid omitting the error when fallback to legacy.
+      writer.Write(
+          "log::debug!(\"Failed to fetch the original property '%s' ('{}'), "
+          "falling back to the legacy one '%s'.\", result.unwrap_err());\n",
+          prop.prop_name().c_str(), prop.legacy_prop_name().c_str());
+      writer.Write("match system_properties::read(\"%s\") {\n",
+                   prop.legacy_prop_name().c_str());
+      writer.Indent();
+      writer.Write("Err(e) => Err(SysPropError::FetchError(e)),\n");
+      writer.Write(
+          "Ok(Some(val)) => "
+          "%s(val.as_str()).map_err(SysPropError::ParseError).map(Some),\n",
+          parser.c_str());
+      writer.Write("Ok(None) => Ok(None),\n");
+      writer.Dedent();
+      writer.Write("}\n");
+    } else {
+      writer.Write("result\n");
+    }
+    writer.Dedent();
+    writer.Write("}\n\n");
+
+    // Write setter.
+    if (prop.access() == sysprop::Readonly) continue;
+    std::string prop_accept_type = GetRustAcceptType(prop);
+    std::string formatter = GetTypeFormatter(prop);
+    writer.Write(
+        "/// Sets the value of the property '%s', "
+        "returns 'Ok' if successful.\n",
+        prop.prop_name().c_str());
+    if (prop.deprecated()) writer.Write("%s\n", kDeprecated);
+    writer.Write("pub fn set_%s(v: %s) -> Result<()> {\n", prop_id.c_str(),
+                 prop_accept_type.c_str());
+    writer.Indent();
+    std::string write_arg;
+    if (prop.type() == sysprop::String) {
+      write_arg = "v";
+    } else {
+      // We need to borrow single values.
+      std::string format_arg = prop.type() >= 20 ? "v" : "&v";
+      writer.Write("let value = %s(%s);\n", formatter.c_str(),
+                   format_arg.c_str());
+      write_arg = "value.as_str()";
+    }
+    writer.Write(
+        "system_properties::write(\"%s\", "
+        "%s).map_err(SysPropError::SetError)\n",
+        prop.prop_name().c_str(), write_arg.c_str());
+    writer.Dedent();
+    writer.Write("}\n\n");
+  }
+  return writer.Code();
+}
+
+};  // namespace
+
+Result<void> GenerateRustLibrary(const std::string& input_file_path,
+                                 sysprop::Scope scope,
+                                 const std::string& rust_output_dir) {
+  sysprop::Properties props;
+
+  if (auto res = ParseProps(input_file_path); res.ok()) {
+    props = std::move(*res);
+  } else {
+    return res.error();
+  }
+
+  std::string lib_path = rust_output_dir + "/lib.rs";
+  std::string lib_result = GenerateRustSource(props, scope);
+  if (!android::base::WriteStringToFile(lib_result, lib_path)) {
+    return ErrnoErrorf("Writing generated rust lib to {} failed", lib_path);
+  }
+
+  std::string parsers_path = rust_output_dir + "/gen_parsers_and_formatters.rs";
+  std::string parsers_result = std::string(kParsersAndFormatters);
+  if (!android::base::WriteStringToFile(parsers_result, parsers_path)) {
+    return ErrnoErrorf("Writing generated rust lib to {} failed", parsers_path);
+  }
+
+  return {};
+}
diff --git a/RustMain.cpp b/RustMain.cpp
new file mode 100644
index 0000000..3414c3c
--- /dev/null
+++ b/RustMain.cpp
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#define LOG_TAG "sysprop_rust"
+
+#include <android-base/logging.h>
+#include <android-base/result.h>
+#include <getopt.h>
+
+#include <cstdio>
+#include <cstdlib>
+#include <string>
+
+#include "RustGen.h"
+#include "sysprop.pb.h"
+
+using android::base::Result;
+
+namespace {
+
+struct Arguments {
+  std::string input_file_path;
+  std::string rust_output_dir;
+  sysprop::Scope scope;
+};
+
+[[noreturn]] void PrintUsage(const char* exe_name) {
+  std::printf(
+      "Usage %s --scope (internal|public) --rust-output-dir dir "
+      "sysprop_file\n",
+      exe_name);
+  std::exit(EXIT_FAILURE);
+}
+
+Result<void> ParseArgs(int argc, char* argv[], Arguments* args) {
+  for (;;) {
+    static struct option long_options[] = {
+        {"rust-output-dir", required_argument, 0, 'r'},
+        {"scope", required_argument, 0, 's'},
+    };
+
+    int opt = getopt_long_only(argc, argv, "", long_options, nullptr);
+    if (opt == -1) break;
+
+    switch (opt) {
+      case 'r':
+        args->rust_output_dir = optarg;
+        break;
+      case 's':
+        if (strcmp(optarg, "public") == 0) {
+          args->scope = sysprop::Scope::Public;
+        } else if (strcmp(optarg, "internal") == 0) {
+          args->scope = sysprop::Scope::Internal;
+        } else {
+          return Errorf("Invalid option {} for scope", optarg);
+        }
+        break;
+      default:
+        PrintUsage(argv[0]);
+    }
+  }
+
+  if (optind >= argc) {
+    return Errorf("No input file specified");
+  }
+
+  if (optind + 1 < argc) {
+    return Errorf("More than one input file");
+  }
+
+  args->input_file_path = argv[optind];
+  if (args->rust_output_dir.empty()) args->rust_output_dir = ".";
+
+  return {};
+}
+
+}  // namespace
+
+int main(int argc, char* argv[]) {
+  Arguments args;
+  std::string err;
+  if (auto res = ParseArgs(argc, argv, &args); !res.ok()) {
+    LOG(ERROR) << res.error();
+    PrintUsage(argv[0]);
+  }
+
+  if (auto res = GenerateRustLibrary(args.input_file_path, args.scope,
+                                     args.rust_output_dir);
+      !res.ok()) {
+    LOG(FATAL) << "Error during generating rust sysprop from "
+               << args.input_file_path << ": " << res.error();
+  }
+}
\ No newline at end of file
diff --git a/include/Common.h b/include/Common.h
index 8ad6fb1..7d3a2ad 100644
--- a/include/Common.h
+++ b/include/Common.h
@@ -32,3 +32,5 @@
 android::base::Result<sysprop::SyspropLibraryApis> ParseApiFile(
     const std::string& file_path);
 std::string ToUpper(std::string str);
+std::string CamelCaseToSnakeCase(const std::string& str);
+std::string SnakeCaseToCamelCase(const std::string& str);
diff --git a/include/RustGen.h b/include/RustGen.h
new file mode 100644
index 0000000..4843a52
--- /dev/null
+++ b/include/RustGen.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <android-base/result.h>
+
+#include <string>
+
+#include "sysprop.pb.h"
+
+android::base::Result<void> GenerateRustLibrary(
+    const std::string& input_file_path, sysprop::Scope scope,
+    const std::string& rust_output_dir);
diff --git a/tests/CommonTest.cpp b/tests/CommonTest.cpp
new file mode 100644
index 0000000..cd343f7
--- /dev/null
+++ b/tests/CommonTest.cpp
@@ -0,0 +1,39 @@
+/* Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android-base/test_utils.h>
+#include <gtest/gtest.h>
+
+#include "Common.h"
+
+TEST(SyspropTest, CamelToSnakeTest) {
+  EXPECT_EQ(CamelCaseToSnakeCase("CurrentAPIVersion"), "current_api_version");
+  EXPECT_EQ(CamelCaseToSnakeCase("CurrentAPI"), "current_api");
+  EXPECT_EQ(CamelCaseToSnakeCase("APIVersion"), "api_version");
+  EXPECT_EQ(CamelCaseToSnakeCase("API"), "api");
+  EXPECT_EQ(CamelCaseToSnakeCase("API2"), "api2");
+  EXPECT_EQ(CamelCaseToSnakeCase("SomeRandomCamelCase"),
+            "some_random_camel_case");
+  EXPECT_EQ(CamelCaseToSnakeCase("snake_case"), "snake_case");
+  EXPECT_EQ(CamelCaseToSnakeCase("Strange_Camel_Case"), "strange_camel_case");
+}
+
+TEST(SyspropTest, SnakeToCamelTest) {
+  EXPECT_EQ(SnakeCaseToCamelCase("current_api_version"), "CurrentApiVersion");
+  EXPECT_EQ(SnakeCaseToCamelCase("random"), "Random");
+  EXPECT_EQ(SnakeCaseToCamelCase("SCREAMING_SNAKE_CASE_100"),
+            "ScreamingSnakeCase100");
+  EXPECT_EQ(SnakeCaseToCamelCase("double__underscore"), "DoubleUnderscore");
+}
\ No newline at end of file
diff --git a/tests/RustGenTest.cpp b/tests/RustGenTest.cpp
new file mode 100644
index 0000000..fdddf93
--- /dev/null
+++ b/tests/RustGenTest.cpp
@@ -0,0 +1,555 @@
+/* Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <android-base/file.h>
+#include <android-base/test_utils.h>
+#include <gtest/gtest.h>
+#include <unistd.h>
+
+#include <string>
+
+#include "RustGen.h"
+
+namespace {
+
+constexpr const char* kTestSyspropFile =
+    R"(owner: Platform
+module: "android.sysprop.PlatformProperties"
+prop {
+    api_name: "test_double"
+    type: Double
+    prop_name: "android.test_double"
+    scope: Internal
+    access: ReadWrite
+}
+prop {
+    api_name: "test_int"
+    type: Integer
+    prop_name: "android.test_int"
+    scope: Public
+    access: ReadWrite
+}
+prop {
+    api_name: "test_string"
+    type: String
+    prop_name: "android.test.string"
+    scope: Public
+    access: Readonly
+    legacy_prop_name: "legacy.android.test.string"
+}
+prop {
+    api_name: "test_enum"
+    type: Enum
+    prop_name: "android.test.enum"
+    enum_values: "a|b|c|D|e|f|G"
+    scope: Internal
+    access: ReadWrite
+}
+prop {
+    api_name: "test_BOOLeaN"
+    type: Boolean
+    prop_name: "ro.android.test.b"
+    scope: Public
+    access: Writeonce
+}
+prop {
+    api_name: "android_os_test-long"
+    type: Long
+    scope: Public
+    access: ReadWrite
+}
+prop {
+    api_name: "test_double_list"
+    type: DoubleList
+    scope: Internal
+    access: ReadWrite
+}
+prop {
+    api_name: "test_list_int"
+    type: IntegerList
+    scope: Public
+    access: ReadWrite
+}
+prop {
+    api_name: "test_strlist"
+    type: StringList
+    scope: Public
+    access: ReadWrite
+    deprecated: true
+}
+prop {
+    api_name: "el"
+    type: EnumList
+    enum_values: "enu|mva|lue"
+    scope: Internal
+    access: ReadWrite
+    deprecated: true
+}
+)";
+
+constexpr const char* kExpectedPublicOutput =
+    R"(//! Autogenerated system properties.
+//!
+//! This is autogenerated crate. The crate contains methods for easy access to
+//! the Android system properties.
+
+// Generated by the sysprop generator. DO NOT EDIT!
+
+use std::fmt;
+use rustutils::system_properties;
+
+mod gen_parsers_and_formatters;
+
+/// Errors this crate could generate.
+#[derive(Debug)]
+pub enum SysPropError {
+    /// Failed to fetch the system property.
+    FetchError(system_properties::PropertyWatcherError),
+    /// Failed to set the system property.
+    SetError(system_properties::PropertyWatcherError),
+    /// Failed to parse the system property value.
+    ParseError(String),
+}
+
+impl fmt::Display for SysPropError {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match self {
+            SysPropError::FetchError(err) =>
+                write!(f, "failed to fetch the system property: {}", err),
+            SysPropError::SetError(err) =>
+                write!(f, "failed to set the system property: {}", err),
+            SysPropError::ParseError(err) =>
+                write!(f, "failed to parse the system property value: {}", err),
+        }
+    }
+}
+
+/// Result type specific for this crate.
+pub type Result<T> = std::result::Result<T, SysPropError>;
+
+/// Returns the value of the property 'android.test_int' if set.
+pub fn test_int() -> Result<Option<i32>> {
+    let result = match system_properties::read("android.test_int") {
+        Err(e) => Err(SysPropError::FetchError(e)),
+        Ok(Some(val)) => gen_parsers_and_formatters::parse(val.as_str()).map_err(SysPropError::ParseError).map(Some),
+        Ok(None) => Ok(None),
+    };
+    result
+}
+
+/// Sets the value of the property 'android.test_int', returns 'Ok' if successful.
+pub fn set_test_int(v: i32) -> Result<()> {
+    let value = gen_parsers_and_formatters::format(&v);
+    system_properties::write("android.test_int", value.as_str()).map_err(SysPropError::SetError)
+}
+
+/// Returns the value of the property 'android.test.string' if set.
+pub fn test_string() -> Result<Option<String>> {
+    let result = match system_properties::read("android.test.string") {
+        Err(e) => Err(SysPropError::FetchError(e)),
+        Ok(Some(val)) => gen_parsers_and_formatters::parse(val.as_str()).map_err(SysPropError::ParseError).map(Some),
+        Ok(None) => Ok(None),
+    };
+    if result.is_ok() { return result; }
+    log::debug!("Failed to fetch the original property 'android.test.string' ('{}'), falling back to the legacy one 'legacy.android.test.string'.", result.unwrap_err());
+    match system_properties::read("legacy.android.test.string") {
+        Err(e) => Err(SysPropError::FetchError(e)),
+        Ok(Some(val)) => gen_parsers_and_formatters::parse(val.as_str()).map_err(SysPropError::ParseError).map(Some),
+        Ok(None) => Ok(None),
+    }
+}
+
+/// Returns the value of the property 'ro.android.test.b' if set.
+pub fn test_boo_lea_n() -> Result<Option<bool>> {
+    let result = match system_properties::read("ro.android.test.b") {
+        Err(e) => Err(SysPropError::FetchError(e)),
+        Ok(Some(val)) => gen_parsers_and_formatters::parse_bool(val.as_str()).map_err(SysPropError::ParseError).map(Some),
+        Ok(None) => Ok(None),
+    };
+    result
+}
+
+/// Sets the value of the property 'ro.android.test.b', returns 'Ok' if successful.
+pub fn set_test_boo_lea_n(v: bool) -> Result<()> {
+    let value = gen_parsers_and_formatters::format_bool(&v);
+    system_properties::write("ro.android.test.b", value.as_str()).map_err(SysPropError::SetError)
+}
+
+/// Returns the value of the property 'android_os_test-long' if set.
+pub fn android_os_test_long() -> Result<Option<i64>> {
+    let result = match system_properties::read("android_os_test-long") {
+        Err(e) => Err(SysPropError::FetchError(e)),
+        Ok(Some(val)) => gen_parsers_and_formatters::parse(val.as_str()).map_err(SysPropError::ParseError).map(Some),
+        Ok(None) => Ok(None),
+    };
+    result
+}
+
+/// Sets the value of the property 'android_os_test-long', returns 'Ok' if successful.
+pub fn set_android_os_test_long(v: i64) -> Result<()> {
+    let value = gen_parsers_and_formatters::format(&v);
+    system_properties::write("android_os_test-long", value.as_str()).map_err(SysPropError::SetError)
+}
+
+/// Returns the value of the property 'test_list_int' if set.
+pub fn test_list_int() -> Result<Option<Vec<i32>>> {
+    let result = match system_properties::read("test_list_int") {
+        Err(e) => Err(SysPropError::FetchError(e)),
+        Ok(Some(val)) => gen_parsers_and_formatters::parse_list(val.as_str()).map_err(SysPropError::ParseError).map(Some),
+        Ok(None) => Ok(None),
+    };
+    result
+}
+
+/// Sets the value of the property 'test_list_int', returns 'Ok' if successful.
+pub fn set_test_list_int(v: &[i32]) -> Result<()> {
+    let value = gen_parsers_and_formatters::format_list(v);
+    system_properties::write("test_list_int", value.as_str()).map_err(SysPropError::SetError)
+}
+
+/// Returns the value of the property 'test_strlist' if set.
+#[deprecated]
+pub fn test_strlist() -> Result<Option<Vec<String>>> {
+    let result = match system_properties::read("test_strlist") {
+        Err(e) => Err(SysPropError::FetchError(e)),
+        Ok(Some(val)) => gen_parsers_and_formatters::parse_list(val.as_str()).map_err(SysPropError::ParseError).map(Some),
+        Ok(None) => Ok(None),
+    };
+    result
+}
+
+/// Sets the value of the property 'test_strlist', returns 'Ok' if successful.
+#[deprecated]
+pub fn set_test_strlist(v: &[String]) -> Result<()> {
+    let value = gen_parsers_and_formatters::format_list(v);
+    system_properties::write("test_strlist", value.as_str()).map_err(SysPropError::SetError)
+}
+
+)";
+
+constexpr const char* kExpectedInternalOutput =
+    R"(//! Autogenerated system properties.
+//!
+//! This is autogenerated crate. The crate contains methods for easy access to
+//! the Android system properties.
+
+// Generated by the sysprop generator. DO NOT EDIT!
+
+use std::fmt;
+use rustutils::system_properties;
+
+mod gen_parsers_and_formatters;
+
+/// Errors this crate could generate.
+#[derive(Debug)]
+pub enum SysPropError {
+    /// Failed to fetch the system property.
+    FetchError(system_properties::PropertyWatcherError),
+    /// Failed to set the system property.
+    SetError(system_properties::PropertyWatcherError),
+    /// Failed to parse the system property value.
+    ParseError(String),
+}
+
+impl fmt::Display for SysPropError {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match self {
+            SysPropError::FetchError(err) =>
+                write!(f, "failed to fetch the system property: {}", err),
+            SysPropError::SetError(err) =>
+                write!(f, "failed to set the system property: {}", err),
+            SysPropError::ParseError(err) =>
+                write!(f, "failed to parse the system property value: {}", err),
+        }
+    }
+}
+
+/// Result type specific for this crate.
+pub type Result<T> = std::result::Result<T, SysPropError>;
+
+/// Returns the value of the property 'android.test_double' if set.
+pub fn test_double() -> Result<Option<f64>> {
+    let result = match system_properties::read("android.test_double") {
+        Err(e) => Err(SysPropError::FetchError(e)),
+        Ok(Some(val)) => gen_parsers_and_formatters::parse(val.as_str()).map_err(SysPropError::ParseError).map(Some),
+        Ok(None) => Ok(None),
+    };
+    result
+}
+
+/// Sets the value of the property 'android.test_double', returns 'Ok' if successful.
+pub fn set_test_double(v: f64) -> Result<()> {
+    let value = gen_parsers_and_formatters::format(&v);
+    system_properties::write("android.test_double", value.as_str()).map_err(SysPropError::SetError)
+}
+
+/// Returns the value of the property 'android.test_int' if set.
+pub fn test_int() -> Result<Option<i32>> {
+    let result = match system_properties::read("android.test_int") {
+        Err(e) => Err(SysPropError::FetchError(e)),
+        Ok(Some(val)) => gen_parsers_and_formatters::parse(val.as_str()).map_err(SysPropError::ParseError).map(Some),
+        Ok(None) => Ok(None),
+    };
+    result
+}
+
+/// Sets the value of the property 'android.test_int', returns 'Ok' if successful.
+pub fn set_test_int(v: i32) -> Result<()> {
+    let value = gen_parsers_and_formatters::format(&v);
+    system_properties::write("android.test_int", value.as_str()).map_err(SysPropError::SetError)
+}
+
+/// Returns the value of the property 'android.test.string' if set.
+pub fn test_string() -> Result<Option<String>> {
+    let result = match system_properties::read("android.test.string") {
+        Err(e) => Err(SysPropError::FetchError(e)),
+        Ok(Some(val)) => gen_parsers_and_formatters::parse(val.as_str()).map_err(SysPropError::ParseError).map(Some),
+        Ok(None) => Ok(None),
+    };
+    if result.is_ok() { return result; }
+    log::debug!("Failed to fetch the original property 'android.test.string' ('{}'), falling back to the legacy one 'legacy.android.test.string'.", result.unwrap_err());
+    match system_properties::read("legacy.android.test.string") {
+        Err(e) => Err(SysPropError::FetchError(e)),
+        Ok(Some(val)) => gen_parsers_and_formatters::parse(val.as_str()).map_err(SysPropError::ParseError).map(Some),
+        Ok(None) => Ok(None),
+    }
+}
+
+#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Hash, Ord)]
+pub enum TestEnumValues {
+    A,
+    B,
+    C,
+    D,
+    E,
+    F,
+    G,
+}
+
+impl std::str::FromStr for TestEnumValues {
+    type Err = String;
+
+    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
+        match s {
+            "a" => Ok(TestEnumValues::A),
+            "b" => Ok(TestEnumValues::B),
+            "c" => Ok(TestEnumValues::C),
+            "D" => Ok(TestEnumValues::D),
+            "e" => Ok(TestEnumValues::E),
+            "f" => Ok(TestEnumValues::F),
+            "G" => Ok(TestEnumValues::G),
+            _ => Err(format!("'{}' cannot be parsed for TestEnumValues", s)),
+        }
+    }
+}
+
+impl fmt::Display for TestEnumValues {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match self {
+            TestEnumValues::A => write!(f, "a"),
+            TestEnumValues::B => write!(f, "b"),
+            TestEnumValues::C => write!(f, "c"),
+            TestEnumValues::D => write!(f, "D"),
+            TestEnumValues::E => write!(f, "e"),
+            TestEnumValues::F => write!(f, "f"),
+            TestEnumValues::G => write!(f, "G"),
+            _ => Err(fmt::Error),
+        }
+    }
+}
+
+/// Returns the value of the property 'android.test.enum' if set.
+pub fn test_enum() -> Result<Option<TestEnumValues>> {
+    let result = match system_properties::read("android.test.enum") {
+        Err(e) => Err(SysPropError::FetchError(e)),
+        Ok(Some(val)) => gen_parsers_and_formatters::parse(val.as_str()).map_err(SysPropError::ParseError).map(Some),
+        Ok(None) => Ok(None),
+    };
+    result
+}
+
+/// Sets the value of the property 'android.test.enum', returns 'Ok' if successful.
+pub fn set_test_enum(v: TestEnumValues) -> Result<()> {
+    let value = gen_parsers_and_formatters::format(&v);
+    system_properties::write("android.test.enum", value.as_str()).map_err(SysPropError::SetError)
+}
+
+/// Returns the value of the property 'ro.android.test.b' if set.
+pub fn test_boo_lea_n() -> Result<Option<bool>> {
+    let result = match system_properties::read("ro.android.test.b") {
+        Err(e) => Err(SysPropError::FetchError(e)),
+        Ok(Some(val)) => gen_parsers_and_formatters::parse_bool(val.as_str()).map_err(SysPropError::ParseError).map(Some),
+        Ok(None) => Ok(None),
+    };
+    result
+}
+
+/// Sets the value of the property 'ro.android.test.b', returns 'Ok' if successful.
+pub fn set_test_boo_lea_n(v: bool) -> Result<()> {
+    let value = gen_parsers_and_formatters::format_bool(&v);
+    system_properties::write("ro.android.test.b", value.as_str()).map_err(SysPropError::SetError)
+}
+
+/// Returns the value of the property 'android_os_test-long' if set.
+pub fn android_os_test_long() -> Result<Option<i64>> {
+    let result = match system_properties::read("android_os_test-long") {
+        Err(e) => Err(SysPropError::FetchError(e)),
+        Ok(Some(val)) => gen_parsers_and_formatters::parse(val.as_str()).map_err(SysPropError::ParseError).map(Some),
+        Ok(None) => Ok(None),
+    };
+    result
+}
+
+/// Sets the value of the property 'android_os_test-long', returns 'Ok' if successful.
+pub fn set_android_os_test_long(v: i64) -> Result<()> {
+    let value = gen_parsers_and_formatters::format(&v);
+    system_properties::write("android_os_test-long", value.as_str()).map_err(SysPropError::SetError)
+}
+
+/// Returns the value of the property 'test_double_list' if set.
+pub fn test_double_list() -> Result<Option<Vec<f64>>> {
+    let result = match system_properties::read("test_double_list") {
+        Err(e) => Err(SysPropError::FetchError(e)),
+        Ok(Some(val)) => gen_parsers_and_formatters::parse_list(val.as_str()).map_err(SysPropError::ParseError).map(Some),
+        Ok(None) => Ok(None),
+    };
+    result
+}
+
+/// Sets the value of the property 'test_double_list', returns 'Ok' if successful.
+pub fn set_test_double_list(v: &[f64]) -> Result<()> {
+    let value = gen_parsers_and_formatters::format_list(v);
+    system_properties::write("test_double_list", value.as_str()).map_err(SysPropError::SetError)
+}
+
+/// Returns the value of the property 'test_list_int' if set.
+pub fn test_list_int() -> Result<Option<Vec<i32>>> {
+    let result = match system_properties::read("test_list_int") {
+        Err(e) => Err(SysPropError::FetchError(e)),
+        Ok(Some(val)) => gen_parsers_and_formatters::parse_list(val.as_str()).map_err(SysPropError::ParseError).map(Some),
+        Ok(None) => Ok(None),
+    };
+    result
+}
+
+/// Sets the value of the property 'test_list_int', returns 'Ok' if successful.
+pub fn set_test_list_int(v: &[i32]) -> Result<()> {
+    let value = gen_parsers_and_formatters::format_list(v);
+    system_properties::write("test_list_int", value.as_str()).map_err(SysPropError::SetError)
+}
+
+/// Returns the value of the property 'test_strlist' if set.
+#[deprecated]
+pub fn test_strlist() -> Result<Option<Vec<String>>> {
+    let result = match system_properties::read("test_strlist") {
+        Err(e) => Err(SysPropError::FetchError(e)),
+        Ok(Some(val)) => gen_parsers_and_formatters::parse_list(val.as_str()).map_err(SysPropError::ParseError).map(Some),
+        Ok(None) => Ok(None),
+    };
+    result
+}
+
+/// Sets the value of the property 'test_strlist', returns 'Ok' if successful.
+#[deprecated]
+pub fn set_test_strlist(v: &[String]) -> Result<()> {
+    let value = gen_parsers_and_formatters::format_list(v);
+    system_properties::write("test_strlist", value.as_str()).map_err(SysPropError::SetError)
+}
+
+#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Hash, Ord)]
+pub enum ElValues {
+    Enu,
+    Mva,
+    Lue,
+}
+
+impl std::str::FromStr for ElValues {
+    type Err = String;
+
+    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
+        match s {
+            "enu" => Ok(ElValues::Enu),
+            "mva" => Ok(ElValues::Mva),
+            "lue" => Ok(ElValues::Lue),
+            _ => Err(format!("'{}' cannot be parsed for ElValues", s)),
+        }
+    }
+}
+
+impl fmt::Display for ElValues {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match self {
+            ElValues::Enu => write!(f, "enu"),
+            ElValues::Mva => write!(f, "mva"),
+            ElValues::Lue => write!(f, "lue"),
+            _ => Err(fmt::Error),
+        }
+    }
+}
+
+/// Returns the value of the property 'el' if set.
+#[deprecated]
+pub fn el() -> Result<Option<Vec<ElValues>>> {
+    let result = match system_properties::read("el") {
+        Err(e) => Err(SysPropError::FetchError(e)),
+        Ok(Some(val)) => gen_parsers_and_formatters::parse_list(val.as_str()).map_err(SysPropError::ParseError).map(Some),
+        Ok(None) => Ok(None),
+    };
+    result
+}
+
+/// Sets the value of the property 'el', returns 'Ok' if successful.
+#[deprecated]
+pub fn set_el(v: &[ElValues]) -> Result<()> {
+    let value = gen_parsers_and_formatters::format_list(v);
+    system_properties::write("el", value.as_str()).map_err(SysPropError::SetError)
+}
+
+)";
+
+}  // namespace
+
+using namespace std::string_literals;
+
+TEST(SyspropTest, RustGenTest) {
+  TemporaryFile temp_file;
+
+  // strlen is optimized for constants, so don't worry about it.
+  ASSERT_EQ(write(temp_file.fd, kTestSyspropFile, strlen(kTestSyspropFile)),
+            strlen(kTestSyspropFile));
+  close(temp_file.fd);
+  temp_file.fd = -1;
+
+  TemporaryDir temp_dir;
+
+  std::pair<sysprop::Scope, const char*> tests[] = {
+      {sysprop::Scope::Internal, kExpectedInternalOutput},
+      {sysprop::Scope::Public, kExpectedPublicOutput},
+  };
+
+  for (auto [scope, expected_output] : tests) {
+    std::string rust_output_path = temp_dir.path + "/lib.rs"s;
+
+    ASSERT_RESULT_OK(GenerateRustLibrary(temp_file.path, scope, temp_dir.path));
+
+    std::string rust_output;
+    ASSERT_TRUE(
+        android::base::ReadFileToString(rust_output_path, &rust_output, true));
+    EXPECT_EQ(rust_output, expected_output);
+
+    unlink(rust_output.c_str());
+    rmdir((temp_dir.path + "/com/somecompany"s).c_str());
+    rmdir((temp_dir.path + "/com"s).c_str());
+  }
+}
\ No newline at end of file