import std.stdio : writeln, writefln, stderr, File;
import std.path : buildPath;
import std.file : tempDir;
import std.getopt : Option;
immutable string skeletonDir = "~/.config/skeletons";
immutable string saveFile = "tmp.d_last_file";
immutable string defaultPattern = "tmp.XXXXX";
struct ArgvOptions {
string pattern, skeleton, extension;
bool last, failed;
this(const string pattern) { this.pattern = pattern; }
}
char
randomChar() {
import std.random : rndGen;
scope(exit) rndGen.popFront();
char value = rndGen.front % 62;
switch (value) {
case 0: .. case 25: value += 'a'; break;
case 26: .. case 51: value = 'A' + value % 26; break;
default: value = '0' + value % 52; break;
}
return value;
}
string
patternToFilename(const string pattern) {
import std.algorithm.iteration : map;
import std.conv : to;
return pattern.dup.map!(ch => ch == 'X' ? randomChar() : ch)
.to!string;
}
void
writeSkeletonToBaseFile(File file, const string skeleton) {
import std.file : exists, isDir, isFile, readText;
import std.path : expandTilde;
if (skeleton.length <= 0) {
return;
}
immutable string dir = expandTilde(skeletonDir);
if (!(dir.exists && dir.isDir)) {
stderr.writefln("invalid skeleton directory : %s", skeletonDir);
return;
}
auto skeletonFile = buildPath(dir, skeleton);
if (!(skeletonFile.exists && skeletonFile.isFile)) {
stderr.writefln("no %s skeleton file exists", skeleton);
return;
}
file.write(skeletonFile.readText);
}
string
createTempFile(const ArgvOptions opts) {
string dirname = tempDir();
string basename = patternToFilename(opts.pattern) ~ '.' ~ opts.extension;
string filename = buildPath(dirname, basename);
File file = File(filename, "w");
scope(exit) file.close;
writeSkeletonToBaseFile(file, opts.skeleton);
return filename;
}
File
openSaveFile(string mode) => File(buildPath(tempDir(), saveFile), mode);
void
saveEntry(const string filename) {
auto saveFile = openSaveFile("w");
scope(exit) saveFile.close;
saveFile.writeln(filename);
}
string
getLastCreation() {
import std.string : chomp;
auto saveFile = openSaveFile("r");
scope(exit) saveFile.close;
return saveFile.readln.chomp;
}
void
usage(const string prgName, const Option[] opts) {
import std.algorithm.iteration : reduce;
import std.algorithm.comparison : max;
ulong maxLength = reduce!((a, b) => max(a, b.optShort.length + b.optLong.length + 2))(ulong.min, opts);
stderr.writefln("usage: %s [opts] ext\n\nOptions:", prgName);
foreach (opt; opts) {
string combined = opt.optShort ~ ", " ~ opt.optLong;
stderr.writefln(" %-*s %s", maxLength, combined, opt.help);
}
}
bool
processArgv(string[] argv, ref ArgvOptions opts) {
import std.getopt : getopt, config;
auto result = getopt(
argv, config.caseSensitive, config.passThrough,
"skeleton|s", "Skeleton filename used as base file content.", &opts.skeleton,
"pattern|p", "Temporary file pattern. X's are replaced with random char from [a-zA-Z0-9].", &opts.pattern,
"last|l", "Print the last created file and exit.", &opts.last,
);
if (result.helpWanted || (!opts.last && argv.length != 2)) {
usage(argv[0], result.options);
return false;
}
if (opts.last) {
return true;
}
opts.extension = argv[1];
return true;
}
int
main(string[] argv) {
auto opts = ArgvOptions(defaultPattern);
immutable bool success = processArgv(argv, opts);
if (!success) {
return 1;
}
if (opts.last) {
string last = getLastCreation();
writeln(last);
return 0;
}
string filename = createTempFile(opts);
saveEntry(filename);
writeln(filename);
return 0;
}