1 module app; 2 3 version (app){ 4 import std.stdio; 5 import std.conv : to; 6 import core.stdc.stdlib; 7 import utils.misc; 8 import std.file; 9 import std.path : baseName; 10 import pngtext.pngtext; 11 // QUI for the text editor 12 import editor; 13 14 /// help text 15 enum string HELP_TEXT = 16 "pngtext - hides data inside png images 17 Made by Nafees Hassan (Nafees10@GitHub.com) at https://github.com/Nafees10/pngtext 18 usage: 19 pngtext [command] [options] 20 or: 21 pngtext [pngFile.png] # to open a basic text editor 22 commands: 23 write write to a png file, if --file is not specified, stdin is used to 24 input data 25 read read data from a png file. 26 size calculate how many bytes a png image can store. 27 editor opens a text editor in terminal to edit hidden text. 28 options: 29 --file -f specify file containing data to write into png image 30 --input -i specify original png image to write to, or read from 31 --ouput -o specify file to write output to, for write, and read. Default is 32 same as --input, will overwrite. 33 --quality -q specify quality, for use with size command. 34 1 - Highest, 2 - Medium, 3 - Low, 4 - Zero quality. Default: 1 35 --version -v display this program's version and build info 36 --help -h display this message"; 37 38 /// build info 39 enum string BUILD_INFO = CONST_INFO; 40 41 void main(string[] args){ 42 if (args.length >= 2){ 43 if (args[1] == "--version" || args[1] == "-v"){ 44 writeln(BUILD_INFO); 45 }else if (args[1] == "--help" || args[1] == "-h"){ 46 writeln(HELP_TEXT); 47 }else{ 48 string command; 49 string[string] options; 50 string[] errors; 51 options = readArgs(args[1 .. args.length].dup, command, errors); 52 foreach (error; errors){ 53 stderr.writeln ("error: "~error); 54 } 55 if (errors.length > 0){ 56 exit(1); 57 } 58 errors = validateOptions(options.dup, command); 59 foreach (error; errors){ 60 stderr.writeln ("error: "~error); 61 } 62 if (errors.length > 0){ 63 exit(1); 64 } 65 PNGText pngEdit = command == "editor" ? null : new PNGText; 66 if (command == "write"){ 67 string inputFile = options["input"]; 68 string outputFile = "output" in options ? options["output"] : inputFile; 69 char[] text; 70 if ("file" in options){ 71 text = cast(char[])read(options["file"]); 72 }else{ 73 text = []; 74 while (!stdin.eof){ 75 char c; 76 readf ("%s", c); 77 text ~= c; 78 } 79 if (text[$-1] == 0xFF) 80 text.length--; // remove the 0xFF from end 81 } 82 try{ 83 pngEdit.filename = inputFile; 84 pngEdit.load; 85 pngEdit.data = cast(ubyte[]) text; 86 pngEdit.encode; 87 pngEdit.filename = outputFile; 88 pngEdit.save; 89 }catch (Exception e){ 90 stderr.writeln(e.msg); 91 exit(1); 92 } 93 }else if (command == "read"){ 94 string inputFile = options["input"]; 95 char[] text; 96 try{ 97 pngEdit.filename = inputFile; 98 pngEdit.load; 99 pngEdit.decode; 100 text = cast(char[])pngEdit.data; 101 }catch (Exception e){ 102 stderr.writeln ("Failed to read from png image:\n",e.msg); 103 } 104 if ("output" in options){ 105 try{ 106 File outputFile = File(options["output"], "w"); 107 outputFile.write(cast(char[])text); 108 outputFile.close(); 109 }catch (Exception e){ 110 stderr.writeln ("Failed to write to output file:\n",e.msg); 111 } 112 }else{ 113 write (cast(string)text); 114 } 115 }else if (command == "size"){ 116 string inputFile = options["input"]; 117 string quality = "quality" in options ? options["quality"] : "1"; 118 if (["1","2","3","4"].hasElement(quality)){ 119 try{ 120 pngEdit.filename = inputFile; 121 pngEdit.load(); 122 writeln(pngEdit.capacity(quality == "1" ? DENSITY_LOW : quality == "2" ? DENSITY_MEDIUM : 123 quality == "3" ? DENSITY_HIGH : DENSITY_MAX)); 124 }catch (Exception e){ 125 stderr.writeln ("Failed to read png image:\n",e.msg); 126 } 127 }else{ 128 stderr.writeln ("Invalid value for --quality provided"); 129 exit (1); 130 } 131 }else if (command == "editor"){ 132 App editor = new App(options["input"], "output" in options ? options["output"] : options["input"], baseName(args[0]) == "pnghacker"); 133 editor.run(); 134 .destroy(editor); 135 } 136 .destroy(pngEdit); 137 } 138 }else{ 139 stderr.writeln("usage:\n pngtext [command] [options]"); 140 stderr.writeln("or, to open basic text editor:\n pngtext [pngFile.png]"); 141 stderr.writeln("or enter following for help:\n pngtext --help"); 142 } 143 } 144 145 /// reads arguments, returns values for options in assoc array 146 /// `args` is the array containing arguments. arg0 (executable name) should not be included in this 147 /// `command` is the string in which the provided command will be "returned" 148 /// `errors` is the array to put any errors in 149 private string[string] readArgs(string[] args, ref string command, ref string[] errors){ 150 /// stores list of possible options 151 string[] optionNames = [ 152 "file", "input", "quality", "output" 153 ]; 154 /// returns option name from the option provided in arg 155 /// returns zero length string if invalid 156 static string getOptionName(string option){ 157 /// stores full option names for short names 158 const string[string] completeOptionNames = [ 159 "-f" : "file", 160 "-i" : "input", 161 "-q" : "quality", 162 "-o" : "output" 163 ]; 164 if (option.length >= 3 && option[0 .. 2] == "--"){ 165 option = option[2 .. option.length]; 166 }else if (option.length == 2 && option[0] == '-' && option in completeOptionNames){ 167 option = completeOptionNames[option]; 168 }else{ 169 option = ""; 170 } 171 return option; 172 } 173 string[string] r; 174 errors = []; 175 if (args.length == 0){ 176 errors ~= "no arguments provided"; 177 } 178 if (["read","write","size","editor"].indexOf(args[0]) == -1){ 179 // beware tiny hack below 180 command = "editor"; 181 args = [command, "-i", args[0]]; 182 }else{ 183 command = args[0]; 184 } 185 args = args[1 .. args.length]; 186 for (uinteger i = 0; i < args.length; i ++){ 187 if (args[i][0] == '-'){ 188 // is an option, add error if is !valid 189 string optionName = getOptionName(args[i]); 190 if (optionName.length == 0 || optionNames.indexOf(optionName) == -1){ 191 errors ~= args[i]~" is not a valid option, use --help"; 192 break; 193 } 194 // get value 195 if (args.length > i+1){ 196 r[optionName] = args[i+1]; 197 i += 1; 198 }else{ 199 errors ~= "value for "~optionName~" not provided"; 200 } 201 } 202 } 203 if (errors.length > 0){ 204 r.clear; 205 } 206 return r; 207 } 208 209 /// validates if all the values provided for options are correct 210 /// Returns: array containing errors, [] if no errors found 211 private string[] validateOptions(string[string] options, string command){ 212 // assume that only correct options were passed, no "option does not exist" checks are here 213 string[] errors = []; 214 if (command == "write"){ 215 foreach (option; ["input"]){ 216 if (option !in options){ 217 errors ~= "--"~option~" not specified"; 218 } 219 } 220 }else if (command == "read" || command == "size"){ 221 if ("input" !in options){ 222 errors ~= "--input not specified"; 223 } 224 }else if (command == "editor"){ 225 if ("input" !in options){ 226 errors ~= "--input not specified"; 227 } 228 } 229 // check if provided files exist 230 string[] filesToCheck = []; 231 if ("input" in options) 232 filesToCheck ~= options["input"]; 233 if ("file" in options) 234 filesToCheck ~= options["file"]; 235 foreach (toCheck; filesToCheck){ 236 if (!exists(toCheck) || !isFile(toCheck)){ 237 errors ~= "file "~toCheck~" does not exist"; 238 } 239 } 240 return errors; 241 } 242 }