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