diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 517f483..5419f12 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,6 +20,6 @@ jobs: version: 0.15.1 - run: | zig build -Dtarget=x86_64-windows - zig build -Dtarget=aarch64-windows + zig build -Dtarget=aarch64-linux zig build -Dtarget=x86_64-linux zig build -Dtarget=aarch64-macos \ No newline at end of file diff --git a/src/wc/main.cpp b/src/wc/main.cpp index 8fdaa2c..e05b88c 100644 --- a/src/wc/main.cpp +++ b/src/wc/main.cpp @@ -1,13 +1,14 @@ #include #include #include -#include +#include #include #include "wc.hpp" #include "options.hpp" #include "params.hpp" +#include "word_count.hpp" -std::string format_output(const wc::CountResult& result, const wc::Options& options, const std::string& filename = "", bool is_total = false) { +std::string format_output(const wc::WordCount& result, const wc::Options& options, std::string_view filename = "", bool is_total = false) { std::string output; // For 'only' mode, don't add leading spaces @@ -22,7 +23,7 @@ std::string format_output(const wc::CountResult& result, const wc::Options& opti output += std::format("{:>8}", result.bytes); } if (options.show_chars) { - output += std::format("{:>8}", result.characters); + output += std::format("{:>8}", result.chars); } if (options.show_max_line_length) { output += std::format("{:>8}", result.max_line_length); @@ -39,7 +40,7 @@ std::string format_output(const wc::CountResult& result, const wc::Options& opti output += std::format("{:>8}", result.bytes); } if (options.show_chars) { - output += std::format("{:>8}", result.characters); + output += std::format("{:>8}", result.chars); } if (options.show_max_line_length) { output += std::format("{:>8}", result.max_line_length); @@ -47,7 +48,7 @@ std::string format_output(const wc::CountResult& result, const wc::Options& opti // Add filename or "total" label if (!filename.empty()) { - output += " " + filename; + output += std::format(" {}", filename); } } @@ -69,81 +70,60 @@ int main(int argc, char* argv[]) { << "License Apache 2.0: https://www.apache.org/licenses/LICENSE-2.0\n" << "This is free software: you are free to change and redistribute it.\n" << "There is NO WARRANTY, to the extent permitted by law.\n\n" - << "Written by guuzaa (guuzaa@outlook.com).\n"; + << "Written by Jowell Young (guuzaa@outlook.com).\n"; return 0; } // Handle files0-from option std::vector files_to_process = options.files; - if (!options.files0_from.empty()) { - std::ifstream file_list(options.files0_from); - if (!file_list.is_open()) { - throw std::runtime_error(std::format("Failed to open file list: {}", options.files0_from)); - } - - std::string line; - while (std::getline(file_list, line, '\0')) { - if (!line.empty()) { - files_to_process.push_back(line); + if (options.files0_from.has_value()) { + if (!options.files0_from.value().is_stdin()) { + auto file_list = std::ifstream(options.files0_from.value().as_str()); + if (!file_list.is_open()) { + throw std::runtime_error(std::format("Failed to open file list: {}", options.files0_from.value().as_str())); + } + std::string line; + while (std::getline(file_list, line, '\0')) { + if (!line.empty()) { + files_to_process.push_back(line); + } + } + } else { + std::string line; + while (std::getline(std::cin, line, '\0')) { + if (!line.empty()) { + files_to_process.push_back(line); + } } } } // If no files specified, read from stdin if (files_to_process.empty()) { - std::stringstream buffer; - buffer << std::cin.rdbuf(); - auto result = wc::WordCounter::count_string(buffer.str()); + auto result = wc::WordCounter::count(std::cin); std::cout << format_output(result, options) << std::endl; return 0; } - size_t total_lines = 0; - size_t total_words = 0; - size_t total_chars = 0; - size_t total_bytes = 0; - size_t total_max_line_length = 0; - + wc::WordCount totals{}; // Process each file for (const auto& file : files_to_process) { auto result = wc::WordCounter::count_file(file); - // Update totals - total_lines += result.lines; - total_words += result.words; - total_chars += result.characters; - total_bytes += result.bytes; - total_max_line_length = std::max(total_max_line_length, result.max_line_length); + totals += result; // Print individual file results if not in 'only' mode if (options.total != wc::TotalWhen::Only) { - std::cout << format_output(result, options, file) << std::endl; + std::cout << format_output(result, options, file) << '\n'; } } // Handle total line based on the --total option - bool should_print_total = false; - - switch (options.total) { - case wc::TotalWhen::Auto: - should_print_total = files_to_process.size() > 1; - break; - case wc::TotalWhen::Always: - should_print_total = true; - break; - case wc::TotalWhen::Only: - should_print_total = true; - break; - case wc::TotalWhen::Never: - should_print_total = false; - break; - } - - if (should_print_total) { - wc::CountResult total{total_lines, total_words, total_chars, total_bytes, total_max_line_length}; - std::string total_label = (options.total == wc::TotalWhen::Only) ? "" : "total"; - std::cout << format_output(total, options, total_label, true) << std::endl; + if (wc::is_total_row_visible(options.total, files_to_process.size())) { + std::string_view total_label = (options.total == wc::TotalWhen::Only) ? "" : "total"; + std::cout << format_output(totals, options, total_label, true) << '\n'; } + std::cout << std::flush; return 0; } catch (const std::exception& e) { diff --git a/src/wc/options.cpp b/src/wc/options.cpp index 86838a8..d951c7c 100644 --- a/src/wc/options.cpp +++ b/src/wc/options.cpp @@ -1,7 +1,6 @@ #include "options.hpp" #include "params.hpp" #include -#include #include namespace wc { @@ -15,9 +14,9 @@ Options OptionParser::parse(int argc, char* argv[]) { if (arg.find("=") != std::string::npos) { auto [opt, value] = split_long_option(arg); if (opt == "--total") { - options.total = parse_total_when(value); + options.total = parse_total_when_from_str(value); } else if (opt == "--files0-from") { - options.files0_from = value; + options.files0_from = {value}; } else { throw std::runtime_error(std::format("Invalid option with value: {}", opt)); } @@ -30,25 +29,6 @@ Options OptionParser::parse(int argc, char* argv[]) { options.files.push_back(arg); } } - - // If help or version is requested, ensure no other options or files are present - if (options.show_help || options.show_version) { - if (options.show_lines || options.show_words || options.show_bytes || - options.show_chars || options.show_max_line_length || !options.files.empty() || - !options.files0_from.empty()) { - throw std::runtime_error("Error: --help and -V/--version cannot be combined with other options or files."); - } - return options; - } - - // If no options were specified (and help/version wasn't requested), show all counts - if (!options.show_lines && !options.show_words && !options.show_bytes && - !options.show_chars && !options.show_max_line_length) { - options.show_lines = true; - options.show_words = true; - options.show_bytes = true; - } - return options; } @@ -60,7 +40,7 @@ std::pair OptionParser::split_long_option(const std::s return {opt.substr(0, pos), opt.substr(pos + 1)}; } -TotalWhen OptionParser::parse_total_when(const std::string& value) { +TotalWhen parse_total_when_from_str(std::string_view value) { if (value == "auto") return TotalWhen::Auto; if (value == "always") return TotalWhen::Always; if (value == "only") return TotalWhen::Only; @@ -68,7 +48,7 @@ TotalWhen OptionParser::parse_total_when(const std::string& value) { throw std::runtime_error(std::format("Invalid value for --total: {}", value)); } -void OptionParser::parse_option(const std::string& opt, Options& options) { +void OptionParser::parse_option(std::string_view opt, Options& options) { for (char c : opt.substr(1)) { bool found = false; for (const auto& param : COMMAND_PARAMS) { @@ -103,7 +83,7 @@ void OptionParser::parse_option(const std::string& opt, Options& options) { } } -void OptionParser::parse_long_option(const std::string& opt, Options& options) { +void OptionParser::parse_long_option(std::string_view opt, Options& options) { bool found = false; for (const auto& param : COMMAND_PARAMS) { if (opt == param.long_name) { @@ -131,4 +111,17 @@ void OptionParser::parse_long_option(const std::string& opt, Options& options) { } } +bool is_total_row_visible(TotalWhen when, size_t num_inputs) { + switch (when) { + case TotalWhen::Auto: + return num_inputs > 1; + case TotalWhen::Always: + return true; + case TotalWhen::Only: + return true; + case TotalWhen::Never: + return false; + } +} + } // namespace wc \ No newline at end of file diff --git a/src/wc/options.hpp b/src/wc/options.hpp index 1526d81..24c7228 100644 --- a/src/wc/options.hpp +++ b/src/wc/options.hpp @@ -1,6 +1,8 @@ #pragma once +#include #include +#include #include #include @@ -12,16 +14,35 @@ enum class TotalWhen { Only, Never }; +bool is_total_row_visible(TotalWhen when, size_t num_inputs); +TotalWhen parse_total_when_from_str(std::string_view value); + +class Input { +public: + Input() = delete; + Input(std::string_view files0): files0_from_(files0) {} + + bool is_stdin() { + return this->files0_from_ == "-"; + } + + std::string as_str() { + return files0_from_; + } + +private: + std::string files0_from_; +}; struct Options { - bool show_lines = false; - bool show_words = false; - bool show_bytes = false; + bool show_lines = true; + bool show_words = true; + bool show_bytes = true; bool show_chars = false; bool show_max_line_length = false; bool show_help = false; bool show_version = false; - std::string files0_from; + std::optional files0_from = {}; TotalWhen total = TotalWhen::Auto; std::vector files; }; @@ -31,9 +52,8 @@ class OptionParser { static Options parse(int argc, char* argv[]); private: - static void parse_option(const std::string& opt, Options& options); - static void parse_long_option(const std::string& opt, Options& options); - static TotalWhen parse_total_when(const std::string& value); + static void parse_option(std::string_view opt, Options& options); + static void parse_long_option(std::string_view opt, Options& options); static std::pair split_long_option(const std::string& opt); }; diff --git a/src/wc/params.cpp b/src/wc/params.cpp index 54d0d27..b4813ca 100644 --- a/src/wc/params.cpp +++ b/src/wc/params.cpp @@ -1,6 +1,5 @@ #include "params.hpp" #include -#include #include namespace wc { diff --git a/src/wc/wc.cpp b/src/wc/wc.cpp index 2b02b87..4f19b03 100644 --- a/src/wc/wc.cpp +++ b/src/wc/wc.cpp @@ -1,29 +1,30 @@ #include "wc.hpp" +#include "word_count.hpp" +#include #include #include #include #include -#include namespace wc { -CountResult WordCounter::count_file(const std::filesystem::path& path) { - std::ifstream file(path); - if (!file.is_open()) { - throw std::runtime_error(std::format("Failed to open file: {}", path.string())); - } - +WordCount WordCounter::count(const std::istream& read) { std::stringstream buffer; - buffer << file.rdbuf(); + buffer << read.rdbuf(); return count_string(buffer.str()); } -CountResult WordCounter::count_string(std::string_view content) { - return count_content(content); +WordCount WordCounter::count_file(const std::filesystem::path& path) { + try { + std::ifstream file(path); + return count(file); + } catch (const std::exception& e) { + throw std::runtime_error(std::format("Failed to open file: {}", path.string())); + } } -CountResult WordCounter::count_content(std::string_view content) { - CountResult result{}; +WordCount WordCounter::count_string(std::string_view content) { + WordCount result{}; result.bytes = content.size(); result.max_line_length = 0; @@ -31,7 +32,7 @@ CountResult WordCounter::count_content(std::string_view content) { size_t current_line_length = 0; for (char c : content) { - result.characters++; + result.chars++; if (c == '\n') { result.lines++; diff --git a/src/wc/wc.hpp b/src/wc/wc.hpp index 24f570c..3fe44b6 100644 --- a/src/wc/wc.hpp +++ b/src/wc/wc.hpp @@ -2,24 +2,17 @@ #include #include +#include "word_count.hpp" namespace wc { -struct CountResult { - size_t lines; - size_t words; - size_t characters; - size_t bytes; - size_t max_line_length; -}; - class WordCounter { public: - static CountResult count_file(const std::filesystem::path& path); - static CountResult count_string(std::string_view content); - + static WordCount count(const std::istream& read); + static WordCount count_file(const std::filesystem::path& path); + private: - static CountResult count_content(std::string_view content); + static WordCount count_string(std::string_view content); }; } // namespace wc \ No newline at end of file diff --git a/src/wc/word_count.hpp b/src/wc/word_count.hpp new file mode 100644 index 0000000..899ee72 --- /dev/null +++ b/src/wc/word_count.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include + +namespace wc { + +struct WordCount { + size_t lines; + size_t words; + size_t chars; + size_t bytes; + size_t max_line_length; + + WordCount operator+(const WordCount &other) const { + return WordCount{lines + other.lines, words + other.words, + chars + other.chars, bytes + other.bytes, + std::max(max_line_length, other.max_line_length)}; + } + + void operator+=(const WordCount &other) { *this = *this + other; } +}; + +} // namespace wc