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