Serenity Operating System
1/*
2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright notice, this
9 * list of conditions and the following disclaimer.
10 *
11 * 2. Redistributions in binary form must reproduce the above copyright notice,
12 * this list of conditions and the following disclaimer in the documentation
13 * and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include <AK/String.h>
28#include <AK/Vector.h>
29#include <LibCore/ArgsParser.h>
30
31#include <stdio.h>
32#include <sys/stat.h>
33
34struct Count {
35 String name;
36 bool exists = true;
37 unsigned int lines = 0;
38 unsigned int characters = 0;
39 unsigned int words = 0;
40 size_t bytes = 0;
41};
42
43bool output_line = false;
44bool output_byte = false;
45bool output_character = false;
46bool output_word = false;
47
48void wc_out(Count& count)
49{
50 if (output_line)
51 printf("%7i ", count.lines);
52 if (output_word)
53 printf("%7i ", count.words);
54 if (output_byte)
55 printf("%7lu ", count.bytes);
56 if (output_character)
57 printf("%7i ", count.characters);
58
59 printf("%14s\n", count.name.characters());
60}
61
62Count get_count(const String& file_name)
63{
64 Count count;
65 FILE* file_pointer = nullptr;
66 if (file_name == "-") {
67 count.name = "";
68 file_pointer = stdin;
69 } else {
70 count.name = file_name;
71 if ((file_pointer = fopen(file_name.characters(), "r")) == NULL) {
72 fprintf(stderr, "wc: unable to open %s\n", file_name.characters());
73 count.exists = false;
74 return count;
75 }
76 }
77 bool tab_flag = false;
78 bool space_flag = false;
79 bool line_flag = true;
80 int current_character;
81 while ((current_character = fgetc(file_pointer)) != EOF) {
82 count.characters++;
83 if (current_character >= 'A' && current_character <= 'z' && (space_flag || line_flag || tab_flag)) {
84 count.words++;
85 space_flag = false;
86 line_flag = false;
87 tab_flag = false;
88 }
89 switch (current_character) {
90 case '\n':
91 count.lines++;
92 line_flag = true;
93 break;
94 case ' ':
95 space_flag = true;
96 break;
97 case '\t':
98 tab_flag = true;
99 break;
100 }
101 }
102 fclose(file_pointer);
103 if (file_pointer != stdin) {
104 struct stat st;
105 stat(file_name.characters(), &st);
106 count.bytes = st.st_size;
107 }
108 return count;
109}
110
111Count get_total_count(Vector<Count>& counts)
112{
113 Count total_count { "total" };
114 for (auto& count : counts) {
115 total_count.lines += count.lines;
116 total_count.words += count.words;
117 total_count.characters += count.characters;
118 total_count.bytes += count.bytes;
119 }
120 return total_count;
121}
122
123int main(int argc, char** argv)
124{
125 if (pledge("stdio rpath", nullptr) < 0) {
126 perror("pledge");
127 return 1;
128 }
129
130 Vector<const char*> files;
131
132 Core::ArgsParser args_parser;
133 args_parser.add_option(output_line, "Output line count", "lines", 'l');
134 args_parser.add_option(output_byte, "Output byte count", "bytes", 'c');
135 args_parser.add_option(output_word, "Output word count", "words", 'w');
136 args_parser.add_positional_argument(files, "File to process", "file", Core::ArgsParser::Required::No);
137 args_parser.parse(argc, argv);
138
139 if (!output_line && !output_byte && !output_word)
140 output_line = output_byte = output_word = true;
141
142 Vector<Count> counts;
143 for (auto& file : files) {
144 Count count = get_count(file);
145 counts.append(count);
146 }
147
148 if (pledge("stdio", nullptr) < 0) {
149 perror("pledge");
150 return 1;
151 }
152
153 if (files.size() > 1) {
154 Count total_count = get_total_count(counts);
155 counts.append(total_count);
156 }
157
158 if (files.is_empty()) {
159 Count count = get_count("-");
160 counts.append(count);
161 }
162
163 for (auto& count : counts)
164 if (count.exists)
165 wc_out(count);
166
167 return 0;
168}