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 }