diff --git a/example/info.cpp b/example/info.cpp index 9b53b1e..ab419a3 100644 --- a/example/info.cpp +++ b/example/info.cpp @@ -7,6 +7,8 @@ int main() println("Papilio Charontis version {0[0]}.{0[1]}.{0[2]}", get_version()); println(); + println("Is terminal: {}", os::is_terminal(stdout)); + println("Library and compiler information:"); println("PAPILIO_CPLUSPLUS = {:d}L", PAPILIO_CPLUSPLUS); diff --git a/include/papilio/color.hpp b/include/papilio/color.hpp index fe4b331..2c0d087 100644 --- a/include/papilio/color.hpp +++ b/include/papilio/color.hpp @@ -129,10 +129,12 @@ PAPILIO_EXPORT class text_style } template - static Iterator reset(Iterator it) + Iterator reset(Iterator it) const { constexpr char esc[] = "\033[0m"; - return std::copy_n(esc, 4, it); + if(has_style() || has_foreground()) + it = std::copy_n(esc, 4, it); + return it; } private: diff --git a/include/papilio/os/os.hpp b/include/papilio/os/os.hpp new file mode 100644 index 0000000..82b852b --- /dev/null +++ b/include/papilio/os/os.hpp @@ -0,0 +1,28 @@ +#ifndef PAPILIO_OS_OS_HPP +#define PAPILIO_OS_OS_HPP + +#pragma once + +#include +#include +#include +#include + +namespace papilio::os +{ +bool is_terminal(std::FILE* file); + +void output_conv( + std::FILE* file, + std::string_view out +); + +void output_nonconv( + std::FILE* file, + std::string_view out +); +} // namespace papilio::os + +#include + +#endif diff --git a/include/papilio/print.hpp b/include/papilio/print.hpp index 8c33e82..6254920 100644 --- a/include/papilio/print.hpp +++ b/include/papilio/print.hpp @@ -3,10 +3,9 @@ #pragma once -#include // FILE* #include #include -#include "macros.hpp" +#include "os/os.hpp" #include "format.hpp" #include "color.hpp" #include "detail/prefix.hpp" @@ -15,139 +14,14 @@ namespace papilio { namespace detail { - // output iterator for C FILE* - class fp_iterator - { - public: - using iterator_category = std::output_iterator_tag; - using value_type = char; - using difference_type = std::ptrdiff_t; - - fp_iterator() = delete; - fp_iterator(const fp_iterator&) noexcept = default; - - fp_iterator(std::FILE* fp) noexcept - : m_fp(fp) {} - - fp_iterator& operator=(const fp_iterator&) noexcept = default; - - fp_iterator& operator=(char ch) - { - write(ch); - return *this; - } - - fp_iterator& operator*() noexcept - { - return *this; - } - - fp_iterator& operator++() noexcept - { - return *this; - } - - fp_iterator operator++(int) noexcept - { - return *this; - } - - std::FILE* get() const noexcept - { - return m_fp; - } - - private: - std::FILE* m_fp; - - void write(char ch); - }; - - class fp_iterator_conv_base - { - public: - fp_iterator_conv_base() noexcept = default; - fp_iterator_conv_base(const fp_iterator_conv_base&) noexcept = default; - - fp_iterator_conv_base& operator=(const fp_iterator_conv_base&) noexcept = default; - -#ifdef PAPILIO_PLATFORM_WINDOWS - - protected: - fp_iterator_conv_base(unsigned int win_cp) noexcept - : m_win_cp(win_cp) {} - - unsigned int get_cp() const noexcept - { - return m_win_cp; - } - - private: - unsigned int m_win_cp = 0; // 0 == CP_ACP -#else - - protected: - fp_iterator_conv_base([[maybe_unused]] unsigned int win_cp) noexcept {} - - unsigned int get_cp() const noexcept = delete; -#endif - }; - - class fp_iterator_conv : public fp_iterator_conv_base - { - public: - using iterator_category = std::output_iterator_tag; - using value_type = char; - using difference_type = std::ptrdiff_t; - - fp_iterator_conv() = delete; - fp_iterator_conv(const fp_iterator_conv&) noexcept = default; - - fp_iterator_conv(std::FILE* fp, unsigned int win_cp = 0) noexcept - : fp_iterator_conv_base(win_cp), m_underlying(fp) {} - - fp_iterator_conv& operator=(const fp_iterator_conv&) noexcept = default; - - fp_iterator_conv& operator=(char ch) - { - write(ch); - return *this; - } - - fp_iterator_conv& operator*() noexcept - { - return *this; - } - - fp_iterator_conv& operator++() noexcept - { - return *this; - } - - fp_iterator_conv operator++(int) noexcept - { - return *this; - } - - private: - char8_t m_buf[4] = {u8'\0', u8'\0', u8'\0', u8'\0'}; - std::uint8_t m_byte_len = 0; - std::uint8_t m_byte_idx = 0; - fp_iterator m_underlying; - - void write(char ch); - }; - - unsigned int get_output_cp_win() noexcept; - - inline auto create_iter(std::FILE* file) noexcept - { -#ifdef PAPILIO_PLATFORM_WINDOWS - return fp_iterator_conv(file, get_output_cp_win()); -#else - return fp_iterator(file); -#endif - } + void vprint_impl( + std::FILE* file, + std::string_view fmt, + format_args_ref args, + bool conv_unicode, + bool newline, + text_style st = text_style() + ); } // namespace detail PAPILIO_EXPORT void println(std::FILE* file); @@ -156,91 +30,63 @@ PAPILIO_EXPORT void println(); PAPILIO_EXPORT template void print(std::FILE* file, format_string fmt, Args&&... args) { - using iter_t = detail::fp_iterator; - using context_type = basic_format_context; - PAPILIO_NS vformat_to( - iter_t(file), + detail::vprint_impl( + file, fmt.get(), - PAPILIO_NS make_format_args(std::forward(args)...) + PAPILIO_NS make_format_args(std::forward(args)...), + os::is_terminal(file), + false ); } PAPILIO_EXPORT template void print(format_string fmt, Args&&... args) { - using iter_t = detail::fp_iterator_conv; - using context_type = basic_format_context; - PAPILIO_NS vformat_to( - iter_t(stdout, detail::get_output_cp_win()), - fmt.get(), - PAPILIO_NS make_format_args(std::forward(args)...) - ); + PAPILIO_NS print(stdout, fmt, std::forward(args)...); } PAPILIO_EXPORT template void print(text_style st, format_string fmt, Args&&... args) { - using iter_t = detail::fp_iterator_conv; - using context_type = basic_format_context; - - auto it = iter_t(stdout, detail::get_output_cp_win()); - - it = st.set(it); - - it = PAPILIO_NS vformat_to( - it, + detail::vprint_impl( + stdout, fmt.get(), - PAPILIO_NS make_format_args(std::forward(args)...) + PAPILIO_NS make_format_args(std::forward(args)...), + os::is_terminal(stdout), + false, + st ); - - st.reset(it); } PAPILIO_EXPORT template void println(std::FILE* file, format_string fmt, Args&&... args) { - using iter_t = detail::fp_iterator; - using context_type = basic_format_context; - auto it = PAPILIO_NS vformat_to( - iter_t(file), + detail::vprint_impl( + file, fmt.get(), - PAPILIO_NS make_format_args(std::forward(args)...) + PAPILIO_NS make_format_args(std::forward(args)...), + os::is_terminal(file), + true ); - *it = '\n'; } PAPILIO_EXPORT template void println(format_string fmt, Args&&... args) { - using iter_t = detail::fp_iterator_conv; - using context_type = basic_format_context; - auto it = vformat_to( - iter_t(stdout, detail::get_output_cp_win()), - fmt.get(), - PAPILIO_NS make_format_args(std::forward(args)...) - ); - *it = '\n'; + PAPILIO_NS println(stdout, fmt, std::forward(args)...); } PAPILIO_EXPORT template void println(text_style st, format_string fmt, Args&&... args) { - using iter_t = detail::fp_iterator_conv; - using context_type = basic_format_context; - - auto it = iter_t(stdout, detail::get_output_cp_win()); - - it = st.set(it); - - it = PAPILIO_NS vformat_to( - it, + detail::vprint_impl( + stdout, fmt.get(), - PAPILIO_NS make_format_args(std::forward(args)...) + PAPILIO_NS make_format_args(std::forward(args)...), + os::is_terminal(stdout), + true, + st ); - - it = st.reset(it); - - *it = '\n'; } PAPILIO_EXPORT template diff --git a/src/os/general.cpp b/src/os/general.cpp new file mode 100644 index 0000000..3b0438c --- /dev/null +++ b/src/os/general.cpp @@ -0,0 +1,27 @@ +#include "os_impl.hpp" +#include + +namespace papilio::os +{ +void output_nonconv( + std::FILE* file, + std::string_view out +) +{ + using namespace std; + + size_t result = fwrite( + out.data(), 1, out.size(), file + ); + if(result < out.size()) + { + throw std::system_error( + std::make_error_code(std::errc::io_error) + ); + } + + fflush(file); +} +} // namespace papilio::os + +#include diff --git a/src/os/os_impl.hpp b/src/os/os_impl.hpp new file mode 100644 index 0000000..5e86b82 --- /dev/null +++ b/src/os/os_impl.hpp @@ -0,0 +1,17 @@ +#ifndef PAPILIO_OS_OS_IMPL_HPP +#define PAPILIO_OS_OS_IMPL_HPP + +#pragma once + +#include +#include + +#ifdef PAPILIO_PLATFORM_WINDOWS +# define PAPILIO_OS_IMPL_WINAPI 1 +#else +# define PAPILIO_OS_IMPL_POSIX 1 +#endif + +#include + +#endif diff --git a/src/os/posix.cpp b/src/os/posix.cpp new file mode 100644 index 0000000..3e3635e --- /dev/null +++ b/src/os/posix.cpp @@ -0,0 +1,26 @@ +#include "os_impl.hpp" +#include + +#ifdef PAPILIO_OS_IMPL_POSIX + +# include + +namespace papilio::os +{ +bool is_terminal(std::FILE* file) +{ + return isatty(fileno(file)) != 0; +} + +void output_conv( + std::FILE* file, + std::string_view out +) +{ + output_nonconv(file, out); +} +} // namespace papilio::os + +#endif + +#include diff --git a/src/os/winapi.cpp b/src/os/winapi.cpp new file mode 100644 index 0000000..1ff8500 --- /dev/null +++ b/src/os/winapi.cpp @@ -0,0 +1,60 @@ +#include "os_impl.hpp" +#include +#include + +#ifdef PAPILIO_OS_IMPL_WINAPI + +# define WIN32_LEAN_AND_MEAN +# include +# include + +namespace papilio::os +{ +static HANDLE get_file_handle(std::FILE* file) +{ + return reinterpret_cast(_get_osfhandle(_fileno(file))); +} + +bool is_terminal(std::FILE* file) +{ + DWORD mode = 0; + GetConsoleMode( + get_file_handle(file), + &mode + ); + return mode != 0; +} + +void output_conv( + std::FILE* file, + std::string_view out +) +{ + if(is_terminal(file)) + { + auto wout = utf::string_ref(out).to_wstring(); + + BOOL result = WriteConsoleW( + get_file_handle(file), + wout.data(), + static_cast(wout.size()), + nullptr, + nullptr + ); + if(!result) + { + throw std::system_error( + std::make_error_code(std::errc::io_error) + ); + } + } + else + { + output_nonconv(file, out); + } +} +} // namespace papilio::os + +#endif + +#include diff --git a/src/print.cpp b/src/print.cpp index 9a6dbb5..6fe7c43 100644 --- a/src/print.cpp +++ b/src/print.cpp @@ -1,109 +1,50 @@ #include -#include -#ifdef PAPILIO_PLATFORM_WINDOWS -# define WIN32_LEAN_AND_MEAN -# include -#endif +#include #include namespace papilio { namespace detail { - void fp_iterator::write(char ch) + static void output_impl( + std::FILE* file, + std::string_view out, + bool conv_unicode + ) { - int result = std::fputc(ch, m_fp); - if(result == EOF) - { - throw std::system_error( - std::make_error_code(std::errc::io_error) - ); - } + if(conv_unicode) + os::output_conv(file, out); + else + os::output_nonconv(file, out); } - void fp_iterator_conv::write(char ch) + void vprint_impl( + std::FILE* file, + std::string_view fmt, + format_args_ref args, + bool conv_unicode, + bool newline, + text_style st + ) { - if(m_byte_len == 0) - { - if(!utf::is_leading_byte(std::uint8_t(ch))) - { - throw std::system_error( - std::make_error_code(std::errc::io_error) - ); - } - m_byte_len = utf::byte_count(std::uint8_t(ch)); - } - - m_buf[m_byte_idx] = static_cast(ch); - ++m_byte_idx; - - if(m_byte_idx == m_byte_len) + std::string out; { -#ifndef PAPILIO_PLATFORM_WINDOWS - m_underlying = std::copy( - m_buf, m_buf + m_byte_len, m_underlying - ); -#else - wchar_t wbuf[2]; - int wconv_result = ::MultiByteToWideChar( - CP_UTF8, - MB_ERR_INVALID_CHARS, - reinterpret_cast<::LPSTR>(m_buf), - m_byte_len, - wbuf, - 2 - ); - if(wconv_result == 0) - { - throw std::system_error( - std::make_error_code(std::errc::io_error) - ); - } - - char mbbuf[8]; - int mbconv_result = ::WideCharToMultiByte( - get_cp(), - 0, - wbuf, - wconv_result, - mbbuf, - static_cast(std::size(mbbuf)), - nullptr, - nullptr - ); - if(mbconv_result == 0) - { - throw std::system_error( - std::make_error_code(std::errc::io_error) - ); - } - - m_underlying = std::copy_n( - mbbuf, - mbconv_result, - m_underlying - ); -#endif + auto it = std::back_inserter(out); - m_byte_len = 0; - m_byte_idx = 0; + st.set(it); + out += PAPILIO_NS vformat(fmt, args); + st.reset(it); } - } + if(newline) + out.push_back('\n'); - unsigned int get_output_cp_win() noexcept - { -#ifdef PAPILIO_PLATFORM_WINDOWS - return ::GetConsoleCP(); -#else - return 0; -#endif + output_impl(file, out, conv_unicode); } } // namespace detail void println(std::FILE* file) { - detail::fp_iterator it(file); - *it = '\n'; + detail::output_impl(file, "\n", os::is_terminal(file)); } void println()