sed.cpp 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. /*
  2. * Copyright (c) 2022, Eli Youngs <eli.m.youngs@gmail.com>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include <AK/CharacterTypes.h>
  7. #include <AK/GenericLexer.h>
  8. #include <AK/Vector.h>
  9. #include <LibCore/ArgsParser.h>
  10. #include <LibCore/File.h>
  11. #include <LibCore/System.h>
  12. #include <LibMain/Main.h>
  13. #include <LibRegex/RegexMatcher.h>
  14. #include <LibRegex/RegexOptions.h>
  15. struct SubstitutionCommand {
  16. Regex<PosixExtended> regex;
  17. StringView replacement;
  18. PosixOptions options;
  19. Optional<StringView> output_filepath;
  20. };
  21. static Vector<StringView> split_flags(StringView const& input)
  22. {
  23. Vector<StringView> flags;
  24. auto lexer = GenericLexer(input);
  25. while (!lexer.is_eof()) {
  26. StringView flag;
  27. if (lexer.next_is(is_ascii_digit)) {
  28. flag = lexer.consume_while(is_ascii_digit);
  29. } else if (lexer.peek() == 'w') {
  30. flag = lexer.consume_all();
  31. } else {
  32. flag = lexer.consume(1);
  33. }
  34. flags.append(flag);
  35. }
  36. return flags;
  37. }
  38. static ErrorOr<SubstitutionCommand> parse_command(StringView command)
  39. {
  40. auto generic_error_message = "Incomplete substitution command"sv;
  41. auto lexer = GenericLexer(command);
  42. auto address = lexer.consume_until('s');
  43. if (!address.is_empty())
  44. warnln("sed: Addresses are currently ignored");
  45. if (!lexer.consume_specific('s'))
  46. return Error::from_string_view(generic_error_message);
  47. if (lexer.is_eof())
  48. return Error::from_string_view(generic_error_message);
  49. auto delimiter = lexer.consume();
  50. if (delimiter == '\n' || delimiter == '\\')
  51. return Error::from_string_literal("\\n and \\ cannot be used as delimiters.");
  52. auto pattern = lexer.consume_until(delimiter);
  53. if (pattern.is_empty())
  54. return Error::from_string_literal("Substitution patterns cannot be empty.");
  55. if (!lexer.consume_specific(delimiter))
  56. return Error::from_string_view(generic_error_message);
  57. auto replacement = lexer.consume_until(delimiter);
  58. // According to Posix, "s/x/y" is an invalid substitution command.
  59. // It must have a closing delimiter: "s/x/y/"
  60. if (!lexer.consume_specific(delimiter))
  61. return Error::from_string_literal("The substitution command was not properly terminated.");
  62. PosixOptions options = PosixOptions(PosixFlags::Global | PosixFlags::SingleMatch);
  63. Optional<StringView> output_filepath;
  64. auto flags = split_flags(lexer.consume_all());
  65. for (auto const& flag : flags) {
  66. if (flag.starts_with('w')) {
  67. auto flag_filepath = flag.substring_view(1).trim_whitespace();
  68. if (flag_filepath.is_empty())
  69. return Error::from_string_literal("No filepath was provided for the 'w' flag.");
  70. output_filepath = flag_filepath;
  71. } else if (flag == "g"sv) {
  72. // Allow multiple matches per line by un-setting the SingleMatch flag
  73. options &= ~PosixFlags::SingleMatch;
  74. } else if (flag == "i"sv || flag == "I"sv) {
  75. options |= PosixFlags::Insensitive;
  76. } else {
  77. warnln("sed: Unsupported flag: {}", flag);
  78. }
  79. }
  80. return SubstitutionCommand { Regex<PosixExtended> { pattern }, replacement, options, output_filepath };
  81. }
  82. ErrorOr<int> serenity_main(Main::Arguments args)
  83. {
  84. TRY(Core::System::pledge("stdio cpath rpath wpath"));
  85. Core::ArgsParser args_parser;
  86. StringView command_input;
  87. Vector<StringView> filepaths;
  88. args_parser.add_positional_argument(command_input, "Command", "command_input", Core::ArgsParser::Required::Yes);
  89. args_parser.add_positional_argument(filepaths, "File", "file", Core::ArgsParser::Required::No);
  90. args_parser.parse(args);
  91. auto command = TRY(parse_command(command_input));
  92. Optional<NonnullOwnPtr<Core::File>> maybe_output_file;
  93. if (command.output_filepath.has_value())
  94. maybe_output_file = TRY(Core::File::open_file_or_standard_stream(command.output_filepath.release_value(), Core::File::OpenMode::Write));
  95. if (filepaths.is_empty())
  96. filepaths = { "-"sv };
  97. Array<u8, PAGE_SIZE> buffer {};
  98. for (auto const& filepath : filepaths) {
  99. auto file_unbuffered = TRY(Core::File::open_file_or_standard_stream(filepath, Core::File::OpenMode::Read));
  100. auto file = TRY(Core::BufferedFile::create(move(file_unbuffered)));
  101. while (!file->is_eof()) {
  102. auto line = TRY(file->read_line(buffer));
  103. // Substitutions can apply to blank lines in the middle of a file,
  104. // but not to the trailing newline that marks the end of a file.
  105. if (line.is_empty() && file->is_eof())
  106. break;
  107. auto result = command.regex.replace(line, command.replacement, command.options);
  108. outln(result);
  109. if (maybe_output_file.has_value()) {
  110. auto const& output_file = maybe_output_file.value();
  111. TRY(output_file->write_until_depleted(result.bytes()));
  112. TRY(output_file->write_until_depleted("\n"sv.bytes()));
  113. }
  114. }
  115. }
  116. return 0;
  117. }