/** * @file namedtempfile.h * @author Nat Goodspeed * @date 2012-01-13 * @brief NamedTempFile class for tests that need disk files as fixtures. * * $LicenseInfo:firstyear=2012&license=viewerlgpl$ * Copyright (c) 2012, Linden Research, Inc. * $/LicenseInfo$ */ #if ! defined(LL_NAMEDTEMPFILE_H) #define LL_NAMEDTEMPFILE_H #include "llerror.h" #include "llstring.h" #include "stringize.h" #include #include #include #include #include #include #include #include /** * Create a text file with specified content "somewhere in the * filesystem," cleaning up when it goes out of scope. */ class NamedTempFile: public boost::noncopyable { LOG_CLASS(NamedTempFile); public: NamedTempFile(const std::string_view& pfx, const std::string_view& content, const std::string_view& sfx=std::string_view("")) { createFile(pfx, [&content](std::ostream& out){ out << content; }, sfx); } // Disambiguate when passing string literal -- unclear why a string // literal should be ambiguous wrt std::string_view and Streamer NamedTempFile(const std::string_view& pfx, const char* content, const std::string_view& sfx=std::string_view("")) { createFile(pfx, [&content](std::ostream& out){ out << content; }, sfx); } // Function that accepts an ostream ref and (presumably) writes stuff to // it, e.g.: // (boost::phoenix::placeholders::arg1 << "the value is " << 17 << '\n') typedef std::function Streamer; NamedTempFile(const std::string_view& pfx, const Streamer& func, const std::string_view& sfx=std::string_view("")) { createFile(pfx, func, sfx); } virtual ~NamedTempFile() { boost::filesystem::remove(mPath); } std::string getName() const { return mPath.string(); } template void peep_via(CALLABLE&& callable) const { std::forward(callable)(stringize("File '", mPath, "' contains:")); boost::filesystem::ifstream reader(mPath, std::ios::binary); std::string line; while (std::getline(reader, line)) std::forward(callable)(line); std::forward(callable)("---"); } void peep_log() const { peep_via([](const std::string& line){ LL_DEBUGS() << line << LL_ENDL; }); } void peep(std::ostream& out=std::cout) const { peep_via([&out](const std::string& line){ out << line << '\n'; }); } friend std::ostream& operator<<(std::ostream& out, const NamedTempFile& self) { self.peep(out); return out; } static boost::filesystem::path temp_path(const std::string_view& pfx="", const std::string_view& sfx="") { // This variable is set by GitHub actions and is the recommended place // to put temp files belonging to an actions job. const char* RUNNER_TEMP = getenv("RUNNER_TEMP"); boost::filesystem::path tempdir{ // if RUNNER_TEMP is set and not empty (RUNNER_TEMP && *RUNNER_TEMP)? boost::filesystem::path(RUNNER_TEMP) : // use RUNNER_TEMP if available boost::filesystem::temp_directory_path()}; // else canonical temp dir boost::filesystem::path tempname{ // use filename template recommended by unique_path() doc, but // with underscores instead of hyphens: some use cases involve // temporary Python scripts tempdir / stringize(pfx, "%%%%_%%%%_%%%%_%%%%", sfx) }; return boost::filesystem::unique_path(tempname); } protected: void createFile(const std::string_view& pfx, const Streamer& func, const std::string_view& sfx) { // Create file in a temporary place. mPath = temp_path(pfx, sfx); boost::filesystem::ofstream out{ mPath, std::ios::binary }; // Write desired content. func(out); } boost::filesystem::path mPath; }; /** * Create a NamedTempFile with a specified filename extension. This is useful * when, for instance, you must be able to use the file in a Python import * statement. */ class NamedExtTempFile: public NamedTempFile { LOG_CLASS(NamedExtTempFile); public: NamedExtTempFile(const std::string& ext, const std::string_view& content): NamedTempFile(remove_dot(ext), content, ensure_dot(ext)) {} // Disambiguate when passing string literal NamedExtTempFile(const std::string& ext, const char* content): NamedTempFile(remove_dot(ext), content, ensure_dot(ext)) {} NamedExtTempFile(const std::string& ext, const Streamer& func): NamedTempFile(remove_dot(ext), func, ensure_dot(ext)) {} static std::string ensure_dot(const std::string& ext) { if (ext.empty()) { // What SHOULD we do when the caller makes a point of using // NamedExtTempFile to generate a file with a particular // extension, then passes an empty extension? Use just "."? That // sounds like a Bad Idea, especially on Windows. Treat that as a // coding error. LL_ERRS("NamedExtTempFile") << "passed empty extension" << LL_ENDL; } if (ext[0] == '.') { return ext; } return "." + ext; } static std::string remove_dot(const std::string& ext) { std::string::size_type found = ext.find_first_not_of("."); if (found == std::string::npos) { return ext; } return ext.substr(found); } }; #endif /* ! defined(LL_NAMEDTEMPFILE_H) */