1
0
mirror of https://github.com/fadden/6502bench.git synced 2024-07-02 04:29:28 +00:00
6502bench/SourceGenWPF/ExternalFile.cs
Andy McFadden bf310d17bc Load a hard-coded project file
Fixed some stuff that crashed.  The project is loaded but nothing
visually interesting happens yet.

I'm still not entirely sure what the deal with declaring resources
is, but it seems you can either declare a ResourceDictionary and put
everything in it, or you can declare a bunch of items, which are then
implicitly placed in a ResourceDictionary.  This matters if you want
to have your string definitions merged in with everything else.  All
of the examples I found did one thing or the other, not both at once,
so it took some fiddling.  Yay WPF.
2019-05-08 18:08:46 -07:00

271 lines
10 KiB
C#

/*
* Copyright 2019 faddenSoft
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using System.Diagnostics;
using System.IO;
namespace SourceGenWPF {
/// <summary>
/// Manages references to external files, notably symbol files (.sym65) and extension
/// scripts. Identifiers look like "RT:subdir/file.sym65".
///
/// Instances are immutable.
/// </summary>
public class ExternalFile {
private const string INVALID_IDENT = "!!!INVALID!!!"; // probably don't need localization
/// <summary>
/// Pathname separator character for use for file identifiers. We want this
/// to be the same on all platforms, with local conversion, so we should probably be
/// using something like ':' that makes Windows barf. In practice, being rigorous
/// doesn't seem important, and '/' is pretty universal these days. Just don't do \.
/// </summary>
private const char PATH_SEP_CHAR = '/';
private const string RUNTIME_DIR_PREFIX = "RT:";
private const string PROJECT_DIR_PREFIX = "PROJ:";
private enum Location {
Unknown = 0,
RuntimeDir,
ProjectDir
}
// Not sure there's value in tracking the type, except for some validation checks.
private enum Type {
Unknown = 0,
SymbolFile,
ExtensionScript
}
/// <summary>
/// Identifier for this file.
/// </summary>
public string Identifier { get { return mIdent; } }
private string mIdent;
/// <summary>
/// File location.
/// </summary>
private Location mIdentLocation;
/// <summary>
/// File type.
/// </summary>
private Type mIdentType;
/// <summary>
/// Identifier without location prefix or filename extension.
/// </summary>
private string mInnards;
/// <summary>
/// Creates a new ExternalFile instance from the identifier.
/// </summary>
public static ExternalFile CreateFromIdent(string ident) {
if (!DecodeIdent(ident, out Location identLocation, out Type identType,
out string innards)) {
return null;
}
return new ExternalFile(ident, identLocation, identType, innards);
}
/// <summary>
/// Creates a new ExternalFile instance from a full path.
/// </summary>
/// <param name="pathName">Full path of external file, in canonical
/// form.</param>
/// <param name="projectDir">Full path to directory in which project file lives, in
/// canonical form. If the project hasn't been saved yet, pass an empty string.</param>
/// <returns>New object, or null if the path isn't valid.</returns>
public static ExternalFile CreateFromPath(string pathName, string projectDir) {
string stripDir;
string rtDir = RuntimeDataAccess.GetDirectory();
string prefix;
// Check path prefix for RT:, and full directory name for PROJ:.
if (pathName.StartsWith(rtDir)) {
stripDir = rtDir;
prefix = RUNTIME_DIR_PREFIX;
} else if (!string.IsNullOrEmpty(projectDir) &&
Path.GetDirectoryName(pathName) == projectDir) {
stripDir = projectDir;
prefix = PROJECT_DIR_PREFIX;
} else {
Debug.WriteLine("Path not in RuntimeData or project: " + pathName);
return null;
}
// Remove directory component.
string partialPath = pathName.Substring(stripDir.Length);
// If directory string didn't end with '/' or '\\', remove char from start.
if (partialPath[0] == '\\' || partialPath[0] == '/') {
partialPath = partialPath.Substring(1);
}
// Replace canonical path sep with '/'.
partialPath = partialPath.Replace(Path.DirectorySeparatorChar, PATH_SEP_CHAR);
string ident = prefix + partialPath;
Debug.WriteLine("Converted path '" + pathName + "' to ident '" + ident + "'");
return CreateFromIdent(ident);
}
/// <summary>
/// Internal constructor.
/// </summary>
private ExternalFile(string ident, Location identLocation, Type identType,
string innards) {
mIdent = ident;
mIdentLocation = identLocation;
mIdentType = identType;
mInnards = innards;
}
/// <summary>
/// Decodes an ident string into its constituent parts.
/// </summary>
private static bool DecodeIdent(string ident, out Location identLocation,
out Type identType, out string innards) {
identLocation = Location.Unknown;
identType = Type.Unknown;
innards = string.Empty;
int prefixLen;
if (ident.StartsWith(RUNTIME_DIR_PREFIX)) {
identLocation = Location.RuntimeDir;
prefixLen = RUNTIME_DIR_PREFIX.Length;
} else if (ident.StartsWith(PROJECT_DIR_PREFIX)) {
identLocation = Location.ProjectDir;
prefixLen = PROJECT_DIR_PREFIX.Length;
} else {
return false;
}
int extLen;
if (ident.EndsWith(PlatformSymbols.FILENAME_EXT)) {
identType = Type.SymbolFile;
extLen = PlatformSymbols.FILENAME_EXT.Length;
} else if (ident.EndsWith(Sandbox.ScriptManager.FILENAME_EXT)) {
identType = Type.ExtensionScript;
extLen = Sandbox.ScriptManager.FILENAME_EXT.Length;
} else {
return false;
}
// Fail idents with no actual name, e.g. "RT:.cs".
if (ident.Length == prefixLen + extLen) {
return false;
}
innards = ident.Substring(prefixLen, ident.Length - prefixLen - extLen);
return true;
}
/// <summary>
/// Strips the prefix and filename extension off of an identifier.
/// </summary>
/// <returns>Stripped identifier, or null if the identifier was malformed.</returns>
public string GetInnards() {
return mInnards;
}
/// <summary>
/// Converts an identifier to a full path. For PROJ: identifiers, the project
/// directory argument is used.
/// </summary>
/// <param name="ident">Identifier to convert.</param>
/// <param name="projectDir">Full path to directory in which project file lives, in
/// canonical form. If the project hasn't been saved yet, pass an empty string.</param>
/// <returns>Full path, or null if the identifier points to a file outside the
/// directory, or if this is a ProjectDir ident and the project dir isn't set.</returns>
public string GetPathName(string projectDir) {
string dir;
bool subdirAllowed;
switch (mIdentLocation) {
case Location.RuntimeDir:
dir = RuntimeDataAccess.GetDirectory();
if (string.IsNullOrEmpty(dir)) {
// Could happen if we can't find the runtime data directory, though
// we probably should've failed before now.
Debug.Assert(false);
return null;
}
subdirAllowed = true;
break;
case Location.ProjectDir:
if (string.IsNullOrEmpty(projectDir)) {
// Shouldn't happen in practice -- we don't create PROJ: identifiers
// unless a project directory has been established.
Debug.Assert(false);
return null;
}
dir = projectDir;
subdirAllowed = false;
break;
default:
Debug.Assert(false);
return null;
}
int extLen = mIdent.IndexOf(':') + 1;
string fullPath = Path.GetFullPath(Path.Combine(dir, mIdent.Substring(extLen)));
// Confirm the file actually lives in the directory. RT: files can be anywhere
// below the RuntimeData directory, while PROJ: files must live in the project
// directory.
if (subdirAllowed) {
dir += Path.DirectorySeparatorChar;
if (!fullPath.StartsWith(dir)) {
Debug.WriteLine("WARNING: ident resolves outside subdir: " + mIdent);
Debug.Assert(false);
return null;
}
} else {
if (dir != Path.GetDirectoryName(fullPath)) {
Debug.WriteLine("WARNING: ident resolves outside dir: " + mIdent);
return null;
}
}
return fullPath;
}
/// <summary>
/// Generates a script DLL name from the ident. If the ident is for a project-scope
/// extension script, the project's file name will be included.
/// </summary>
/// <param name="projectPathName">Full path to project.</param>
/// <returns>DLL filename.</returns>
public string GenerateDllName(string projectFileName) {
switch (mIdentLocation) {
case Location.RuntimeDir:
return "RT_" + mInnards.Replace(PATH_SEP_CHAR, '_') + ".dll";
case Location.ProjectDir:
string noExt = Path.GetFileNameWithoutExtension(projectFileName);
return "PROJ_" + noExt + "_" + mInnards.Replace(PATH_SEP_CHAR, '_') + ".dll";
default:
Debug.Assert(false);
return null;
}
}
}
}