diff options
author | Philip Chimento <philip@endlessm.com> | 2013-07-10 07:59:58 -0700 |
---|---|---|
committer | Philip Chimento <philip@endlessm.com> | 2013-12-09 15:38:08 -0800 |
commit | 26ed31c165a522e9681e36c68e1d56721d6b3d0f (patch) | |
tree | 6acdee196adb22388f584d228222fbc8a0ad0341 | |
parent | bdaf5797440bd384477a125252c7b52c42afc044 (diff) |
Subcommand 'eos-application-manifest init'
This implements the 'init' subcommand of the application manifest tool,
which creates a new manifest in the current directory.
[endlessm/eos-sdk#154]
[endlessm/eos-sdk#154]
-rw-r--r-- | test/Makefile.am | 1 | ||||
-rw-r--r-- | test/tools/eos-application-manifest/testInit.js | 293 | ||||
-rw-r--r-- | tools/Makefile.am.inc | 3 | ||||
-rw-r--r-- | tools/eos-application-manifest/commands/init.js | 197 |
4 files changed, 494 insertions, 0 deletions
diff --git a/test/Makefile.am b/test/Makefile.am index 81cae8c..e5fef17 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -33,6 +33,7 @@ test_flexy_grid_LDADD = $(TEST_LIBS) javascript_tests = \ test/tools/eos-run-test/sanitycheck.js \ + test/tools/eos-application-manifest/testInit.js \ test/webhelper/testTranslate.js \ test/webhelper/testWebActions.js \ test/wikipedia/models/testCategoryModel.js \ diff --git a/test/tools/eos-application-manifest/testInit.js b/test/tools/eos-application-manifest/testInit.js new file mode 100644 index 0000000..5f6fe95 --- /dev/null +++ b/test/tools/eos-application-manifest/testInit.js @@ -0,0 +1,293 @@ +imports.searchPath.unshift('tools/eos-application-manifest'); + +const Module = imports.commands.init; + +function testParseOneArgumentWithoutValue() { + let input = ['--parameter']; + let expected = { parameter: null }; + let actual = Module.parseRemainingArgs(input); + assertEquals(JSON.stringify(expected), JSON.stringify(actual)); +} + +function testParseOneArgumentWithValue() { + let input = ['--parameter=value']; + let expected = { parameter: "value" }; + let actual = Module.parseRemainingArgs(input); + assertEquals(JSON.stringify(expected), JSON.stringify(actual)); +} + +function testParseOneArgumentFollowedByValue() { + let input = ['--parameter', 'value']; + let expected = { parameter: "value" }; + let actual = Module.parseRemainingArgs(input); + assertEquals(JSON.stringify(expected), JSON.stringify(actual)); +} + +function testParseTwoArgumentsWithoutValue() { + let input = ['--parameter-one', '--parameter-two']; + let expected = { 'parameter-one': null, 'parameter-two': null }; + let actual = Module.parseRemainingArgs(input); + assertEquals(JSON.stringify(expected), JSON.stringify(actual)); +} + +function testParseArgumentWithValueFollowedByArgument() { + let input = ['--parameter-one=value', '--parameter-two']; + let expected = { 'parameter-one': 'value', 'parameter-two': null }; + let actual = Module.parseRemainingArgs(input); + assertEquals(JSON.stringify(expected), JSON.stringify(actual)); +} + +function testParseArgumentFollowedByValueFollowedByArgument() { + let input = ['--parameter-one', 'value', '--parameter-two']; + let expected = { 'parameter-one': 'value', 'parameter-two': null }; + let actual = Module.parseRemainingArgs(input); + assertEquals(JSON.stringify(expected), JSON.stringify(actual)); +} + +function testValidateMinimalCorrectArgumentsWithAppclass() { + let input = { + appname: 'Smoke Grinder', + author: 'Joe Coder <joe@coder.com>', + appversion: '1.0', + appclass: 'SmokeGrinder.App' + }; + assertTrue(Module.validateRemainingArgs(input)); +} + +function testValidateMinimalCorrectArgumentsWithExec() { + let input = { + appname: 'Smoke Grinder', + author: 'Joe Coder <joe@coder.com>', + appversion: '1.0', + exec: 'bin/smoke-grinder-launch' + }; + assertTrue(Module.validateRemainingArgs(input)); +} + +function testValidateMaximalCorrectArguments() { + let input = { + appname: 'Smoke Grinder', + author: 'Joe Coder <joe@coder.com>', + appversion: '1.0', + appclass: 'SmokeGrinder.App', + 'manifest-version': '0', + website: 'http://coder.example.com', + description: 'An app that does exciting things', + locale: 'en', + license: 'GPL' + }; + assertTrue(Module.validateRemainingArgs(input)); +} + +function testValidateBadArgumentsAppnameMissing() { + let input = { + author: 'Joe Coder <joe@coder.com>', + appversion: '1.0', + appclass: 'SmokeGrinder.App' + }; + assertFalse(Module.validateRemainingArgs(input)); +} + +function testValidateBadArgumentsAuthorMissing() { + let input = { + appname: 'Smoke Grinder', + appversion: '1.0', + appclass: 'SmokeGrinder.App' + }; + assertFalse(Module.validateRemainingArgs(input)); +} + +function testValidateBadArgumentsAppversionMissing() { + let input = { + appname: 'Smoke Grinder', + author: 'Joe Coder <joe@coder.com>', + appclass: 'SmokeGrinder.App' + }; + assertFalse(Module.validateRemainingArgs(input)); +} + +function testValidateBadArgumentsLauncherMissing() { + let input = { + appname: 'Smoke Grinder', + author: 'Joe Coder <joe@coder.com>', + appversion: '1.0', + }; + assertFalse(Module.validateRemainingArgs(input)); +} + +function testValidateBadArgumentsTooManyLaunchers() { + let input = { + appname: 'Smoke Grinder', + author: 'Joe Coder <joe@coder.com>', + appversion: '1.0', + appclass: 'SmokeGrinder.App', + exec: 'bin/smoke-grinder-launch' + }; + assertFalse(Module.validateRemainingArgs(input)); +} + +function testValidateBadUnknownArgument() { + let input = { + appname: 'Smoke Grinder', + author: 'Joe Coder <joe@coder.com>', + appversion: '1.0', + appclass: 'SmokeGrinder.App', + unrecognized_argument: 'a value' + }; + assertFalse(Module.validateRemainingArgs(input)); +} + +function testValidateBadArgumentsWithoutValues() { + let input = { + appname: null, + author: null, + appversion: null, + appclass: null + }; + assertFalse(Module.validateRemainingArgs(input)); +} + +function testValidateBadManifestVersion() { + let input = { + appname: 'Smoke Grinder', + author: 'Joe Coder <joe@coder.com>', + appversion: '1.0', + appclass: 'SmokeGrinder.App', + 'manifest-version': 'FF' + }; + assertFalse(Module.validateRemainingArgs(input)); +} + +function testValidateTooHighManifestVersion() { + let input = { + appname: 'Smoke Grinder', + author: 'Joe Coder <joe@coder.com>', + appversion: '1.0', + appclass: 'SmokeGrinder.App', + 'manifest-version': 99999 + }; + assertFalse(Module.validateRemainingArgs(input)); +} + +function testCreateFilenameForManifest() { + let input = { + applicationId: 'com.endlessm.smoke-grinder' + }; + let expected = 'com.endlessm.smoke-grinder.json'; + let actual = Module.createFilenameForManifest(input); +} + +function testCreateManifestFromMinimalArgsWithAppclass() { + let inputApplicationId = 'com.coder.smoke-grinder'; + let inputArgDict = { + appname: 'Smoke Grinder', + author: 'Joe Coder <joe@coder.com>', + appversion: '1.0.1', + appclass: 'SmokeGrinder.App' + }; + let expected = { + manifestVersion: 1, + applicationId: "com.coder.smoke-grinder", + applicationName: { + en: "Smoke Grinder" + }, + authorName: "Joe Coder <joe@coder.com>", + authorWebsite: "", + description: { + en: "" + }, + version: "1.0.1", + changes: { + en: [], + }, + license: "", + resources: [], + applicationClass: "SmokeGrinder.App", + icons: {}, + categories: [], + permissions: [], + metadata: {} + }; + let actual = Module.createManifest(inputApplicationId, inputArgDict); + assertEquals(JSON.stringify(expected), JSON.stringify(actual)); +} + +function testCreateManifestFromMinimalArgsWithExec() { + let inputApplicationId = 'com.coder.smoke-grinder'; + let inputArgDict = { + appname: 'Smoke Grinder', + author: 'Joe Coder <joe@coder.com>', + appversion: '1.0.1', + exec: 'bin/smoke-grinder-launch' + }; + let expected = { + manifestVersion: 1, + applicationId: "com.coder.smoke-grinder", + applicationName: { + en: "Smoke Grinder" + }, + authorName: "Joe Coder <joe@coder.com>", + authorWebsite: "", + description: { + en: "" + }, + version: "1.0.1", + changes: { + en: [], + }, + license: "", + resources: [], + exec: "bin/smoke-grinder-launch", + icons: {}, + categories: [], + permissions: [], + metadata: {} + }; + let actual = Module.createManifest(inputApplicationId, inputArgDict); + assertEquals(JSON.stringify(expected), JSON.stringify(actual)); +} + +function testCreateManifestFromMaximalArgs() { + let inputApplicationId = 'com.coder.smoke-grinder'; + let inputArgDict = { + appname: 'Smoke Grinder', + author: 'Joe Coder <joe@coder.com>', + appversion: '1.0.1', + appclass: 'SmokeGrinder.App', + 'manifest-version': '0', + website: 'http://coder.example.com', + description: 'An app that does exciting things', + locale: 'pt_BR', + license: 'GPL' + }; + let expected = { + manifestVersion: 0, + applicationId: "com.coder.smoke-grinder", + applicationName: { + pt_BR: "Smoke Grinder" + }, + authorName: "Joe Coder <joe@coder.com>", + authorWebsite: "http://coder.example.com", + description: { + pt_BR: "An app that does exciting things" + }, + version: "1.0.1", + changes: { + pt_BR: [], + }, + license: "GPL", + resources: [], + applicationClass: "SmokeGrinder.App", + icons: {}, + categories: [], + permissions: [], + metadata: {} + }; + let actual = Module.createManifest(inputApplicationId, inputArgDict); + assertEquals(JSON.stringify(expected), JSON.stringify(actual)); +} + +function testSummary() { + let summary = Module.summary(); + assertTrue(typeof summary == 'string'); +} diff --git a/tools/Makefile.am.inc b/tools/Makefile.am.inc index f81694f..ab668df 100644 --- a/tools/Makefile.am.inc +++ b/tools/Makefile.am.inc @@ -28,4 +28,7 @@ commandsdir = $(libexecdir)/eos-application-manifest/commands dist_commands_DATA = \ tools/eos-application-manifest/commands/help.js \ tools/eos-application-manifest/commands/version.js \ + tools/eos-application-manifest/commands/init.js \ $(NULL) + +EXTRA_DIST += $(tools_test_modules) diff --git a/tools/eos-application-manifest/commands/init.js b/tools/eos-application-manifest/commands/init.js new file mode 100644 index 0000000..8fdc165 --- /dev/null +++ b/tools/eos-application-manifest/commands/init.js @@ -0,0 +1,197 @@ +const Gio = imports.gi.Gio; +const GLib = imports.gi.GLib; +const System = imports.system; + +const DEFAULT_LOCALE = 'en'; +const CURRENT_MANIFEST_VERSION = 1; + +function execute(args) { + let [id, argdict] = parseArgs(args); + let manifest = createManifest(id, argdict); + let json_manifest = JSON.stringify(manifest, null, " "); + + let filename = createFilenameForManifest(manifest); + let cwd = Gio.File.new_for_path(GLib.get_current_dir()); + let file = cwd.get_child(filename); + + file.replace_contents(json_manifest, null /* etag */, + false /* make backup */, Gio.FileCreateFlags.REPLACE_DESTINATION, + null /* cancellable */); +} + +// Takes a manifest with a valid application ID and creates the appropriate +// filename to put it in. +function createFilenameForManifest(manifest) { + return manifest.applicationId + '.json'; +} + +// Takes a valid application ID and validated list of command-line arguments +// and creates a valid application manifest. +function createManifest(id, argdict) { + let locale = argdict.locale || DEFAULT_LOCALE; + let manifestVersion = CURRENT_MANIFEST_VERSION; + if ('manifest-version' in argdict) + manifestVersion = parseInt(argdict['manifest-version'], 10); + + let launcherKey, launcherValue; + if (typeof argdict.appclass != 'undefined') { + launcherKey = 'applicationClass'; + launcherValue = argdict.appclass; + } else { + launcherKey = 'exec'; + launcherValue = argdict.exec; + } + + let manifest = {}; + manifest.manifestVersion = manifestVersion; + manifest.applicationId = id; + manifest.applicationName = createLocaleDict(locale, argdict.appname); + manifest.authorName = argdict.author; + manifest.authorWebsite = argdict.website || ''; + manifest.description = createLocaleDict(locale, argdict.description || ''); + manifest.version = argdict.appversion; + manifest.changes = createLocaleDict(locale, []); + manifest.license = argdict.license || ''; + manifest.resources = []; + manifest[launcherKey] = launcherValue; + manifest.icons = {}; + manifest.categories = []; + manifest.permissions = []; + manifest.metadata = {}; + + return manifest; +} + +// Takes a locale and a JS object and creates a dictionary such that for +// locale == 'pt_BR', return value is { 'pt_BR': value }. +function createLocaleDict(locale, value) { + let retval = {}; + retval[locale] = value; + return retval; +} + +// Parse command line arguments and return a valid application ID and a +// dictionary of other parameters passed. Validates the parameters. +function parseArgs(args) { + if (typeof args == 'undefined' || args.length === 0) { + help(); + throw new Error(); + } + + // App ID is the mandatory first argument + let id = args.shift(); + if (!Gio.Application.id_is_valid(id)) + throw new Error('"%s" is not a valid application ID. Rules:\n\ +- must contain only the ASCII characters "[A-Z][a-z][0-9]_-." and must not\n\ + begin with a digit.\n\ +- must contain at least one "."" (period) character (and thus at least three\n\ + characters).\n\ +- must not begin or end with a "." (period) character.\n\ +- must not contain consecutive "."" (period) characters.\n\ +- must not exceed 255 characters.'.format(id)); + + let argdict = parseRemainingArgs(args); + if (!validateRemainingArgs(argdict)) { + help(); + throw new Error(); + } + + return [id, argdict]; +} + +// Parse all arguments after the application ID has been removed +function parseRemainingArgs(args) { + let retval = {}; + for (let count = 0; count < args.length; count++) { + if (args[count].startsWith('--')) { + if (args[count].indexOf('=') != -1) { + // One parameter of the form --parameter=value + let expr = args[count].slice(2); + let [parameter, value] = expr.split('='); + retval[parameter] = value; + continue; + } + // Otherwise, the --parameter consumes the next argument if it's + // there and not also a --parameter + if (typeof args[count + 1] == 'undefined' || + args[count + 1].startsWith('--')) { + let parameter = args[count].slice(2); + retval[parameter] = null; + continue; + } + let parameter = args[count].slice(2); + retval[parameter] = args[count + 1]; + count++; + } + } + return retval; +} + +// Validate all arguments besides the application ID +function validateRemainingArgs(args) { + let requiredArgs = ['appname', 'author', 'appversion']; + let knownArgs = ['appclass', 'exec', 'manifest-version', 'website', + 'description', 'locale', 'license']; + + for (let arg of requiredArgs) { + if (!(arg in args)) + return false; + } + + // Only one of --appclass or --exec + if ('appclass' in args && 'exec' in args) + return false; + if (!('appclass' in args) && !('exec' in args)) + return false; + + for (let arg in args) { + if (requiredArgs.indexOf(arg) == -1 && knownArgs.indexOf(arg) == -1) + return false; + if (args[arg] === null) + return false; + } + + // Validate any content requirements for certain parameters here + + if ('manifest-version' in args) { + let versionNumber = parseInt(args['manifest-version'], 10); + if (Number.isNaN(versionNumber) || versionNumber < 0 || + versionNumber > CURRENT_MANIFEST_VERSION) + return false; + } + + return true; +} + +function summary() { + return 'Generate a minimal valid manifest'; +} + +function help() { + print('Generates a minimal valid manifest in the current directory.\n\n\ +Usage: %s init <application_id>\n\ + --appname=<name> --appversion=<version>\n\ + --author=<author> {--appclass=<application_class>|--exec=<path>}\n\ + [--manifest-version=<version>] [--website=<website>]\n\ + [--description=<description>] [--locale=<locale>]\n\ + [--license=<license>]\n\n\ +Required options for a minimal valid manifest:\n\ + <application_id> - A unique application ID, e.g. "com.example.weather"\n\ + --appname - Human-readable application name, e.g. "Weather Reader"\n\ + --author - Author name, e.g. "Joe Coder <coder@example.com>"\n\ + --appversion - Application version number\n\ + --appclass - Name of a class derived from Endless.Application with\n\ + its module to import, e.g. WeatherReader.App\n\ + --exec - Path to an executable within the package, that\n\ + launches the application, e.g. "bin/weather-reader"\n\ +(Use only one of either --appclass or --exec.)\n\n\ +Other options:\n\ + --manifest-version - Version of the manifest file specification to use\n\ + [default: the current one]\n\ + --website - URI with more information about the application\n\ + --description - Long description of the application\n\ + --locale - Locale information for --appname and --description\n\ + [default "en"]\n\ + --license - Software license under which the application is\n\ + provided to users'.format(System.programInvocationName)); +} |