2020-06-03 23:43:08 +02:00
|
|
|
namespace OS {
|
|
|
|
// import the CodePad application module
|
|
|
|
const App = OS.application.CodePad;
|
|
|
|
|
|
|
|
declare var CoffeeScript: any;
|
|
|
|
declare var JSZip: any;
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
*
|
|
|
|
* @class ExtensionMaker
|
|
|
|
* @extends {App.BaseExtension}
|
|
|
|
*/
|
|
|
|
class ExtensionMaker extends App.BaseExtension {
|
|
|
|
constructor(app: application.CodePad) {
|
|
|
|
super(app);
|
|
|
|
}
|
|
|
|
|
|
|
|
// public functions
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
*
|
|
|
|
* @memberof ExtensionMaker
|
|
|
|
*/
|
|
|
|
create(): void {
|
2020-12-16 00:57:29 +01:00
|
|
|
this.logger().clear();
|
2020-06-03 23:43:08 +02:00
|
|
|
this.app
|
|
|
|
.openDialog("FileDialog", {
|
|
|
|
title: "__(New CodePad extension at)",
|
|
|
|
file: { basename: __("ExtensionName") },
|
|
|
|
mimes: ["dir"],
|
|
|
|
})
|
|
|
|
.then((d) => {
|
|
|
|
return this.mktpl(d.file.path, d.name);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
*
|
|
|
|
* @memberof ExtensionMaker
|
|
|
|
*/
|
|
|
|
buildnrun(): void {
|
2020-12-16 00:57:29 +01:00
|
|
|
this.logger().clear();
|
2020-06-03 23:43:08 +02:00
|
|
|
this.metadata("extension.json")
|
|
|
|
.then(async (meta) => {
|
|
|
|
try {
|
|
|
|
await this.build(meta);
|
|
|
|
try {
|
|
|
|
return this.run(meta);
|
|
|
|
} catch (e) {
|
2020-12-16 00:57:29 +01:00
|
|
|
return this.logger().error(__("Unable to run extension: {0}", e.stack));
|
2020-06-03 23:43:08 +02:00
|
|
|
}
|
|
|
|
} catch (e_1) {
|
2020-12-16 00:57:29 +01:00
|
|
|
return this.logger().error(__("Unable to build extension: {0}", e_1.stack));
|
2020-06-03 23:43:08 +02:00
|
|
|
}
|
|
|
|
})
|
2020-12-16 00:57:29 +01:00
|
|
|
.catch((e) => this.logger().error(__("Unable to read meta-data:{0}", e.stack)));
|
2020-06-03 23:43:08 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
*
|
|
|
|
* @memberof ExtensionMaker
|
|
|
|
*/
|
|
|
|
release(): void {
|
2020-12-16 00:57:29 +01:00
|
|
|
this.logger().clear();
|
2020-06-03 23:43:08 +02:00
|
|
|
this.metadata("extension.json")
|
|
|
|
.then(async (meta) => {
|
|
|
|
try {
|
|
|
|
await this.build(meta);
|
|
|
|
try {
|
|
|
|
return this.mkar(
|
|
|
|
`${meta.root}/build/debug`,
|
|
|
|
`${meta.root}/build/release/${meta.meta.name}.zip`
|
|
|
|
);
|
|
|
|
} catch (e) {
|
2020-12-16 00:57:29 +01:00
|
|
|
return this.logger().error(
|
|
|
|
__("Unable to create archive: {0}",
|
|
|
|
e.stack
|
|
|
|
));
|
2020-06-03 23:43:08 +02:00
|
|
|
}
|
|
|
|
} catch (e_1) {
|
2020-12-16 00:57:29 +01:00
|
|
|
return this.logger().error(__("Unable to build extension: {0}", e_1.stack));
|
2020-06-03 23:43:08 +02:00
|
|
|
}
|
|
|
|
})
|
2020-12-16 00:57:29 +01:00
|
|
|
.catch((e) => this.logger().error(__("Unable to read meta-data: {0}", e.stack)));
|
2020-06-03 23:43:08 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
*
|
|
|
|
* @memberof ExtensionMaker
|
|
|
|
*/
|
|
|
|
install(): void {
|
2020-12-16 00:57:29 +01:00
|
|
|
this.logger().clear();
|
2020-06-03 23:43:08 +02:00
|
|
|
this.app
|
|
|
|
.openDialog("FileDialog", {
|
|
|
|
title: "__(Select extension archive)",
|
|
|
|
mimes: [".*/zip"],
|
|
|
|
})
|
|
|
|
.then(async (d) => {
|
|
|
|
try {
|
|
|
|
await this.installZip(d.file.path);
|
2020-12-16 00:57:29 +01:00
|
|
|
this.logger().info(__("Extension installed"));
|
2020-06-03 23:43:08 +02:00
|
|
|
return this.app.loadExtensionMetaData();
|
|
|
|
} catch (e) {
|
2020-12-16 00:57:29 +01:00
|
|
|
return this.logger().error(__("Unable to install extension: {0}", e.stack));
|
2020-06-03 23:43:08 +02:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
*
|
|
|
|
* @private
|
|
|
|
* @param {string} path
|
|
|
|
* @param {string} name
|
|
|
|
* @memberof ExtensionMaker
|
|
|
|
*/
|
|
|
|
private mktpl(path: string, name: string): void {
|
|
|
|
const rpath = `${path}/${name}`;
|
|
|
|
const dirs = [
|
|
|
|
rpath,
|
|
|
|
`${rpath}/build`,
|
|
|
|
`${rpath}/build/release`,
|
|
|
|
`${rpath}/build/debug`,
|
|
|
|
];
|
|
|
|
const files = [
|
|
|
|
["templates/ext-main.tpl", `${rpath}/${name}.coffee`],
|
|
|
|
["templates/ext-extension.tpl", `${rpath}/extension.json`],
|
|
|
|
];
|
|
|
|
this.mkdirAll(dirs)
|
|
|
|
.then(async () => {
|
|
|
|
try {
|
|
|
|
await this.mkfileAll(files, path, name);
|
|
|
|
this.app.currdir = rpath.asFileHandle();
|
2020-06-22 11:28:44 +02:00
|
|
|
this.app.toggleSideBar();
|
2020-12-20 16:45:51 +01:00
|
|
|
return this.app.eum.active.openFile(
|
2020-06-03 23:43:08 +02:00
|
|
|
`${rpath}/${name}.coffee`.asFileHandle() as application.CodePadFileHandle
|
|
|
|
);
|
|
|
|
} catch (e) {
|
2020-12-16 00:57:29 +01:00
|
|
|
return this.logger().error(
|
|
|
|
__("Unable to create extension template: {0}",
|
|
|
|
e.stack)
|
2020-06-03 23:43:08 +02:00
|
|
|
);
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.catch((e) =>
|
2020-12-16 00:57:29 +01:00
|
|
|
this.logger().error(__("Unable to create extension directories: {0}", e.stack))
|
2020-06-03 23:43:08 +02:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
*
|
|
|
|
* @private
|
|
|
|
* @param {string[]} list
|
2020-12-20 16:45:51 +01:00
|
|
|
* @returns {Promise<void>}
|
2020-06-03 23:43:08 +02:00
|
|
|
* @memberof ExtensionMaker
|
|
|
|
*/
|
2020-12-20 16:45:51 +01:00
|
|
|
private verify(list: string[]): Promise<void> {
|
2020-06-03 23:43:08 +02:00
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
if (list.length === 0) {
|
|
|
|
return resolve();
|
|
|
|
}
|
|
|
|
const file = list.splice(0, 1)[0].asFileHandle();
|
2020-12-16 00:57:29 +01:00
|
|
|
this.logger().info(__("Verifying: {0}", file.path));
|
2020-06-03 23:43:08 +02:00
|
|
|
return file
|
|
|
|
.read()
|
|
|
|
.then((data) => {
|
|
|
|
try {
|
|
|
|
CoffeeScript.nodes(data);
|
|
|
|
return this.verify(list)
|
|
|
|
.then(() => resolve())
|
|
|
|
.catch((e) => reject(__e(e)));
|
|
|
|
} catch (ex) {
|
|
|
|
return reject(__e(ex));
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.catch((e) => reject(__e(e)));
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
*
|
|
|
|
* @private
|
|
|
|
* @param {GenericObject<any>} meta
|
|
|
|
* @returns {Promise<any>}
|
|
|
|
* @memberof ExtensionMaker
|
|
|
|
*/
|
|
|
|
private compile(meta: GenericObject<any>): Promise<any> {
|
|
|
|
return new Promise(async (resolve, reject) => {
|
|
|
|
try {
|
|
|
|
await this.import([`${this.basedir()}/libs/coffeescript.js`]);
|
|
|
|
const list = meta.coffees.map(
|
|
|
|
(v) => `${meta.root}/${v}`
|
|
|
|
);
|
|
|
|
try {
|
2020-12-16 00:57:29 +01:00
|
|
|
await this.verify(list.map((x: string) => x));
|
2020-06-03 23:43:08 +02:00
|
|
|
try {
|
|
|
|
const code = await this.cat(list, "");
|
|
|
|
const jsrc = CoffeeScript.compile(code);
|
2020-12-16 00:57:29 +01:00
|
|
|
this.logger().info(__("Compiled successful"));
|
2020-06-03 23:43:08 +02:00
|
|
|
return resolve(jsrc);
|
|
|
|
} catch (e) {
|
|
|
|
return reject(__e(e));
|
|
|
|
}
|
|
|
|
} catch (e_1) {
|
|
|
|
return reject(__e(e_1));
|
|
|
|
}
|
|
|
|
} catch (e_2) {
|
|
|
|
return reject(__e(e_2));
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
*
|
|
|
|
* @private
|
|
|
|
* @param {GenericObject<any>} meta
|
2020-12-20 16:45:51 +01:00
|
|
|
* @returns {Promise<void>}
|
2020-06-03 23:43:08 +02:00
|
|
|
* @memberof ExtensionMaker
|
|
|
|
*/
|
2020-12-20 16:45:51 +01:00
|
|
|
private build(meta: GenericObject<any>): Promise<void> {
|
2020-06-03 23:43:08 +02:00
|
|
|
return new Promise(async (resolve, reject) => {
|
|
|
|
try {
|
|
|
|
const src = await this.compile(meta);
|
|
|
|
let v: string;
|
|
|
|
try {
|
|
|
|
const jsrc = await this.cat(
|
|
|
|
(() => {
|
|
|
|
const result = [];
|
|
|
|
for (v of meta.javascripts) {
|
|
|
|
result.push(`${meta.root}/${v}`);
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
})(),
|
|
|
|
src
|
|
|
|
);
|
2020-12-20 16:45:51 +01:00
|
|
|
await new Promise<void>((r, e) =>
|
2020-06-03 23:43:08 +02:00
|
|
|
`${meta.root}/build/debug/${meta.meta.name}.js`
|
|
|
|
.asFileHandle()
|
|
|
|
.setCache(jsrc)
|
|
|
|
.write("text/plain")
|
|
|
|
.then((d) => r())
|
|
|
|
.catch((ex) => e(__e(ex)))
|
|
|
|
);
|
|
|
|
await new Promise((r, e) =>
|
|
|
|
`${meta.root}/build/debug/extension.json`
|
|
|
|
.asFileHandle()
|
|
|
|
.setCache(meta.meta)
|
|
|
|
.write("object")
|
|
|
|
.then((data) => r(data))
|
|
|
|
.catch((ex_1) => e(__e(ex_1)))
|
|
|
|
);
|
|
|
|
await this.copy(
|
|
|
|
(() => {
|
|
|
|
const result1 = [];
|
|
|
|
for (v of meta.copies) {
|
|
|
|
result1.push(`${meta.root}/${v}`);
|
|
|
|
}
|
|
|
|
return result1;
|
|
|
|
})(),
|
|
|
|
`${meta.root}/build/debug`
|
|
|
|
);
|
|
|
|
return resolve();
|
|
|
|
} catch (e) {
|
|
|
|
return reject(__e(e));
|
|
|
|
}
|
|
|
|
} catch (e_1) {
|
|
|
|
return reject(__e(e_1));
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
*
|
|
|
|
* @private
|
|
|
|
* @param {GenericObject<any>} meta
|
2020-12-20 16:45:51 +01:00
|
|
|
* @returns {Promise<void>}
|
2020-06-03 23:43:08 +02:00
|
|
|
* @memberof ExtensionMaker
|
|
|
|
*/
|
2020-12-20 16:45:51 +01:00
|
|
|
private run(meta: GenericObject<any>): Promise<void> {
|
2020-06-03 23:43:08 +02:00
|
|
|
return new Promise(async (resolve, reject) => {
|
|
|
|
const path = `${meta.root}/build/debug/${meta.meta.name}.js`;
|
2020-06-04 17:49:48 +02:00
|
|
|
if (API.shared[path]) {
|
|
|
|
delete API.shared[path];
|
2020-06-03 23:43:08 +02:00
|
|
|
}
|
|
|
|
try {
|
2020-06-04 17:49:48 +02:00
|
|
|
await API.requires(path);
|
2020-06-03 23:43:08 +02:00
|
|
|
let v: GenericObject<any>;
|
|
|
|
if (this.app.extensions[meta.meta.name]) {
|
2020-12-20 19:38:34 +01:00
|
|
|
this.app.extensions[meta.meta.name].text = meta.meta.text;
|
|
|
|
this.app.extensions[meta.meta.name].nodes = [];
|
|
|
|
this.app.extensions[meta.meta.name].ext = new App.extensions[meta.meta.name](this.app);
|
2020-06-03 23:43:08 +02:00
|
|
|
for (v of meta.meta.actions) {
|
|
|
|
this.app.extensions[meta.meta.name].addAction(v);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
this.app.extensions[meta.meta.name] = new App.CMDMenu(
|
|
|
|
meta.meta.text
|
|
|
|
);
|
|
|
|
this.app.extensions[meta.meta.name].name =
|
|
|
|
meta.meta.name;
|
|
|
|
for (v of meta.meta.actions) {
|
|
|
|
this.app.extensions[meta.meta.name].addAction(v);
|
|
|
|
}
|
|
|
|
this.app.spotlight.addAction(
|
|
|
|
this.app.extensions[meta.meta.name]
|
|
|
|
);
|
|
|
|
this.app.extensions[meta.meta.name].onchildselect(
|
2020-06-04 17:49:48 +02:00
|
|
|
(e: GUI.TagEventType<GUI.tag.ListItemEventData>) => {
|
2020-06-03 23:43:08 +02:00
|
|
|
return this.app.loadAndRunExtensionAction(
|
2020-06-04 17:49:48 +02:00
|
|
|
e.data.item.data as any
|
2020-06-03 23:43:08 +02:00
|
|
|
);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
this.app.spotlight.run(this.app);
|
|
|
|
return resolve();
|
|
|
|
} catch (e) {
|
|
|
|
return reject(__e(e));
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
*
|
|
|
|
* @private
|
|
|
|
* @param {string[]} files
|
|
|
|
* @param {*} zip
|
2020-12-20 16:45:51 +01:00
|
|
|
* @returns {Promise<void>}
|
2020-06-03 23:43:08 +02:00
|
|
|
* @memberof ExtensionMaker
|
|
|
|
*/
|
2020-12-20 16:45:51 +01:00
|
|
|
private installExtension(files: string[], zip: any): Promise<void> {
|
2020-06-03 23:43:08 +02:00
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
const idx = files.indexOf("extension.json");
|
|
|
|
if (idx < 0) {
|
2020-06-04 17:49:48 +02:00
|
|
|
reject(API.throwe(__("No meta-data found")));
|
2020-06-03 23:43:08 +02:00
|
|
|
}
|
|
|
|
const metafile = files.splice(idx, 1)[0];
|
|
|
|
// read the meta file
|
|
|
|
return zip
|
|
|
|
.file(metafile)
|
|
|
|
.async("uint8array")
|
|
|
|
.then((d: Uint8Array) => {
|
|
|
|
const meta = JSON.parse(
|
|
|
|
new TextDecoder("utf-8").decode(d)
|
|
|
|
);
|
|
|
|
return this.installFiles(files, zip, meta)
|
|
|
|
.then(() => resolve())
|
|
|
|
.catch((e) => reject(__e(e)));
|
|
|
|
})
|
|
|
|
.catch((e: Error) => reject(__e(e)));
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
*
|
|
|
|
* @private
|
|
|
|
* @param {string[]} files
|
|
|
|
* @param {*} zip
|
|
|
|
* @param {GenericObject<any>} meta
|
|
|
|
* @returns {Promise<any>}
|
|
|
|
* @memberof ExtensionMaker
|
|
|
|
*/
|
|
|
|
private installFiles(
|
|
|
|
files: string[],
|
|
|
|
zip: any,
|
2020-12-20 16:45:51 +01:00
|
|
|
meta: GenericObject<void>
|
|
|
|
): Promise<void> {
|
2020-06-03 23:43:08 +02:00
|
|
|
if (files.length === 0) {
|
|
|
|
return this.installMeta(meta);
|
|
|
|
}
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
const file = files.splice(0, 1)[0];
|
|
|
|
const path = `${this.basedir()}/${file}`;
|
|
|
|
return zip
|
|
|
|
.file(file)
|
|
|
|
.async("uint8array")
|
|
|
|
.then((d: Uint8Array) => {
|
|
|
|
return path
|
|
|
|
.asFileHandle()
|
|
|
|
.setCache(new Blob([d], { type: "octet/stream" }))
|
|
|
|
.write("text/plain")
|
|
|
|
.then((r) => {
|
|
|
|
if (r.error) {
|
|
|
|
return reject(r.error);
|
|
|
|
}
|
|
|
|
return this.installFiles(files, zip, meta)
|
|
|
|
.then(() => resolve())
|
|
|
|
.catch((e) => reject(__e(e)));
|
|
|
|
})
|
|
|
|
.catch((e) => reject(__e(e)));
|
|
|
|
})
|
|
|
|
.catch((e: Error) => reject(__e(e)));
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
*
|
|
|
|
* @private
|
|
|
|
* @param {GenericObject<any>} meta
|
2020-12-20 16:45:51 +01:00
|
|
|
* @returns {Promise<void>}
|
2020-06-03 23:43:08 +02:00
|
|
|
* @memberof ExtensionMaker
|
|
|
|
*/
|
2020-12-20 16:45:51 +01:00
|
|
|
private installMeta(meta: GenericObject<any>): Promise<void> {
|
2020-06-03 23:43:08 +02:00
|
|
|
return new Promise(async (resolve, reject) => {
|
2020-12-16 00:57:29 +01:00
|
|
|
const file = `${this.app.meta().path
|
|
|
|
}/extensions.json`.asFileHandle();
|
2020-06-03 23:43:08 +02:00
|
|
|
try {
|
|
|
|
const data = await file.read("json");
|
|
|
|
const names = [];
|
|
|
|
for (let v of data) {
|
|
|
|
names.push(v.name);
|
|
|
|
}
|
|
|
|
const idx = names.indexOf(meta.name);
|
|
|
|
if (idx >= 0) {
|
|
|
|
data.splice(idx, 1);
|
|
|
|
}
|
|
|
|
data.push(meta);
|
|
|
|
try {
|
|
|
|
await file.setCache(data).write("object");
|
|
|
|
return resolve();
|
|
|
|
} catch (e) {
|
|
|
|
return reject(__e(e));
|
|
|
|
}
|
|
|
|
} catch (e_1) {
|
|
|
|
return reject(__e(e_1));
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
*
|
|
|
|
* @private
|
|
|
|
* @param {string} path
|
|
|
|
* @returns {Promise<any>}
|
|
|
|
* @memberof ExtensionMaker
|
|
|
|
*/
|
2020-12-20 16:45:51 +01:00
|
|
|
private installZip(path: string): Promise<void> {
|
2020-06-03 23:43:08 +02:00
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
this.import(["os://scripts/jszip.min.js"])
|
|
|
|
.then(() => {
|
|
|
|
path.asFileHandle()
|
|
|
|
.read("binary")
|
|
|
|
.then((data) => {
|
|
|
|
JSZip.loadAsync(data)
|
|
|
|
.then((zip: any) => {
|
|
|
|
const pth = this.basedir();
|
|
|
|
const dir = [];
|
|
|
|
const files = [];
|
|
|
|
for (let name in zip.files) {
|
|
|
|
const file = zip.files[name];
|
|
|
|
if (file.dir) {
|
|
|
|
dir.push(pth + "/" + name);
|
|
|
|
} else {
|
|
|
|
files.push(name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (dir.length > 0) {
|
|
|
|
this.mkdirAll(dir)
|
|
|
|
.then(() => {
|
|
|
|
this.installExtension(
|
|
|
|
files,
|
|
|
|
zip
|
|
|
|
)
|
|
|
|
.then(() => resolve())
|
|
|
|
.catch((e) =>
|
|
|
|
reject(__e(e))
|
|
|
|
);
|
|
|
|
})
|
|
|
|
.catch((e) => reject(__e(e)));
|
|
|
|
} else {
|
|
|
|
this.installExtension(files, zip)
|
|
|
|
.then(() => resolve())
|
|
|
|
.catch((e) => reject(__e(e)));
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.catch((e: Error) => reject(__e(e)));
|
|
|
|
})
|
|
|
|
.catch((e) => reject(__e(e)));
|
|
|
|
})
|
|
|
|
.catch((e) => reject(__e(e)));
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
App.extensions.ExtensionMaker = ExtensionMaker;
|
|
|
|
}
|