Serenity Operating System
1/*
2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
3 *
4 * SPDX-License-Identifier: BSD-2-Clause
5 */
6
7#include <AK/DeprecatedString.h>
8#include <AK/Vector.h>
9#include <LibCore/ArgsParser.h>
10#include <LibMain/Main.h>
11#include <errno.h>
12#include <stdio.h>
13#include <string.h>
14
15enum NumberStyle {
16 NumberAllLines,
17 NumberNonEmptyLines,
18 NumberNoLines,
19};
20
21ErrorOr<int> serenity_main(Main::Arguments arguments)
22{
23 NumberStyle number_style = NumberNonEmptyLines;
24 int increment = 1;
25 StringView separator = " "sv;
26 int start_number = 1;
27 int number_width = 6;
28 Vector<DeprecatedString> files;
29
30 Core::ArgsParser args_parser;
31
32 Core::ArgsParser::Option number_style_option {
33 Core::ArgsParser::OptionArgumentMode::Required,
34 "Line numbering style: 't' for non-empty lines, 'a' for all lines, 'n' for no lines",
35 "body-numbering",
36 'b',
37 "style",
38 [&number_style](StringView s) {
39 if (s == "t"sv)
40 number_style = NumberNonEmptyLines;
41 else if (s == "a"sv)
42 number_style = NumberAllLines;
43 else if (s == "n"sv)
44 number_style = NumberNoLines;
45 else
46 return false;
47
48 return true;
49 }
50 };
51
52 args_parser.add_option(move(number_style_option));
53 args_parser.add_option(increment, "Line count increment", "increment", 'i', "number");
54 args_parser.add_option(separator, "Separator between line numbers and lines", "separator", 's', "string");
55 args_parser.add_option(start_number, "Initial line number", "startnum", 'v', "number");
56 args_parser.add_option(number_width, "Number width", "width", 'w', "number");
57 args_parser.add_positional_argument(files, "Files to process", "file", Core::ArgsParser::Required::No);
58 args_parser.parse(arguments);
59
60 Vector<FILE*> file_pointers;
61 if (!files.is_empty()) {
62 for (auto& file : files) {
63 FILE* file_pointer = fopen(file.characters(), "r");
64 if (!file_pointer) {
65 warnln("Failed to open {}: {}", file, strerror(errno));
66 continue;
67 }
68 file_pointers.append(file_pointer);
69 }
70 } else {
71 file_pointers.append(stdin);
72 }
73
74 for (auto& file_pointer : file_pointers) {
75 int line_number = start_number - increment; // so the line number can start at 1 when added below
76 int previous_character = 0;
77 int next_character = 0;
78 while ((next_character = fgetc(file_pointer)) != EOF) {
79 if (previous_character == 0 || previous_character == '\n') {
80 if (next_character == '\n' && number_style != NumberAllLines) {
81 // Skip printing line count on empty lines.
82 outln();
83 continue;
84 }
85 if (number_style != NumberNoLines)
86 out("{1:{0}}{2}", number_width, (line_number += increment), separator);
87 else
88 out("{1:{0}}", number_width, "");
89 }
90 putchar(next_character);
91 previous_character = next_character;
92 }
93 fclose(file_pointer);
94 if (previous_character != '\n')
95 outln(); // for cases where files have no trailing newline
96 }
97 return 0;
98}