cleanup + fixed no source files bug
This commit is contained in:
parent
70b7b7912b
commit
81d51c0dbf
5 changed files with 132 additions and 216 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,3 +1,4 @@
|
|||
/target
|
||||
/testing_grounds
|
||||
/build
|
||||
Makefile
|
||||
|
|
128
Cargo.lock
generated
128
Cargo.lock
generated
|
@ -11,54 +11,12 @@ dependencies = [
|
|||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d664a92ecae85fd0a7392615844904654d1d5f5514837f471ddef4a057aba1b6"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
"anstyle-query",
|
||||
"anstyle-wincon",
|
||||
"colorchoice",
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c"
|
||||
dependencies = [
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-query"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-wincon"
|
||||
version = "3.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.4.11"
|
||||
|
@ -75,10 +33,8 @@ version = "4.4.11"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a216b506622bb1d316cd51328dce24e07bdff4a6128a47c7e7fad11878d5adbb"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
"strsim",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -99,12 +55,6 @@ version = "0.6.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1"
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.4.1"
|
||||
|
@ -179,12 +129,6 @@ version = "0.8.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.41"
|
||||
|
@ -201,75 +145,3 @@ name = "unicode-ident"
|
|||
version = "1.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
|
||||
|
|
|
@ -6,6 +6,7 @@ edition = "2021"
|
|||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "4.4.10", features = ["derive"] }
|
||||
clap = { version = "4.4.10", default_features = false, features = ["std",
|
||||
"derive", "usage", "help", "error-context"] }
|
||||
path-clean = "1.0.1"
|
||||
regex = "1.10.2"
|
||||
|
|
27
Makefile
27
Makefile
|
@ -1,27 +0,0 @@
|
|||
TARGET = foo
|
||||
CC = gcc
|
||||
CFLAGS = -std=c99
|
||||
LDLIBS = -lm
|
||||
#=mgen_start=#
|
||||
BUILDDIR = ./build
|
||||
OBJDIR = $(BUILDDIR)/obj
|
||||
$(BUILDDIR)/$(TARGET): $(OBJDIR)/main.o $(OBJDIR)/ops.o $(OBJDIR)/traps.o | $(OBJDIR) $(BUILDDIR)
|
||||
$(CC) $(LDFLAGS) -o $(BUILDDIR)/$(TARGET) $(OBJDIR)/main.o $(OBJDIR)/ops.o $(OBJDIR)/traps.o $(LDLIBS)
|
||||
$(OBJDIR)/main.o: ./testing_grounds/src/main.c ./testing_grounds/src/main.h ./testing_grounds/src/ops.h ./testing_grounds/src/traps.h | $(OBJDIR)
|
||||
$(CC) $(CFLAGS) -o $(OBJDIR)/main.o -c ./testing_grounds/src/main.c
|
||||
$(OBJDIR)/ops.o: ./testing_grounds/src/ops.c ./testing_grounds/src/main.h | $(OBJDIR)
|
||||
$(CC) $(CFLAGS) -o $(OBJDIR)/ops.o -c ./testing_grounds/src/ops.c
|
||||
$(OBJDIR)/traps.o: ./testing_grounds/src/traps.c ./testing_grounds/src/traps.h ./testing_grounds/src/main.h | $(OBJDIR)
|
||||
$(CC) $(CFLAGS) -o $(OBJDIR)/traps.o -c ./testing_grounds/src/traps.c
|
||||
$(BUILDDIR):
|
||||
mkdir -p $(BUILDDIR)
|
||||
$(OBJDIR):
|
||||
mkdir -p $(OBJDIR)
|
||||
.PHONY: clean
|
||||
clean:
|
||||
rm -r $(OBJDIR)
|
||||
rm -r $(BUILDDIR)
|
||||
.PHONY: run
|
||||
run: $(BUILDDIR)/$(TARGET)
|
||||
./$(BUILDDIR)/$(TARGET)
|
||||
#=mgen_end=#
|
183
src/main.rs
183
src/main.rs
|
@ -1,25 +1,40 @@
|
|||
use std::path::PathBuf;
|
||||
use std::io::ErrorKind;
|
||||
use std::fs::{self, DirEntry};
|
||||
use std::process::exit;
|
||||
|
||||
use regex::Regex;
|
||||
use clap::Parser;
|
||||
use path_clean::PathClean;
|
||||
|
||||
const START_KEY: &str = "#=mgen_start=#\n";
|
||||
const END_KEY: &str = "#=mgen_end=#";
|
||||
const END_KEY: &str = "\n#=mgen_end=#";
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
struct Args {
|
||||
/// Replace default make output with nice build messages (not yet implemented)
|
||||
/// Replace default make output with nice build messages (colors!)
|
||||
#[arg(short, long)]
|
||||
pretty: bool,
|
||||
|
||||
/// Generate a 'clean' rule that removes all build files
|
||||
#[arg(short, long)]
|
||||
clean: bool,
|
||||
|
||||
/// Generate a 'run' rule that allows you run the executable with 'make run'
|
||||
#[arg(short, long)]
|
||||
run: bool,
|
||||
|
||||
/// Configure build rule to create a library rather than an executable (NYI)
|
||||
#[arg(short, long)]
|
||||
library: bool,
|
||||
|
||||
/// Path to makefile
|
||||
#[arg(short, long, default_value = "./Makefile")]
|
||||
makefile: String,
|
||||
}
|
||||
|
||||
thread_local! {static ARGS: Args = Args::parse()}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[allow(dead_code)]
|
||||
enum BuildRuleKind<T> {
|
||||
|
@ -67,10 +82,9 @@ impl BuildRule {
|
|||
match path.exists() {
|
||||
true => {
|
||||
headers.push(String::from(path.to_str().unwrap()));
|
||||
println!("e {:?}", path);
|
||||
continue;
|
||||
},
|
||||
false => println!("n {:?}", path),
|
||||
false => (),
|
||||
}
|
||||
|
||||
for dir in makefile.include_dirs.iter() {
|
||||
|
@ -79,15 +93,13 @@ impl BuildRule {
|
|||
match path.exists() {
|
||||
true => {
|
||||
headers.push(String::from(path.to_str().unwrap()));
|
||||
println!("e {:?}", path);
|
||||
continue 'fileloop;
|
||||
},
|
||||
false => println!("n {:?}", path),
|
||||
false => (),
|
||||
}
|
||||
}
|
||||
|
||||
let path = file_dir.join(PathBuf::from(include_file));
|
||||
println!("t {:?}", path);
|
||||
headers.push(String::from(path.to_str().unwrap()));
|
||||
}
|
||||
|
||||
|
@ -117,17 +129,55 @@ impl BuildRule {
|
|||
}
|
||||
}
|
||||
|
||||
impl ToString for BuildRule {
|
||||
impl ToString for BuildRuleKind<BuildRule> {
|
||||
fn to_string(&self) -> String {
|
||||
let rule = match self {
|
||||
BuildRuleKind::Object(v) => v,
|
||||
BuildRuleKind::Target(v) => v,
|
||||
BuildRuleKind::Directory(v) => v,
|
||||
BuildRuleKind::Convenience(v) => v,
|
||||
};
|
||||
let mut ret = String::new();
|
||||
ret.push_str(&format!("{}:", self.target));
|
||||
for prereq in &self.prerequisites {
|
||||
ret.push_str(&format!(" {}", prereq));
|
||||
}
|
||||
ret.push_str(&format!("{}: ", rule.target));
|
||||
ret.push_str(&rule.prerequisites.join(" "));
|
||||
ret.push_str(&format!("\n"));
|
||||
for recipe_comm in &self.recipe_commands {
|
||||
ret.push_str(&format!("\t{}\n", recipe_comm));
|
||||
|
||||
ARGS.with(|args| {
|
||||
let mut command_prefix = "\t";
|
||||
let mut special = false;
|
||||
if args.pretty {
|
||||
command_prefix = "\t@";
|
||||
let verb = match self {
|
||||
BuildRuleKind::Object(_) => "Building",
|
||||
BuildRuleKind::Target(_) => "Linking",
|
||||
BuildRuleKind::Directory(_) => "Creating directory",
|
||||
BuildRuleKind::Convenience(_) => {
|
||||
special = true;
|
||||
if rule.target.contains("clean") {
|
||||
ret.push_str(&format!("{}printf '\\e[90m:: Cleaning up ...\\e[0m'\n", command_prefix));
|
||||
}
|
||||
if rule.target.contains("run") {
|
||||
ret.push_str(&format!("{}printf '\\e[34m:: Running\\e[0m \\e[1m%s\\e[0m \\e[34m...\\e[0m\\n' $(TARGET)\n", command_prefix));
|
||||
}
|
||||
""
|
||||
},
|
||||
};
|
||||
if !special {
|
||||
ret.push_str(&format!("{}printf '\\e[90m:: {}\\e[0m \\e[1m%s\\e[0m \\e[90m...\\e[0m' {}\n", command_prefix, verb,
|
||||
(PathBuf::from(&rule.target)).file_name().unwrap().to_str().unwrap()));
|
||||
}
|
||||
}
|
||||
|
||||
ret.push_str(&rule.recipe_commands.iter()
|
||||
.map(|v| format!("{}{}", command_prefix, v))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n"));
|
||||
|
||||
if args.pretty {
|
||||
ret.push_str(&format!("\n{}printf ' \\e[32mdone\\e[0m\\n'", command_prefix));
|
||||
}
|
||||
});
|
||||
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
@ -197,7 +247,6 @@ impl Makefile {
|
|||
include_dirs.push(String::from(dir));
|
||||
}
|
||||
|
||||
println!("{:?}", include_dirs);
|
||||
|
||||
include_dirs
|
||||
}
|
||||
|
@ -221,27 +270,23 @@ impl Makefile {
|
|||
}
|
||||
}
|
||||
|
||||
fn join_build_rule_vec(vector: &Vec<BuildRule>, seperator: &str) -> String{
|
||||
let mut ret = String::new();
|
||||
|
||||
for (i, rule) in vector.iter().enumerate() {
|
||||
if i != 0 {
|
||||
ret.push_str(seperator);
|
||||
}
|
||||
ret.push_str(&rule.to_string());
|
||||
}
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
impl ToString for RuleList {
|
||||
fn to_string(&self) -> String {
|
||||
let targets = join_build_rule_vec(&self.get_target_rules(), "");
|
||||
let objects = join_build_rule_vec(&self.get_object_rules(), "");
|
||||
let directories = join_build_rule_vec(&self.get_directory_rules(), "");
|
||||
let conveniences = join_build_rule_vec(&self.get_convenience_rules(), "");
|
||||
let targets = &self.get_target_rules().iter().map(|v| v.to_string()).collect::<Vec<_>>().join("\n");
|
||||
let objects = &self.get_object_rules().iter().map(|v| v.to_string()).collect::<Vec<_>>().join("\n");
|
||||
let directories = &self.get_directory_rules().iter().map(|v| v.to_string()).collect::<Vec<_>>().join("\n");
|
||||
let conveniences = &self.get_convenience_rules().iter().map(|v| v.to_string()).collect::<Vec<_>>().join("\n");
|
||||
|
||||
[targets, objects, directories, conveniences].concat()
|
||||
let all = [targets, objects, directories, conveniences];
|
||||
let mut nonzero: Vec<String> = Vec::new();
|
||||
|
||||
for category in all {
|
||||
if category.len() != 0 {
|
||||
nonzero.push(category.clone());
|
||||
}
|
||||
}
|
||||
|
||||
nonzero.join("\n\n")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -335,44 +380,44 @@ impl RuleList {
|
|||
}));
|
||||
}
|
||||
|
||||
fn get_target_rules(&self) -> Vec<BuildRule> {
|
||||
let mut ret: Vec<BuildRule> = Vec::new();
|
||||
fn get_target_rules(&self) -> Vec<BuildRuleKind<BuildRule>> {
|
||||
let mut ret: Vec<BuildRuleKind<BuildRule>> = Vec::new();
|
||||
for rule in &self.0 {
|
||||
match rule {
|
||||
BuildRuleKind::Target(r) => ret.push(r.clone()),
|
||||
BuildRuleKind::Target(r) => ret.push(BuildRuleKind::Target(r.clone())),
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
fn get_object_rules(&self) -> Vec<BuildRule> {
|
||||
let mut ret: Vec<BuildRule> = Vec::new();
|
||||
fn get_object_rules(&self) -> Vec<BuildRuleKind<BuildRule>> {
|
||||
let mut ret: Vec<BuildRuleKind<BuildRule>> = Vec::new();
|
||||
for rule in &self.0 {
|
||||
match rule {
|
||||
BuildRuleKind::Object(r) => ret.push(r.clone()),
|
||||
BuildRuleKind::Object(r) => ret.push(BuildRuleKind::Object(r.clone())),
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
fn get_directory_rules(&self) -> Vec<BuildRule> {
|
||||
let mut ret: Vec<BuildRule> = Vec::new();
|
||||
fn get_directory_rules(&self) -> Vec<BuildRuleKind<BuildRule>> {
|
||||
let mut ret: Vec<BuildRuleKind<BuildRule>> = Vec::new();
|
||||
for rule in &self.0 {
|
||||
match rule {
|
||||
BuildRuleKind::Directory(r) => ret.push(r.clone()),
|
||||
BuildRuleKind::Directory(r) => ret.push(BuildRuleKind::Directory(r.clone())),
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
fn get_convenience_rules(&self) -> Vec<BuildRule> {
|
||||
let mut ret: Vec<BuildRule> = Vec::new();
|
||||
fn get_convenience_rules(&self) -> Vec<BuildRuleKind<BuildRule>> {
|
||||
let mut ret: Vec<BuildRuleKind<BuildRule>> = Vec::new();
|
||||
for rule in &self.0 {
|
||||
match rule {
|
||||
BuildRuleKind::Convenience(r) => ret.push(r.clone()),
|
||||
BuildRuleKind::Convenience(r) => ret.push(BuildRuleKind::Convenience(r.clone())),
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
|
@ -392,31 +437,55 @@ fn file_is_hidden(file: &DirEntry) -> bool{
|
|||
}
|
||||
|
||||
fn main() {
|
||||
let args = Args::parse();
|
||||
let makefile = Makefile::new_from_path(&args.makefile);
|
||||
|
||||
let mut generated_content = String::new();
|
||||
|
||||
// Only include directory defaults if the user has not specified them
|
||||
if ! makefile.contains_builddir_def() {
|
||||
generated_content.push_str("BUILDDIR = ./build\n");
|
||||
}
|
||||
|
||||
if ! makefile.contains_objdir_def() {
|
||||
generated_content.push_str("OBJDIR = $(BUILDDIR)/obj\n");
|
||||
}
|
||||
let mut makefile_name = String::new();
|
||||
ARGS.with(|args| {
|
||||
makefile_name.push_str(&args.makefile);
|
||||
});
|
||||
let makefile = Makefile::new_from_path(&makefile_name);
|
||||
|
||||
let mut rule_list = RuleList::new();
|
||||
rule_list.recursively_gen_objects(".", &makefile).unwrap_or_else(|e| {
|
||||
panic!("error: problem in recursive search: {e}");
|
||||
});
|
||||
|
||||
if rule_list.0.is_empty() {
|
||||
eprintln!("no source files found, nothing to do. exiting...");
|
||||
exit(0);
|
||||
}
|
||||
|
||||
let mut generated_content = String::new();
|
||||
|
||||
// Only include directory defaults if the user has not specified them
|
||||
let mut dirdefwritten = false;
|
||||
if ! makefile.contains_builddir_def() {
|
||||
generated_content.push_str("BUILDDIR = build\n");
|
||||
dirdefwritten = true;
|
||||
}
|
||||
|
||||
if ! makefile.contains_objdir_def() {
|
||||
generated_content.push_str("OBJDIR = $(BUILDDIR)/obj\n");
|
||||
dirdefwritten = true;
|
||||
}
|
||||
|
||||
if dirdefwritten {
|
||||
generated_content.push_str("\n");
|
||||
}
|
||||
|
||||
|
||||
|
||||
rule_list.add_target_rule();
|
||||
|
||||
rule_list.add_directory_rule("$(BUILDDIR)");
|
||||
rule_list.add_directory_rule("$(OBJDIR)");
|
||||
ARGS.with(|args| {
|
||||
if args.clean {
|
||||
rule_list.add_clean_rule();
|
||||
}
|
||||
if args.run {
|
||||
rule_list.add_run_rule();
|
||||
}
|
||||
});
|
||||
|
||||
generated_content.push_str(&rule_list.to_string());
|
||||
|
||||
|
|
Loading…
Reference in a new issue