summaryrefslogtreecommitdiff
path: root/tools/eos-application-manifest/commands/init.js
blob: 8fdc1658779534a8cddd65a1a995621fdd72dd8b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
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));
}