1
0
Files
otyaSMILEBASIC/SMILEBASIC/project.d
2017-07-09 22:44:25 +09:00

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;
}