mirror of
https://github.com/amyinspace/MagicSetEditor2.git
synced 2026-06-13 14:07:01 -04:00
Change tabs to two spaces.
This commit is contained in:
@@ -34,10 +34,10 @@ template <> void GetDefaultMember::handle(const wxDateTime& v) { value = to_sc
|
||||
// ----------------------------------------------------------------------------- : GetMember
|
||||
|
||||
GetMember::GetMember(const String& name)
|
||||
: target_name(name)
|
||||
: target_name(name)
|
||||
{}
|
||||
|
||||
// caused by the pattern: if (!tag.isComplex()) { REFLECT_NAMELESS(stuff) }
|
||||
template <> void GetMember::handle(const String& v) {
|
||||
throw InternalError(_("GetDefaultMember::handle"));
|
||||
throw InternalError(_("GetDefaultMember::handle"));
|
||||
}
|
||||
|
||||
+107
-107
@@ -24,42 +24,42 @@ template <typename T> class Scriptable;
|
||||
/** The member is wrapped in a ScriptValue */
|
||||
class GetDefaultMember {
|
||||
public:
|
||||
/// Tell the reflection code we are not reading
|
||||
inline bool reading() const { return false; }
|
||||
inline bool scripting() const { return true; }
|
||||
inline bool isComplex() const { return false; }
|
||||
inline void addAlias(int, const Char*, const Char*) {}
|
||||
inline void handleIgnore(int, const Char*) {}
|
||||
|
||||
/// The result, or script_nil if the member was not found
|
||||
inline ScriptValueP result() { return value; }
|
||||
|
||||
// --------------------------------------------------- : Handling objects
|
||||
|
||||
/// Handle an object: we don't match things with a name
|
||||
template <typename T>
|
||||
void handle(const Char* name, const T& object) {}
|
||||
/// Don't handle a value
|
||||
template <typename T>
|
||||
inline void handleNoScript(const Char* name, T& value) {}
|
||||
/// Tell the reflection code we are not reading
|
||||
inline bool reading() const { return false; }
|
||||
inline bool scripting() const { return true; }
|
||||
inline bool isComplex() const { return false; }
|
||||
inline void addAlias(int, const Char*, const Char*) {}
|
||||
inline void handleIgnore(int, const Char*) {}
|
||||
|
||||
/// The result, or script_nil if the member was not found
|
||||
inline ScriptValueP result() { return value; }
|
||||
|
||||
// --------------------------------------------------- : Handling objects
|
||||
|
||||
/// Handle an object: we don't match things with a name
|
||||
template <typename T>
|
||||
void handle(const Char* name, const T& object) {}
|
||||
/// Don't handle a value
|
||||
template <typename T>
|
||||
inline void handleNoScript(const Char* name, T& value) {}
|
||||
|
||||
/// Handle an object: investigate children, or store it if we know how
|
||||
void handle(const Char *);
|
||||
template <typename T> void handle(const T&);
|
||||
|
||||
/// Handle a Defaultable: investigate children
|
||||
template <typename T> void handle(const Defaultable<T>&);
|
||||
template <typename T> void handle(const Scriptable<T>& );
|
||||
template <typename T> void handle(const vector<T>& c) { value = to_script(&c); }
|
||||
template <typename K, typename V> void handle(const map<K,V>& c) { value = to_script(&c); }
|
||||
template <typename K, typename V> void handle(const IndexMap<K,V>& c) { value = to_script(&c); }
|
||||
template <typename K, typename V> void handle(const DelayedIndexMaps<K,V>&) {}
|
||||
template <typename K, typename V> void handle(const DelayedIndexMapsData<K,V>& c);
|
||||
template <typename T> void handle(const intrusive_ptr<T>& p) { value = to_script(p); }
|
||||
void handle(const ScriptValueP&);
|
||||
void handle(const ScriptP&);
|
||||
/// Handle an object: investigate children, or store it if we know how
|
||||
void handle(const Char *);
|
||||
template <typename T> void handle(const T&);
|
||||
|
||||
/// Handle a Defaultable: investigate children
|
||||
template <typename T> void handle(const Defaultable<T>&);
|
||||
template <typename T> void handle(const Scriptable<T>& );
|
||||
template <typename T> void handle(const vector<T>& c) { value = to_script(&c); }
|
||||
template <typename K, typename V> void handle(const map<K,V>& c) { value = to_script(&c); }
|
||||
template <typename K, typename V> void handle(const IndexMap<K,V>& c) { value = to_script(&c); }
|
||||
template <typename K, typename V> void handle(const DelayedIndexMaps<K,V>&) {}
|
||||
template <typename K, typename V> void handle(const DelayedIndexMapsData<K,V>& c);
|
||||
template <typename T> void handle(const intrusive_ptr<T>& p) { value = to_script(p); }
|
||||
void handle(const ScriptValueP&);
|
||||
void handle(const ScriptP&);
|
||||
private:
|
||||
ScriptValueP value; ///< The value we found (if any)
|
||||
ScriptValueP value; ///< The value we found (if any)
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : GetMember
|
||||
@@ -68,96 +68,96 @@ class GetDefaultMember {
|
||||
/** The member is wrapped in a ScriptValue */
|
||||
class GetMember : private GetDefaultMember {
|
||||
public:
|
||||
/// Construct a member getter that looks for the given name
|
||||
GetMember(const String& name);
|
||||
|
||||
/// Tell the reflection code we are not reading
|
||||
inline bool reading() const { return false; }
|
||||
inline bool scripting() const { return true; }
|
||||
inline bool isComplex() const { return true; }
|
||||
inline void addAlias(int, const Char*, const Char*) {}
|
||||
inline void handleIgnore(int, const Char*) {}
|
||||
|
||||
/// The result, or script_nil if the member was not found
|
||||
inline ScriptValueP result() { return gdm.result(); }
|
||||
|
||||
// --------------------------------------------------- : Handling objects
|
||||
|
||||
/// Handle an object: we are done if the name matches
|
||||
template <typename T>
|
||||
void handle(const Char* name, const T& object) {
|
||||
if (!gdm.result() && cannocial_name_compare(target_name, name)) {
|
||||
gdm.handle(object);
|
||||
}
|
||||
}
|
||||
/// Don't handle a value
|
||||
template <typename T>
|
||||
inline void handleNoScript(const Char* name, T& value) {}
|
||||
/// Handle an object: investigate children
|
||||
template <typename T> void handle(const T&);
|
||||
/// Handle an index map: invistigate keys
|
||||
template <typename K, typename V> void handle(const IndexMap<K,V>& m) {
|
||||
if (gdm.result()) return;
|
||||
for (typename IndexMap<K,V>::const_iterator it = m.begin() ; it != m.end() ; ++it) {
|
||||
if (get_key_name(*it) == target_name) {
|
||||
gdm.handle(*it);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
template <typename K, typename V> void handle(const DelayedIndexMaps<K,V>&);
|
||||
template <typename K, typename V> void handle(const DelayedIndexMapsData<K,V>&);
|
||||
|
||||
/// Construct a member getter that looks for the given name
|
||||
GetMember(const String& name);
|
||||
|
||||
/// Tell the reflection code we are not reading
|
||||
inline bool reading() const { return false; }
|
||||
inline bool scripting() const { return true; }
|
||||
inline bool isComplex() const { return true; }
|
||||
inline void addAlias(int, const Char*, const Char*) {}
|
||||
inline void handleIgnore(int, const Char*) {}
|
||||
|
||||
/// The result, or script_nil if the member was not found
|
||||
inline ScriptValueP result() { return gdm.result(); }
|
||||
|
||||
// --------------------------------------------------- : Handling objects
|
||||
|
||||
/// Handle an object: we are done if the name matches
|
||||
template <typename T>
|
||||
void handle(const Char* name, const T& object) {
|
||||
if (!gdm.result() && cannocial_name_compare(target_name, name)) {
|
||||
gdm.handle(object);
|
||||
}
|
||||
}
|
||||
/// Don't handle a value
|
||||
template <typename T>
|
||||
inline void handleNoScript(const Char* name, T& value) {}
|
||||
/// Handle an object: investigate children
|
||||
template <typename T> void handle(const T&);
|
||||
/// Handle an index map: invistigate keys
|
||||
template <typename K, typename V> void handle(const IndexMap<K,V>& m) {
|
||||
if (gdm.result()) return;
|
||||
for (typename IndexMap<K,V>::const_iterator it = m.begin() ; it != m.end() ; ++it) {
|
||||
if (get_key_name(*it) == target_name) {
|
||||
gdm.handle(*it);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
template <typename K, typename V> void handle(const DelayedIndexMaps<K,V>&);
|
||||
template <typename K, typename V> void handle(const DelayedIndexMapsData<K,V>&);
|
||||
|
||||
private:
|
||||
const String& target_name; ///< The name we are looking for
|
||||
GetDefaultMember gdm; ///< Object to store and retrieve the value
|
||||
const String& target_name; ///< The name we are looking for
|
||||
GetDefaultMember gdm; ///< Object to store and retrieve the value
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : Reflection
|
||||
|
||||
/// Implement reflection as used by GetDefaultMember
|
||||
#define REFLECT_OBJECT_GET_DEFAULT_MEMBER(Cls) REFLECT_WRITE_YES(Cls,GetDefaultMember)
|
||||
#define REFLECT_OBJECT_GET_MEMBER(Cls) REFLECT_WRITE_YES(Cls,GetMember)
|
||||
#define REFLECT_OBJECT_GET_DEFAULT_MEMBER_NOT(Cls) REFLECT_WRITE_NO(Cls,GetDefaultMember)
|
||||
#define REFLECT_OBJECT_GET_MEMBER_NOT(Cls) REFLECT_WRITE_NO(Cls,GetMember)
|
||||
#define REFLECT_OBJECT_GET_DEFAULT_MEMBER(Cls) REFLECT_WRITE_YES(Cls,GetDefaultMember)
|
||||
#define REFLECT_OBJECT_GET_MEMBER(Cls) REFLECT_WRITE_YES(Cls,GetMember)
|
||||
#define REFLECT_OBJECT_GET_DEFAULT_MEMBER_NOT(Cls) REFLECT_WRITE_NO(Cls,GetDefaultMember)
|
||||
#define REFLECT_OBJECT_GET_MEMBER_NOT(Cls) REFLECT_WRITE_NO(Cls,GetMember)
|
||||
|
||||
#define REFLECT_WRITE_YES(Cls, Tag) \
|
||||
template<> void Tag::handle<Cls>(const Cls& object) { \
|
||||
const_cast<Cls&>(object).reflect(*this); \
|
||||
} \
|
||||
void Cls::reflect(Tag& tag) { \
|
||||
reflect_impl(tag); \
|
||||
}
|
||||
#define REFLECT_WRITE_YES(Cls, Tag) \
|
||||
template<> void Tag::handle<Cls>(const Cls& object) { \
|
||||
const_cast<Cls&>(object).reflect(*this); \
|
||||
} \
|
||||
void Cls::reflect(Tag& tag) { \
|
||||
reflect_impl(tag); \
|
||||
}
|
||||
|
||||
#define REFLECT_WRITE_NO(Cls, Tag) \
|
||||
template<> void Tag::handle<Cls>(const Cls& object) {} \
|
||||
void Cls::reflect(Tag& tag) {}
|
||||
#define REFLECT_WRITE_NO(Cls, Tag) \
|
||||
template<> void Tag::handle<Cls>(const Cls& object) {} \
|
||||
void Cls::reflect(Tag& tag) {}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Reflection for enumerations
|
||||
|
||||
/// Implement enum reflection as used by GetMember
|
||||
#define REFLECT_ENUM_GET_MEMBER(Enum) \
|
||||
template<> void GetDefaultMember::handle<Enum>(const Enum& enum_) { \
|
||||
EnumGetMember egm(*this); \
|
||||
reflect_ ## Enum(const_cast<Enum&>(enum_), egm); \
|
||||
}
|
||||
#define REFLECT_ENUM_GET_MEMBER(Enum) \
|
||||
template<> void GetDefaultMember::handle<Enum>(const Enum& enum_) { \
|
||||
EnumGetMember egm(*this); \
|
||||
reflect_ ## Enum(const_cast<Enum&>(enum_), egm); \
|
||||
}
|
||||
|
||||
/// 'Tag' to be used when reflecting enumerations for GetMember
|
||||
class EnumGetMember {
|
||||
public:
|
||||
inline EnumGetMember(GetDefaultMember& gdm)
|
||||
: gdm(gdm) {}
|
||||
|
||||
/// Handle a possible value for the enum, if the name matches the name in the input
|
||||
template <typename Enum>
|
||||
inline void handle(const Char* name, Enum value, Enum enum_) {
|
||||
if (enum_ == value) {
|
||||
gdm.handle(String(name));
|
||||
}
|
||||
}
|
||||
|
||||
inline EnumGetMember(GetDefaultMember& gdm)
|
||||
: gdm(gdm) {}
|
||||
|
||||
/// Handle a possible value for the enum, if the name matches the name in the input
|
||||
template <typename Enum>
|
||||
inline void handle(const Char* name, Enum value, Enum enum_) {
|
||||
if (enum_ == value) {
|
||||
gdm.handle(String(name));
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
GetDefaultMember& gdm; ///< The object to store output in
|
||||
GetDefaultMember& gdm; ///< The object to store output in
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : EOF
|
||||
|
||||
+421
-421
File diff suppressed because it is too large
Load Diff
+171
-171
@@ -49,154 +49,154 @@ DECLARE_DYNAMIC_ARG(Package*, clipboard_package);
|
||||
*/
|
||||
class Package : public IntrusivePtrVirtualBase {
|
||||
public:
|
||||
// --------------------------------------------------- : Managing the outside of the package
|
||||
// --------------------------------------------------- : Managing the outside of the package
|
||||
|
||||
/// Creates a new package
|
||||
Package();
|
||||
virtual ~Package();
|
||||
/// Creates a new package
|
||||
Package();
|
||||
virtual ~Package();
|
||||
|
||||
/// Is a file opened?
|
||||
bool isOpened() const;
|
||||
/// Must the package be saved with saveAs()?
|
||||
/// This is the case if the package has not been saved/opened before
|
||||
bool needSaveAs() const;
|
||||
/// Determines the short name of this package: the filename without path or extension
|
||||
String name() const;
|
||||
/// Return the relative filename of this file, the name and extension
|
||||
String relativeFilename() const;
|
||||
/// Return the absolute filename of this file
|
||||
const String& absoluteFilename() const;
|
||||
/// The time this package was last modified
|
||||
inline wxDateTime lastModified() const { return modified; }
|
||||
/// Is a file opened?
|
||||
bool isOpened() const;
|
||||
/// Must the package be saved with saveAs()?
|
||||
/// This is the case if the package has not been saved/opened before
|
||||
bool needSaveAs() const;
|
||||
/// Determines the short name of this package: the filename without path or extension
|
||||
String name() const;
|
||||
/// Return the relative filename of this file, the name and extension
|
||||
String relativeFilename() const;
|
||||
/// Return the absolute filename of this file
|
||||
const String& absoluteFilename() const;
|
||||
/// The time this package was last modified
|
||||
inline wxDateTime lastModified() const { return modified; }
|
||||
|
||||
/// Open a package
|
||||
/**
|
||||
* Should only be called when the package is constructed using the default constructor!
|
||||
*
|
||||
* If 'fast' is set, then for directories a full directory listing is not performed.
|
||||
* This means that the file_infos will not be fully initialized.
|
||||
*
|
||||
* @pre open not called before [TODO]
|
||||
*/
|
||||
void open(const String& package, bool fast = false);
|
||||
/// Open a package
|
||||
/**
|
||||
* Should only be called when the package is constructed using the default constructor!
|
||||
*
|
||||
* If 'fast' is set, then for directories a full directory listing is not performed.
|
||||
* This means that the file_infos will not be fully initialized.
|
||||
*
|
||||
* @pre open not called before [TODO]
|
||||
*/
|
||||
void open(const String& package, bool fast = false);
|
||||
|
||||
/// Saves the package
|
||||
/**
|
||||
* By default saves as a zip file, unless it was already a directory.
|
||||
*
|
||||
* If remove_unused=true all files that were in the file and
|
||||
* are not touched with referenceFile will be deleted from the new archive!
|
||||
* This is a form of garbage collection, to get rid of old picture files for example.
|
||||
*/
|
||||
void save(bool remove_unused = true);
|
||||
/// Saves the package
|
||||
/**
|
||||
* By default saves as a zip file, unless it was already a directory.
|
||||
*
|
||||
* If remove_unused=true all files that were in the file and
|
||||
* are not touched with referenceFile will be deleted from the new archive!
|
||||
* This is a form of garbage collection, to get rid of old picture files for example.
|
||||
*/
|
||||
void save(bool remove_unused = true);
|
||||
|
||||
/// Saves the package under a different filename
|
||||
void saveAs(const String& package, bool remove_unused = true);
|
||||
/// Saves the package under a different filename
|
||||
void saveAs(const String& package, bool remove_unused = true);
|
||||
|
||||
/// Saves the package under a different filename, but keep the old one open
|
||||
void saveCopy(const String& package);
|
||||
/// Saves the package under a different filename, but keep the old one open
|
||||
void saveCopy(const String& package);
|
||||
|
||||
|
||||
// --------------------------------------------------- : Managing the inside of the package
|
||||
// --------------------------------------------------- : Managing the inside of the package
|
||||
|
||||
/// Open an input stream for a file in the package.
|
||||
InputStreamP openIn(const String& file);
|
||||
/// Open an input stream for a file in the package.
|
||||
InputStreamP openIn(const String& file);
|
||||
|
||||
/// Open an output stream for a file in the package.
|
||||
/// (changes are only committed with save())
|
||||
OutputStreamP openOut(const String& file);
|
||||
/// Open an output stream for a file in the package.
|
||||
/// (changes are only committed with save())
|
||||
OutputStreamP openOut(const String& file);
|
||||
|
||||
/// Get a filename that can be written to to modfify a file in the package
|
||||
/// (changes are only committed with save())
|
||||
String nameOut(const String& file);
|
||||
/// Get a filename that can be written to to modfify a file in the package
|
||||
/// (changes are only committed with save())
|
||||
String nameOut(const String& file);
|
||||
|
||||
/// Creates a new, unique, filename with the specified prefix and suffix
|
||||
/// for example newFileName("image/",".jpg") -> "image/1.jpg"
|
||||
/// Returns the name of a temporary file that can be written to.
|
||||
FileName newFileName(const String& prefix, const String& suffix);
|
||||
/// Creates a new, unique, filename with the specified prefix and suffix
|
||||
/// for example newFileName("image/",".jpg") -> "image/1.jpg"
|
||||
/// Returns the name of a temporary file that can be written to.
|
||||
FileName newFileName(const String& prefix, const String& suffix);
|
||||
|
||||
/// Signal that a file is still used by this package.
|
||||
/// Must be called for files not opened using openOut/nameOut
|
||||
/// If they are to be kept in the package.
|
||||
void referenceFile(const String& file);
|
||||
/// Signal that a file is still used by this package.
|
||||
/// Must be called for files not opened using openOut/nameOut
|
||||
/// If they are to be kept in the package.
|
||||
void referenceFile(const String& file);
|
||||
|
||||
/// Get an 'absolute filename' for a file in the package.
|
||||
/// This file can later be opened from anywhere (other process) using openAbsoluteFile()
|
||||
String absoluteName(const String& file);
|
||||
/// Get an 'absolute filename' for a file in the package.
|
||||
/// This file can later be opened from anywhere (other process) using openAbsoluteFile()
|
||||
String absoluteName(const String& file);
|
||||
|
||||
/// Open a file given an absolute filename
|
||||
static InputStreamP openAbsoluteFile(const String& name);
|
||||
/// Open a file given an absolute filename
|
||||
static InputStreamP openAbsoluteFile(const String& name);
|
||||
|
||||
// --------------------------------------------------- : Managing the inside of the package : Reader/writer
|
||||
// --------------------------------------------------- : Managing the inside of the package : Reader/writer
|
||||
|
||||
template <typename T>
|
||||
void readFile(const String& file, T& obj);
|
||||
template <typename T>
|
||||
void readFile(const String& file, T& obj);
|
||||
|
||||
template <typename T>
|
||||
T readFile(const String& file) {
|
||||
T obj;
|
||||
readFile(file, obj);
|
||||
return obj;
|
||||
}
|
||||
template <typename T>
|
||||
T readFile(const String& file) {
|
||||
T obj;
|
||||
readFile(file, obj);
|
||||
return obj;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void writeFile(const String& file, const T& obj, Version file_version) {
|
||||
Writer writer(openOut(file), file_version);
|
||||
writer.handle(obj);
|
||||
}
|
||||
template <typename T>
|
||||
void writeFile(const String& file, const T& obj, Version file_version) {
|
||||
Writer writer(openOut(file), file_version);
|
||||
writer.handle(obj);
|
||||
}
|
||||
|
||||
protected:
|
||||
// TODO: I dislike putting this here very much. There ought to be a better way.
|
||||
virtual VCSP getVCS() { return intrusive(new VCS()); }
|
||||
// TODO: I dislike putting this here very much. There ought to be a better way.
|
||||
virtual VCSP getVCS() { return intrusive(new VCS()); }
|
||||
|
||||
/// true if this is a zip file, false if a directory
|
||||
bool isZipfile() const { return !wxDirExists(filename); }
|
||||
/// true if this is a zip file, false if a directory
|
||||
bool isZipfile() const { return !wxDirExists(filename); }
|
||||
|
||||
// --------------------------------------------------- : Private stuff
|
||||
// --------------------------------------------------- : Private stuff
|
||||
private:
|
||||
|
||||
/// Information about a file in the package
|
||||
struct FileInfo {
|
||||
FileInfo();
|
||||
~FileInfo();
|
||||
bool keep; ///< Should this file be kept in the package? (as opposed to deleting it)
|
||||
bool created; ///< Was this file just created (e.g. should the VCS add it?)
|
||||
String tempName; ///< Name of the temporary file where new contents of this file are placed
|
||||
wxZipEntry* zipEntry; ///< Entry in the zip file for this file
|
||||
/// Is this file changed, and therefore written to a temporary file?
|
||||
inline bool wasWritten() const { return !tempName.empty(); }
|
||||
};
|
||||
/// Information about a file in the package
|
||||
struct FileInfo {
|
||||
FileInfo();
|
||||
~FileInfo();
|
||||
bool keep; ///< Should this file be kept in the package? (as opposed to deleting it)
|
||||
bool created; ///< Was this file just created (e.g. should the VCS add it?)
|
||||
String tempName; ///< Name of the temporary file where new contents of this file are placed
|
||||
wxZipEntry* zipEntry; ///< Entry in the zip file for this file
|
||||
/// Is this file changed, and therefore written to a temporary file?
|
||||
inline bool wasWritten() const { return !tempName.empty(); }
|
||||
};
|
||||
|
||||
/// Filename of the package
|
||||
String filename;
|
||||
/// Last modified time
|
||||
DateTime modified;
|
||||
/// Filename of the package
|
||||
String filename;
|
||||
/// Last modified time
|
||||
DateTime modified;
|
||||
|
||||
public:
|
||||
/// Information on files in the package
|
||||
/** Note: must be public for DECLARE_TYPEOF to work */
|
||||
typedef map<String, FileInfo> FileInfos;
|
||||
inline const FileInfos& getFileInfos() const { return files; }
|
||||
/// When was a file last modified?
|
||||
DateTime modificationTime(const pair<String, FileInfo>& fi) const;
|
||||
/// Information on files in the package
|
||||
/** Note: must be public for DECLARE_TYPEOF to work */
|
||||
typedef map<String, FileInfo> FileInfos;
|
||||
inline const FileInfos& getFileInfos() const { return files; }
|
||||
/// When was a file last modified?
|
||||
DateTime modificationTime(const pair<String, FileInfo>& fi) const;
|
||||
private:
|
||||
/// All files in the package
|
||||
FileInfos files;
|
||||
/// Filestream for reading zip files
|
||||
wxFileInputStream* fileStream;
|
||||
/// Filestream for reading zip files
|
||||
wxZipInputStream* zipStream;
|
||||
/// All files in the package
|
||||
FileInfos files;
|
||||
/// Filestream for reading zip files
|
||||
wxFileInputStream* fileStream;
|
||||
/// Filestream for reading zip files
|
||||
wxZipInputStream* zipStream;
|
||||
|
||||
void loadZipStream();
|
||||
void openDirectory(bool fast = false);
|
||||
void openSubdir(const String&);
|
||||
void openZipfile();
|
||||
void reopen();
|
||||
void removeTempFiles(bool remove_unused);
|
||||
void clearKeepFlag();
|
||||
void saveToZipfile(const String&, bool remove_unused, bool is_copy);
|
||||
void saveToDirectory(const String&, bool remove_unused, bool is_copy);
|
||||
FileInfos::iterator addFile(const String& file);
|
||||
void loadZipStream();
|
||||
void openDirectory(bool fast = false);
|
||||
void openSubdir(const String&);
|
||||
void openZipfile();
|
||||
void reopen();
|
||||
void removeTempFiles(bool remove_unused);
|
||||
void clearKeepFlag();
|
||||
void saveToZipfile(const String&, bool remove_unused, bool is_copy);
|
||||
void saveToDirectory(const String&, bool remove_unused, bool is_copy);
|
||||
FileInfos::iterator addFile(const String& file);
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : Packaged
|
||||
@@ -204,11 +204,11 @@ class Package : public IntrusivePtrVirtualBase {
|
||||
/// Dependencies of a package
|
||||
class PackageDependency : public IntrusivePtrBase<PackageDependency> {
|
||||
public:
|
||||
String package; ///< Name of the package someone depends on
|
||||
vector<String> suggests; ///< Packages suggested to fulfill this dependency
|
||||
Version version; ///< Minimal required version of that package
|
||||
String package; ///< Name of the package someone depends on
|
||||
vector<String> suggests; ///< Packages suggested to fulfill this dependency
|
||||
Version version; ///< Minimal required version of that package
|
||||
|
||||
DECLARE_REFLECTION();
|
||||
DECLARE_REFLECTION();
|
||||
};
|
||||
|
||||
/// Utility class for data types that are always stored in a package.
|
||||
@@ -216,53 +216,53 @@ class PackageDependency : public IntrusivePtrBase<PackageDependency> {
|
||||
*/
|
||||
class Packaged : public Package {
|
||||
public:
|
||||
Packaged();
|
||||
virtual ~Packaged() {}
|
||||
Packaged();
|
||||
virtual ~Packaged() {}
|
||||
|
||||
Version version; ///< Version number of this package
|
||||
Version compatible_version; ///< Earliest version number this package is compatible with
|
||||
String installer_group; ///< Group to place this package in in the installer
|
||||
String short_name; ///< Short name of this package
|
||||
String full_name; ///< Name of this package, for menus etc.
|
||||
String icon_filename; ///< Filename of icon to use in package lists
|
||||
vector<PackageDependencyP> dependencies; ///< Dependencies of this package
|
||||
int position_hint; ///< A hint for the package list
|
||||
Version version; ///< Version number of this package
|
||||
Version compatible_version; ///< Earliest version number this package is compatible with
|
||||
String installer_group; ///< Group to place this package in in the installer
|
||||
String short_name; ///< Short name of this package
|
||||
String full_name; ///< Name of this package, for menus etc.
|
||||
String icon_filename; ///< Filename of icon to use in package lists
|
||||
vector<PackageDependencyP> dependencies; ///< Dependencies of this package
|
||||
int position_hint; ///< A hint for the package list
|
||||
|
||||
/// Get an input stream for the package icon, if there is any
|
||||
InputStreamP openIconFile();
|
||||
/// Get an input stream for the package icon, if there is any
|
||||
InputStreamP openIconFile();
|
||||
|
||||
/// Open a package, and read the data
|
||||
/** if just_header is true, then the package is not fully parsed.
|
||||
*/
|
||||
void open(const String& package, bool just_header = false);
|
||||
/// Ensure the package is fully loaded.
|
||||
void loadFully();
|
||||
void save();
|
||||
void saveAs(const String& package, bool remove_unused = true);
|
||||
void saveCopy(const String& package);
|
||||
/// Open a package, and read the data
|
||||
/** if just_header is true, then the package is not fully parsed.
|
||||
*/
|
||||
void open(const String& package, bool just_header = false);
|
||||
/// Ensure the package is fully loaded.
|
||||
void loadFully();
|
||||
void save();
|
||||
void saveAs(const String& package, bool remove_unused = true);
|
||||
void saveCopy(const String& package);
|
||||
|
||||
/// Check if this package lists a dependency on the given package
|
||||
/** This is done to force people to fill in the dependencies */
|
||||
void requireDependency(Packaged* package);
|
||||
/// Check if this package lists a dependency on the given package
|
||||
/** This is done to force people to fill in the dependencies */
|
||||
void requireDependency(Packaged* package);
|
||||
|
||||
inline bool isFullyLoaded () const {
|
||||
return fully_loaded;
|
||||
}
|
||||
inline bool isFullyLoaded () const {
|
||||
return fully_loaded;
|
||||
}
|
||||
|
||||
protected:
|
||||
/// filename of the data file, and extension of the package file
|
||||
virtual String typeName() const = 0;
|
||||
/// Can be overloaded to do validation after loading
|
||||
virtual void validate(Version file_app_version);
|
||||
/// What file version should be used for writing files?
|
||||
virtual Version fileVersion() const = 0;
|
||||
/// filename of the data file, and extension of the package file
|
||||
virtual String typeName() const = 0;
|
||||
/// Can be overloaded to do validation after loading
|
||||
virtual void validate(Version file_app_version);
|
||||
/// What file version should be used for writing files?
|
||||
virtual Version fileVersion() const = 0;
|
||||
|
||||
DECLARE_REFLECTION_VIRTUAL();
|
||||
DECLARE_REFLECTION_VIRTUAL();
|
||||
|
||||
private:
|
||||
bool fully_loaded; ///< Is the package fully loaded?
|
||||
friend struct JustAsPackageProxy;
|
||||
friend class Installer;
|
||||
bool fully_loaded; ///< Is the package fully loaded?
|
||||
friend struct JustAsPackageProxy;
|
||||
friend class Installer;
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : IncludePackage
|
||||
@@ -270,9 +270,9 @@ class Packaged : public Package {
|
||||
/// A package that just contains a bunch of files that are used from other packages
|
||||
class IncludePackage : public Packaged {
|
||||
protected:
|
||||
String typeName() const;
|
||||
Version fileVersion() const;
|
||||
DECLARE_REFLECTION();
|
||||
String typeName() const;
|
||||
Version fileVersion() const;
|
||||
DECLARE_REFLECTION();
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : Utility
|
||||
@@ -280,9 +280,9 @@ class IncludePackage : public Packaged {
|
||||
/// Open a package with the given filename
|
||||
template <typename T>
|
||||
intrusive_ptr<T> open_package(const String& filename) {
|
||||
intrusive_ptr<T> package(new T);
|
||||
package->open(filename);
|
||||
return package;
|
||||
intrusive_ptr<T> package(new T);
|
||||
package->open(filename);
|
||||
return package;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : readFile definition
|
||||
@@ -291,12 +291,12 @@ intrusive_ptr<T> open_package(const String& filename) {
|
||||
template <typename T>
|
||||
inline void Package::readFile(const String& file, T& obj)
|
||||
{
|
||||
Reader reader(openIn(file), dynamic_cast<Packaged*>(this), absoluteFilename() + _("/") + file);
|
||||
try {
|
||||
reader.handle_greedy(obj);
|
||||
} catch (const ParseError& err) {
|
||||
throw FileParseError(err.what(), absoluteFilename() + _("/") + file); // more detailed message
|
||||
}
|
||||
Reader reader(openIn(file), dynamic_cast<Packaged*>(this), absoluteFilename() + _("/") + file);
|
||||
try {
|
||||
reader.handle_greedy(obj);
|
||||
} catch (const ParseError& err) {
|
||||
throw FileParseError(err.what(), absoluteFilename() + _("/") + file); // more detailed message
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : EOF
|
||||
|
||||
+371
-371
@@ -29,471 +29,471 @@ PackageManager package_manager;
|
||||
|
||||
|
||||
void PackageManager::init() {
|
||||
local.init(true);
|
||||
global.init(false);
|
||||
if (!(local.valid() || global.valid()))
|
||||
throw Error(_("The MSE data files can not be found, there should be a directory called 'data' with these files. ")
|
||||
_("The expected place to find it in was either ") + wxStandardPaths::Get().GetDataDir() + _(" or ") +
|
||||
wxStandardPaths::Get().GetUserDataDir());
|
||||
local.init(true);
|
||||
global.init(false);
|
||||
if (!(local.valid() || global.valid()))
|
||||
throw Error(_("The MSE data files can not be found, there should be a directory called 'data' with these files. ")
|
||||
_("The expected place to find it in was either ") + wxStandardPaths::Get().GetDataDir() + _(" or ") +
|
||||
wxStandardPaths::Get().GetUserDataDir());
|
||||
}
|
||||
void PackageManager::destroy() {
|
||||
loaded_packages.clear();
|
||||
loaded_packages.clear();
|
||||
}
|
||||
void PackageManager::reset() {
|
||||
loaded_packages.clear();
|
||||
loaded_packages.clear();
|
||||
}
|
||||
|
||||
PackagedP PackageManager::openAny(const String& name_, bool just_header) {
|
||||
String name = trim(name_);
|
||||
if (starts_with(name,_("/"))) name = name.substr(1);
|
||||
if (starts_with(name,_(":NO-WARN-DEP:"))) name = name.substr(13);
|
||||
// Attempt to load local data first.
|
||||
String filename;
|
||||
if (wxFileName(name).IsRelative()) {
|
||||
// local data dir?
|
||||
filename = normalize_filename(local.name(name));
|
||||
if (!wxFileExists(filename) && !wxDirExists(filename)) {
|
||||
// global data dir
|
||||
filename = normalize_filename(global.name(name));
|
||||
}
|
||||
} else { // Absolute filename
|
||||
filename = normalize_filename(name);
|
||||
}
|
||||
String name = trim(name_);
|
||||
if (starts_with(name,_("/"))) name = name.substr(1);
|
||||
if (starts_with(name,_(":NO-WARN-DEP:"))) name = name.substr(13);
|
||||
// Attempt to load local data first.
|
||||
String filename;
|
||||
if (wxFileName(name).IsRelative()) {
|
||||
// local data dir?
|
||||
filename = normalize_filename(local.name(name));
|
||||
if (!wxFileExists(filename) && !wxDirExists(filename)) {
|
||||
// global data dir
|
||||
filename = normalize_filename(global.name(name));
|
||||
}
|
||||
} else { // Absolute filename
|
||||
filename = normalize_filename(name);
|
||||
}
|
||||
|
||||
// Is this package already loaded?
|
||||
PackagedP& p = loaded_packages[filename];
|
||||
if (!p) {
|
||||
// load with the right type, based on extension
|
||||
wxFileName fn(filename);
|
||||
if (fn.GetExt() == _("mse-game")) p = intrusive(new Game);
|
||||
else if (fn.GetExt() == _("mse-style")) p = intrusive(new StyleSheet);
|
||||
else if (fn.GetExt() == _("mse-locale")) p = intrusive(new Locale);
|
||||
else if (fn.GetExt() == _("mse-include")) p = intrusive(new IncludePackage);
|
||||
else if (fn.GetExt() == _("mse-symbol-font")) p = intrusive(new SymbolFont);
|
||||
else if (fn.GetExt() == _("mse-export-template")) p = intrusive(new ExportTemplate);
|
||||
else {
|
||||
throw PackageError(_("Unrecognized package type: '") + fn.GetExt() + _("'\nwhile trying to open: ") + name);
|
||||
}
|
||||
p->open(filename, just_header);
|
||||
} else if (!just_header) {
|
||||
p->loadFully();
|
||||
}
|
||||
return p;
|
||||
// Is this package already loaded?
|
||||
PackagedP& p = loaded_packages[filename];
|
||||
if (!p) {
|
||||
// load with the right type, based on extension
|
||||
wxFileName fn(filename);
|
||||
if (fn.GetExt() == _("mse-game")) p = intrusive(new Game);
|
||||
else if (fn.GetExt() == _("mse-style")) p = intrusive(new StyleSheet);
|
||||
else if (fn.GetExt() == _("mse-locale")) p = intrusive(new Locale);
|
||||
else if (fn.GetExt() == _("mse-include")) p = intrusive(new IncludePackage);
|
||||
else if (fn.GetExt() == _("mse-symbol-font")) p = intrusive(new SymbolFont);
|
||||
else if (fn.GetExt() == _("mse-export-template")) p = intrusive(new ExportTemplate);
|
||||
else {
|
||||
throw PackageError(_("Unrecognized package type: '") + fn.GetExt() + _("'\nwhile trying to open: ") + name);
|
||||
}
|
||||
p->open(filename, just_header);
|
||||
} else if (!just_header) {
|
||||
p->loadFully();
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
void PackageManager::findMatching(const String& pattern, vector<PackagedP>& out) {
|
||||
// first find local packages
|
||||
String file = local.findFirstMatching(pattern);
|
||||
while (!file.empty()) {
|
||||
out.push_back(openAny(file, true));
|
||||
file = wxFindNextFile();
|
||||
}
|
||||
// then global packages not already in the list
|
||||
file = global.findFirstMatching(pattern);
|
||||
while (!file.empty()) {
|
||||
PackagedP p = openAny(file, true);
|
||||
if (find(out.begin(), out.end(), p) == out.end()) {
|
||||
out.push_back(p);
|
||||
}
|
||||
file = wxFindNextFile();
|
||||
}
|
||||
// first find local packages
|
||||
String file = local.findFirstMatching(pattern);
|
||||
while (!file.empty()) {
|
||||
out.push_back(openAny(file, true));
|
||||
file = wxFindNextFile();
|
||||
}
|
||||
// then global packages not already in the list
|
||||
file = global.findFirstMatching(pattern);
|
||||
while (!file.empty()) {
|
||||
PackagedP p = openAny(file, true);
|
||||
if (find(out.begin(), out.end(), p) == out.end()) {
|
||||
out.push_back(p);
|
||||
}
|
||||
file = wxFindNextFile();
|
||||
}
|
||||
}
|
||||
|
||||
InputStreamP PackageManager::openFileFromPackage(Packaged*& package, const String& name) {
|
||||
if (!name.empty() && name.GetChar(0) == _('/')) {
|
||||
// absolute name; break name
|
||||
size_t start = name.find_first_not_of(_("/\\"), 1); // allow "//package/name" from incorrect scripts
|
||||
size_t pos = name.find_first_of(_("/\\"), start);
|
||||
if (start < pos && pos != String::npos) {
|
||||
// open package
|
||||
PackagedP p = openAny(name.substr(start, pos-start));
|
||||
if (package && !is_substr(name,start,_(":NO-WARN-DEP:"))) {
|
||||
package->requireDependency(p.get());
|
||||
}
|
||||
package = p.get();
|
||||
return p->openIn(name.substr(pos + 1));
|
||||
}
|
||||
} else if (package) {
|
||||
// relative name
|
||||
return package->openIn(name);
|
||||
}
|
||||
throw FileNotFoundError(name, _("No package name specified, use '/package/filename'"));
|
||||
if (!name.empty() && name.GetChar(0) == _('/')) {
|
||||
// absolute name; break name
|
||||
size_t start = name.find_first_not_of(_("/\\"), 1); // allow "//package/name" from incorrect scripts
|
||||
size_t pos = name.find_first_of(_("/\\"), start);
|
||||
if (start < pos && pos != String::npos) {
|
||||
// open package
|
||||
PackagedP p = openAny(name.substr(start, pos-start));
|
||||
if (package && !is_substr(name,start,_(":NO-WARN-DEP:"))) {
|
||||
package->requireDependency(p.get());
|
||||
}
|
||||
package = p.get();
|
||||
return p->openIn(name.substr(pos + 1));
|
||||
}
|
||||
} else if (package) {
|
||||
// relative name
|
||||
return package->openIn(name);
|
||||
}
|
||||
throw FileNotFoundError(name, _("No package name specified, use '/package/filename'"));
|
||||
}
|
||||
|
||||
String PackageManager::openFilenameFromPackage(Packaged*& package, const String& name) {
|
||||
if (!name.empty() && name.GetChar(0) == _('/')) {
|
||||
// absolute name; break name
|
||||
size_t start = name.find_first_not_of(_("/\\"), 1); // allow "//package/name" from incorrect scripts
|
||||
size_t pos = name.find_first_of(_("/\\"), start);
|
||||
if (start < pos && pos != String::npos) {
|
||||
// open package
|
||||
PackagedP p = openAny(name.substr(start, pos-start));
|
||||
if (package && !is_substr(name,start,_(":NO-WARN-DEP:"))) {
|
||||
package->requireDependency(p.get());
|
||||
}
|
||||
package = p.get();
|
||||
return p->absoluteFilename() + _("/") + name.substr(pos + 1);
|
||||
}
|
||||
} else if (package) {
|
||||
// relative name
|
||||
return package->absoluteFilename() + _("/") + name;
|
||||
}
|
||||
throw FileNotFoundError(name, _("No package name specified, use '/package/filename'"));
|
||||
if (!name.empty() && name.GetChar(0) == _('/')) {
|
||||
// absolute name; break name
|
||||
size_t start = name.find_first_not_of(_("/\\"), 1); // allow "//package/name" from incorrect scripts
|
||||
size_t pos = name.find_first_of(_("/\\"), start);
|
||||
if (start < pos && pos != String::npos) {
|
||||
// open package
|
||||
PackagedP p = openAny(name.substr(start, pos-start));
|
||||
if (package && !is_substr(name,start,_(":NO-WARN-DEP:"))) {
|
||||
package->requireDependency(p.get());
|
||||
}
|
||||
package = p.get();
|
||||
return p->absoluteFilename() + _("/") + name.substr(pos + 1);
|
||||
}
|
||||
} else if (package) {
|
||||
// relative name
|
||||
return package->absoluteFilename() + _("/") + name;
|
||||
}
|
||||
throw FileNotFoundError(name, _("No package name specified, use '/package/filename'"));
|
||||
}
|
||||
|
||||
String PackageManager::getDictionaryDir(bool l) const {
|
||||
String dir = (l ? local : global).getDirectory();
|
||||
if (dir.empty()) return wxEmptyString;
|
||||
else return dir + _("/dictionaries/");
|
||||
String dir = (l ? local : global).getDirectory();
|
||||
if (dir.empty()) return wxEmptyString;
|
||||
else return dir + _("/dictionaries/");
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : PackageManager : on disk
|
||||
|
||||
bool PackageManager::checkDependency(const PackageDependency& dep, bool report_errors) {
|
||||
// mse package?
|
||||
if (dep.package == mse_package) {
|
||||
if (app_version < dep.version) {
|
||||
queue_message(MESSAGE_WARNING, _ERROR_3_("package out of date", _("Magic Set Editor"), app_version.toString(), dep.version.toString()));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// does the package exist?
|
||||
if (!local.exists(dep.package) && !global.exists(dep.package)) {
|
||||
if (report_errors)
|
||||
queue_message(MESSAGE_WARNING, _ERROR_1_("package not found", dep.package));
|
||||
return false;
|
||||
}
|
||||
PackagedP package = openAny(dep.package, true);
|
||||
if (package->version < dep.version) {
|
||||
if (report_errors)
|
||||
queue_message(MESSAGE_WARNING, _ERROR_3_("package out of date", dep.package, package->version.toString(), dep.version.toString()));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
// mse package?
|
||||
if (dep.package == mse_package) {
|
||||
if (app_version < dep.version) {
|
||||
queue_message(MESSAGE_WARNING, _ERROR_3_("package out of date", _("Magic Set Editor"), app_version.toString(), dep.version.toString()));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// does the package exist?
|
||||
if (!local.exists(dep.package) && !global.exists(dep.package)) {
|
||||
if (report_errors)
|
||||
queue_message(MESSAGE_WARNING, _ERROR_1_("package not found", dep.package));
|
||||
return false;
|
||||
}
|
||||
PackagedP package = openAny(dep.package, true);
|
||||
if (package->version < dep.version) {
|
||||
if (report_errors)
|
||||
queue_message(MESSAGE_WARNING, _ERROR_3_("package out of date", dep.package, package->version.toString(), dep.version.toString()));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
bool PackageManager::installedVersion(const String& package_name, Version& version_out) {
|
||||
if (package_name == mse_package) {
|
||||
version_out = app_version;
|
||||
return true;
|
||||
} else {
|
||||
if (!local.exists(package_name) && !global.exists(package_name)) return false;
|
||||
PackagedP package = openAny(package_name, true);
|
||||
version_out = package->version;
|
||||
return true;
|
||||
}
|
||||
if (package_name == mse_package) {
|
||||
version_out = app_version;
|
||||
return true;
|
||||
} else {
|
||||
if (!local.exists(package_name) && !global.exists(package_name)) return false;
|
||||
PackagedP package = openAny(package_name, true);
|
||||
version_out = package->version;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void PackageManager::findAllInstalledPackages(vector<InstallablePackageP>& packages) {
|
||||
// from directories
|
||||
vector<InstallablePackageP> more_packages;
|
||||
global.installedPackages(packages);
|
||||
local.installedPackages(more_packages);
|
||||
merge(packages, more_packages);
|
||||
// the magic appliation package
|
||||
packages.push_back(mse_installable_package());
|
||||
// invariant: sorted:
|
||||
sort(packages);
|
||||
// from directories
|
||||
vector<InstallablePackageP> more_packages;
|
||||
global.installedPackages(packages);
|
||||
local.installedPackages(more_packages);
|
||||
merge(packages, more_packages);
|
||||
// the magic appliation package
|
||||
packages.push_back(mse_installable_package());
|
||||
// invariant: sorted:
|
||||
sort(packages);
|
||||
}
|
||||
|
||||
bool PackageManager::install(const InstallablePackage& package) {
|
||||
bool install_local = package.has(PACKAGE_ACT_LOCAL);
|
||||
return (install_local ? local : global).install(package);
|
||||
bool install_local = package.has(PACKAGE_ACT_LOCAL);
|
||||
return (install_local ? local : global).install(package);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : PackageDirectory
|
||||
|
||||
void PackageDirectory::init(bool local) {
|
||||
is_local = local;
|
||||
if (local) {
|
||||
init(wxStandardPaths::Get().GetUserDataDir() + _("/data"));
|
||||
} else {
|
||||
// determine data directory
|
||||
String dir = wxStandardPaths::Get().GetDataDir();
|
||||
// check if this is the actual data directory, especially during debugging,
|
||||
// the data may be higher up:
|
||||
// exe path = mse/build/debug/mse.exe
|
||||
// data path = mse/data
|
||||
while (!wxDirExists(dir + _("/data"))) {
|
||||
String d = dir;
|
||||
dir = wxPathOnly(dir);
|
||||
if (d == dir) {
|
||||
// we are at the root -> 'data' not found anywhere in the path
|
||||
dir = wxStandardPaths::Get().GetDataDir();
|
||||
break;
|
||||
}
|
||||
}
|
||||
init(dir + _("/data"));
|
||||
}
|
||||
is_local = local;
|
||||
if (local) {
|
||||
init(wxStandardPaths::Get().GetUserDataDir() + _("/data"));
|
||||
} else {
|
||||
// determine data directory
|
||||
String dir = wxStandardPaths::Get().GetDataDir();
|
||||
// check if this is the actual data directory, especially during debugging,
|
||||
// the data may be higher up:
|
||||
// exe path = mse/build/debug/mse.exe
|
||||
// data path = mse/data
|
||||
while (!wxDirExists(dir + _("/data"))) {
|
||||
String d = dir;
|
||||
dir = wxPathOnly(dir);
|
||||
if (d == dir) {
|
||||
// we are at the root -> 'data' not found anywhere in the path
|
||||
dir = wxStandardPaths::Get().GetDataDir();
|
||||
break;
|
||||
}
|
||||
}
|
||||
init(dir + _("/data"));
|
||||
}
|
||||
}
|
||||
void PackageDirectory::init(const String& dir) {
|
||||
if (wxDirExists(dir))
|
||||
directory = dir;
|
||||
else
|
||||
directory.clear();
|
||||
if (wxDirExists(dir))
|
||||
directory = dir;
|
||||
else
|
||||
directory.clear();
|
||||
}
|
||||
|
||||
String PackageDirectory::name(const String& name) const {
|
||||
return directory + _("/") + name;
|
||||
return directory + _("/") + name;
|
||||
}
|
||||
bool PackageDirectory::exists(const String& filename) const {
|
||||
String fn = name(filename);
|
||||
return wxFileExists(fn) || wxDirExists(fn);
|
||||
String fn = name(filename);
|
||||
return wxFileExists(fn) || wxDirExists(fn);
|
||||
}
|
||||
|
||||
String PackageDirectory::findFirstMatching(const String& pattern) const {
|
||||
if (!wxDirExists(directory)) return String();
|
||||
return wxFindFirstFile(directory + _("/") + pattern, 0);
|
||||
if (!wxDirExists(directory)) return String();
|
||||
return wxFindFirstFile(directory + _("/") + pattern, 0);
|
||||
}
|
||||
|
||||
bool compare_name(const PackageVersionP& a, const PackageVersionP& b) {
|
||||
return a->name < b->name;
|
||||
return a->name < b->name;
|
||||
}
|
||||
|
||||
void PackageDirectory::installedPackages(vector<InstallablePackageP>& packages_out) {
|
||||
loadDatabase();
|
||||
// find all package files
|
||||
vector<String> in_dir;
|
||||
for (String s = findFirstMatching(_("*.mse-*")) ; !s.empty() ; s = wxFindNextFile()) {
|
||||
size_t pos = s.find_last_of(_("/\\"));
|
||||
if (pos != String::npos) s = s.substr(pos+1);
|
||||
// TODO : check for valid package names
|
||||
in_dir.push_back(s);
|
||||
}
|
||||
sort(in_dir.begin(), in_dir.end());
|
||||
// merge with package database
|
||||
bool db_changed = false;
|
||||
vector<PackageVersionP>::const_iterator it1 = packages.begin();
|
||||
vector<String>::const_iterator it2 = in_dir.begin();
|
||||
while (it2 != in_dir.end()) {
|
||||
if (it1 == packages.end() || (*it1)->name > *it2) {
|
||||
// add new package to db
|
||||
try {
|
||||
PackagedP pack = package_manager.openAny(*it2, true);
|
||||
db_changed = true;
|
||||
PackageVersionP ver(new PackageVersion(
|
||||
is_local ? PackageVersion::STATUS_LOCAL : PackageVersion::STATUS_GLOBAL));
|
||||
ver->check_status(*pack);
|
||||
packages_out.push_back(intrusive(new InstallablePackage(intrusive(new PackageDescription(*pack)), ver)));
|
||||
} catch (const Error&) {}
|
||||
++it2;
|
||||
} else if ((*it1)->name < *it2) {
|
||||
// delete package from db
|
||||
db_changed = true;
|
||||
++it1;
|
||||
} else {
|
||||
// ok, a package already in the db
|
||||
try {
|
||||
PackagedP pack = package_manager.openAny(*it2, true);
|
||||
(*it1)->check_status(*pack);
|
||||
packages_out.push_back(intrusive(new InstallablePackage(intrusive(new PackageDescription(*pack)), *it1)));
|
||||
} catch (const Error&) { db_changed = true; }
|
||||
++it1, ++it2;
|
||||
}
|
||||
}
|
||||
if (it1 != packages.end()) db_changed = true;
|
||||
// has the database of installed packages changed?
|
||||
if (db_changed) {
|
||||
packages.clear();
|
||||
FOR_EACH(p, packages_out) {
|
||||
if (p->installed) packages.push_back(p->installed);
|
||||
}
|
||||
saveDatabase();
|
||||
}
|
||||
loadDatabase();
|
||||
// find all package files
|
||||
vector<String> in_dir;
|
||||
for (String s = findFirstMatching(_("*.mse-*")) ; !s.empty() ; s = wxFindNextFile()) {
|
||||
size_t pos = s.find_last_of(_("/\\"));
|
||||
if (pos != String::npos) s = s.substr(pos+1);
|
||||
// TODO : check for valid package names
|
||||
in_dir.push_back(s);
|
||||
}
|
||||
sort(in_dir.begin(), in_dir.end());
|
||||
// merge with package database
|
||||
bool db_changed = false;
|
||||
vector<PackageVersionP>::const_iterator it1 = packages.begin();
|
||||
vector<String>::const_iterator it2 = in_dir.begin();
|
||||
while (it2 != in_dir.end()) {
|
||||
if (it1 == packages.end() || (*it1)->name > *it2) {
|
||||
// add new package to db
|
||||
try {
|
||||
PackagedP pack = package_manager.openAny(*it2, true);
|
||||
db_changed = true;
|
||||
PackageVersionP ver(new PackageVersion(
|
||||
is_local ? PackageVersion::STATUS_LOCAL : PackageVersion::STATUS_GLOBAL));
|
||||
ver->check_status(*pack);
|
||||
packages_out.push_back(intrusive(new InstallablePackage(intrusive(new PackageDescription(*pack)), ver)));
|
||||
} catch (const Error&) {}
|
||||
++it2;
|
||||
} else if ((*it1)->name < *it2) {
|
||||
// delete package from db
|
||||
db_changed = true;
|
||||
++it1;
|
||||
} else {
|
||||
// ok, a package already in the db
|
||||
try {
|
||||
PackagedP pack = package_manager.openAny(*it2, true);
|
||||
(*it1)->check_status(*pack);
|
||||
packages_out.push_back(intrusive(new InstallablePackage(intrusive(new PackageDescription(*pack)), *it1)));
|
||||
} catch (const Error&) { db_changed = true; }
|
||||
++it1, ++it2;
|
||||
}
|
||||
}
|
||||
if (it1 != packages.end()) db_changed = true;
|
||||
// has the database of installed packages changed?
|
||||
if (db_changed) {
|
||||
packages.clear();
|
||||
FOR_EACH(p, packages_out) {
|
||||
if (p->installed) packages.push_back(p->installed);
|
||||
}
|
||||
saveDatabase();
|
||||
}
|
||||
}
|
||||
|
||||
void PackageDirectory::bless(const String& package_name) {
|
||||
PackagedP pack = package_manager.openAny(package_name, true);
|
||||
// already have this package?
|
||||
FOR_EACH(ver, packages) {
|
||||
if (ver->name == package_name) {
|
||||
ver->check_status(*pack);
|
||||
ver->bless();
|
||||
return;
|
||||
}
|
||||
}
|
||||
// a new package
|
||||
PackageVersionP ver(new PackageVersion(
|
||||
is_local ? PackageVersion::STATUS_LOCAL : PackageVersion::STATUS_GLOBAL));
|
||||
ver->check_status(*pack);
|
||||
ver->bless();
|
||||
packages.push_back(ver);
|
||||
sort(packages.begin(), packages.end(), compare_name);
|
||||
PackagedP pack = package_manager.openAny(package_name, true);
|
||||
// already have this package?
|
||||
FOR_EACH(ver, packages) {
|
||||
if (ver->name == package_name) {
|
||||
ver->check_status(*pack);
|
||||
ver->bless();
|
||||
return;
|
||||
}
|
||||
}
|
||||
// a new package
|
||||
PackageVersionP ver(new PackageVersion(
|
||||
is_local ? PackageVersion::STATUS_LOCAL : PackageVersion::STATUS_GLOBAL));
|
||||
ver->check_status(*pack);
|
||||
ver->bless();
|
||||
packages.push_back(ver);
|
||||
sort(packages.begin(), packages.end(), compare_name);
|
||||
}
|
||||
|
||||
void PackageDirectory::removeFromDatabase(const String& package_name) {
|
||||
size_t i = 0, j = 0;
|
||||
for ( ; i < packages.size() ; ++i) {
|
||||
if (packages[i]->name != package_name) {
|
||||
packages[j++] = packages[i];
|
||||
}
|
||||
}
|
||||
packages.resize(j);
|
||||
size_t i = 0, j = 0;
|
||||
for ( ; i < packages.size() ; ++i) {
|
||||
if (packages[i]->name != package_name) {
|
||||
packages[j++] = packages[i];
|
||||
}
|
||||
}
|
||||
packages.resize(j);
|
||||
}
|
||||
|
||||
IMPLEMENT_REFLECTION(PackageDirectory) {
|
||||
REFLECT(packages);
|
||||
REFLECT(packages);
|
||||
}
|
||||
|
||||
void PackageDirectory::loadDatabase() {
|
||||
if (!packages.empty()) return;
|
||||
String filename = databaseFile();
|
||||
if (wxFileExists(filename)) {
|
||||
// packages file not existing is not an error
|
||||
shared_ptr<wxFileInputStream> file = shared(new wxFileInputStream(filename));
|
||||
if (!file->Ok()) return; // failure is not an error
|
||||
Reader reader(file, nullptr, filename);
|
||||
reader.handle_greedy(*this);
|
||||
sort(packages.begin(), packages.end(), compare_name);
|
||||
}
|
||||
if (!packages.empty()) return;
|
||||
String filename = databaseFile();
|
||||
if (wxFileExists(filename)) {
|
||||
// packages file not existing is not an error
|
||||
shared_ptr<wxFileInputStream> file = shared(new wxFileInputStream(filename));
|
||||
if (!file->Ok()) return; // failure is not an error
|
||||
Reader reader(file, nullptr, filename);
|
||||
reader.handle_greedy(*this);
|
||||
sort(packages.begin(), packages.end(), compare_name);
|
||||
}
|
||||
}
|
||||
|
||||
void PackageDirectory::saveDatabase() {
|
||||
Writer writer(shared(new wxFileOutputStream(databaseFile())), app_version);
|
||||
writer.handle(*this);
|
||||
Writer writer(shared(new wxFileOutputStream(databaseFile())), app_version);
|
||||
writer.handle(*this);
|
||||
}
|
||||
String PackageDirectory::databaseFile() {
|
||||
return name(_("packages"));
|
||||
return name(_("packages"));
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : PackageDirectory : installing
|
||||
|
||||
bool PackageDirectory::install(const InstallablePackage& package) {
|
||||
String n = name(package.description->name);
|
||||
if (package.action & PACKAGE_ACT_REMOVE) {
|
||||
if (!remove_file_or_dir(n)) return false;
|
||||
removeFromDatabase(package.description->name);
|
||||
} else if (package.action & PACKAGE_ACT_INSTALL) {
|
||||
if (!remove_file_or_dir(n + _(".new"))) return false;
|
||||
bool ok = actual_install(package, n + _(".new"));
|
||||
if (!ok) return false;
|
||||
move_ignored_files(n, n + _(".new")); // copy over files from the old installed version to the new one
|
||||
if (!remove_file_or_dir(n)) return false;
|
||||
if (!rename_file_or_dir(n + _(".new"), n)) return false;
|
||||
bless(package.description->name);
|
||||
}
|
||||
saveDatabase();
|
||||
return true;
|
||||
String n = name(package.description->name);
|
||||
if (package.action & PACKAGE_ACT_REMOVE) {
|
||||
if (!remove_file_or_dir(n)) return false;
|
||||
removeFromDatabase(package.description->name);
|
||||
} else if (package.action & PACKAGE_ACT_INSTALL) {
|
||||
if (!remove_file_or_dir(n + _(".new"))) return false;
|
||||
bool ok = actual_install(package, n + _(".new"));
|
||||
if (!ok) return false;
|
||||
move_ignored_files(n, n + _(".new")); // copy over files from the old installed version to the new one
|
||||
if (!remove_file_or_dir(n)) return false;
|
||||
if (!rename_file_or_dir(n + _(".new"), n)) return false;
|
||||
bless(package.description->name);
|
||||
}
|
||||
saveDatabase();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PackageDirectory::actual_install(const InstallablePackage& package, const String& install_dir) {
|
||||
String name = package.description->name;
|
||||
if (!package.installer->installer) {
|
||||
queue_message(MESSAGE_ERROR, _("Installer not found for package: ") + name);
|
||||
return false;
|
||||
}
|
||||
Installer& installer = *package.installer->installer;
|
||||
// install files
|
||||
const Packaged::FileInfos& file_infos = installer.getFileInfos();
|
||||
for (Packaged::FileInfos::const_iterator it = file_infos.begin() ; it != file_infos.end() ; ++it) {
|
||||
String file = it->first;
|
||||
if (!is_substr_i(file,0,name)) continue; // not the right package
|
||||
// correct filename
|
||||
String local_file = install_dir + file.substr(name.length());
|
||||
create_parent_dirs(local_file);
|
||||
// copy file
|
||||
InputStreamP is = installer.openIn(file);
|
||||
wxFileOutputStream os (local_file);
|
||||
if (!os.IsOk()) {
|
||||
int act = wxMessageBox(_ERROR_1_("cannot create file", file), _TITLE_("cannot create file"), wxICON_ERROR | wxYES_NO);
|
||||
if (act == wxNO) return false;
|
||||
}
|
||||
os.Write(*is);
|
||||
}
|
||||
// update package database
|
||||
// TODO: bless the package?
|
||||
return true;
|
||||
String name = package.description->name;
|
||||
if (!package.installer->installer) {
|
||||
queue_message(MESSAGE_ERROR, _("Installer not found for package: ") + name);
|
||||
return false;
|
||||
}
|
||||
Installer& installer = *package.installer->installer;
|
||||
// install files
|
||||
const Packaged::FileInfos& file_infos = installer.getFileInfos();
|
||||
for (Packaged::FileInfos::const_iterator it = file_infos.begin() ; it != file_infos.end() ; ++it) {
|
||||
String file = it->first;
|
||||
if (!is_substr_i(file,0,name)) continue; // not the right package
|
||||
// correct filename
|
||||
String local_file = install_dir + file.substr(name.length());
|
||||
create_parent_dirs(local_file);
|
||||
// copy file
|
||||
InputStreamP is = installer.openIn(file);
|
||||
wxFileOutputStream os (local_file);
|
||||
if (!os.IsOk()) {
|
||||
int act = wxMessageBox(_ERROR_1_("cannot create file", file), _TITLE_("cannot create file"), wxICON_ERROR | wxYES_NO);
|
||||
if (act == wxNO) return false;
|
||||
}
|
||||
os.Write(*is);
|
||||
}
|
||||
// update package database
|
||||
// TODO: bless the package?
|
||||
return true;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : PackageVersion
|
||||
|
||||
template <> void Writer::handle(const PackageVersion::FileInfo& f) {
|
||||
if (f.status == PackageVersion::FILE_DELETED) {
|
||||
handle(_("D ") + f.file);
|
||||
} else {
|
||||
handle(format_string(_("%s%s %s"),
|
||||
f.status == PackageVersion::FILE_ADDED ? _("A")
|
||||
: f.status == PackageVersion::FILE_MODIFIED ? _("M") : _(""),
|
||||
f.time.Format(_("%Y%m%dT%H%M%S")),
|
||||
f.file));
|
||||
}
|
||||
if (f.status == PackageVersion::FILE_DELETED) {
|
||||
handle(_("D ") + f.file);
|
||||
} else {
|
||||
handle(format_string(_("%s%s %s"),
|
||||
f.status == PackageVersion::FILE_ADDED ? _("A")
|
||||
: f.status == PackageVersion::FILE_MODIFIED ? _("M") : _(""),
|
||||
f.time.Format(_("%Y%m%dT%H%M%S")),
|
||||
f.file));
|
||||
}
|
||||
}
|
||||
template <> void Reader::handle(PackageVersion::FileInfo& f) {
|
||||
String s; handle(s);
|
||||
// read status
|
||||
if (s.size() < 2) {f.status = PackageVersion::FILE_ADDED; return; }
|
||||
f.status = s.GetChar(0) == _('M') ? PackageVersion::FILE_MODIFIED
|
||||
: s.GetChar(0) == _('A') ? PackageVersion::FILE_ADDED
|
||||
: s.GetChar(0) == _('D') ? PackageVersion::FILE_DELETED
|
||||
: PackageVersion::FILE_UNCHANGED;
|
||||
if (f.status == PackageVersion::FILE_DELETED) {
|
||||
if (s.GetChar(1) != _(' ')) {f.status = PackageVersion::FILE_ADDED; return; }
|
||||
f.file = s.substr(2);
|
||||
return;
|
||||
} else if (f.status != PackageVersion::FILE_UNCHANGED) {
|
||||
s = s.substr(1);
|
||||
}
|
||||
if (s.size() < 8+1+6+1) {f.status = PackageVersion::FILE_ADDED; return; }
|
||||
if (s.GetChar(8+1+6) != _(' ')) {f.status = PackageVersion::FILE_ADDED; return; } // invalid format
|
||||
// read time, filename
|
||||
f.time.ParseFormat(s, _("%Y%m%dT%H%M%S"));
|
||||
f.file = s.substr(8+1+6+1);
|
||||
String s; handle(s);
|
||||
// read status
|
||||
if (s.size() < 2) {f.status = PackageVersion::FILE_ADDED; return; }
|
||||
f.status = s.GetChar(0) == _('M') ? PackageVersion::FILE_MODIFIED
|
||||
: s.GetChar(0) == _('A') ? PackageVersion::FILE_ADDED
|
||||
: s.GetChar(0) == _('D') ? PackageVersion::FILE_DELETED
|
||||
: PackageVersion::FILE_UNCHANGED;
|
||||
if (f.status == PackageVersion::FILE_DELETED) {
|
||||
if (s.GetChar(1) != _(' ')) {f.status = PackageVersion::FILE_ADDED; return; }
|
||||
f.file = s.substr(2);
|
||||
return;
|
||||
} else if (f.status != PackageVersion::FILE_UNCHANGED) {
|
||||
s = s.substr(1);
|
||||
}
|
||||
if (s.size() < 8+1+6+1) {f.status = PackageVersion::FILE_ADDED; return; }
|
||||
if (s.GetChar(8+1+6) != _(' ')) {f.status = PackageVersion::FILE_ADDED; return; } // invalid format
|
||||
// read time, filename
|
||||
f.time.ParseFormat(s, _("%Y%m%dT%H%M%S"));
|
||||
f.file = s.substr(8+1+6+1);
|
||||
}
|
||||
IMPLEMENT_REFLECTION_NO_SCRIPT(PackageVersion) {
|
||||
REFLECT_NO_SCRIPT(name);
|
||||
REFLECT_NO_SCRIPT(version);
|
||||
REFLECT_NO_SCRIPT(status);
|
||||
REFLECT_NO_SCRIPT(files);
|
||||
REFLECT_NO_SCRIPT(name);
|
||||
REFLECT_NO_SCRIPT(version);
|
||||
REFLECT_NO_SCRIPT(status);
|
||||
REFLECT_NO_SCRIPT(files);
|
||||
}
|
||||
|
||||
void PackageVersion::check_status(Packaged& package) {
|
||||
status &= ~STATUS_MODIFIED;
|
||||
if (!(status & STATUS_BLESSED)) status |= STATUS_MODIFIED;
|
||||
name = package.relativeFilename();
|
||||
version = package.version;
|
||||
// Merge our files list with the list from the package
|
||||
vector<FileInfo> new_files;
|
||||
Package::FileInfos fis = package.getFileInfos();
|
||||
Package::FileInfos::const_iterator it1 = fis.begin();
|
||||
vector<FileInfo>::iterator it2 = files.begin();
|
||||
//% size_t it2 = 0, size = files.size();
|
||||
//% bool need_sort = false;
|
||||
while(it1 != fis.end() || it2 != files.end()) {
|
||||
if (it1 != fis.end() && it2 != files.end() && it1->first == it2->file) {
|
||||
DateTime mtime = package.modificationTime(*it1);
|
||||
if (mtime != it2->time) {
|
||||
it2->time = mtime;
|
||||
it2->status = FILE_MODIFIED;
|
||||
new_files.push_back(*it2);
|
||||
status |= STATUS_MODIFIED;
|
||||
}
|
||||
++it1; ++it2;
|
||||
} else if (it1 != fis.end() && (it2 == files.end() || it1->first < it2->file)) {
|
||||
// this is a new file
|
||||
DateTime mtime = package.modificationTime(*it1);
|
||||
new_files.push_back(FileInfo(it1->first, mtime, FILE_ADDED));
|
||||
status |= STATUS_MODIFIED;
|
||||
++it1;
|
||||
} else {
|
||||
// this file is no longer in the package, it was deleted
|
||||
if (it2->status != FILE_ADDED) {
|
||||
it2->status = FILE_DELETED;
|
||||
new_files.push_back(*it2);
|
||||
status |= STATUS_MODIFIED;
|
||||
}
|
||||
++it2;
|
||||
}
|
||||
}
|
||||
swap(files,new_files);
|
||||
status &= ~STATUS_MODIFIED;
|
||||
if (!(status & STATUS_BLESSED)) status |= STATUS_MODIFIED;
|
||||
name = package.relativeFilename();
|
||||
version = package.version;
|
||||
// Merge our files list with the list from the package
|
||||
vector<FileInfo> new_files;
|
||||
Package::FileInfos fis = package.getFileInfos();
|
||||
Package::FileInfos::const_iterator it1 = fis.begin();
|
||||
vector<FileInfo>::iterator it2 = files.begin();
|
||||
//% size_t it2 = 0, size = files.size();
|
||||
//% bool need_sort = false;
|
||||
while(it1 != fis.end() || it2 != files.end()) {
|
||||
if (it1 != fis.end() && it2 != files.end() && it1->first == it2->file) {
|
||||
DateTime mtime = package.modificationTime(*it1);
|
||||
if (mtime != it2->time) {
|
||||
it2->time = mtime;
|
||||
it2->status = FILE_MODIFIED;
|
||||
new_files.push_back(*it2);
|
||||
status |= STATUS_MODIFIED;
|
||||
}
|
||||
++it1; ++it2;
|
||||
} else if (it1 != fis.end() && (it2 == files.end() || it1->first < it2->file)) {
|
||||
// this is a new file
|
||||
DateTime mtime = package.modificationTime(*it1);
|
||||
new_files.push_back(FileInfo(it1->first, mtime, FILE_ADDED));
|
||||
status |= STATUS_MODIFIED;
|
||||
++it1;
|
||||
} else {
|
||||
// this file is no longer in the package, it was deleted
|
||||
if (it2->status != FILE_ADDED) {
|
||||
it2->status = FILE_DELETED;
|
||||
new_files.push_back(*it2);
|
||||
status |= STATUS_MODIFIED;
|
||||
}
|
||||
++it2;
|
||||
}
|
||||
}
|
||||
swap(files,new_files);
|
||||
}
|
||||
|
||||
inline bool is_deleted(PackageVersion::FileInfo f) {
|
||||
return f.status == PackageVersion::FILE_DELETED;
|
||||
return f.status == PackageVersion::FILE_DELETED;
|
||||
}
|
||||
void PackageVersion::bless() {
|
||||
files.erase(remove_if(files.begin(),files.end(),is_deleted),files.end());
|
||||
FOR_EACH(f,files) {
|
||||
f.status = FILE_UNCHANGED;
|
||||
}
|
||||
status &= ~STATUS_MODIFIED;
|
||||
status |= STATUS_BLESSED;
|
||||
files.erase(remove_if(files.begin(),files.end(),is_deleted),files.end());
|
||||
FOR_EACH(f,files) {
|
||||
f.status = FILE_UNCHANGED;
|
||||
}
|
||||
status &= ~STATUS_MODIFIED;
|
||||
status |= STATUS_BLESSED;
|
||||
}
|
||||
|
||||
+169
-169
@@ -25,33 +25,33 @@ class PackageDependency;
|
||||
/// Information on a package in a repository
|
||||
class PackageVersionData : public IntrusivePtrVirtualBase {
|
||||
public:
|
||||
PackageVersionData() {}
|
||||
|
||||
String name; ///< Name of the package
|
||||
String short_name; ///< Name to show on package list.
|
||||
String description; ///< html description
|
||||
bool hidden; ///< Not shown in the package list (installed automatically as a dependency)
|
||||
|
||||
PackageVersionP base_version; ///< The locally installed version (if installed)
|
||||
PackageVersionP local_version; ///< Modifications made to the locally installed version?
|
||||
PackageVersionP remote_version; ///< The version available from the server (if available)
|
||||
bool global; ///< The installed package is in the global location, not the user-local one
|
||||
bool new_global; ///<
|
||||
|
||||
bool source_local; ///< Is the source
|
||||
String source; ///< Where can the package be downloaded/found?
|
||||
|
||||
DECLARE_REFLECTION();
|
||||
PackageVersionData() {}
|
||||
|
||||
String name; ///< Name of the package
|
||||
String short_name; ///< Name to show on package list.
|
||||
String description; ///< html description
|
||||
bool hidden; ///< Not shown in the package list (installed automatically as a dependency)
|
||||
|
||||
PackageVersionP base_version; ///< The locally installed version (if installed)
|
||||
PackageVersionP local_version; ///< Modifications made to the locally installed version?
|
||||
PackageVersionP remote_version; ///< The version available from the server (if available)
|
||||
bool global; ///< The installed package is in the global location, not the user-local one
|
||||
bool new_global; ///<
|
||||
|
||||
bool source_local; ///< Is the source
|
||||
String source; ///< Where can the package be downloaded/found?
|
||||
|
||||
DECLARE_REFLECTION();
|
||||
};
|
||||
|
||||
class UpdateData {
|
||||
vector<PackageDependencyP> packages; ///< package/latest-version-number pairs
|
||||
String new_updates_url; ///< updates url has changed?
|
||||
vector<PackageDependencyP> packages; ///< package/latest-version-number pairs
|
||||
String new_updates_url; ///< updates url has changed?
|
||||
};
|
||||
|
||||
IMPLEMENT_REFLECTION_NO_SCRIPT(UpdateData) {
|
||||
REFLECT_NO_SCRIPT(packages);
|
||||
REFLECT_NO_SCRIPT(new_updates_url);
|
||||
REFLECT_NO_SCRIPT(packages);
|
||||
REFLECT_NO_SCRIPT(new_updates_url);
|
||||
}
|
||||
*/
|
||||
|
||||
@@ -60,45 +60,45 @@ IMPLEMENT_REFLECTION_NO_SCRIPT(UpdateData) {
|
||||
/// A directory for packages
|
||||
class PackageDirectory {
|
||||
public:
|
||||
void init(bool local);
|
||||
void init(const String& dir);
|
||||
|
||||
bool valid() const { return !directory.empty(); }
|
||||
|
||||
/// Name of a package in this directory
|
||||
String name(const String& name) const;
|
||||
/// Does a package with the given name exist?
|
||||
bool exists(const String& name) const;
|
||||
|
||||
/// Get the package directory
|
||||
inline String getDirectory() const { return directory; }
|
||||
|
||||
/// Find all packages that match a filename pattern (using wxFindFirst)
|
||||
String findFirstMatching(const String& pattern) const;
|
||||
|
||||
/// Get all installed packages
|
||||
void installedPackages(vector<InstallablePackageP>& packages);
|
||||
|
||||
/// Install/uninstall a package
|
||||
bool install(const InstallablePackage& package);
|
||||
|
||||
/// Bless a package
|
||||
void bless(const String& package_name);
|
||||
/// Remove a package from the database
|
||||
void removeFromDatabase(const String& package_name);
|
||||
|
||||
void loadDatabase();
|
||||
void saveDatabase();
|
||||
void init(bool local);
|
||||
void init(const String& dir);
|
||||
|
||||
bool valid() const { return !directory.empty(); }
|
||||
|
||||
/// Name of a package in this directory
|
||||
String name(const String& name) const;
|
||||
/// Does a package with the given name exist?
|
||||
bool exists(const String& name) const;
|
||||
|
||||
/// Get the package directory
|
||||
inline String getDirectory() const { return directory; }
|
||||
|
||||
/// Find all packages that match a filename pattern (using wxFindFirst)
|
||||
String findFirstMatching(const String& pattern) const;
|
||||
|
||||
/// Get all installed packages
|
||||
void installedPackages(vector<InstallablePackageP>& packages);
|
||||
|
||||
/// Install/uninstall a package
|
||||
bool install(const InstallablePackage& package);
|
||||
|
||||
/// Bless a package
|
||||
void bless(const String& package_name);
|
||||
/// Remove a package from the database
|
||||
void removeFromDatabase(const String& package_name);
|
||||
|
||||
void loadDatabase();
|
||||
void saveDatabase();
|
||||
private:
|
||||
bool is_local;
|
||||
String directory;
|
||||
vector<PackageVersionP> packages; // sorted by name
|
||||
|
||||
String databaseFile();
|
||||
// Do the actual installation of a package
|
||||
bool actual_install(const InstallablePackage& package, const String& install_dir);
|
||||
|
||||
DECLARE_REFLECTION();
|
||||
bool is_local;
|
||||
String directory;
|
||||
vector<PackageVersionP> packages; // sorted by name
|
||||
|
||||
String databaseFile();
|
||||
// Do the actual installation of a package
|
||||
bool actual_install(const InstallablePackage& package, const String& install_dir);
|
||||
|
||||
DECLARE_REFLECTION();
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : PackageManager
|
||||
@@ -109,77 +109,77 @@ class PackageDirectory {
|
||||
*/
|
||||
class PackageManager {
|
||||
public:
|
||||
/// Initialize the package manager
|
||||
void init();
|
||||
/// Empty the list of packages.
|
||||
/** This function MUST be called before the program terminates, otherwise
|
||||
* we could get into fights with pool allocators used by ScriptValues */
|
||||
void destroy();
|
||||
/// Empty the list of packages, they will all be reloaded
|
||||
void reset();
|
||||
|
||||
// --------------------------------------------------- : Packages in memory
|
||||
|
||||
/// Open a package with the specified name (including extension)
|
||||
template <typename T>
|
||||
intrusive_ptr<T> open(const String& name) {
|
||||
PackagedP p = openAny(name);
|
||||
intrusive_ptr<T> typedP = dynamic_pointer_cast<T>(p);
|
||||
if (typedP) {
|
||||
return typedP;
|
||||
} else {
|
||||
throw InternalError(format_string(_("Package %s loaded as wrong type"),name));
|
||||
}
|
||||
}
|
||||
|
||||
/// Open a package with the specified name, the type of package is determined by its extension!
|
||||
/** @param if just_header is true, then the package is not fully parsed.
|
||||
*/
|
||||
PackagedP openAny(const String& name, bool just_header = false);
|
||||
|
||||
/// Find all packages that match a filename pattern, store them in out
|
||||
/** Only reads the package headers */
|
||||
void findMatching(const String& pattern, vector<PackagedP>& out);
|
||||
|
||||
/// Open a file from a package, with a name encoded as "/package/file"
|
||||
/** If 'package' is set then:
|
||||
* - tries to open a relative file from the package if the name is "file"
|
||||
* - verifies a dependency from that package if an absolute filename is used
|
||||
* this is to force people to fill in the dependencies
|
||||
* Afterwards, package will be set to the package the file is opened from
|
||||
*/
|
||||
InputStreamP openFileFromPackage(Packaged*& package, const String& name);
|
||||
|
||||
/// Get a filename to open from a package
|
||||
/** WARNING: this is a bit of a hack, since not all package types support names in this way.
|
||||
* It is needed for third party libraries (i.e. hunspell) that load stuff from files.
|
||||
*/
|
||||
String openFilenameFromPackage(Packaged*& package, const String& name);
|
||||
|
||||
// --------------------------------------------------- : Packages on disk
|
||||
|
||||
/// Check if the given dependency is currently installed
|
||||
bool checkDependency(const PackageDependency& dep, bool report_errors = true);
|
||||
/// Determine the latest version of the given package
|
||||
/** If it is not installed, returns false */
|
||||
bool installedVersion(const String& pkg, Version& version_out);
|
||||
|
||||
/// Get all installed packages
|
||||
void findAllInstalledPackages(vector<InstallablePackageP>& packages);
|
||||
|
||||
/// Install/uninstall a package, returns success
|
||||
bool install(const InstallablePackage& package);
|
||||
|
||||
// --------------------------------------------------- : Other package like things
|
||||
|
||||
/// Get the directory for dictionary files
|
||||
String getDictionaryDir(bool local) const;
|
||||
|
||||
// --------------------------------------------------- : Packages on a server
|
||||
|
||||
/// Initialize the package manager
|
||||
void init();
|
||||
/// Empty the list of packages.
|
||||
/** This function MUST be called before the program terminates, otherwise
|
||||
* we could get into fights with pool allocators used by ScriptValues */
|
||||
void destroy();
|
||||
/// Empty the list of packages, they will all be reloaded
|
||||
void reset();
|
||||
|
||||
// --------------------------------------------------- : Packages in memory
|
||||
|
||||
/// Open a package with the specified name (including extension)
|
||||
template <typename T>
|
||||
intrusive_ptr<T> open(const String& name) {
|
||||
PackagedP p = openAny(name);
|
||||
intrusive_ptr<T> typedP = dynamic_pointer_cast<T>(p);
|
||||
if (typedP) {
|
||||
return typedP;
|
||||
} else {
|
||||
throw InternalError(format_string(_("Package %s loaded as wrong type"),name));
|
||||
}
|
||||
}
|
||||
|
||||
/// Open a package with the specified name, the type of package is determined by its extension!
|
||||
/** @param if just_header is true, then the package is not fully parsed.
|
||||
*/
|
||||
PackagedP openAny(const String& name, bool just_header = false);
|
||||
|
||||
/// Find all packages that match a filename pattern, store them in out
|
||||
/** Only reads the package headers */
|
||||
void findMatching(const String& pattern, vector<PackagedP>& out);
|
||||
|
||||
/// Open a file from a package, with a name encoded as "/package/file"
|
||||
/** If 'package' is set then:
|
||||
* - tries to open a relative file from the package if the name is "file"
|
||||
* - verifies a dependency from that package if an absolute filename is used
|
||||
* this is to force people to fill in the dependencies
|
||||
* Afterwards, package will be set to the package the file is opened from
|
||||
*/
|
||||
InputStreamP openFileFromPackage(Packaged*& package, const String& name);
|
||||
|
||||
/// Get a filename to open from a package
|
||||
/** WARNING: this is a bit of a hack, since not all package types support names in this way.
|
||||
* It is needed for third party libraries (i.e. hunspell) that load stuff from files.
|
||||
*/
|
||||
String openFilenameFromPackage(Packaged*& package, const String& name);
|
||||
|
||||
// --------------------------------------------------- : Packages on disk
|
||||
|
||||
/// Check if the given dependency is currently installed
|
||||
bool checkDependency(const PackageDependency& dep, bool report_errors = true);
|
||||
/// Determine the latest version of the given package
|
||||
/** If it is not installed, returns false */
|
||||
bool installedVersion(const String& pkg, Version& version_out);
|
||||
|
||||
/// Get all installed packages
|
||||
void findAllInstalledPackages(vector<InstallablePackageP>& packages);
|
||||
|
||||
/// Install/uninstall a package, returns success
|
||||
bool install(const InstallablePackage& package);
|
||||
|
||||
// --------------------------------------------------- : Other package like things
|
||||
|
||||
/// Get the directory for dictionary files
|
||||
String getDictionaryDir(bool local) const;
|
||||
|
||||
// --------------------------------------------------- : Packages on a server
|
||||
|
||||
private:
|
||||
map<String, PackagedP> loaded_packages;
|
||||
PackageDirectory local, global;
|
||||
map<String, PackagedP> loaded_packages;
|
||||
PackageDirectory local, global;
|
||||
};
|
||||
|
||||
/// The global PackageManager instance
|
||||
@@ -190,48 +190,48 @@ extern PackageManager package_manager;
|
||||
/// Version information for an installed package
|
||||
class PackageVersion : public IntrusivePtrBase<PackageVersion> {
|
||||
public:
|
||||
PackageVersion() : status(0) {}
|
||||
PackageVersion(int status) : status(status) {}
|
||||
|
||||
String name;
|
||||
Version version;
|
||||
vector<PackageDependencyP> dependencies;
|
||||
enum Status
|
||||
{ STATUS_LOCAL = 0x01 ///< Package is installed in the local package dir
|
||||
, STATUS_GLOBAL = 0x02 ///< Package is installed in the global package dir
|
||||
, STATUS_BLESSED = 0x10 ///< Package is an official version, installed with the installer
|
||||
, STATUS_MODIFIED = 0x20 ///< Package has been modified after installing
|
||||
, STATUS_FIXED = 0x40 ///< The package can not be uninstalled
|
||||
};
|
||||
int status;
|
||||
|
||||
/// Check the status of the files in this package
|
||||
void check_status(Packaged& package);
|
||||
/// Set blessed status to true
|
||||
void bless();
|
||||
|
||||
PackageVersion() : status(0) {}
|
||||
PackageVersion(int status) : status(status) {}
|
||||
|
||||
String name;
|
||||
Version version;
|
||||
vector<PackageDependencyP> dependencies;
|
||||
enum Status
|
||||
{ STATUS_LOCAL = 0x01 ///< Package is installed in the local package dir
|
||||
, STATUS_GLOBAL = 0x02 ///< Package is installed in the global package dir
|
||||
, STATUS_BLESSED = 0x10 ///< Package is an official version, installed with the installer
|
||||
, STATUS_MODIFIED = 0x20 ///< Package has been modified after installing
|
||||
, STATUS_FIXED = 0x40 ///< The package can not be uninstalled
|
||||
};
|
||||
int status;
|
||||
|
||||
/// Check the status of the files in this package
|
||||
void check_status(Packaged& package);
|
||||
/// Set blessed status to true
|
||||
void bless();
|
||||
|
||||
public:
|
||||
/// Status of a single file
|
||||
enum FileStatus
|
||||
{ FILE_UNCHANGED
|
||||
, FILE_MODIFIED
|
||||
, FILE_ADDED
|
||||
, FILE_DELETED
|
||||
};
|
||||
/// Information on files in this package
|
||||
struct FileInfo {
|
||||
inline FileInfo() {}
|
||||
inline FileInfo(const String& file, const DateTime& time, FileStatus status)
|
||||
: file(file), time(time), status(status)
|
||||
{}
|
||||
String file;
|
||||
DateTime time;
|
||||
FileStatus status;
|
||||
inline bool operator < (const FileInfo& f) const { return file < f.file; }
|
||||
};
|
||||
/// Status of a single file
|
||||
enum FileStatus
|
||||
{ FILE_UNCHANGED
|
||||
, FILE_MODIFIED
|
||||
, FILE_ADDED
|
||||
, FILE_DELETED
|
||||
};
|
||||
/// Information on files in this package
|
||||
struct FileInfo {
|
||||
inline FileInfo() {}
|
||||
inline FileInfo(const String& file, const DateTime& time, FileStatus status)
|
||||
: file(file), time(time), status(status)
|
||||
{}
|
||||
String file;
|
||||
DateTime time;
|
||||
FileStatus status;
|
||||
inline bool operator < (const FileInfo& f) const { return file < f.file; }
|
||||
};
|
||||
private:
|
||||
vector<FileInfo> files; // sorted by filename
|
||||
DECLARE_REFLECTION();
|
||||
vector<FileInfo> files; // sorted by filename
|
||||
DECLARE_REFLECTION();
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : EOF
|
||||
|
||||
+332
-332
@@ -22,112 +22,112 @@ IMPLEMENT_DYNAMIC_ARG(ReaderPragmaHandler,reader_pragma_handler,nullptr);
|
||||
// ----------------------------------------------------------------------------- : Reader
|
||||
|
||||
Reader::Reader(const InputStreamP& input, Packaged* package, const String& filename, bool ignore_invalid)
|
||||
: indent(0), expected_indent(0), state(OUTSIDE)
|
||||
, ignore_invalid(ignore_invalid)
|
||||
, filename(filename), package(package), line_number(0), previous_line_number(0)
|
||||
, input(input)
|
||||
: indent(0), expected_indent(0), state(OUTSIDE)
|
||||
, ignore_invalid(ignore_invalid)
|
||||
, filename(filename), package(package), line_number(0), previous_line_number(0)
|
||||
, input(input)
|
||||
{
|
||||
moveNext();
|
||||
handleAppVersion();
|
||||
moveNext();
|
||||
handleAppVersion();
|
||||
}
|
||||
|
||||
Reader::Reader(Reader* parent, Packaged* pkg, const String& filename, bool ignore_invalid)
|
||||
: indent(0), expected_indent(0), state(OUTSIDE)
|
||||
, ignore_invalid(ignore_invalid)
|
||||
, filename(filename), package(pkg), line_number(0), previous_line_number(0)
|
||||
, input(package_manager.openFileFromPackage(package, filename))
|
||||
: indent(0), expected_indent(0), state(OUTSIDE)
|
||||
, ignore_invalid(ignore_invalid)
|
||||
, filename(filename), package(pkg), line_number(0), previous_line_number(0)
|
||||
, input(package_manager.openFileFromPackage(package, filename))
|
||||
{
|
||||
moveNext();
|
||||
// in an included file, use the app version of the parent if we have none
|
||||
handleAppVersion();
|
||||
if (file_app_version == 0) {
|
||||
file_app_version = parent->file_app_version;
|
||||
}
|
||||
moveNext();
|
||||
// in an included file, use the app version of the parent if we have none
|
||||
handleAppVersion();
|
||||
if (file_app_version == 0) {
|
||||
file_app_version = parent->file_app_version;
|
||||
}
|
||||
}
|
||||
|
||||
void Reader::addAlias(Version end_version, const Char* a, const Char* b) {
|
||||
Alias& alias = aliasses[a];
|
||||
alias.new_key = b;
|
||||
alias.end_version = end_version;
|
||||
Alias& alias = aliasses[a];
|
||||
alias.new_key = b;
|
||||
alias.end_version = end_version;
|
||||
}
|
||||
|
||||
void Reader::handleIgnore(int end_version, const Char* a) {
|
||||
if (file_app_version < end_version) {
|
||||
if (enterBlock(a)) exitBlock();
|
||||
}
|
||||
if (file_app_version < end_version) {
|
||||
if (enterBlock(a)) exitBlock();
|
||||
}
|
||||
}
|
||||
|
||||
void Reader::handleAppVersion() {
|
||||
if (enterBlock(_("mse_version"))) {
|
||||
handle(file_app_version);
|
||||
if (app_version < file_app_version) {
|
||||
queue_message(MESSAGE_WARNING, _ERROR_2_("newer version", filename, file_app_version.toString()));
|
||||
}
|
||||
exitBlock();
|
||||
}
|
||||
if (enterBlock(_("mse_version"))) {
|
||||
handle(file_app_version);
|
||||
if (app_version < file_app_version) {
|
||||
queue_message(MESSAGE_WARNING, _ERROR_2_("newer version", filename, file_app_version.toString()));
|
||||
}
|
||||
exitBlock();
|
||||
}
|
||||
}
|
||||
|
||||
void Reader::warning(const String& msg, int line_number_delta, bool warn_on_previous_line) {
|
||||
warnings += String(_("\nOn line "))
|
||||
<< ((warn_on_previous_line ? previous_line_number : line_number) + line_number_delta)
|
||||
<< _(": \t") << msg;
|
||||
warnings += String(_("\nOn line "))
|
||||
<< ((warn_on_previous_line ? previous_line_number : line_number) + line_number_delta)
|
||||
<< _(": \t") << msg;
|
||||
}
|
||||
|
||||
void Reader::showWarnings() {
|
||||
if (!warnings.empty()) {
|
||||
queue_message(MESSAGE_WARNING, _("Warnings while reading file:\n") + filename + _("\n") + warnings);
|
||||
warnings.clear();
|
||||
}
|
||||
if (!warnings.empty()) {
|
||||
queue_message(MESSAGE_WARNING, _("Warnings while reading file:\n") + filename + _("\n") + warnings);
|
||||
warnings.clear();
|
||||
}
|
||||
}
|
||||
|
||||
bool Reader::enterAnyBlock() {
|
||||
if (state == ENTERED) moveNext(); // on the key of the parent block, first move inside it
|
||||
if (indent != expected_indent) return false; // not enough indentation
|
||||
state = ENTERED;
|
||||
expected_indent += 1; // the indent inside the block must be at least this much
|
||||
return true;
|
||||
if (state == ENTERED) moveNext(); // on the key of the parent block, first move inside it
|
||||
if (indent != expected_indent) return false; // not enough indentation
|
||||
state = ENTERED;
|
||||
expected_indent += 1; // the indent inside the block must be at least this much
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Reader::enterBlock(const Char* name) {
|
||||
if (state == ENTERED) moveNext(); // on the key of the parent block, first move inside it
|
||||
if (indent != expected_indent) return false; // not enough indentation
|
||||
if (cannocial_name_compare(key, name)) {
|
||||
state = ENTERED;
|
||||
expected_indent += 1; // the indent inside the block must be at least this much
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
if (state == ENTERED) moveNext(); // on the key of the parent block, first move inside it
|
||||
if (indent != expected_indent) return false; // not enough indentation
|
||||
if (cannocial_name_compare(key, name)) {
|
||||
state = ENTERED;
|
||||
expected_indent += 1; // the indent inside the block must be at least this much
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void Reader::exitBlock() {
|
||||
assert(expected_indent > 0);
|
||||
expected_indent -= 1;
|
||||
assert(state != UNHANDLED);
|
||||
previous_value.clear();
|
||||
if (state == ENTERED) moveNext(); // leave this key
|
||||
// Dump the remainder of the block
|
||||
// TODO: issue warnings?
|
||||
while (indent > expected_indent) {
|
||||
moveNext();
|
||||
}
|
||||
state = HANDLED;
|
||||
assert(expected_indent > 0);
|
||||
expected_indent -= 1;
|
||||
assert(state != UNHANDLED);
|
||||
previous_value.clear();
|
||||
if (state == ENTERED) moveNext(); // leave this key
|
||||
// Dump the remainder of the block
|
||||
// TODO: issue warnings?
|
||||
while (indent > expected_indent) {
|
||||
moveNext();
|
||||
}
|
||||
state = HANDLED;
|
||||
}
|
||||
|
||||
void Reader::moveNext() {
|
||||
previous_line_number = line_number;
|
||||
state = HANDLED;
|
||||
key.clear();
|
||||
indent = -1; // if no line is read it never has the expected indentation
|
||||
// repeat until we have a good line
|
||||
while (key.empty() && !input->Eof()) {
|
||||
readLine();
|
||||
}
|
||||
// did we reach the end of the file?
|
||||
if (key.empty() && input->Eof()) {
|
||||
line_number += 1;
|
||||
indent = -1;
|
||||
}
|
||||
previous_line_number = line_number;
|
||||
state = HANDLED;
|
||||
key.clear();
|
||||
indent = -1; // if no line is read it never has the expected indentation
|
||||
// repeat until we have a good line
|
||||
while (key.empty() && !input->Eof()) {
|
||||
readLine();
|
||||
}
|
||||
// did we reach the end of the file?
|
||||
if (key.empty() && input->Eof()) {
|
||||
line_number += 1;
|
||||
indent = -1;
|
||||
}
|
||||
}
|
||||
|
||||
/// Faster vector, uses large local storage
|
||||
@@ -138,28 +138,28 @@ void Reader::moveNext() {
|
||||
*/
|
||||
template <typename T> class LocalVector {
|
||||
public:
|
||||
LocalVector() : the_size(0), alloced(SMALL_SIZE), buffer(small) {}
|
||||
~LocalVector() { if (buffer != small) free(buffer); }
|
||||
void push_back(T t) {
|
||||
if (the_size >= alloced) {
|
||||
// double buffer size
|
||||
if (buffer != small) {
|
||||
buffer = (T*)realloc(buffer, sizeof(T) * alloced * 2);
|
||||
} else {
|
||||
buffer = (T*)malloc(sizeof(T) * alloced * 2);
|
||||
memcpy(buffer, small, alloced * sizeof(T));
|
||||
}
|
||||
alloced *= 2;
|
||||
}
|
||||
buffer[the_size++] = t;
|
||||
}
|
||||
inline const T* get() const { return buffer; }
|
||||
inline size_t size() const { return the_size; }
|
||||
LocalVector() : the_size(0), alloced(SMALL_SIZE), buffer(small) {}
|
||||
~LocalVector() { if (buffer != small) free(buffer); }
|
||||
void push_back(T t) {
|
||||
if (the_size >= alloced) {
|
||||
// double buffer size
|
||||
if (buffer != small) {
|
||||
buffer = (T*)realloc(buffer, sizeof(T) * alloced * 2);
|
||||
} else {
|
||||
buffer = (T*)malloc(sizeof(T) * alloced * 2);
|
||||
memcpy(buffer, small, alloced * sizeof(T));
|
||||
}
|
||||
alloced *= 2;
|
||||
}
|
||||
buffer[the_size++] = t;
|
||||
}
|
||||
inline const T* get() const { return buffer; }
|
||||
inline size_t size() const { return the_size; }
|
||||
private:
|
||||
static const int SMALL_SIZE = 1024;
|
||||
size_t the_size, alloced;
|
||||
T* buffer;
|
||||
T small[SMALL_SIZE];
|
||||
static const int SMALL_SIZE = 1024;
|
||||
size_t the_size, alloced;
|
||||
T* buffer;
|
||||
T small[SMALL_SIZE];
|
||||
};
|
||||
|
||||
/// Read an UTF-8 encoded line from an input stream
|
||||
@@ -167,290 +167,290 @@ template <typename T> class LocalVector {
|
||||
*/
|
||||
String read_utf8_line(wxInputStream& input, bool eat_bom = true, bool until_eof = false);
|
||||
String read_utf8_line(wxInputStream& input, bool eat_bom, bool until_eof) {
|
||||
LocalVector<char> buffer;
|
||||
while (!input.Eof()) {
|
||||
Byte c = input.GetC(); if (input.LastRead() <= 0) break;
|
||||
if (!until_eof) {
|
||||
if (c == '\n') break;
|
||||
if (c == '\r') {
|
||||
if (input.Eof()) break;
|
||||
c = input.GetC(); if (input.LastRead() <= 0) break;
|
||||
if (c != '\n') {
|
||||
input.Ungetch(c); // \r but not \r\n
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
buffer.push_back(c);
|
||||
}
|
||||
// convert to string
|
||||
buffer.push_back('\0');
|
||||
size_t size = wxConvUTF8.MB2WC(nullptr, buffer.get(), 0);
|
||||
if (size == size_t(-1)) {
|
||||
throw ParseError(_("Invalid UTF-8 sequence"));
|
||||
} else if (size == 0) {
|
||||
return _("");
|
||||
}
|
||||
#ifdef UNICODE
|
||||
#if wxVERSION_NUMBER >= 2900
|
||||
String result = wxString::FromUTF8(buffer.get(), buffer.size());
|
||||
return eat_bom ? decodeUTF8BOM(result) : result;
|
||||
#else
|
||||
// NOTE: wx doc is wrong, parameter to GetWritableChar is numer of characters, not bytes
|
||||
String result;
|
||||
Char* result_buf = result.GetWriteBuf(size + 1);
|
||||
wxConvUTF8.MB2WC(result_buf, buffer.get(), size + 1);
|
||||
result.UngetWriteBuf(size);
|
||||
return eat_bom ? decodeUTF8BOM(result) : result;
|
||||
#endif
|
||||
#else
|
||||
String result;
|
||||
// first to wchar, then back to local
|
||||
vector<wchar_t> buf2; buf2.resize(size+1);
|
||||
wxConvUTF8.MB2WC(&buf2[0], buffer.get(), size + 1);
|
||||
// eat BOM?
|
||||
if (eat_bom && buf2[0]==0xFEFF ) {
|
||||
buf2.erase(buf2.begin()); // remove BOM
|
||||
}
|
||||
// convert
|
||||
#ifdef __WXMSW__
|
||||
// size includes null terminator
|
||||
size = ::WideCharToMultiByte(CP_ACP, 0, &buf2[0], -1, nullptr, 0, nullptr, nullptr);
|
||||
Char* result_buf = result.GetWriteBuf(size);
|
||||
::WideCharToMultiByte(CP_ACP, 0, &buf2[0], -1, result_buf, (int)size, nullptr, nullptr);
|
||||
result.UngetWriteBuf(size - 1);
|
||||
#else
|
||||
for (size_t i = 0 ; i < size ; ++i) {
|
||||
wchar_t wc = buf2[i];
|
||||
if (wc < 0xFF) {
|
||||
result += (Char)wc;
|
||||
} else {
|
||||
// not valid in Latin1
|
||||
result += '?';
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return result;
|
||||
#endif
|
||||
LocalVector<char> buffer;
|
||||
while (!input.Eof()) {
|
||||
Byte c = input.GetC(); if (input.LastRead() <= 0) break;
|
||||
if (!until_eof) {
|
||||
if (c == '\n') break;
|
||||
if (c == '\r') {
|
||||
if (input.Eof()) break;
|
||||
c = input.GetC(); if (input.LastRead() <= 0) break;
|
||||
if (c != '\n') {
|
||||
input.Ungetch(c); // \r but not \r\n
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
buffer.push_back(c);
|
||||
}
|
||||
// convert to string
|
||||
buffer.push_back('\0');
|
||||
size_t size = wxConvUTF8.MB2WC(nullptr, buffer.get(), 0);
|
||||
if (size == size_t(-1)) {
|
||||
throw ParseError(_("Invalid UTF-8 sequence"));
|
||||
} else if (size == 0) {
|
||||
return _("");
|
||||
}
|
||||
#ifdef UNICODE
|
||||
#if wxVERSION_NUMBER >= 2900
|
||||
String result = wxString::FromUTF8(buffer.get(), buffer.size());
|
||||
return eat_bom ? decodeUTF8BOM(result) : result;
|
||||
#else
|
||||
// NOTE: wx doc is wrong, parameter to GetWritableChar is numer of characters, not bytes
|
||||
String result;
|
||||
Char* result_buf = result.GetWriteBuf(size + 1);
|
||||
wxConvUTF8.MB2WC(result_buf, buffer.get(), size + 1);
|
||||
result.UngetWriteBuf(size);
|
||||
return eat_bom ? decodeUTF8BOM(result) : result;
|
||||
#endif
|
||||
#else
|
||||
String result;
|
||||
// first to wchar, then back to local
|
||||
vector<wchar_t> buf2; buf2.resize(size+1);
|
||||
wxConvUTF8.MB2WC(&buf2[0], buffer.get(), size + 1);
|
||||
// eat BOM?
|
||||
if (eat_bom && buf2[0]==0xFEFF ) {
|
||||
buf2.erase(buf2.begin()); // remove BOM
|
||||
}
|
||||
// convert
|
||||
#ifdef __WXMSW__
|
||||
// size includes null terminator
|
||||
size = ::WideCharToMultiByte(CP_ACP, 0, &buf2[0], -1, nullptr, 0, nullptr, nullptr);
|
||||
Char* result_buf = result.GetWriteBuf(size);
|
||||
::WideCharToMultiByte(CP_ACP, 0, &buf2[0], -1, result_buf, (int)size, nullptr, nullptr);
|
||||
result.UngetWriteBuf(size - 1);
|
||||
#else
|
||||
for (size_t i = 0 ; i < size ; ++i) {
|
||||
wchar_t wc = buf2[i];
|
||||
if (wc < 0xFF) {
|
||||
result += (Char)wc;
|
||||
} else {
|
||||
// not valid in Latin1
|
||||
result += '?';
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return result;
|
||||
#endif
|
||||
}
|
||||
|
||||
void Reader::readLine(bool in_string) {
|
||||
line_number += 1;
|
||||
// We have to do our own line reading, because wxTextInputStream is insane
|
||||
try {
|
||||
line = read_utf8_line(*input, line_number == 1);
|
||||
} catch (const ParseError& e) {
|
||||
throw ParseError(e.what() + String(_(" on line ")) << line_number);
|
||||
}
|
||||
// pragma handler
|
||||
if (reader_pragma_handler()) reader_pragma_handler()(line);
|
||||
// read indentation
|
||||
indent = 0;
|
||||
while ((UInt)indent < line.size() && line.GetChar(indent) == _('\t')) {
|
||||
indent += 1;
|
||||
}
|
||||
// read key / value
|
||||
if (line.find_first_not_of(_(" \t")) == String::npos || line.GetChar(indent) == _('#')) {
|
||||
// empty line or comment
|
||||
key.clear();
|
||||
return;
|
||||
}
|
||||
size_t pos = line.find_first_of(_(':'), indent);
|
||||
key = line.substr(indent, pos - indent);
|
||||
if (!ignore_invalid && !in_string && starts_with(key, _(" "))) {
|
||||
warning(_("key: '") + key + _("' starts with a space; only use TABs for indentation!"), 0, false);
|
||||
// try to fix up: 8 spaces is a tab
|
||||
while (starts_with(key, _(" "))) {
|
||||
key = key.substr(8);
|
||||
indent += 1;
|
||||
}
|
||||
}
|
||||
key = canonical_name_form(trim(key));
|
||||
value = pos == String::npos ? _("") : trim_left(line.substr(pos+1));
|
||||
if (key.empty() && pos!=String::npos) key = _(" "); // we don't want an empty key if there was a colon
|
||||
line_number += 1;
|
||||
// We have to do our own line reading, because wxTextInputStream is insane
|
||||
try {
|
||||
line = read_utf8_line(*input, line_number == 1);
|
||||
} catch (const ParseError& e) {
|
||||
throw ParseError(e.what() + String(_(" on line ")) << line_number);
|
||||
}
|
||||
// pragma handler
|
||||
if (reader_pragma_handler()) reader_pragma_handler()(line);
|
||||
// read indentation
|
||||
indent = 0;
|
||||
while ((UInt)indent < line.size() && line.GetChar(indent) == _('\t')) {
|
||||
indent += 1;
|
||||
}
|
||||
// read key / value
|
||||
if (line.find_first_not_of(_(" \t")) == String::npos || line.GetChar(indent) == _('#')) {
|
||||
// empty line or comment
|
||||
key.clear();
|
||||
return;
|
||||
}
|
||||
size_t pos = line.find_first_of(_(':'), indent);
|
||||
key = line.substr(indent, pos - indent);
|
||||
if (!ignore_invalid && !in_string && starts_with(key, _(" "))) {
|
||||
warning(_("key: '") + key + _("' starts with a space; only use TABs for indentation!"), 0, false);
|
||||
// try to fix up: 8 spaces is a tab
|
||||
while (starts_with(key, _(" "))) {
|
||||
key = key.substr(8);
|
||||
indent += 1;
|
||||
}
|
||||
}
|
||||
key = canonical_name_form(trim(key));
|
||||
value = pos == String::npos ? _("") : trim_left(line.substr(pos+1));
|
||||
if (key.empty() && pos!=String::npos) key = _(" "); // we don't want an empty key if there was a colon
|
||||
}
|
||||
|
||||
void Reader::unknownKey() {
|
||||
// ignore?
|
||||
if (ignore_invalid) {
|
||||
do {
|
||||
moveNext();
|
||||
} while (indent > expected_indent);
|
||||
return;
|
||||
}
|
||||
// aliasses?
|
||||
map<String,Alias>::const_iterator it = aliasses.find(key);
|
||||
if (it != aliasses.end()) {
|
||||
if (aliasses.find(it->second.new_key) != aliasses.end()) {
|
||||
// alias points to another alias, don't follow it, there is the risk of infinite loops
|
||||
} else if (it->second.end_version <= file_app_version) {
|
||||
// alias not used for this version, use in warning
|
||||
if (indent == expected_indent) {
|
||||
warning(_("Unexpected key: '") + key + _("' use '") + it->second.new_key + _("'"), 0, false);
|
||||
do {
|
||||
moveNext();
|
||||
} while (indent > expected_indent);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// try this key instead
|
||||
key = it->second.new_key;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (indent >= expected_indent) {
|
||||
warning(_("Unexpected key: '") + key + _("'"), 0, false);
|
||||
do {
|
||||
moveNext();
|
||||
} while (indent > expected_indent);
|
||||
}
|
||||
// else: could be a nameless value, which doesn't call exitBlock to move past its own key
|
||||
// ignore?
|
||||
if (ignore_invalid) {
|
||||
do {
|
||||
moveNext();
|
||||
} while (indent > expected_indent);
|
||||
return;
|
||||
}
|
||||
// aliasses?
|
||||
map<String,Alias>::const_iterator it = aliasses.find(key);
|
||||
if (it != aliasses.end()) {
|
||||
if (aliasses.find(it->second.new_key) != aliasses.end()) {
|
||||
// alias points to another alias, don't follow it, there is the risk of infinite loops
|
||||
} else if (it->second.end_version <= file_app_version) {
|
||||
// alias not used for this version, use in warning
|
||||
if (indent == expected_indent) {
|
||||
warning(_("Unexpected key: '") + key + _("' use '") + it->second.new_key + _("'"), 0, false);
|
||||
do {
|
||||
moveNext();
|
||||
} while (indent > expected_indent);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// try this key instead
|
||||
key = it->second.new_key;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (indent >= expected_indent) {
|
||||
warning(_("Unexpected key: '") + key + _("'"), 0, false);
|
||||
do {
|
||||
moveNext();
|
||||
} while (indent > expected_indent);
|
||||
}
|
||||
// else: could be a nameless value, which doesn't call exitBlock to move past its own key
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Handling basic types
|
||||
|
||||
void Reader::unhandle() {
|
||||
assert(state == HANDLED);
|
||||
state = UNHANDLED;
|
||||
assert(state == HANDLED);
|
||||
state = UNHANDLED;
|
||||
}
|
||||
|
||||
const String& Reader::getValue() {
|
||||
assert(state != HANDLED); // don't try to handle things twice
|
||||
if (state == UNHANDLED) {
|
||||
state = HANDLED;
|
||||
return previous_value;
|
||||
} else if (value.empty()) {
|
||||
// a multiline string
|
||||
previous_value.clear();
|
||||
int pending_newlines = 0;
|
||||
// read all lines that are indented enough
|
||||
readLine(true);
|
||||
previous_line_number = line_number;
|
||||
while (indent >= expected_indent && !input->Eof()) {
|
||||
previous_value.resize(previous_value.size() + pending_newlines, _('\n'));
|
||||
pending_newlines = 0;
|
||||
previous_value += line.substr(expected_indent); // strip expected indent
|
||||
do {
|
||||
readLine(true);
|
||||
pending_newlines++;
|
||||
// skip empty lines that are not indented enough
|
||||
} while(trim(line).empty() && indent < expected_indent && !input->Eof());
|
||||
}
|
||||
// moveNext(), but without the initial readLine()
|
||||
state = HANDLED;
|
||||
while (key.empty() && !input->Eof()) {
|
||||
readLine();
|
||||
}
|
||||
// did we reach the end of the file?
|
||||
if (key.empty() && input->Eof()) {
|
||||
line_number += 1;
|
||||
indent = -1;
|
||||
}
|
||||
if (indent >= expected_indent) {
|
||||
warning(_("Blank line or comment in text block, that is insufficiently indented.\n")
|
||||
_("\t\tEither indent the comment/blank line, or add a 'key:' after it.\n")
|
||||
_("\t\tThis could cause more more error messages.\n"), -1, false);
|
||||
}
|
||||
return previous_value;
|
||||
} else {
|
||||
previous_value = value;
|
||||
moveNext();
|
||||
return previous_value;
|
||||
}
|
||||
assert(state != HANDLED); // don't try to handle things twice
|
||||
if (state == UNHANDLED) {
|
||||
state = HANDLED;
|
||||
return previous_value;
|
||||
} else if (value.empty()) {
|
||||
// a multiline string
|
||||
previous_value.clear();
|
||||
int pending_newlines = 0;
|
||||
// read all lines that are indented enough
|
||||
readLine(true);
|
||||
previous_line_number = line_number;
|
||||
while (indent >= expected_indent && !input->Eof()) {
|
||||
previous_value.resize(previous_value.size() + pending_newlines, _('\n'));
|
||||
pending_newlines = 0;
|
||||
previous_value += line.substr(expected_indent); // strip expected indent
|
||||
do {
|
||||
readLine(true);
|
||||
pending_newlines++;
|
||||
// skip empty lines that are not indented enough
|
||||
} while(trim(line).empty() && indent < expected_indent && !input->Eof());
|
||||
}
|
||||
// moveNext(), but without the initial readLine()
|
||||
state = HANDLED;
|
||||
while (key.empty() && !input->Eof()) {
|
||||
readLine();
|
||||
}
|
||||
// did we reach the end of the file?
|
||||
if (key.empty() && input->Eof()) {
|
||||
line_number += 1;
|
||||
indent = -1;
|
||||
}
|
||||
if (indent >= expected_indent) {
|
||||
warning(_("Blank line or comment in text block, that is insufficiently indented.\n")
|
||||
_("\t\tEither indent the comment/blank line, or add a 'key:' after it.\n")
|
||||
_("\t\tThis could cause more more error messages.\n"), -1, false);
|
||||
}
|
||||
return previous_value;
|
||||
} else {
|
||||
previous_value = value;
|
||||
moveNext();
|
||||
return previous_value;
|
||||
}
|
||||
}
|
||||
|
||||
template <> void Reader::handle(String& s) {
|
||||
s = getValue();
|
||||
s = getValue();
|
||||
}
|
||||
template <> void Reader::handle(int& i) {
|
||||
long l = 0;
|
||||
if (!getValue().ToLong(&l)) {
|
||||
warning(_("Expected integer instead of '") + previous_value + _("'"));
|
||||
}
|
||||
i = l;
|
||||
long l = 0;
|
||||
if (!getValue().ToLong(&l)) {
|
||||
warning(_("Expected integer instead of '") + previous_value + _("'"));
|
||||
}
|
||||
i = l;
|
||||
}
|
||||
template <> void Reader::handle(unsigned int& i) {
|
||||
long l = 0;
|
||||
if (!getValue().ToLong(&l)) {
|
||||
warning(_("Expected non-negative integer instead of '") + previous_value + _("'"));
|
||||
} else if (l < 0) {
|
||||
warning(wxString::Format(_("Expected non-negative integer instead of %d"),(int)l));
|
||||
}
|
||||
i = abs(l); // abs, because it will seem strange if -1 comes out as MAX_INT
|
||||
long l = 0;
|
||||
if (!getValue().ToLong(&l)) {
|
||||
warning(_("Expected non-negative integer instead of '") + previous_value + _("'"));
|
||||
} else if (l < 0) {
|
||||
warning(wxString::Format(_("Expected non-negative integer instead of %d"),(int)l));
|
||||
}
|
||||
i = abs(l); // abs, because it will seem strange if -1 comes out as MAX_INT
|
||||
}
|
||||
template <> void Reader::handle(double& d) {
|
||||
if (!getValue().ToDouble(&d)) {
|
||||
warning(_("Expected floating point number instead of '") + previous_value + _("'"));
|
||||
}
|
||||
if (!getValue().ToDouble(&d)) {
|
||||
warning(_("Expected floating point number instead of '") + previous_value + _("'"));
|
||||
}
|
||||
}
|
||||
template <> void Reader::handle(bool& b) {
|
||||
const String& v = getValue();
|
||||
if (v==_("true") || v==_("1") || v==_("yes")) {
|
||||
b = true;
|
||||
} else if (v==_("false") || v==_("0") || v==_("no")) {
|
||||
b = false;
|
||||
} else {
|
||||
warning(_("Expected boolean ('true' or 'false') instead of '") + v + _("'"));
|
||||
}
|
||||
const String& v = getValue();
|
||||
if (v==_("true") || v==_("1") || v==_("yes")) {
|
||||
b = true;
|
||||
} else if (v==_("false") || v==_("0") || v==_("no")) {
|
||||
b = false;
|
||||
} else {
|
||||
warning(_("Expected boolean ('true' or 'false') instead of '") + v + _("'"));
|
||||
}
|
||||
}
|
||||
template <> void Reader::handle(tribool& tb) {
|
||||
bool b;
|
||||
handle(b);
|
||||
tb = b;
|
||||
bool b;
|
||||
handle(b);
|
||||
tb = b;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Handling less basic util types
|
||||
|
||||
template <> void Reader::handle(wxDateTime& date) {
|
||||
if (!date.ParseDateTime(getValue().c_str())) {
|
||||
throw ParseError(_("Expected a date and time"));
|
||||
}
|
||||
if (!date.ParseDateTime(getValue().c_str())) {
|
||||
throw ParseError(_("Expected a date and time"));
|
||||
}
|
||||
}
|
||||
|
||||
template <> void Reader::handle(Vector2D& vec) {
|
||||
if (!wxSscanf(getValue().c_str(), _("(%lf,%lf)"), &vec.x, &vec.y)) {
|
||||
throw ParseError(_("Expected (x,y)"));
|
||||
}
|
||||
if (!wxSscanf(getValue().c_str(), _("(%lf,%lf)"), &vec.x, &vec.y)) {
|
||||
throw ParseError(_("Expected (x,y)"));
|
||||
}
|
||||
}
|
||||
|
||||
template <> void Reader::handle(FileName& f) {
|
||||
if (clipboard_package()) {
|
||||
String str = getValue();
|
||||
if (!str.empty()) {
|
||||
// copy file into current package
|
||||
try {
|
||||
String packaged_name = clipboard_package()->newFileName(_("image"),_("")); // a new unique name in the package, assume it's an image
|
||||
OutputStreamP out = clipboard_package()->openOut(packaged_name);
|
||||
InputStreamP in = Package::openAbsoluteFile(str);
|
||||
out->Write(*in); // copy
|
||||
f.assign(packaged_name);
|
||||
} catch (Error) {
|
||||
// ignore errors
|
||||
}
|
||||
} else {
|
||||
f.assign(str);
|
||||
}
|
||||
} else {
|
||||
handle(static_cast<String&>(f));
|
||||
}
|
||||
if (clipboard_package()) {
|
||||
String str = getValue();
|
||||
if (!str.empty()) {
|
||||
// copy file into current package
|
||||
try {
|
||||
String packaged_name = clipboard_package()->newFileName(_("image"),_("")); // a new unique name in the package, assume it's an image
|
||||
OutputStreamP out = clipboard_package()->openOut(packaged_name);
|
||||
InputStreamP in = Package::openAbsoluteFile(str);
|
||||
out->Write(*in); // copy
|
||||
f.assign(packaged_name);
|
||||
} catch (Error) {
|
||||
// ignore errors
|
||||
}
|
||||
} else {
|
||||
f.assign(str);
|
||||
}
|
||||
} else {
|
||||
handle(static_cast<String&>(f));
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : EnumReader
|
||||
|
||||
String EnumReader::notDoneErrorMessage() const {
|
||||
if (!first) throw InternalError(_("No first value in EnumReader"));
|
||||
return _ERROR_2_("unrecognized value", read, first);
|
||||
if (!first) throw InternalError(_("No first value in EnumReader"));
|
||||
return _ERROR_2_("unrecognized value", read, first);
|
||||
}
|
||||
|
||||
void EnumReader::warnIfNotDone(Reader* errors_to) {
|
||||
if (!done) {
|
||||
// warning: unknown value
|
||||
errors_to->warning(notDoneErrorMessage());
|
||||
}
|
||||
if (!done) {
|
||||
// warning: unknown value
|
||||
errors_to->warning(notDoneErrorMessage());
|
||||
}
|
||||
}
|
||||
|
||||
void EnumReader::errorIfNotDone() {
|
||||
if (!done) {
|
||||
throw ParseError(notDoneErrorMessage());
|
||||
}
|
||||
if (!done) {
|
||||
throw ParseError(notDoneErrorMessage());
|
||||
}
|
||||
}
|
||||
|
||||
+216
-216
@@ -33,163 +33,163 @@ typedef shared_ptr<wxInputStream> InputStreamP;
|
||||
*/
|
||||
class Reader {
|
||||
private:
|
||||
/// Construct a reader that reads a file in a package
|
||||
/** Used for "include file" keys.
|
||||
* package can be nullptr
|
||||
*/
|
||||
Reader(Reader* parent, Packaged* package, const String& filename, bool ignore_invalid = false);
|
||||
/// Construct a reader that reads a file in a package
|
||||
/** Used for "include file" keys.
|
||||
* package can be nullptr
|
||||
*/
|
||||
Reader(Reader* parent, Packaged* package, const String& filename, bool ignore_invalid = false);
|
||||
public:
|
||||
/// Construct a reader that reads from the given input stream
|
||||
/** filename is used only for error messages
|
||||
*/
|
||||
Reader(const InputStreamP& input, Packaged* package = nullptr, const String& filename = wxEmptyString, bool ignore_invalid = false);
|
||||
|
||||
~Reader() { showWarnings(); }
|
||||
|
||||
/// Tell the reflection code we are reading
|
||||
inline bool reading() const { return true; }
|
||||
/// Tell the reflection code we are not related to scripting
|
||||
inline bool scripting() const { return false; }
|
||||
/// Is the thing currently being read 'complex', i.e. does it have children
|
||||
inline bool isComplex() const { return indent != expected_indent - 1 || value.empty(); }
|
||||
/// Add a as an alias for b, all keys a will be replaced with b, only if file_app_version < end_version
|
||||
void addAlias(Version end_version, const Char* a, const Char* b);
|
||||
/// Ignore old keys
|
||||
void handleIgnore(int, const Char*);
|
||||
|
||||
/// Read and check the application version
|
||||
void handleAppVersion();
|
||||
|
||||
/// Add a warning message, but continue reading
|
||||
void warning(const String& msg, int line_number_delta = 0, bool warn_on_previous_line = true);
|
||||
/// Show all warning messages, but continue reading
|
||||
void showWarnings();
|
||||
|
||||
// --------------------------------------------------- : Handling objects
|
||||
/// Handle an object that can read as much as it can eat
|
||||
template <typename T>
|
||||
void handle_greedy(T& object) {
|
||||
do {
|
||||
handle(object);
|
||||
if (state != HANDLED) unknownKey(object);
|
||||
state = OUTSIDE;
|
||||
} while (indent >= expected_indent);
|
||||
}
|
||||
|
||||
/// Handle an object: read it if it's name matches
|
||||
template <typename T>
|
||||
void handle(const Char* name, T& object) {
|
||||
if (enterBlock(name)) {
|
||||
handle_greedy(object);
|
||||
exitBlock();
|
||||
}
|
||||
}
|
||||
/// Handle a value
|
||||
template <typename T>
|
||||
inline void handleNoScript(const Char* name, T& value) { handle(name,value); }
|
||||
|
||||
/// Reads a vector from the input stream
|
||||
template <typename T>
|
||||
void handle(const Char* name, vector<T>& vector);
|
||||
|
||||
/// Reads an object of type T from the input stream
|
||||
template <typename T> void handle(T&);
|
||||
/// Reads a intrusive_ptr from the input stream
|
||||
template <typename T> void handle(intrusive_ptr<T>&);
|
||||
/// Reads a map from the input stream
|
||||
template <typename V> void handle(map<String,V>&);
|
||||
/// Reads an IndexMap from the input stream, reads only keys that already exist in the map
|
||||
template <typename K, typename V> void handle(IndexMap<K,V>&);
|
||||
template <typename K, typename V> void handle(DelayedIndexMaps<K,V>&);
|
||||
template <typename K, typename V> void handle(DelayedIndexMapsData<K,V>&);
|
||||
/// Reads a Defaultable from the input stream
|
||||
template <typename T> void handle(Defaultable<T>&);
|
||||
/// Reads a Scriptable from the input stream
|
||||
template <typename T> void handle(Scriptable<T>&);
|
||||
// special behaviour
|
||||
void handle(GameP&);
|
||||
void handle(StyleSheetP&);
|
||||
|
||||
/// Indicate that the last value from getValue() was not handled, allowing it to be handled again
|
||||
void unhandle();
|
||||
|
||||
/// The package being read from
|
||||
inline Packaged* getPackage() const { return package; }
|
||||
|
||||
// --------------------------------------------------- : Data
|
||||
/// App version this file was made with
|
||||
Version file_app_version;
|
||||
/// Construct a reader that reads from the given input stream
|
||||
/** filename is used only for error messages
|
||||
*/
|
||||
Reader(const InputStreamP& input, Packaged* package = nullptr, const String& filename = wxEmptyString, bool ignore_invalid = false);
|
||||
|
||||
~Reader() { showWarnings(); }
|
||||
|
||||
/// Tell the reflection code we are reading
|
||||
inline bool reading() const { return true; }
|
||||
/// Tell the reflection code we are not related to scripting
|
||||
inline bool scripting() const { return false; }
|
||||
/// Is the thing currently being read 'complex', i.e. does it have children
|
||||
inline bool isComplex() const { return indent != expected_indent - 1 || value.empty(); }
|
||||
/// Add a as an alias for b, all keys a will be replaced with b, only if file_app_version < end_version
|
||||
void addAlias(Version end_version, const Char* a, const Char* b);
|
||||
/// Ignore old keys
|
||||
void handleIgnore(int, const Char*);
|
||||
|
||||
/// Read and check the application version
|
||||
void handleAppVersion();
|
||||
|
||||
/// Add a warning message, but continue reading
|
||||
void warning(const String& msg, int line_number_delta = 0, bool warn_on_previous_line = true);
|
||||
/// Show all warning messages, but continue reading
|
||||
void showWarnings();
|
||||
|
||||
// --------------------------------------------------- : Handling objects
|
||||
/// Handle an object that can read as much as it can eat
|
||||
template <typename T>
|
||||
void handle_greedy(T& object) {
|
||||
do {
|
||||
handle(object);
|
||||
if (state != HANDLED) unknownKey(object);
|
||||
state = OUTSIDE;
|
||||
} while (indent >= expected_indent);
|
||||
}
|
||||
|
||||
/// Handle an object: read it if it's name matches
|
||||
template <typename T>
|
||||
void handle(const Char* name, T& object) {
|
||||
if (enterBlock(name)) {
|
||||
handle_greedy(object);
|
||||
exitBlock();
|
||||
}
|
||||
}
|
||||
/// Handle a value
|
||||
template <typename T>
|
||||
inline void handleNoScript(const Char* name, T& value) { handle(name,value); }
|
||||
|
||||
/// Reads a vector from the input stream
|
||||
template <typename T>
|
||||
void handle(const Char* name, vector<T>& vector);
|
||||
|
||||
/// Reads an object of type T from the input stream
|
||||
template <typename T> void handle(T&);
|
||||
/// Reads a intrusive_ptr from the input stream
|
||||
template <typename T> void handle(intrusive_ptr<T>&);
|
||||
/// Reads a map from the input stream
|
||||
template <typename V> void handle(map<String,V>&);
|
||||
/// Reads an IndexMap from the input stream, reads only keys that already exist in the map
|
||||
template <typename K, typename V> void handle(IndexMap<K,V>&);
|
||||
template <typename K, typename V> void handle(DelayedIndexMaps<K,V>&);
|
||||
template <typename K, typename V> void handle(DelayedIndexMapsData<K,V>&);
|
||||
/// Reads a Defaultable from the input stream
|
||||
template <typename T> void handle(Defaultable<T>&);
|
||||
/// Reads a Scriptable from the input stream
|
||||
template <typename T> void handle(Scriptable<T>&);
|
||||
// special behaviour
|
||||
void handle(GameP&);
|
||||
void handle(StyleSheetP&);
|
||||
|
||||
/// Indicate that the last value from getValue() was not handled, allowing it to be handled again
|
||||
void unhandle();
|
||||
|
||||
/// The package being read from
|
||||
inline Packaged* getPackage() const { return package; }
|
||||
|
||||
// --------------------------------------------------- : Data
|
||||
/// App version this file was made with
|
||||
Version file_app_version;
|
||||
private:
|
||||
/// The line we read
|
||||
String line;
|
||||
/// The key and value of the last line we read
|
||||
String key, value;
|
||||
/// Value of the *previous* line, only valid in state==HANDLED
|
||||
String previous_value;
|
||||
/// Indentation of the last line we read
|
||||
int indent;
|
||||
/// Indentation of the block we are in
|
||||
int expected_indent;
|
||||
/// State of the reader
|
||||
enum State {
|
||||
OUTSIDE, ///< We have not entered the block of the current key
|
||||
ENTERED, ///< We just entered the block of the current key
|
||||
HANDLED, ///< We have handled a value, and moved to the next line, previous_value is the value we just handled
|
||||
UNHANDLED, ///< Something has been 'unhandled()'
|
||||
} state;
|
||||
/// Aliasses for compatability
|
||||
struct Alias {
|
||||
String new_key;
|
||||
Version end_version;
|
||||
};
|
||||
/// Aliasses for compatability
|
||||
map<String, Alias> aliasses;
|
||||
/// Should all invalid keys be ignored?
|
||||
bool ignore_invalid;
|
||||
|
||||
/// Filename for error messages
|
||||
String filename;
|
||||
/// Package this file is from, if any
|
||||
Packaged* package;
|
||||
/// Line number of the current line for error messages
|
||||
int line_number;
|
||||
/// Line number of the previous_line
|
||||
int previous_line_number;
|
||||
/// Input stream we are reading from
|
||||
InputStreamP input;
|
||||
/// Accumulated warning messages
|
||||
String warnings;
|
||||
|
||||
// --------------------------------------------------- : Reading the stream
|
||||
|
||||
/// Is there a block with the given key under the current cursor? if so, enter it
|
||||
bool enterBlock(const Char* name);
|
||||
/// Enter any block, no matter what the key
|
||||
bool enterAnyBlock();
|
||||
/// Leave the block we are in
|
||||
void exitBlock();
|
||||
|
||||
/// Move to the next non empty line
|
||||
void moveNext();
|
||||
/// Reads the next line from the input, and stores it in line/key/value/indent
|
||||
void readLine(bool in_string = false);
|
||||
|
||||
/// Return the value on the current line
|
||||
const String& getValue();
|
||||
|
||||
/// No line was read, because nothing mathes the current key
|
||||
/** Maybe the key is "include file" */
|
||||
template <typename T>
|
||||
void unknownKey(T& v) {
|
||||
if (key == _("include file")) {
|
||||
Reader reader(this, package, value, ignore_invalid);
|
||||
reader.handle_greedy(v);
|
||||
moveNext();
|
||||
} else {
|
||||
unknownKey();
|
||||
}
|
||||
}
|
||||
void unknownKey();
|
||||
/// The line we read
|
||||
String line;
|
||||
/// The key and value of the last line we read
|
||||
String key, value;
|
||||
/// Value of the *previous* line, only valid in state==HANDLED
|
||||
String previous_value;
|
||||
/// Indentation of the last line we read
|
||||
int indent;
|
||||
/// Indentation of the block we are in
|
||||
int expected_indent;
|
||||
/// State of the reader
|
||||
enum State {
|
||||
OUTSIDE, ///< We have not entered the block of the current key
|
||||
ENTERED, ///< We just entered the block of the current key
|
||||
HANDLED, ///< We have handled a value, and moved to the next line, previous_value is the value we just handled
|
||||
UNHANDLED, ///< Something has been 'unhandled()'
|
||||
} state;
|
||||
/// Aliasses for compatability
|
||||
struct Alias {
|
||||
String new_key;
|
||||
Version end_version;
|
||||
};
|
||||
/// Aliasses for compatability
|
||||
map<String, Alias> aliasses;
|
||||
/// Should all invalid keys be ignored?
|
||||
bool ignore_invalid;
|
||||
|
||||
/// Filename for error messages
|
||||
String filename;
|
||||
/// Package this file is from, if any
|
||||
Packaged* package;
|
||||
/// Line number of the current line for error messages
|
||||
int line_number;
|
||||
/// Line number of the previous_line
|
||||
int previous_line_number;
|
||||
/// Input stream we are reading from
|
||||
InputStreamP input;
|
||||
/// Accumulated warning messages
|
||||
String warnings;
|
||||
|
||||
// --------------------------------------------------- : Reading the stream
|
||||
|
||||
/// Is there a block with the given key under the current cursor? if so, enter it
|
||||
bool enterBlock(const Char* name);
|
||||
/// Enter any block, no matter what the key
|
||||
bool enterAnyBlock();
|
||||
/// Leave the block we are in
|
||||
void exitBlock();
|
||||
|
||||
/// Move to the next non empty line
|
||||
void moveNext();
|
||||
/// Reads the next line from the input, and stores it in line/key/value/indent
|
||||
void readLine(bool in_string = false);
|
||||
|
||||
/// Return the value on the current line
|
||||
const String& getValue();
|
||||
|
||||
/// No line was read, because nothing mathes the current key
|
||||
/** Maybe the key is "include file" */
|
||||
template <typename T>
|
||||
void unknownKey(T& v) {
|
||||
if (key == _("include file")) {
|
||||
Reader reader(this, package, value, ignore_invalid);
|
||||
reader.handle_greedy(v);
|
||||
moveNext();
|
||||
} else {
|
||||
unknownKey();
|
||||
}
|
||||
}
|
||||
void unknownKey();
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : Container types
|
||||
@@ -200,7 +200,7 @@ class Reader {
|
||||
*/
|
||||
template <typename T>
|
||||
intrusive_ptr<T> read_new(Reader& reader) {
|
||||
return intrusive(new T());
|
||||
return intrusive(new T());
|
||||
}
|
||||
|
||||
/// Update the 'index' member of a value for use by IndexMap
|
||||
@@ -208,92 +208,92 @@ template <typename T> void update_index(T&, size_t index) {}
|
||||
|
||||
template <typename T>
|
||||
void Reader::handle(const Char* name, vector<T>& vector) {
|
||||
String vectorKey = singular_form(name);
|
||||
while (enterBlock(vectorKey)) {
|
||||
vector.resize(vector.size() + 1);
|
||||
handle_greedy(vector.back());
|
||||
update_index(vector.back(), vector.size() - 1); // update index for IndexMap
|
||||
exitBlock();
|
||||
}
|
||||
String vectorKey = singular_form(name);
|
||||
while (enterBlock(vectorKey)) {
|
||||
vector.resize(vector.size() + 1);
|
||||
handle_greedy(vector.back());
|
||||
update_index(vector.back(), vector.size() - 1); // update index for IndexMap
|
||||
exitBlock();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void Reader::handle(intrusive_ptr<T>& pointer) {
|
||||
if (!pointer) pointer = read_new<T>(*this);
|
||||
handle(*pointer);
|
||||
if (!pointer) pointer = read_new<T>(*this);
|
||||
handle(*pointer);
|
||||
}
|
||||
|
||||
template <typename V>
|
||||
void Reader::handle(map<String, V>& m) {
|
||||
while (enterAnyBlock()) {
|
||||
handle_greedy(m[key]);
|
||||
exitBlock();
|
||||
}
|
||||
while (enterAnyBlock()) {
|
||||
handle_greedy(m[key]);
|
||||
exitBlock();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename K, typename V>
|
||||
void Reader::handle(IndexMap<K,V>& m) {
|
||||
for (typename IndexMap<K,V>::iterator it = m.begin() ; it != m.end() ; ++it) {
|
||||
handle(get_key_name(*it).c_str(), *it);
|
||||
}
|
||||
for (typename IndexMap<K,V>::iterator it = m.begin() ; it != m.end() ; ++it) {
|
||||
handle(get_key_name(*it).c_str(), *it);
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Reflection
|
||||
|
||||
/// Implement reflection as used by Reader
|
||||
#define REFLECT_OBJECT_READER(Cls) \
|
||||
template<> void Reader::handle<Cls>(Cls& object) { \
|
||||
object.reflect(*this); \
|
||||
} \
|
||||
void Cls::reflect(Reader& reader) { \
|
||||
reflect_impl(reader); \
|
||||
}
|
||||
#define REFLECT_OBJECT_READER(Cls) \
|
||||
template<> void Reader::handle<Cls>(Cls& object) { \
|
||||
object.reflect(*this); \
|
||||
} \
|
||||
void Cls::reflect(Reader& reader) { \
|
||||
reflect_impl(reader); \
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Reflection for enumerations
|
||||
|
||||
/// Implement enum reflection as used by Reader
|
||||
#define REFLECT_ENUM_READER(Enum) \
|
||||
template<> void Reader::handle<Enum>(Enum& enum_) { \
|
||||
EnumReader reader(getValue()); \
|
||||
reflect_ ## Enum(enum_, reader); \
|
||||
reader.warnIfNotDone(this); \
|
||||
} \
|
||||
void parse_enum(const String& value, Enum& out) { \
|
||||
EnumReader reader(value); \
|
||||
reflect_ ## Enum(out, reader); \
|
||||
reader.errorIfNotDone(); \
|
||||
}
|
||||
#define REFLECT_ENUM_READER(Enum) \
|
||||
template<> void Reader::handle<Enum>(Enum& enum_) { \
|
||||
EnumReader reader(getValue()); \
|
||||
reflect_ ## Enum(enum_, reader); \
|
||||
reader.warnIfNotDone(this); \
|
||||
} \
|
||||
void parse_enum(const String& value, Enum& out) { \
|
||||
EnumReader reader(value); \
|
||||
reflect_ ## Enum(out, reader); \
|
||||
reader.errorIfNotDone(); \
|
||||
}
|
||||
|
||||
/// 'Tag' to be used when reflecting enumerations for Reader
|
||||
class EnumReader {
|
||||
public:
|
||||
inline EnumReader(String read)
|
||||
: read(read), first(nullptr), done(false) {}
|
||||
|
||||
/// Handle a possible value for the enum, if the name matches the name in the input
|
||||
template <typename Enum>
|
||||
inline void handle(const Char* name, Enum value, Enum& enum_) {
|
||||
if (!done) {
|
||||
if (read == name) {
|
||||
done = true;
|
||||
enum_ = value;
|
||||
} else if (!first) {
|
||||
first = name;
|
||||
enum_ = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline bool isDone() const { return done; }
|
||||
void warnIfNotDone(Reader* errors_to);
|
||||
void errorIfNotDone();
|
||||
|
||||
inline EnumReader(String read)
|
||||
: read(read), first(nullptr), done(false) {}
|
||||
|
||||
/// Handle a possible value for the enum, if the name matches the name in the input
|
||||
template <typename Enum>
|
||||
inline void handle(const Char* name, Enum value, Enum& enum_) {
|
||||
if (!done) {
|
||||
if (read == name) {
|
||||
done = true;
|
||||
enum_ = value;
|
||||
} else if (!first) {
|
||||
first = name;
|
||||
enum_ = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline bool isDone() const { return done; }
|
||||
void warnIfNotDone(Reader* errors_to);
|
||||
void errorIfNotDone();
|
||||
|
||||
private:
|
||||
String read; ///< The string to match to a value name
|
||||
Char const* first; ///< Has the first (default) value been handled? If so, what is its name.
|
||||
bool done; ///< Was anything matched?
|
||||
|
||||
String notDoneErrorMessage() const;
|
||||
String read; ///< The string to match to a value name
|
||||
Char const* first; ///< Has the first (default) value been handled? If so, what is its name.
|
||||
bool done; ///< Was anything matched?
|
||||
|
||||
String notDoneErrorMessage() const;
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : EOF
|
||||
|
||||
+86
-86
@@ -18,133 +18,133 @@ using boost::tribool;
|
||||
// ----------------------------------------------------------------------------- : Writer
|
||||
|
||||
Writer::Writer(const OutputStreamP& output, Version file_app_version)
|
||||
: indentation(0)
|
||||
, output(output), stream(*output)
|
||||
: indentation(0)
|
||||
, output(output), stream(*output)
|
||||
{
|
||||
stream.WriteString(BYTE_ORDER_MARK);
|
||||
handle(_("mse_version"), file_app_version);
|
||||
stream.WriteString(BYTE_ORDER_MARK);
|
||||
handle(_("mse_version"), file_app_version);
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Writer::enterBlock(const Char* name) {
|
||||
// don't write the key yet
|
||||
pending_opened.push_back(name);
|
||||
// don't write the key yet
|
||||
pending_opened.push_back(name);
|
||||
}
|
||||
|
||||
void Writer::exitBlock() {
|
||||
if (pending_opened.empty()) {
|
||||
assert(indentation > 0);
|
||||
indentation -= 1;
|
||||
} else {
|
||||
// this block was apparently empty, ignore it
|
||||
pending_opened.pop_back();
|
||||
}
|
||||
if (pending_opened.empty()) {
|
||||
assert(indentation > 0);
|
||||
indentation -= 1;
|
||||
} else {
|
||||
// this block was apparently empty, ignore it
|
||||
pending_opened.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
void Writer::writePending() {
|
||||
// In enterBlock we have delayed the actual writing of the keys until this point
|
||||
// here we write all the pending keys, and increase indentation along the way.
|
||||
for (size_t i = 0 ; i < pending_opened.size() ; ++i) {
|
||||
if (i > 0) {
|
||||
// before entering a sub-block, write a colon after the parent's name
|
||||
stream.WriteString(_(":\n"));
|
||||
}
|
||||
indentation += 1;
|
||||
writeIndentation();
|
||||
writeUTF8(stream, canonical_name_form(pending_opened[i]));
|
||||
}
|
||||
pending_opened.clear();
|
||||
// In enterBlock we have delayed the actual writing of the keys until this point
|
||||
// here we write all the pending keys, and increase indentation along the way.
|
||||
for (size_t i = 0 ; i < pending_opened.size() ; ++i) {
|
||||
if (i > 0) {
|
||||
// before entering a sub-block, write a colon after the parent's name
|
||||
stream.WriteString(_(":\n"));
|
||||
}
|
||||
indentation += 1;
|
||||
writeIndentation();
|
||||
writeUTF8(stream, canonical_name_form(pending_opened[i]));
|
||||
}
|
||||
pending_opened.clear();
|
||||
}
|
||||
|
||||
void Writer::writeIndentation() {
|
||||
for(int i = 1 ; i < indentation ; ++i) {
|
||||
stream.PutChar(_('\t'));
|
||||
}
|
||||
for(int i = 1 ; i < indentation ; ++i) {
|
||||
stream.PutChar(_('\t'));
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Handling basic types
|
||||
|
||||
void Writer::handle(const String& value) {
|
||||
if (pending_opened.empty()) {
|
||||
throw InternalError(_("Can only write a value in a key that was just opened"));
|
||||
}
|
||||
writePending();
|
||||
// write indentation and key
|
||||
if (value.find_first_of(_('\n')) != String::npos || (!value.empty() && isSpace(value.GetChar(0)))) {
|
||||
// multiline string, or contains leading whitespace
|
||||
stream.WriteString(_(":\n"));
|
||||
indentation += 1;
|
||||
// split lines, and write each line
|
||||
size_t start = 0, end, size = value.size();
|
||||
while (start < size) {
|
||||
end = value.find_first_of(_("\n\r"), start); // until end of line
|
||||
// write the line
|
||||
writeIndentation();
|
||||
writeUTF8(stream, value.substr(start, end - start));
|
||||
// Skip \r and \n
|
||||
if (end == String::npos) break;
|
||||
stream.PutChar(_('\n'));
|
||||
start = end + 1;
|
||||
if (start < size) {
|
||||
Char c1 = value.GetChar(start - 1);
|
||||
Char c2 = value.GetChar(start);
|
||||
// skip second character of \r\n or \n\r
|
||||
if (c1 != c2 && (c2 == _('\r') || c2 == _('\n'))) start += 1;
|
||||
}
|
||||
}
|
||||
indentation -= 1;
|
||||
} else {
|
||||
stream.WriteString(_(": "));
|
||||
writeUTF8(stream, value);
|
||||
}
|
||||
stream.PutChar(_('\n'));
|
||||
if (pending_opened.empty()) {
|
||||
throw InternalError(_("Can only write a value in a key that was just opened"));
|
||||
}
|
||||
writePending();
|
||||
// write indentation and key
|
||||
if (value.find_first_of(_('\n')) != String::npos || (!value.empty() && isSpace(value.GetChar(0)))) {
|
||||
// multiline string, or contains leading whitespace
|
||||
stream.WriteString(_(":\n"));
|
||||
indentation += 1;
|
||||
// split lines, and write each line
|
||||
size_t start = 0, end, size = value.size();
|
||||
while (start < size) {
|
||||
end = value.find_first_of(_("\n\r"), start); // until end of line
|
||||
// write the line
|
||||
writeIndentation();
|
||||
writeUTF8(stream, value.substr(start, end - start));
|
||||
// Skip \r and \n
|
||||
if (end == String::npos) break;
|
||||
stream.PutChar(_('\n'));
|
||||
start = end + 1;
|
||||
if (start < size) {
|
||||
Char c1 = value.GetChar(start - 1);
|
||||
Char c2 = value.GetChar(start);
|
||||
// skip second character of \r\n or \n\r
|
||||
if (c1 != c2 && (c2 == _('\r') || c2 == _('\n'))) start += 1;
|
||||
}
|
||||
}
|
||||
indentation -= 1;
|
||||
} else {
|
||||
stream.WriteString(_(": "));
|
||||
writeUTF8(stream, value);
|
||||
}
|
||||
stream.PutChar(_('\n'));
|
||||
}
|
||||
|
||||
template <> void Writer::handle(const int& value) {
|
||||
handle(String() << value);
|
||||
handle(String() << value);
|
||||
}
|
||||
template <> void Writer::handle(const unsigned int& value) {
|
||||
handle(String() << value);
|
||||
handle(String() << value);
|
||||
}
|
||||
template <> void Writer::handle(const double& value) {
|
||||
handle(String() << value);
|
||||
handle(String() << value);
|
||||
}
|
||||
template <> void Writer::handle(const bool& value) {
|
||||
handle(value ? _("true") : _("false"));
|
||||
handle(value ? _("true") : _("false"));
|
||||
}
|
||||
template <> void Writer::handle(const tribool& value) {
|
||||
if (!indeterminate(value)) {
|
||||
handle(value ? _("true") : _("false"));
|
||||
}
|
||||
if (!indeterminate(value)) {
|
||||
handle(value ? _("true") : _("false"));
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Handling less basic util types
|
||||
|
||||
template <> void Writer::handle(const wxDateTime& date) {
|
||||
if (date.IsValid()) {
|
||||
handle(date.Format(_("%Y-%m-%d %H:%M:%S")));
|
||||
}
|
||||
if (date.IsValid()) {
|
||||
handle(date.Format(_("%Y-%m-%d %H:%M:%S")));
|
||||
}
|
||||
}
|
||||
template <> void Writer::handle(const Vector2D& vec) {
|
||||
handle(String::Format(_("(%.10lf,%.10lf)"), vec.x, vec.y));
|
||||
handle(String::Format(_("(%.10lf,%.10lf)"), vec.x, vec.y));
|
||||
}
|
||||
template <> void Writer::handle(const Color& col) {
|
||||
handle(String::Format(_("rgb(%u,%u,%u)"), col.Red(), col.Green(), col.Blue()));
|
||||
handle(String::Format(_("rgb(%u,%u,%u)"), col.Red(), col.Green(), col.Blue()));
|
||||
}
|
||||
|
||||
template <> void Writer::handle(const FileName& value) {
|
||||
if (clipboard_package() && !value.empty()) {
|
||||
// use absolute names on clipboard
|
||||
try {
|
||||
handle(clipboard_package()->absoluteName(value));
|
||||
} catch (const Error&) {
|
||||
// ignore errors
|
||||
}
|
||||
} else {
|
||||
handle(static_cast<const String&>(value));
|
||||
if (writing_package()) {
|
||||
writing_package()->referenceFile(value);
|
||||
}
|
||||
}
|
||||
if (clipboard_package() && !value.empty()) {
|
||||
// use absolute names on clipboard
|
||||
try {
|
||||
handle(clipboard_package()->absoluteName(value));
|
||||
} catch (const Error&) {
|
||||
// ignore errors
|
||||
}
|
||||
} else {
|
||||
handle(static_cast<const String&>(value));
|
||||
if (writing_package()) {
|
||||
writing_package()->referenceFile(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+105
-105
@@ -25,145 +25,145 @@ typedef shared_ptr<wxOutputStream> OutputStreamP;
|
||||
/// The Writer can be used for writing (serializing) objects
|
||||
class Writer {
|
||||
public:
|
||||
/// Construct a writer that writes to the given output stream
|
||||
Writer(const OutputStreamP& output, Version file_app_version);
|
||||
|
||||
/// Tell the reflection code we are not reading
|
||||
inline bool reading() const { return false; }
|
||||
inline bool scripting() const { return false; }
|
||||
inline bool isComplex() const { return true; }
|
||||
inline void addAlias(int, const Char*, const Char*) {}
|
||||
inline void handleIgnore(int, const Char*) {}
|
||||
|
||||
// --------------------------------------------------- : Handling objects
|
||||
/// Handle an object: write it under the given name
|
||||
template <typename T>
|
||||
void handle(const Char* name, const T& object) {
|
||||
enterBlock(name);
|
||||
handle(object);
|
||||
exitBlock();
|
||||
}
|
||||
/// Handle a value
|
||||
template <typename T>
|
||||
inline void handleNoScript(const Char* name, T& value) { handle(name,value); }
|
||||
|
||||
/// Write a vector to the output stream
|
||||
template <typename T>
|
||||
void handle(const Char* name, const vector<T>& vector);
|
||||
|
||||
/// Write a string to the output stream
|
||||
void handle(const String& str);
|
||||
void handle(const Char* str) { handle(String(str)); }
|
||||
|
||||
/// Write an object of type T to the output stream
|
||||
template <typename T> void handle(const T&);
|
||||
/// Write a intrusive_ptr to the output stream
|
||||
template <typename T> void handle(const intrusive_ptr<T>&);
|
||||
/// Write a map to the output stream
|
||||
template <typename K, typename V> void handle(const map<K,V>&);
|
||||
/// Write an IndexMap to the output stream
|
||||
template <typename K, typename V> void handle(const IndexMap<K,V>&);
|
||||
template <typename K, typename V> void handle(const DelayedIndexMaps<K,V>&);
|
||||
template <typename K, typename V> void handle(const DelayedIndexMapsData<K,V>&);
|
||||
/// Write an object of type Defaultable<T> to the output stream
|
||||
template <typename T> void handle(const Defaultable<T>&);
|
||||
/// Write an object of type Scriptable<T> to the output stream
|
||||
template <typename T> void handle(const Scriptable<T>&);
|
||||
// special behaviour
|
||||
void handle(const GameP&);
|
||||
void handle(const StyleSheetP&);
|
||||
|
||||
/// Construct a writer that writes to the given output stream
|
||||
Writer(const OutputStreamP& output, Version file_app_version);
|
||||
|
||||
/// Tell the reflection code we are not reading
|
||||
inline bool reading() const { return false; }
|
||||
inline bool scripting() const { return false; }
|
||||
inline bool isComplex() const { return true; }
|
||||
inline void addAlias(int, const Char*, const Char*) {}
|
||||
inline void handleIgnore(int, const Char*) {}
|
||||
|
||||
// --------------------------------------------------- : Handling objects
|
||||
/// Handle an object: write it under the given name
|
||||
template <typename T>
|
||||
void handle(const Char* name, const T& object) {
|
||||
enterBlock(name);
|
||||
handle(object);
|
||||
exitBlock();
|
||||
}
|
||||
/// Handle a value
|
||||
template <typename T>
|
||||
inline void handleNoScript(const Char* name, T& value) { handle(name,value); }
|
||||
|
||||
/// Write a vector to the output stream
|
||||
template <typename T>
|
||||
void handle(const Char* name, const vector<T>& vector);
|
||||
|
||||
/// Write a string to the output stream
|
||||
void handle(const String& str);
|
||||
void handle(const Char* str) { handle(String(str)); }
|
||||
|
||||
/// Write an object of type T to the output stream
|
||||
template <typename T> void handle(const T&);
|
||||
/// Write a intrusive_ptr to the output stream
|
||||
template <typename T> void handle(const intrusive_ptr<T>&);
|
||||
/// Write a map to the output stream
|
||||
template <typename K, typename V> void handle(const map<K,V>&);
|
||||
/// Write an IndexMap to the output stream
|
||||
template <typename K, typename V> void handle(const IndexMap<K,V>&);
|
||||
template <typename K, typename V> void handle(const DelayedIndexMaps<K,V>&);
|
||||
template <typename K, typename V> void handle(const DelayedIndexMapsData<K,V>&);
|
||||
/// Write an object of type Defaultable<T> to the output stream
|
||||
template <typename T> void handle(const Defaultable<T>&);
|
||||
/// Write an object of type Scriptable<T> to the output stream
|
||||
template <typename T> void handle(const Scriptable<T>&);
|
||||
// special behaviour
|
||||
void handle(const GameP&);
|
||||
void handle(const StyleSheetP&);
|
||||
|
||||
private:
|
||||
// --------------------------------------------------- : Data
|
||||
/// Indentation of the current block
|
||||
int indentation;
|
||||
/// Blocks opened to which nothing has been written
|
||||
vector<const Char*> pending_opened;
|
||||
|
||||
/// Output stream we are writing to
|
||||
OutputStreamP output;
|
||||
/// Text stream wrapping the output stream
|
||||
wxTextOutputStream stream;
|
||||
|
||||
// --------------------------------------------------- : Writing to the stream
|
||||
|
||||
/// Start a new block with the given name
|
||||
void enterBlock(const Char* name);
|
||||
/// Leave the block we are in
|
||||
void exitBlock();
|
||||
|
||||
/// Write the pending_opened with the required indentation
|
||||
void writePending();
|
||||
/// Output some taps to represent the indentation level
|
||||
void writeIndentation();
|
||||
// --------------------------------------------------- : Data
|
||||
/// Indentation of the current block
|
||||
int indentation;
|
||||
/// Blocks opened to which nothing has been written
|
||||
vector<const Char*> pending_opened;
|
||||
|
||||
/// Output stream we are writing to
|
||||
OutputStreamP output;
|
||||
/// Text stream wrapping the output stream
|
||||
wxTextOutputStream stream;
|
||||
|
||||
// --------------------------------------------------- : Writing to the stream
|
||||
|
||||
/// Start a new block with the given name
|
||||
void enterBlock(const Char* name);
|
||||
/// Leave the block we are in
|
||||
void exitBlock();
|
||||
|
||||
/// Write the pending_opened with the required indentation
|
||||
void writePending();
|
||||
/// Output some taps to represent the indentation level
|
||||
void writeIndentation();
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : Container types
|
||||
|
||||
template <typename T>
|
||||
void Writer::handle(const Char* name, const vector<T>& vec) {
|
||||
String vectorKey = singular_form(name);
|
||||
for (typename vector<T>::const_iterator it = vec.begin() ; it != vec.end() ; ++it) {
|
||||
handle(vectorKey, *it);
|
||||
}
|
||||
String vectorKey = singular_form(name);
|
||||
for (typename vector<T>::const_iterator it = vec.begin() ; it != vec.end() ; ++it) {
|
||||
handle(vectorKey, *it);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void Writer::handle(const intrusive_ptr<T>& pointer) {
|
||||
if (pointer) handle(*pointer);
|
||||
if (pointer) handle(*pointer);
|
||||
}
|
||||
|
||||
template <typename K, typename V>
|
||||
void Writer::handle(const map<K,V>& m) {
|
||||
for (typename map<K,V>::const_iterator it = m.begin() ; it != m.end() ; ++it) {
|
||||
handle(it->first.c_str(), it->second);
|
||||
}
|
||||
for (typename map<K,V>::const_iterator it = m.begin() ; it != m.end() ; ++it) {
|
||||
handle(it->first.c_str(), it->second);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename K, typename V>
|
||||
void Writer::handle(const IndexMap<K,V>& m) {
|
||||
for (typename IndexMap<K,V>::const_iterator it = m.begin() ; it != m.end() ; ++it) {
|
||||
handle(get_key_name(*it).c_str(), *it);
|
||||
}
|
||||
for (typename IndexMap<K,V>::const_iterator it = m.begin() ; it != m.end() ; ++it) {
|
||||
handle(get_key_name(*it).c_str(), *it);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------- : Reflection
|
||||
|
||||
/// Implement reflection as used by Writer
|
||||
#define REFLECT_OBJECT_WRITER(Cls) \
|
||||
template<> void Writer::handle<Cls>(const Cls& object) { \
|
||||
const_cast<Cls&>(object).reflect(*this); \
|
||||
} \
|
||||
void Cls::reflect(Writer& writer) { \
|
||||
reflect_impl(writer); \
|
||||
}
|
||||
#define REFLECT_OBJECT_WRITER(Cls) \
|
||||
template<> void Writer::handle<Cls>(const Cls& object) { \
|
||||
const_cast<Cls&>(object).reflect(*this); \
|
||||
} \
|
||||
void Cls::reflect(Writer& writer) { \
|
||||
reflect_impl(writer); \
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Reflection for enumerations
|
||||
|
||||
/// Implement enum reflection as used by Writer
|
||||
#define REFLECT_ENUM_WRITER(Enum) \
|
||||
template<> void Writer::handle<Enum>(const Enum& enum_) { \
|
||||
EnumWriter writer(*this); \
|
||||
reflect_ ## Enum(const_cast<Enum&>(enum_), writer); \
|
||||
}
|
||||
#define REFLECT_ENUM_WRITER(Enum) \
|
||||
template<> void Writer::handle<Enum>(const Enum& enum_) { \
|
||||
EnumWriter writer(*this); \
|
||||
reflect_ ## Enum(const_cast<Enum&>(enum_), writer); \
|
||||
}
|
||||
|
||||
/// 'Tag' to be used when reflecting enumerations for Writer
|
||||
class EnumWriter {
|
||||
public:
|
||||
inline EnumWriter(Writer& writer)
|
||||
: writer(writer) {}
|
||||
|
||||
/// Handle a possible value for the enum, if the name matches the name in the input
|
||||
template <typename Enum>
|
||||
inline void handle(const Char* name, Enum value, Enum enum_) {
|
||||
if (enum_ == value) {
|
||||
writer.handle(name);
|
||||
}
|
||||
}
|
||||
|
||||
inline EnumWriter(Writer& writer)
|
||||
: writer(writer) {}
|
||||
|
||||
/// Handle a possible value for the enum, if the name matches the name in the input
|
||||
template <typename Enum>
|
||||
inline void handle(const Char* name, Enum value, Enum enum_) {
|
||||
if (enum_ == value) {
|
||||
writer.handle(name);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
Writer& writer; ///< The writer to write output to
|
||||
Writer& writer; ///< The writer to write output to
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : EOF
|
||||
|
||||
Reference in New Issue
Block a user