From 99c5bea7662e1e32c5bdf9f4debf45a6f6c7c7ec Mon Sep 17 00:00:00 2001 From: ciao Date: Sun, 26 Oct 2025 11:08:07 +0800 Subject: [PATCH 1/2] chore: refactor wc --- .github/workflows/ci.yml | 2 +- src/wc/main.cpp | 60 ++++++++++++---------------------------- src/wc/options.cpp | 43 ++++++++++++---------------- src/wc/options.hpp | 34 ++++++++++++++++++----- src/wc/params.cpp | 1 - src/wc/wc.cpp | 11 ++++---- src/wc/wc.hpp | 15 +++------- src/wc/word_count.hpp | 23 +++++++++++++++ 8 files changed, 97 insertions(+), 92 deletions(-) create mode 100644 src/wc/word_count.hpp 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..28c7658 100644 --- a/src/wc/main.cpp +++ b/src/wc/main.cpp @@ -1,13 +1,15 @@ #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 +24,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 +41,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 +49,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,16 +71,17 @@ 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 (options.files0_from.has_value()) { + // TODO support stdin + std::ifstream file_list(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)); + throw std::runtime_error(std::format("Failed to open file list: {}", options.files0_from.value().as_str())); } std::string line; @@ -98,52 +101,25 @@ int main(int argc, char* argv[]) { 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..8083d44 100644 --- a/src/wc/wc.cpp +++ b/src/wc/wc.cpp @@ -1,4 +1,5 @@ #include "wc.hpp" +#include "word_count.hpp" #include #include #include @@ -7,7 +8,7 @@ namespace wc { -CountResult WordCounter::count_file(const std::filesystem::path& path) { +WordCount 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())); @@ -18,12 +19,12 @@ CountResult WordCounter::count_file(const std::filesystem::path& path) { return count_string(buffer.str()); } -CountResult WordCounter::count_string(std::string_view content) { +WordCount WordCounter::count_string(std::string_view content) { return count_content(content); } -CountResult WordCounter::count_content(std::string_view content) { - CountResult result{}; +WordCount WordCounter::count_content(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..582a08b 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_file(const std::filesystem::path& path); + static WordCount count_string(std::string_view content); private: - static CountResult count_content(std::string_view content); + static WordCount count_content(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 From 50de90c3301c423304c5e14dcdb8e7acaef4b1f1 Mon Sep 17 00:00:00 2001 From: ciao Date: Sun, 26 Oct 2025 14:38:24 +0800 Subject: [PATCH 2/2] feat: file0-from supports - for stdin --- src/wc/main.cpp | 32 ++++++++++++++++++-------------- src/wc/wc.cpp | 22 +++++++++++----------- src/wc/wc.hpp | 6 +++--- 3 files changed, 32 insertions(+), 28 deletions(-) diff --git a/src/wc/main.cpp b/src/wc/main.cpp index 28c7658..e05b88c 100644 --- a/src/wc/main.cpp +++ b/src/wc/main.cpp @@ -2,7 +2,6 @@ #include #include #include -#include #include #include "wc.hpp" #include "options.hpp" @@ -78,25 +77,30 @@ int main(int argc, char* argv[]) { // Handle files0-from option std::vector files_to_process = options.files; if (options.files0_from.has_value()) { - // TODO support stdin - std::ifstream file_list(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); + 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; } diff --git a/src/wc/wc.cpp b/src/wc/wc.cpp index 8083d44..4f19b03 100644 --- a/src/wc/wc.cpp +++ b/src/wc/wc.cpp @@ -1,29 +1,29 @@ #include "wc.hpp" #include "word_count.hpp" +#include #include #include #include #include -#include namespace wc { -WordCount 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()); } -WordCount 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())); + } } -WordCount WordCounter::count_content(std::string_view content) { +WordCount WordCounter::count_string(std::string_view content) { WordCount result{}; result.bytes = content.size(); result.max_line_length = 0; diff --git a/src/wc/wc.hpp b/src/wc/wc.hpp index 582a08b..3fe44b6 100644 --- a/src/wc/wc.hpp +++ b/src/wc/wc.hpp @@ -8,11 +8,11 @@ namespace wc { class WordCounter { public: + static WordCount count(const std::istream& read); static WordCount count_file(const std::filesystem::path& path); - static WordCount count_string(std::string_view content); - + private: - static WordCount count_content(std::string_view content); + static WordCount count_string(std::string_view content); }; } // namespace wc \ No newline at end of file