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 }