Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
86 changes: 33 additions & 53 deletions src/wc/main.cpp
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
#include <iostream>
#include <format>
#include <filesystem>
#include <sstream>
#include <ostream>
#include <fstream>
#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
Expand All @@ -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);
Expand All @@ -39,15 +40,15 @@ 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);
}

// Add filename or "total" label
if (!filename.empty()) {
output += " " + filename;
output += std::format(" {}", filename);
}
}

Expand All @@ -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<std::string> 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) {
Expand Down
43 changes: 18 additions & 25 deletions src/wc/options.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
#include "options.hpp"
#include "params.hpp"
#include <stdexcept>
#include <algorithm>
#include <format>

namespace wc {
Expand All @@ -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));
}
Expand All @@ -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;
}

Expand All @@ -60,15 +40,15 @@ std::pair<std::string, std::string> 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;
if (value == "never") return TotalWhen::Never;
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) {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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
34 changes: 27 additions & 7 deletions src/wc/options.hpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#pragma once

#include <optional>
#include <string>
#include <string_view>
#include <vector>
#include <utility>

Expand All @@ -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<Input> files0_from = {};
TotalWhen total = TotalWhen::Auto;
std::vector<std::string> files;
};
Expand All @@ -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<std::string, std::string> split_long_option(const std::string& opt);
};

Expand Down
1 change: 0 additions & 1 deletion src/wc/params.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
#include "params.hpp"
#include <iostream>
#include <filesystem>
#include <string>

namespace wc {
Expand Down
27 changes: 14 additions & 13 deletions src/wc/wc.cpp
Original file line number Diff line number Diff line change
@@ -1,37 +1,38 @@
#include "wc.hpp"
#include "word_count.hpp"
#include <format>
#include <fstream>
#include <sstream>
#include <algorithm>
#include <cctype>
#include <format>

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;

bool in_word = false;
size_t current_line_length = 0;

for (char c : content) {
result.characters++;
result.chars++;

if (c == '\n') {
result.lines++;
Expand Down
Loading