1 module dud.convert;
2 
3 import std.array : empty, front;
4 import std.algorithm.iteration : filter;
5 import std.path;
6 import std.experimental.logger;
7 import std.file;
8 import std.format : format;
9 import std.stdio;
10 import std.typecons : nullable, Nullable;
11 
12 import dud.options;
13 import dud.pkgdescription;
14 import dud.pkgdescription.json;
15 import dud.pkgdescription.sdl;
16 import dud.pkgdescription.output;
17 import dud.pkgdescription.exception;
18 
19 @safe:
20 
21 int convert(ref string[] args) {
22 	const OptionReturn!(ConvertOptions) opts = getConvertOptions(args);
23 	if(args == ["convert"]) {
24 		writefln("Failed to process cmd options [%(%s, %)]", args);
25 		return 1;
26 	}
27 
28 	if(opts.common.help) {
29 		() @trusted { writeOptions(stdout.lockingTextWriter(), opts); }();
30 		return 0;
31 	}
32 
33 	const string relPath = opts.options.inputFilename.empty
34 		? dubFileInCWD()
35 		: opts.options.inputFilename;
36 
37 	tracef(opts.common.vverbose, "Relative input file name '%s'", relPath);
38 	const string absNormInputPath = relPath.absolutePath().buildNormalizedPath();
39 	tracef(opts.common.vverbose, "Absolute normalized input file name '%s'",
40 		absNormInputPath);
41 
42 	if(!exists(absNormInputPath)) {
43 		writefln("Input '%s' doesn't exists in the filesystem",
44 			absNormInputPath);
45 		return 1;
46 	}
47 
48 	if(!isFile(absNormInputPath)) {
49 		writefln("No File '%s' exists in the filesystem", absNormInputPath);
50 		return 1;
51 	}
52 
53 	const string inExt = extension(absNormInputPath);
54 	tracef(opts.common.vverbose, "Input file extension '%s'", inExt);
55 
56 	if(inExt != ".json" && inExt != ".sdl") {
57 		writefln("The file '%s' has an unsupported extension '%s'",
58 			absNormInputPath, inExt);
59 		return 0;
60 	}
61 
62 	const string outFilename = opts.options.outputFilename.empty
63 		? buildOutFilename(opts.options.outputTargetType)
64 		: opts.options.outputFilename;
65 
66 	tracef(opts.common.vverbose, "Relative output file name '%s'", outFilename);
67 	const string absNormOutputPath = outFilename.absolutePath()
68 		.buildNormalizedPath();
69 	tracef(opts.common.vverbose, "Absolute normalized output file name '%s'",
70 		outFilename);
71 
72 	if(absNormOutputPath.empty
73 			&& opts.options.outputTargetType == ConvertTargetFormat.undefined)
74 	{
75 		writefln("Could determine output file name as target format was "
76 				~ "undefined");
77 		return 1;
78 	}
79 
80 	if(!opts.options.override_ && exists(absNormOutputPath)) {
81 		writefln("The given output file '%s' exists and no option were set"
82 			~ " to override the file", absNormOutputPath);
83 		return 1;
84 	}
85 
86 	const string outExt = extension(absNormOutputPath);
87 	tracef(opts.common.vverbose, "Output file extension '%s'", outExt);
88 
89 	if(!extMatchesConvertTargetFormat(outExt, opts.options.outputTargetType)) {
90 		writefln("The target format '%s' does not match the given output file"
91 			~ " name '%s'", opts.options.outputTargetType, absNormOutputPath);
92 		return 1;
93 	}
94 
95 	Nullable!PackageDescription nParse = parse(absNormInputPath, inExt,
96 			opts.common);
97 	if(nParse.isNull()) {
98 		writefln("Failed to parse file '%s'", absNormInputPath);
99 		return 2;
100 	}
101 
102 	PackageDescription nnParse = nParse.get();
103 
104 	tracef(opts.common.vverbose, "Write output to '%s'", absNormOutputPath);
105 	const int writeRslt = writeOutput(nnParse, absNormOutputPath, outExt,
106 			opts.common);
107 	if(writeRslt != 0) {
108 		writefln("Failed to copy the PackageDescription into file '%s'",
109 			absNormOutputPath);
110 		return 1;
111 	}
112 
113 	if(!opts.options.keepInput) {
114 		tracef("Removing '%s", absNormInputPath);
115 		remove(absNormInputPath);
116 		if(exists(absNormInputPath)) {
117 			writefln("Failed to remove '%s'", absNormInputPath);
118 			return 1;
119 		}
120 	}
121 
122 	return 0;
123 }
124 
125 string dubFileInCWD() {
126 	const string cwd = getcwd();
127 	const string js = buildPath(cwd, "dub.json");
128 	const string sdl = buildPath(cwd, "dub.sdl");
129 	const string pkg = buildPath(cwd, "package.json");
130 
131 	auto ret = [js, sdl, pkg].filter!(it => exists(it));
132 	return ret.empty ? "" : ret.front;
133 }
134 
135 string buildOutFilename(const ConvertTargetFormat ctf) {
136 	const string cwd = getcwd();
137 	final switch(ctf) {
138 		case ConvertTargetFormat.json:
139 			return buildPath(cwd, "dub.json");
140 		case ConvertTargetFormat.sdl:
141 			return buildPath(cwd, "dub.sdl");
142 		case ConvertTargetFormat.undefined:
143 			return "";
144 	}
145 }
146 
147 bool extMatchesConvertTargetFormat(string ext, const ConvertTargetFormat ctf)
148 	pure nothrow @nogc
149 {
150 	final switch(ctf) {
151 		case ConvertTargetFormat.json:
152 			return ext == ".json";
153 		case ConvertTargetFormat.sdl:
154 			return ext == ".sdl";
155 		case ConvertTargetFormat.undefined:
156 			return true;
157 	}
158 }
159 
160 Nullable!PackageDescription parse(const string absNormPath, const string inExt,
161 	const ref CommonOptions opts)
162 {
163 	const string input = readText(absNormPath);
164 	Nullable!PackageDescription ret;
165 	try {
166 		ret = inExt == ".json"
167 			? nullable(jsonToPackageDescription(input))
168 			: nullable(sdlToPackageDescription(input));
169 	} catch(DudPkgDescriptionException e) {
170 		() @trusted {
171 			tracef(opts.vverbose, "%s %s", e.toString(), e.info);
172 		}();
173 		printExceptionChain(e);
174 	}
175 	return ret;
176 }
177 
178 int writeOutput(PackageDescription pkg, const string absOutputFilename,
179 		const string ext, const ref CommonOptions opts)
180 {
181 	import std.json;
182 	auto f = File(absOutputFilename, "w");
183 
184 	try {
185 		if(ext == ".json") {
186 			JSONValue tmp = dud.pkgdescription.output.toJSON(pkg);
187 			f.writeln(tmp.toPrettyString());
188 		} else {
189 			toSDL(pkg, f.lockingTextWriter());
190 		}
191 	} catch(DudPkgDescriptionException e) {
192 		() @trusted {
193 			tracef(opts.vverbose, "%s %s", e.toString(), e.info);
194 		}();
195 		printExceptionChain(e);
196 		return 1;
197 	}
198 	return 0;
199 }
200 
201 void printExceptionChain(Throwable it) @trusted {
202 	while(it !is null) {
203 		writefln(it.msg);
204 		it = it.next();
205 	}
206 }