Commit 0855965e by Maarten L. Hekkelman

Documenting more

Fixed colouring output manipulators
parent fe3cbdab
......@@ -243,7 +243,6 @@ set(project_sources
${PROJECT_SOURCE_DIR}/src/pdb/pdb_record.hpp
${PROJECT_SOURCE_DIR}/src/pdb/pdb2cif_remark_3.hpp
${PROJECT_SOURCE_DIR}/src/pdb/pdb2cif_remark_3.cpp
${PROJECT_SOURCE_DIR}/src/pdb/tls.cpp
)
set(project_headers
......@@ -268,6 +267,8 @@ set(project_headers
${PROJECT_SOURCE_DIR}/include/cif++/model.hpp
${PROJECT_SOURCE_DIR}/include/cif++/pdb.hpp
${PROJECT_SOURCE_DIR}/include/cif++/pdb/cif2pdb.hpp
${PROJECT_SOURCE_DIR}/include/cif++/pdb/io.hpp
${PROJECT_SOURCE_DIR}/include/cif++/pdb/pdb2cif.hpp
......
Introduction
============
Information on 3D structures of proteins originally came formatted in [PDB](http://www.wwpdb.org/documentation/file-format-content/format33/v3.3.html) files. Although the specification for this format had some real restrictions like a mandatory HEADER and CRYST line, many programs implemented this very poorly often writing out only ATOM records. And users became used to this.
Information on 3D structures of proteins originally came formatted in `PDB <http://www.wwpdb.org/documentation/file-format-content/format33/v3.3.html>`_ files. Although the specification for this format had some real restrictions like a mandatory HEADER and CRYST line, many programs implemented this very poorly often writing out only ATOM records. And users became used to this.
The PDB format has some severe limitations rendering it useless for all but very small protein structures. A new format called [mmCIF](https://mmcif.wwpdb.org/) has been around for decades and now is the default format for the Protein Data Bank.
The PDB format has some severe limitations rendering it useless for all but very small protein structures. A new format called `mmCIF <https://mmcif.wwpdb.org/>`_ has been around for decades and now is the default format for the Protein Data Bank.
The software developed in the [PDB-REDO](https://pdb-redo.eu/) project aims at improving 3D models based on original experimental data. For this, the tools need to be able to work with both PDB and mmCIF files. A decision was made to make mmCIF leading internally in all programs and convert PDB directly into mmCIF before processing the data. A robust conversion had to be developed to make this possible since, as noted above, files can come with more or less information making it sometimes needed to do a sequence alignment to find out the exact residue numbers.
The software developed in the `PDB-REDO <https://pdb-redo.eu/>`_ project aims at improving 3D models based on original experimental data. For this, the tools need to be able to work with both PDB and mmCIF files. A decision was made to make mmCIF leading internally in all programs and convert PDB directly into mmCIF before processing the data. A robust conversion had to be developed to make this possible since, as noted above, files can come with more or less information making it sometimes needed to do a sequence alignment to find out the exact residue numbers.
And so libcif++ came to life, a library to work with mmCIF files. Work on this library started early 2017 and has developed quite a bit since then. To reduce dependency on other libraries, some functionality was added that is not strictly related to reading and writing mmCIF files but may be useful nonetheless. This is mostly code that is used in 3D calculations and symmetry operations.
......
......@@ -37,5 +37,5 @@
#include "cif++/model.hpp"
#include "cif++/pdb/io.hpp"
#include "cif++/pdb.hpp"
#include "cif++/gzio.hpp"
\ No newline at end of file
/*-
* SPDX-License-Identifier: BSD-2-Clause
*
* Copyright (c) 2023 NKI/AVL, Netherlands Cancer Institute
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include "cif++/file.hpp"
/**
* @file pdb.hpp
*
* This file presents the API to read and write files in the
* legacy and ancient PDB format.
*
* The code works on the basis of best effort since it is
* impossible to have correct round trip fidelity.
*
*/
namespace cif::pdb
{
/// --------------------------------------------------------------------
// PDB to mmCIF
/** @brief Read a file in either mmCIF or PDB format from file @a file,
* compressed or not, depending on the content.
*/
file read(const std::filesystem::path &file);
/** @brief Read a file in either mmCIF or PDB format from std::istream @a is,
* compressed or not, depending on the content.
*/
file read(std::istream &is);
/**
* @brief Read a file in legacy PDB format from std::istream @a is and
* put the data into @a cifFile
*/
file read_pdb_file(std::istream &pdbFile);
// mmCIF to PDB
/** @brief Write out the data in @a db in legacy PDB format
* to std::ostream @a os
*/
void write(std::ostream &os, const datablock &db);
/** @brief Write out the data in @a f in legacy PDB format
* to std::ostream @a os
*/
inline void write(std::ostream &os, const file &f)
{
write(os, f.front());
}
/** @brief Write out the data in @a db to file @a file
* in legacy PDB format or mmCIF format, depending on the
* filename extension.
*
* If extension of @a file is *.gz* the resulting file will
* be written in gzip compressed format.
*/
void write(const std::filesystem::path &file, const datablock &db);
/** @brief Write out the data in @a f to file @a file
* in legacy PDB format or mmCIF format, depending on the
* filename extension.
*
* If extension of @a file is *.gz* the resulting file will
* be written in gzip compressed format.
*/
inline void write(const std::filesystem::path &p, const file &f)
{
write(p, f.front());
}
// --------------------------------------------------------------------
// Other I/O related routines
/** @brief Return the HEADER line for the data in @a data
*
* The line returned should be compatible with the legacy PDB
* format and is e.g. used in the DSSP program.
*
* @param data The datablock to use as source for the requested data
* @param truncate_at The maximum length of the line returned
*/
std::string get_HEADER_line(const datablock &data, std::string::size_type truncate_at = 127);
/** @brief Return the COMPND line for the data in @a data
*
* The line returned should be compatible with the legacy PDB
* format and is e.g. used in the DSSP program.
*
* @param data The datablock to use as source for the requested data
* @param truncate_at The maximum length of the line returned
*/
std::string get_COMPND_line(const datablock &data, std::string::size_type truncate_at = 127);
/** @brief Return the SOURCE line for the data in @a data
*
* The line returned should be compatible with the legacy PDB
* format and is e.g. used in the DSSP program.
*
* @param data The datablock to use as source for the requested data
* @param truncate_at The maximum length of the line returned
*/
std::string get_SOURCE_line(const datablock &data, std::string::size_type truncate_at = 127);
/** @brief Return the AUTHOR line for the data in @a data
*
* The line returned should be compatible with the legacy PDB
* format and is e.g. used in the DSSP program.
*
* @param data The datablock to use as source for the requested data
* @param truncate_at The maximum length of the line returned
*/
std::string get_AUTHOR_line(const datablock &data, std::string::size_type truncate_at = 127);
} // namespace pdbx
......@@ -26,19 +26,8 @@
#pragma once
#include "cif++/datablock.hpp"
/// \file cif2pdb.hpp
/// \deprecated This file is no longer used. Please use "cif++/pdb.hpp" instead
namespace cif::pdb
{
/// \brief Just the HEADER, COMPND, SOURCE and AUTHOR lines
void write_header_lines(std::ostream &os, const datablock &data);
std::string get_HEADER_line(const datablock &data, std::string::size_type truncate_at = 127);
std::string get_COMPND_line(const datablock &data, std::string::size_type truncate_at = 127);
std::string get_SOURCE_line(const datablock &data, std::string::size_type truncate_at = 127);
std::string get_AUTHOR_line(const datablock &data, std::string::size_type truncate_at = 127);
#warning "Use of this file is deprecated, please use "cif++/pdb.hpp"
} // namespace pdbx
......@@ -26,37 +26,7 @@
#pragma once
#include "cif++/datablock.hpp"
/// \file io.hpp
/// \deprecated This file is no longer used. Please use "cif++/pdb.hpp" instead
namespace cif::pdb
{
/// \brief Read a file in either mmCIF or PDB format, compressed or not,
/// depending on the content.
file read(const std::filesystem::path &file);
/// \brief Read a file in either mmCIF or PDB format, compressed or not,
/// depending on the content.
file read(std::istream &is);
/// \brief Write out a file in PDB format
void write(std::ostream &os, const datablock &db);
/// \brief Write out a file in PDB format
inline void write(std::ostream &os, const file &f)
{
write(os, f.front());
}
/// \brief Write out a file in PDB format or mmCIF format, depending on the filename extension
void write(const std::filesystem::path &file, const datablock &db);
/// \brief Write out a file in PDB format or mmCIF format, depending on the filename extension
inline void write(const std::filesystem::path &p, const file &f)
{
write(p, f.front());
}
}
\ No newline at end of file
#warning "Use of this file is deprecated, please use "cif++/pdb.hpp"
\ No newline at end of file
......@@ -26,13 +26,7 @@
#pragma once
#include "cif++/file.hpp"
/// \file pdb2cif.hpp
/// \deprecated This file is no longer used. Please use "cif++/pdb.hpp" instead
namespace cif::pdb
{
void ReadPDBFile(std::istream &pdbFile, file &cifFile);
} // namespace cif::pdb
\ No newline at end of file
#warning "Use of this file is deprecated, please use "cif++/pdb.hpp"
\ No newline at end of file
......@@ -26,28 +26,7 @@
#pragma once
#include "cif++/datablock.hpp"
#include <string>
#include <tuple>
#include <vector>
/// \file tls.hpp
/// \deprecated This code has been moved to libpdb-redo
namespace cif
{
struct tls_selection;
struct tls_residue;
struct tls_selection
{
virtual ~tls_selection() {}
virtual void collect_residues(cif::datablock &db, std::vector<tls_residue> &residues, std::size_t indentLevel = 0) const = 0;
std::vector<std::tuple<std::string, int, int>> get_ranges(cif::datablock &db, bool pdbNamespace) const;
};
// Low level: get the selections
std::unique_ptr<tls_selection> parse_tls_selection_details(const std::string &program, const std::string &selection);
} // namespace cif
#warning "This code has been moved to libpdb-redo"
/*-
* SPDX-License-Identifier: BSD-2-Clause
*
*
* Copyright (c) 2020 NKI/AVL, Netherlands Cancer Institute
*
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
......@@ -55,13 +55,21 @@
#endif
/** \file utilities.hpp
*
* This file contains code that is very generic in nature like a progress_bar
*
* This file contains code that is very generic in nature like a progress_bar
* and classes you can use to colourise output text.
*/
namespace cif
{
/**
* @brief The global variable VERBOSE contains the level of verbosity
* requested. A value of 0 is normal, with some output on error conditions.
* A value > 0 will result in more output, the higher the value, the more
* output. A value < 0 will make the library silent, even in error
* conditions.
*/
extern CIFPP_EXPORT int VERBOSE;
/// return the git 'build' number
......@@ -69,109 +77,138 @@ std::string get_version_nr();
// --------------------------------------------------------------------
/// Return the width of the current output terminal, or the default 80 in case output is not to a terminal
uint32_t get_terminal_width();
// --------------------------------------------------------------------
/// Return the path of the current executable
std::string get_executable_path();
// --------------------------------------------------------------------
// some manipulators to write coloured text to terminals
/// @brief The defined string colours
enum class StringColour
{
BLACK = 0,
RED,
GREEN,
YELLOW,
BLUE,
MAGENTA,
CYAN,
WHITE,
NONE = 9
};
template<StringColour C>
struct ColourDefinition
{
static constexpr StringColour value = C;
};
enum class TextStyle
{
REGULAR = 22,
BOLD = 1
};
template<TextStyle S>
struct StyleDefinition
{
static constexpr TextStyle value = S;
};
/**
* When writing out text to the terminal it is often useful to have
* some of the text colourised. But only if the output is really a
* terminal since colouring text is done using escape sequences
* an if output is redirected to a file, these escape sequences end up
* in the file making the real text less easy to read.
*
* The code presented here is rather basic. It mimics the std::quoted
* manipulator in that it will colour a string with optionally
* requested colours and text style.
*
* Example:
*
* @code {.cpp}
* using namespace cif::colour;
* std::cout << cif::coloured("Hello, world!", white, red, bold) << '\n';
* @endcode
*
*/
template<typename ForeColour, typename BackColour, typename Style>
struct ColourAndStyle
namespace colour
{
static constexpr StringColour fore_colour = ForeColour::value;
static constexpr StringColour back_colour = BackColour::value;
static constexpr TextStyle text_style = Style::value;
static constexpr int fore_colour_number = static_cast<int>(fore_colour) + 30;
static constexpr int back_colour_number = static_cast<int>(back_colour) + 40;
static constexpr int style_number = static_cast<int>(text_style);
friend std::ostream &operator<<(std::ostream &os, ColourAndStyle clr)
/// @brief The defined colours
enum colour_type
{
bool use_colour = false;
if (os.rdbuf() == std::cout.rdbuf() and isatty(STDOUT_FILENO))
use_colour = true;
else if (os.rdbuf() == std::cerr.rdbuf() and isatty(STDERR_FILENO))
use_colour = true;
if (use_colour)
black = 0,
red,
green,
yellow,
blue,
magenta,
cyan,
white,
none = 9
};
enum style_type
{
bold = 1,
underlined = 4,
blink = 5,
inverse = 7,
regular = 22,
};
namespace detail
{
/**
* @brief Struct for delimited strings.
*/
template <typename StringType>
struct coloured_string_t
{
if (fore_colour == StringColour::NONE and back_colour == StringColour::NONE)
os << "\033[0m";
else
os << "\033[" << fore_colour_number << ';' << style_number << ';' << back_colour_number << 'm';
}
return os;
}
};
static_assert(std::is_reference_v<StringType> or std::is_pointer_v<StringType>,
"String type must be pointer or reference");
coloured_string_t(StringType s, colour_type fc, colour_type bc, style_type st)
: m_str(s)
, m_fore_colour(static_cast<int>(fc) + 30)
, m_back_colour(static_cast<int>(bc) + 40)
, m_style(static_cast<int>(st))
{
}
coloured_string_t &operator=(coloured_string_t &) = delete;
template <typename char_type, typename traits_type>
friend std::basic_ostream<char_type, traits_type> &operator<<(
std::basic_ostream<char_type, traits_type> &os, const coloured_string_t &cs)
{
bool use_colour = false;
if (os.rdbuf() == std::cout.rdbuf() and isatty(STDOUT_FILENO))
use_colour = true;
else if (os.rdbuf() == std::cerr.rdbuf() and isatty(STDERR_FILENO))
use_colour = true;
if (use_colour)
{
os << "\033[" << cs.m_fore_colour << ';' << cs.m_style << ';' << cs.m_back_colour << 'm'
<< cs.m_str
<< "\033[0m";
}
return os;
}
StringType m_str;
int m_fore_colour, m_back_colour;
int m_style;
};
} // namespace detail
} // namespace colour
/**
* @brief Manipulator for coloured strings.
* @param str String to quote.
* @param fg Foreground (=text) colour to use
* @param bg Background colour to use
* @param st Text style to use
*/
template <typename char_type>
inline auto coloured(const char_type *str,
colour::colour_type fg, colour::colour_type bg = colour::colour_type::none,
colour::style_type st = colour::style_type::regular)
{
return colour::detail::coloured_string_t<const char_type *>(str, fg, bg, st);
}
template<typename ForeColour, typename BackColour>
constexpr auto coloured(const ForeColour fore, const BackColour back)
template <typename char_type, typename traits_type, typename allocator_type>
inline auto coloured(const std::basic_string<char_type, traits_type, allocator_type> &str,
colour::colour_type fg, colour::colour_type bg = colour::colour_type::none,
colour::style_type st = colour::style_type::regular)
{
return ColourAndStyle<ForeColour, BackColour, StyleDefinition<TextStyle::REGULAR>>{};
return colour::detail::coloured_string_t<const std::basic_string<char_type, traits_type, allocator_type> &>(str, fg, bg, st);
}
template<typename ForeColour, typename BackColour, typename Style>
constexpr auto coloured(const ForeColour fore, const BackColour back, Style style)
template <typename char_type, typename traits_type, typename allocator_type>
inline auto coloured(std::basic_string<char_type, traits_type, allocator_type> &str,
colour::colour_type fg, colour::colour_type bg = colour::colour_type::none,
colour::style_type st = colour::style_type::regular)
{
return ColourAndStyle<ForeColour, BackColour, Style>{};
return colour::detail::coloured_string_t<std::basic_string<char_type, traits_type, allocator_type> &>(str, fg, bg, st);
}
namespace colour
template <typename char_type, typename traits_type>
inline auto coloured(std::basic_string_view<char_type, traits_type> &str,
colour::colour_type fg, colour::colour_type bg = colour::colour_type::none,
colour::style_type st = colour::style_type::regular)
{
constexpr ColourDefinition<StringColour::BLACK> black = ColourDefinition<StringColour::BLACK>();
constexpr ColourDefinition<StringColour::RED> red = ColourDefinition<StringColour::RED>();
constexpr ColourDefinition<StringColour::GREEN> green = ColourDefinition<StringColour::GREEN>();
constexpr ColourDefinition<StringColour::YELLOW> yellow = ColourDefinition<StringColour::YELLOW>();
constexpr ColourDefinition<StringColour::BLUE> blue = ColourDefinition<StringColour::BLUE>();
constexpr ColourDefinition<StringColour::MAGENTA> magenta = ColourDefinition<StringColour::MAGENTA>();
constexpr ColourDefinition<StringColour::CYAN> cyan = ColourDefinition<StringColour::CYAN>();
constexpr ColourDefinition<StringColour::WHITE> white = ColourDefinition<StringColour::WHITE>();
constexpr ColourDefinition<StringColour::NONE> none = ColourDefinition<StringColour::NONE>();
constexpr StyleDefinition<TextStyle::REGULAR> regular = StyleDefinition<TextStyle::REGULAR>();
constexpr StyleDefinition<TextStyle::BOLD> bold = StyleDefinition<TextStyle::BOLD>();
constexpr auto reset = cif::coloured(none, none, regular);
return colour::detail::coloured_string_t<std::basic_string_view<char_type, traits_type> &>(str, fg, bg, st);
}
// --------------------------------------------------------------------
......
......@@ -25,8 +25,6 @@
*/
#include "cif++.hpp"
#include "cif++/pdb/cif2pdb.hpp"
#include "cif++/gzio.hpp"
#include <cmath>
#include <deque>
......
......@@ -26,9 +26,7 @@
#include "pdb2cif_remark_3.hpp"
#include <cif++.hpp>
#include <cif++/pdb/pdb2cif.hpp>
#include <cif++/gzio.hpp>
#include "cif++.hpp"
#include <iomanip>
#include <map>
......@@ -39,10 +37,8 @@ using cif::category;
using cif::datablock;
using cif::iequals;
using cif::key;
// using cif::row;
using cif::to_lower;
using cif::to_lower_copy;
// using cif::compound_factory;
// --------------------------------------------------------------------
// attempt to come up with better error handling
......@@ -6054,7 +6050,7 @@ int PDBFileParser::PDBChain::AlignResToSeqRes()
// C++ is getting closer to Pascal :-)
auto printAlignment = [&tb, highX, highY, &rx, &ry, this]()
{
std::cerr << std::string(cif::get_terminal_width(), '-') << std::endl
std::cerr << std::string(22, '-') << std::endl
<< "Alignment for chain " << mDbref.chainID << std::endl
<< std::endl;
std::vector<std::pair<std::string, std::string>> alignment;
......@@ -6186,7 +6182,7 @@ bool PDBFileParser::PDBChain::SameSequence(const PDBChain &rhs) const
// --------------------------------------------------------------------
void ReadPDBFile(std::istream &pdbFile, cif::file &cifFile)
void read_pdb_file(std::istream &pdbFile, cif::file &cifFile)
{
PDBFileParser p;
......@@ -6214,7 +6210,7 @@ file read(std::istream &is)
// is 'H'. It is as simple as that.
if (ch == 'h' or ch == 'H')
ReadPDBFile(is, result);
read_pdb_file(is, result);
else
{
try
......
......@@ -1060,7 +1060,7 @@ bool Remark3Parser::match(const char *expr, int nextState)
{
using namespace colour;
std::cerr << coloured(white, red, bold) << "No match:" << reset << " '" << expr << '\'' << std::endl;
std::cerr << coloured("No match:", white, red, bold) << " '" << expr << '\'' << std::endl;
}
return result;
......@@ -1124,7 +1124,7 @@ float Remark3Parser::parse()
{
using namespace colour;
std::cerr << coloured(white, red, bold) << "Dropping line:" << reset << " '" << mLine << '\'' << std::endl;
std::cerr << coloured("Dropping line:", white, red, bold) << " '" << mLine << '\'' << std::endl;
}
++dropped;
......
......@@ -86,23 +86,6 @@ uint32_t get_terminal_width()
return csbi.srWindow.Right - csbi.srWindow.Left + 1;
}
std::string GetExecutablePath()
{
WCHAR buffer[4096];
DWORD n = ::GetModuleFileNameW(nullptr, buffer, sizeof(buffer) / sizeof(WCHAR));
if (n == 0)
throw std::runtime_error("could not get exe path");
std::wstring ws(buffer);
// convert from utf16 to utf8
std::wstring_convert<std::codecvt_utf8<wchar_t>> conv1;
std::string u8str = conv1.to_bytes(ws);
return u8str;
}
#else
#include <limits.h>
......@@ -120,17 +103,6 @@ uint32_t get_terminal_width()
return result;
}
std::string get_executable_path()
{
using namespace std::literals;
// This used to be PATH_MAX, but lets simply assume 1024 is enough...
char path[1024] = "";
if (readlink("/proc/self/exe", path, sizeof(path)) == -1)
throw std::runtime_error("could not get exe path "s + strerror(errno));
return {path};
}
#endif
// --------------------------------------------------------------------
......
......@@ -90,11 +90,11 @@ BOOST_AUTO_TEST_CASE(clr_1)
{
using namespace cif::colour;
std::cout << "Hello, " << cif::coloured(white, red, regular) << "world!" << reset << std::endl
<< "Hello, " << cif::coloured(white, red, bold) << "world!" << reset << std::endl
<< "Hello, " << cif::coloured(black, red) << "world!" << reset << std::endl
<< "Hello, " << cif::coloured(white, green) << "world!" << reset << std::endl
<< "Hello, " << cif::coloured(white, blue) << "world!" << reset << std::endl
<< "Hello, " << cif::coloured(blue, white) << "world!" << reset << std::endl
<< "Hello, " << cif::coloured(red, white, bold) << "world!" << reset << std::endl;
std::cout << "Hello, " << cif::coloured("world!", white, red, regular) << std::endl
<< "Hello, " << cif::coloured("world!", white, red, bold) << std::endl
<< "Hello, " << cif::coloured("world!", black, red) << std::endl
<< "Hello, " << cif::coloured("world!", white, green) << std::endl
<< "Hello, " << cif::coloured("world!", white, blue) << std::endl
<< "Hello, " << cif::coloured("world!", blue, white) << std::endl
<< "Hello, " << cif::coloured("world!", red, white, bold) << std::endl;
}
\ No newline at end of file
#include <cif++/utilities.hpp>
#include "cif++/utilities.hpp"
#include <random>
#include <thread>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment