001package org.unix4j.unix.sed; 002 003import org.unix4j.processor.LineProcessor; 004import org.unix4j.util.StringUtil; 005 006/** 007 * Constants for the sed commands with utility method to derive a constant from 008 * a script (see {@link #fromScript(String)} or from the sed arguments 009 * {@link #fromArgs(SedArguments)}. The constant also provides support for the 010 * instantiation of a new command processor through 011 * {@link #createProcessorFor(SedArguments, LineProcessor)}. 012 */ 013public enum Command { 014 print('p') { 015 @Override 016 public boolean matches(SedArguments args) { 017 return args.isPrint(); 018 } 019 020 @Override 021 public AbstractSedProcessor createProcessorFor(SedArguments args, LineProcessor output) { 022 return new PrintProcessor(this, args, output); 023 } 024 025 @Override 026 public AbstractSedProcessor createProcessorFor(String script, SedArguments args, LineProcessor output) { 027 return new PrintProcessor(this, script, args, output); 028 } 029 }, 030 substitute('s') { 031 @Override 032 public boolean matches(SedArguments args) { 033 return args.isSubstitute(); 034 } 035 036 @Override 037 public AbstractSedProcessor createProcessorFor(SedArguments args, LineProcessor output) { 038 return new SubstituteProcessor(this, args, output); 039 } 040 041 @Override 042 public AbstractSedProcessor createProcessorFor(String script, SedArguments args, LineProcessor output) { 043 return new SubstituteProcessor(this, script, args, output); 044 } 045 }, 046 append('a') { 047 @Override 048 public boolean matches(SedArguments args) { 049 return args.isAppend(); 050 } 051 052 @Override 053 public AbstractSedProcessor createProcessorFor(SedArguments args, LineProcessor output) { 054 return new AppendProcessor(this, args, output); 055 } 056 057 @Override 058 public AbstractSedProcessor createProcessorFor(String script, SedArguments args, LineProcessor output) { 059 return new AppendProcessor(this, script, args, output); 060 } 061 }, 062 insert('i') { 063 @Override 064 public boolean matches(SedArguments args) { 065 return args.isInsert(); 066 } 067 068 @Override 069 public AbstractSedProcessor createProcessorFor(SedArguments args, LineProcessor output) { 070 return new InsertProcessor(this, args, output); 071 } 072 073 @Override 074 public AbstractSedProcessor createProcessorFor(String script, SedArguments args, LineProcessor output) { 075 return new InsertProcessor(this, script, args, output); 076 } 077 }, 078 change('c') { 079 @Override 080 public boolean matches(SedArguments args) { 081 return args.isChange(); 082 } 083 084 @Override 085 public AbstractSedProcessor createProcessorFor(SedArguments args, LineProcessor output) { 086 return new ChangeProcessor(this, args, output); 087 } 088 089 @Override 090 public AbstractSedProcessor createProcessorFor(String script, SedArguments args, LineProcessor output) { 091 return new ChangeProcessor(this, script, args, output); 092 } 093 }, 094 delete('d') { 095 @Override 096 public boolean matches(SedArguments args) { 097 return args.isDelete(); 098 } 099 100 @Override 101 public AbstractSedProcessor createProcessorFor(SedArguments args, LineProcessor output) { 102 return new DeleteProcessor(this, args, output); 103 } 104 105 @Override 106 public AbstractSedProcessor createProcessorFor(String script, SedArguments args, LineProcessor output) { 107 return new DeleteProcessor(this, script, args, output); 108 } 109 }, 110 translate('y') { 111 @Override 112 public boolean matches(SedArguments args) { 113 return args.isTranslate(); 114 } 115 116 @Override 117 public AbstractSedProcessor createProcessorFor(SedArguments args, LineProcessor output) { 118 return new TranslateProcessor(this, args, output); 119 } 120 121 @Override 122 public AbstractSedProcessor createProcessorFor(String script, SedArguments args, LineProcessor output) { 123 return new TranslateProcessor(this, script, args, output); 124 } 125 }; 126 protected final char commandChar; 127 128 private Command(char command) { 129 this.commandChar = command; 130 } 131 132 /** 133 * Returns true if the the command option for this command is set in the 134 * specified sed arguments, and false otherwise. 135 * 136 * @param args 137 * the sed arguments 138 * @return true if the command option for this command is set in args 139 */ 140 abstract public boolean matches(SedArguments args); 141 142 /** 143 * Returns a new instance of the appropriate sed processor for this command. 144 * Note that this method does not check whether the correct command option 145 * is selected in {@code args}. 146 * 147 * @param args 148 * the sed arguments passed to the processor constructor 149 * @param output 150 * the output object passed to the processor constructor 151 * @return a new sed processor instance for this command 152 */ 153 abstract public AbstractSedProcessor createProcessorFor(SedArguments args, LineProcessor output); 154 155 /** 156 * Returns a new instance of the appropriate sed processor for this command. 157 * Note that this method does not check whether the script starts with the 158 * correct command character. 159 * 160 * @param script 161 * the sed script passed to the processor constructor 162 * @param args 163 * the sed arguments passed to the processor constructor 164 * @param output 165 * the output object passed to the processor constructor 166 * @return a new sed processor instance for this command 167 */ 168 abstract public AbstractSedProcessor createProcessorFor(String script, SedArguments args, LineProcessor output); 169 170 /** 171 * Returns the command constant The first non-whitespace character of the 172 * script defines the command for substitute and translate; for all other 173 * commands, it is the first character after a whitespace sequence within 174 * the script (leading whitespace is ignored). 175 * 176 * @param script 177 * the script to analyse 178 * @return the matching command or null if not found 179 */ 180 public static Command fromScript(String script) { 181 final int len = script.length(); 182 final int scriptStart = StringUtil.findStartTrimWhitespace(script); 183 if (scriptStart < len) { 184 final char firstChar = script.charAt(scriptStart); 185 if (firstChar == substitute.commandChar) { 186 return substitute; 187 } else if (firstChar == translate.commandChar) { 188 return translate; 189 } else { 190 final int scriptEnd = AbstractSedProcessor.indexOfNextDelimiter(script, scriptStart); 191 if (scriptEnd < 0) { 192 throw new IllegalArgumentException("sed regexp pattern is not terminated, expected a second unescaped '" + firstChar + "' character in: " + script); 193 } 194 final int whitespaceStart = StringUtil.findWhitespace(script, scriptEnd); 195 196 // either 197 // (a) the command is the first char after the whitespace 198 // following the pattern 199 // (b) the command is one of the characters in the pattern flags 200 // after the second delimiter 201 202 // (a) try to find the command char in the pattern flags 203 Command command = fromCommandChars(script, scriptEnd + 1, whitespaceStart); 204 205 // (b) ok after the whitespace then 206 if (command == null && whitespaceStart < len) { 207 final int commandStart = StringUtil.findStartTrimWhitespace(script, whitespaceStart); 208 command = fromCommandChars(script, commandStart, commandStart + 1); 209 } 210 211 // success? 212 if (command == null) { 213 throw new IllegalArgumentException("command expected in sed script: " + script); 214 } 215 return command; 216 } 217 } 218 return null; 219 } 220 221 /** 222 * Returns the command constant if any of the string characters between 223 * start and end matches one of the command characters. Returns null if no 224 * command character is found. 225 * 226 * @param string 227 * the string with the command chararcter candidates 228 * @param start 229 * the start index in string, inclusive 230 * @param end 231 * the end index in string, exclusive 232 * @return the matching command or null if not found 233 */ 234 private static Command fromCommandChars(String string, int start, int end) { 235 final int s = Math.max(0, start); 236 final int e = Math.min(string.length(), end); 237 if (s < e) { 238 for (final Command command : Command.values()) { 239 for (int i = s; i < e; i++) { 240 if (string.charAt(i) == command.commandChar) { 241 return command; 242 } 243 } 244 } 245 } 246 return null; 247 } 248 249 /** 250 * Returns the command constant taken from the command option set in args 251 * using the {@link #matches(SedArguments)} method Returns null if no 252 * command option is set. 253 * 254 * @param args 255 * the sed command arguments 256 * @return the matching command or null if not found 257 */ 258 public static Command fromArgs(SedArguments args) { 259 for (final Command command : values()) { 260 if (command.matches(args)) { 261 return command; 262 } 263 } 264 return null; 265 } 266}