429 lines
12 KiB
D
429 lines
12 KiB
D
module otya.smilebasic.project;
|
|
import otya.smilebasic.error;
|
|
import otya.smilebasic.data;
|
|
import otya.smilebasic.type;
|
|
import std.path;
|
|
import std.file;
|
|
import std.string;
|
|
import std.traits;
|
|
import std.conv;
|
|
import std.uni;
|
|
import std.typecons;
|
|
import std.range;
|
|
enum DialogResult
|
|
{
|
|
FAILURE = 0,
|
|
SUCCESS = 1,
|
|
CANCEL = -1,
|
|
}
|
|
|
|
class Projects
|
|
{
|
|
wstring currentProject;
|
|
wstring rootPath;
|
|
wstring projectPath;
|
|
string projectPathU8;
|
|
DialogResult result = DialogResult.FAILURE;//Default value
|
|
wstring saveConfirm =
|
|
r"ファイルを書き込みます。
|
|
|
|
名前: %s
|
|
|
|
よろしいですか?
|
|
";
|
|
this(wstring root)
|
|
{
|
|
rootPath = root;
|
|
projectPath = buildPath(root, "PROJECTS"w);
|
|
projectPathU8 = projectPath.to!string;
|
|
if(exists(projectPathU8))
|
|
{
|
|
if(isFile(projectPathU8))
|
|
{
|
|
throw new Exception("PROJECTS is file");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
mkdir(projectPathU8);
|
|
}
|
|
createProjectInternal("[DEFAULT]");
|
|
createProjectInternal("SYS");
|
|
}
|
|
static bool isValidProjectName(C)(C[] filename) if(isSomeChar!(C))
|
|
{
|
|
if(filename.length > 14)
|
|
{
|
|
return false;
|
|
}
|
|
foreach(c; filename)
|
|
{
|
|
if(!((c >= 'A' && c<= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_' || c == '-'))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
static bool isValidFileName(C)(C[] filename) if(isSomeChar!(C))
|
|
{
|
|
if(filename.length > 14)
|
|
{
|
|
return false;
|
|
}
|
|
foreach(c; filename)
|
|
{
|
|
if(!((c >= 'A' && c<= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '.' || c == '_' || c == '-' || c == '@'))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
private void createProjectInternal(wstring name)
|
|
{
|
|
auto path = buildPath(projectPath, name.toUpper).to!string;
|
|
if(!exists(path))
|
|
{
|
|
mkdir(path);
|
|
}
|
|
auto txt = buildPath(path, "TXT").to!string;
|
|
if(!exists(txt))
|
|
{
|
|
mkdir(txt);
|
|
}
|
|
auto dat = buildPath(path, "DAT").to!string;
|
|
if(!exists(dat))
|
|
{
|
|
mkdir(dat);
|
|
}
|
|
}
|
|
static Tuple!(wstring, wstring, wstring) splitResourceName(wstring name)
|
|
{
|
|
auto ind = name.indexOf(":");
|
|
wstring type, project;
|
|
if(ind != -1)
|
|
{
|
|
type = name[0..ind];
|
|
name = name[ind + 1..$];
|
|
}
|
|
ptrdiff_t sep = name.indexOf("/");
|
|
if(sep != -1)
|
|
{
|
|
project = name[0..sep];
|
|
name = name[sep + 1..$];
|
|
}
|
|
return tuple(type, project, name);
|
|
}
|
|
wstring[] getFileList(wstring project, wstring type)
|
|
{
|
|
import std.algorithm;
|
|
import std.functional;
|
|
if (project.empty)
|
|
project = convertProjectName(currentProject);
|
|
else if(!isValidProjectName(project))
|
|
{
|
|
return null;
|
|
}
|
|
auto dir(wstring type)
|
|
{
|
|
auto path = buildPath(projectPath, project, type).to!string;
|
|
return dirEntries(path, SpanMode.shallow, false);
|
|
}
|
|
|
|
if(type == "")
|
|
{
|
|
return chain(dir("TXT").filter!(x=>x.isFile&&isValidFileName(baseName(x.name))).map!((x) => "*"w ~ baseName(x.name).to!wstring),
|
|
dir("DAT").filter!(x=>x.isFile&&isValidFileName(baseName(x.name))).map!((x) => " " ~ baseName(x.name).to!wstring)).array;
|
|
}
|
|
if(type == "TXT")
|
|
{
|
|
return dir("TXT").filter!(x=>x.isFile&&isValidFileName(baseName(x.name))).map!((x) => "*" ~ baseName(x.name).to!wstring).array;
|
|
}
|
|
if(type == "DAT")
|
|
{
|
|
return dir("DAT").filter!(x=>x.isFile&&isValidFileName(baseName(x.name))).map!((x) => " " ~ baseName(x.name).to!wstring).array;
|
|
}
|
|
return null;
|
|
}
|
|
bool loadFile(wstring project, wstring type, wstring name, out wstring contents)
|
|
{
|
|
contents = "";
|
|
result = DialogResult.FAILURE;
|
|
if(!isValidProjectName(project))
|
|
{
|
|
return false;
|
|
}
|
|
type = type.toUpper;
|
|
//TODO:case-sensitive filesystem...
|
|
name = name.toUpper;
|
|
if (project.empty)
|
|
project = convertProjectName(currentProject);
|
|
if(type != "TXT" && type != "DAT")
|
|
{
|
|
return false;
|
|
}
|
|
if(!isValidFileName(name))
|
|
{
|
|
return false;
|
|
}
|
|
auto path = buildPath(projectPath, project, type, name).to!string;
|
|
if (!path.exists)
|
|
{
|
|
//show dialog
|
|
return false;
|
|
}
|
|
|
|
//show dialog
|
|
result = DialogResult.SUCCESS;
|
|
contents = readText(path).to!wstring;
|
|
return true;
|
|
}
|
|
|
|
bool loadDataFile(T)(FileName file, ref Array!T contents, out DataHeader header)
|
|
if (is(T == int) || is(T == double))
|
|
{
|
|
wstring project;
|
|
result = DialogResult.FAILURE;
|
|
if (file.project.empty)
|
|
project = convertProjectName(currentProject);
|
|
else
|
|
{
|
|
project = file.project;
|
|
if (!isValidProjectName(project))
|
|
throw new IllegalFunctionCall("LOAD");
|
|
}
|
|
if (!isValidFilename(file.name))
|
|
throw new IllegalFunctionCall("LOAD");
|
|
auto path = buildPath(projectPath, project, "DAT"w, file.name).to!string;
|
|
|
|
//show dialog
|
|
result = DialogResult.SUCCESS;
|
|
import std.stdio;
|
|
import std.algorithm;
|
|
File f = File(path, "rb");
|
|
DataHeader[1] harray;
|
|
f.rawRead(harray);
|
|
if (harray[0].magic != "PCBN0001")
|
|
{
|
|
f.seek(0);
|
|
harray[0].dimension = 1;
|
|
harray[0].dim[] = [cast(int)(f.size / int.sizeof), 0, 0, 0];
|
|
harray[0].type = DataType.int_;
|
|
}
|
|
header = harray[0];
|
|
if (harray[0].dimension != contents.dimCount)
|
|
{
|
|
throw new TypeMismatch("LOAD");
|
|
}
|
|
auto dim = harray[0].dim[0..harray[0].dimension];
|
|
auto len = dim.reduce!((x, y) => x * y);
|
|
T[] result;
|
|
if (harray[0].type == DataType.int_)
|
|
{
|
|
int[] array = new int[len];
|
|
f.rawRead(array);
|
|
static if (!is(T == int))
|
|
{
|
|
result = array.map!(x => x.to!T).array;
|
|
}
|
|
else
|
|
{
|
|
result = array;
|
|
}
|
|
}
|
|
else if (harray[0].type == DataType.double_)
|
|
{
|
|
double[] array = new double[len];
|
|
f.rawRead(array);
|
|
static if (!is(T == double))
|
|
{
|
|
result = array.map!(x => x.to!T).array;
|
|
}
|
|
else
|
|
{
|
|
result = array;
|
|
}
|
|
}
|
|
else if (harray[0].type == DataType.ushort_)
|
|
{
|
|
ushort[] array = new ushort[len];
|
|
f.rawRead(array);
|
|
result = array.map!(x => x.to!T).array;
|
|
}
|
|
else
|
|
{
|
|
throw new TypeMismatch("LOAD(invalid file)");
|
|
}
|
|
//配列に書き込むというより配列を差し替えている
|
|
contents.array = result;
|
|
contents.dim[] = harray[0].dim[];
|
|
contents.dimCount = harray[0].dimension;
|
|
return true;
|
|
}
|
|
bool loadDataFile(T)(FileName file, ref Array!T contents)
|
|
if (is(T == int) || is(T == double))
|
|
{
|
|
DataHeader dh;
|
|
return loadDataFile(file, contents, dh);
|
|
}
|
|
|
|
void saveTextFile(FileName file, wstring content)
|
|
{
|
|
wstring project;
|
|
result = DialogResult.FAILURE;
|
|
if (file.project.empty)
|
|
project = convertProjectName(currentProject);
|
|
else
|
|
{
|
|
project = file.project;
|
|
if (!isValidProjectName(project))
|
|
throw new IllegalFunctionCall("SAVE");
|
|
}
|
|
if (!isValidFilename(file.name))
|
|
throw new IllegalFunctionCall("SAVE");
|
|
auto path = buildPath(projectPath, project, "TXT"w, file.name).to!string;
|
|
write(path, content.to!string/*UTF8*/);
|
|
result = DialogResult.SUCCESS;
|
|
}
|
|
|
|
void saveDataFile(T)(FileName file, Array!T content)
|
|
if (is(T == int) || is(T == double))
|
|
{
|
|
wstring project;
|
|
result = DialogResult.FAILURE;
|
|
if (file.project.empty)
|
|
project = convertProjectName(currentProject);
|
|
else
|
|
{
|
|
project = file.project;
|
|
if (!isValidProjectName(project))
|
|
throw new IllegalFunctionCall("SAVE");
|
|
}
|
|
if (!isValidFilename(file.name))
|
|
throw new IllegalFunctionCall("SAVE");
|
|
auto path = buildPath(projectPath, project, "DAT"w, file.name).to!string;
|
|
DataHeader[1] harray;
|
|
DataHeader* head = harray.ptr;
|
|
static if (is(T == int))
|
|
{
|
|
head.type = DataType.int_;
|
|
}
|
|
else
|
|
{
|
|
head.type = DataType.double_;
|
|
}
|
|
head.dim[] = content.dim[];
|
|
head.dimension = cast(byte)content.dimCount;
|
|
import std.stdio;
|
|
File f = File(path, "wb");
|
|
f.rawWrite(harray);
|
|
f.rawWrite(content.array);
|
|
result = DialogResult.SUCCESS;
|
|
}
|
|
|
|
wstring convertProjectName(wstring proj)
|
|
{
|
|
if (proj.empty)
|
|
{
|
|
if (currentProject.empty)
|
|
return "[DEFAULT]";
|
|
return currentProject;
|
|
}
|
|
return proj;
|
|
}
|
|
|
|
bool chkfile(wstring path)
|
|
{
|
|
auto file = parseFileName(path);
|
|
if (!Projects.isValidFileName(file.name))
|
|
{
|
|
throw new IllegalFunctionCall("CHKFILE", 1);
|
|
}
|
|
if (!Projects.isValidProjectName(file.project))
|
|
{
|
|
throw new IllegalFunctionCall("CHKFILE", 1);
|
|
}
|
|
if (file.resource == Resource.illegal)
|
|
{
|
|
throw new IllegalFunctionCall("CHKFILE", 1);
|
|
}
|
|
bool isText = file.resource == Resource.none || file.resource == Resource.program || file.resource == Resource.text;
|
|
auto proj = convertProjectName(file.project);
|
|
auto p = buildPath(projectPath, proj, isText ? "TXT"w : "DAT"w, file.name).to!string;
|
|
return p.exists;
|
|
}
|
|
|
|
static Resource getResource(wstring resname)
|
|
{
|
|
switch (std.uni.toUpper(resname))
|
|
{
|
|
case "PRG":
|
|
return Resource.program;
|
|
case "GRP":
|
|
return Resource.graphic;
|
|
case "DAT":
|
|
return Resource.data;
|
|
case "TXT":
|
|
return Resource.text;
|
|
case "GRPF":
|
|
return Resource.graphicFont;
|
|
default:
|
|
return Resource.illegal;
|
|
}
|
|
}
|
|
|
|
static FileName parseFileName(wstring input)
|
|
{
|
|
FileName result;
|
|
auto s = splitResourceName(input);
|
|
if (s[0].length)
|
|
{
|
|
auto resname = s[0];
|
|
auto numind = resname.indexOfAny("1234567890");
|
|
if (numind != -1)
|
|
{
|
|
auto resnum = resname[numind..$].to!int;
|
|
resname = resname[0..numind];
|
|
result.resourceNumber = resnum;
|
|
result.hasResourceNumber = true;
|
|
}
|
|
result.resource = getResource(resname);
|
|
}
|
|
result.project = s[1];
|
|
result.name = s[2];
|
|
return result;
|
|
}
|
|
unittest
|
|
{
|
|
auto a = parseFileName("A");
|
|
assert(a.resource == Resource.none && !a.hasResourceNumber && a.project == "" && a.name == "A");
|
|
a = parseFileName("GRP:B");
|
|
assert(a.resource == Resource.graphic && !a.hasResourceNumber && a.project == "" && a.name == "B");
|
|
a = parseFileName("PRG10:CD");
|
|
assert(a.resource == Resource.program && a.hasResourceNumber && a.resourceNumber == 10 && a.project == "" && a.name == "CD");
|
|
a = parseFileName("DAT:SYS/EF");
|
|
assert(a.resource == Resource.data && !a.hasResourceNumber && a.project == "SYS" && a.name == "EF");
|
|
}
|
|
}
|
|
|
|
enum Resource
|
|
{
|
|
none,
|
|
program,
|
|
graphic,
|
|
data,
|
|
text,
|
|
illegal,
|
|
graphicFont,
|
|
}
|
|
|
|
struct FileName
|
|
{
|
|
Resource resource;
|
|
bool hasResourceNumber;
|
|
int resourceNumber;
|
|
wstring project;
|
|
wstring name;
|
|
}
|