Skip to content

Commit

Permalink
feat(stdlib): Implement roAppInfo (#643)
Browse files Browse the repository at this point in the history
fixes #537
  • Loading branch information
vbuchii authored Apr 30, 2021
1 parent 3afccef commit 4d24b7c
Show file tree
Hide file tree
Showing 9 changed files with 331 additions and 0 deletions.
2 changes: 2 additions & 0 deletions src/brsTypes/components/BrsObjects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { Int64 } from "../Int64";
import { Interpreter } from "../../interpreter";
import { roInvalid } from "./RoInvalid";
import { BrsComponent } from "./BrsComponent";
import { RoAppInfo } from "./RoAppInfo";

/** Map containing a list of brightscript components that can be created. */
export const BrsObjects = new Map<string, Function>([
Expand All @@ -43,6 +44,7 @@ export const BrsObjects = new Map<string, Function>([
["rofloat", (_: Interpreter, literal: Float) => new roFloat(literal)],
["roint", (_: Interpreter, literal: Int32) => new roInt(literal)],
["rolonginteger", (_: Interpreter, literal: Int64) => new roLongInteger(literal)],
["roappinfo", (_: Interpreter) => new RoAppInfo()],
["roinvalid", (_: Interpreter) => new roInvalid()],
]);

Expand Down
145 changes: 145 additions & 0 deletions src/brsTypes/components/RoAppInfo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import { BrsBoolean, BrsString, BrsValue, ValueKind } from "../BrsType";
import { BrsComponent } from "./BrsComponent";
import { BrsType } from "..";
import { Callable, StdlibArgument } from "../Callable";
import { Interpreter } from "../../interpreter";

export class RoAppInfo extends BrsComponent implements BrsValue {
readonly kind = ValueKind.Object;

constructor() {
super("roAppInfo");

this.registerMethods({
ifAppInfo: [
this.getID,
this.isDev,
this.getVersion,
this.getTitle,
this.getSubtitle,
this.getDevID,
this.getValue,
],
});
}

toString(parent?: BrsType) {
return "<Component: roAppInfo>";
}

equalTo(other: BrsType) {
return BrsBoolean.False;
}

/**
* Originally returns the app's channel ID or 'dev' for sideloaded applications.
* @returns {string} - 'dev'
*/
private getID = new Callable("getID", {
signature: {
args: [],
returns: ValueKind.String,
},
impl: (interpreter: Interpreter) => {
return new BrsString("dev");
},
});

/**
* Returns true if the application is sideloaded, i.e. the channel ID is "dev".
* @returns {boolean} - true
*/
private isDev = new Callable("isDev", {
signature: {
args: [],
returns: ValueKind.Boolean,
},
impl: (interpreter: Interpreter) => {
return BrsBoolean.True;
},
});

/**
* Returns the conglomerate version number from the manifest, as formatted major_version + minor_version + build_version.
* @returns {string} - Channel version number. e.g. "1.2.3" or ".." if not available
*/
private getVersion = new Callable("getVersion", {
signature: {
args: [],
returns: ValueKind.String,
},
impl: (interpreter: Interpreter) => {
let manifest = interpreter.manifest;

let version = ["major_version", "minor_version", "build_version"]
.map((key) => manifest.get(key))
.filter((key) => !!key)
.join(".");
version = version !== "" ? version : "..";

return new BrsString(version);
},
});

/**
* Returns the title value from the manifest.
* @returns {string} - title of the channel
*/
private getTitle = new Callable("getTitle", {
signature: {
args: [],
returns: ValueKind.String,
},
impl: (interpreter: Interpreter) => {
let title = interpreter.manifest.get("title");

return title != null ? new BrsString(title.toString()) : new BrsString("");
},
});

/**
* Returns the subtitle value from the manifest.
* @returns {string} - possible subtitle configuration
*/
private getSubtitle = new Callable("getSubtitle", {
signature: {
args: [],
returns: ValueKind.String,
},
impl: (interpreter: Interpreter) => {
let subtitle = interpreter.manifest.get("subtitle");

return subtitle != null ? new BrsString(subtitle.toString()) : new BrsString("");
},
});

/**
* Returns the app's developer ID, or the keyed developer ID, if the application is sideloaded.
* @returns {string} - "34c6fceca75e456f25e7e99531e2425c6c1de443" (default value for sideloaded channels)
*/
private getDevID = new Callable("getDevID", {
signature: {
args: [],
returns: ValueKind.String,
},
impl: (interpreter: Interpreter) => {
// return default value for sideloaded channels
return new BrsString("34c6fceca75e456f25e7e99531e2425c6c1de443");
},
});

/**
* Returns the named manifest value, or an empty string if the entry is does not exist.
*/
private getValue = new Callable("getValue", {
signature: {
args: [new StdlibArgument("key", ValueKind.String)],
returns: ValueKind.String,
},
impl: (interpreter: Interpreter, key: BrsString) => {
let value = interpreter.manifest.get(key.value);

return value != null ? new BrsString(value.toString()) : new BrsString("");
},
});
}
1 change: 1 addition & 0 deletions src/brsTypes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ export * from "./components/ArrayGrid";
export * from "./components/MarkupGrid";
export * from "./components/ContentNode";
export * from "./components/Timer";
export * from "./components/RoAppInfo";

/**
* Determines whether or not the given value is a number.
Expand Down
3 changes: 3 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,9 @@ async function loadFiles(options: Partial<ExecutionOptions>) {
throw new Error("Unable to build interpreter.");
}

// Store manifest as a property on the Interpreter for further reusing
interpreter.manifest = manifest;

let componentLibraryInterpreters = (await pSettle(componentLibrariesToLoad))
.filter((result) => result.isFulfilled)
.map((result) => result.value!.interpreter);
Expand Down
11 changes: 11 additions & 0 deletions src/interpreter/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { EventEmitter } from "events";
import * as PP from "../preprocessor";

import {
BrsType,
Expand Down Expand Up @@ -44,6 +45,7 @@ import { isBoxable, isUnboxable } from "../brsTypes/Boxing";
import { ComponentDefinition } from "../componentprocessor";
import pSettle from "p-settle";
import { CoverageCollector } from "../coverage";
import { ManifestValue } from "../preprocessor/Manifest";

/** The set of options used to configure an interpreter's execution. */
export interface ExecutionOptions {
Expand Down Expand Up @@ -75,6 +77,7 @@ Object.freeze(defaultExecutionOptions);
export class Interpreter implements Expr.Visitor<BrsType>, Stmt.Visitor<BrsType> {
private _environment = new Environment();
private coverageCollector: CoverageCollector | null = null;
private _manifest: PP.Manifest | undefined;

readonly options: ExecutionOptions;
readonly stdout: OutputProxy;
Expand All @@ -94,6 +97,14 @@ export class Interpreter implements Expr.Visitor<BrsType>, Stmt.Visitor<BrsType>
return this._environment;
}

get manifest() {
return this._manifest != null ? this._manifest : new Map<string, ManifestValue>();
}

set manifest(manifest: PP.Manifest) {
this._manifest = manifest;
}

setCoverageCollector(collector: CoverageCollector) {
this.coverageCollector = collector;
}
Expand Down
135 changes: 135 additions & 0 deletions test/brsTypes/components/RoAppInfo.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
const brs = require("brs");
const { BrsBoolean, BrsString, RoAppInfo } = brs.types;
const { Interpreter } = require("../../../lib/interpreter");

describe("RoAppInfo", () => {
let interpreter;

beforeEach(() => {
interpreter = new Interpreter();
interpreter.manifest = new Map();
});

describe("stringification", () => {
it("lists stringified value", () => {
let appInfo = new RoAppInfo();
expect(appInfo.toString()).toEqual(`<Component: roAppInfo>`);
});
});

describe("getID", () => {
it("returns default value for sideloaded app", () => {
let appInfo = new RoAppInfo();
let getID = appInfo.getMethod("getID");

expect(getID).toBeTruthy();
expect(getID.call(interpreter)).toEqual(new BrsString("dev"));
});
});

describe("isDev", () => {
it("returns true for sideloaded app", () => {
let appInfo = new RoAppInfo();
let isDev = appInfo.getMethod("isDev");

expect(isDev).toBeTruthy();
expect(isDev.call(interpreter)).toEqual(BrsBoolean.True);
});
});

describe("getVersion", () => {
it("returns version based on data in the manifest file", () => {
interpreter.manifest = new Map([
["major_version", "4"],
["minor_version", "3"],
["build_version", "0"],
]);
let appInfo = new RoAppInfo();
let getVersion = appInfo.getMethod("getVersion");

expect(getVersion).toBeTruthy();
expect(getVersion.call(interpreter)).toEqual(new BrsString("4.3.0"));
});

it("returns two dots if sub versions aren't defined", () => {
let appInfo = new RoAppInfo();
let getVersion = appInfo.getMethod("getVersion");

expect(getVersion).toBeTruthy();
expect(getVersion.call(interpreter)).toEqual(new BrsString(".."));
});
});

describe("getTitle", () => {
it("returns title based on data in the manifest file", () => {
interpreter.manifest = new Map([["title", "Some title"]]);
let appInfo = new RoAppInfo();
let getTitle = appInfo.getMethod("getTitle");

expect(getTitle).toBeTruthy();
expect(getTitle.call(interpreter)).toEqual(new BrsString("Some title"));
});

it("returns an empty string if title isn't defined", () => {
let appInfo = new RoAppInfo();
let getTitle = appInfo.getMethod("getTitle");

expect(getTitle).toBeTruthy();
expect(getTitle.call(interpreter)).toEqual(new BrsString(""));
});
});

describe("getSubtitle", () => {
it("returns subtitle based on data in the manifest file", () => {
interpreter.manifest = new Map([["subtitle", "Some message"]]);
let appInfo = new RoAppInfo();
let getSubtitle = appInfo.getMethod("getSubtitle");

expect(getSubtitle).toBeTruthy();
expect(getSubtitle.call(interpreter)).toEqual(new BrsString("Some message"));
});

it("returns an empty string if subtitle isn't defined", () => {
let appInfo = new RoAppInfo();
let getSubtitle = appInfo.getMethod("getSubtitle");

expect(getSubtitle).toBeTruthy();
expect(getSubtitle.call(interpreter)).toEqual(new BrsString(""));
});
});

describe("getDevID", () => {
it("returns default value for sideloaded app", () => {
let appInfo = new RoAppInfo();
let getDevID = appInfo.getMethod("getDevID");

expect(getDevID).toBeTruthy();
expect(getDevID.call(interpreter)).toEqual(
new BrsString("34c6fceca75e456f25e7e99531e2425c6c1de443")
);
});
});

describe("getValue", () => {
it("returns value based on data in the manifest file", () => {
interpreter.manifest = new Map([["some_field", "Some text"]]);
let appInfo = new RoAppInfo();
let getValue = appInfo.getMethod("getValue");

expect(getValue).toBeTruthy();
expect(getValue.call(interpreter, new BrsString("some_field"))).toEqual(
new BrsString("Some text")
);
});

it("returns an empty string if field isn't defined", () => {
let appInfo = new RoAppInfo();
let getValue = appInfo.getMethod("getValue");

expect(getValue).toBeTruthy();
expect(getValue.call(interpreter, new BrsString("nonexistentfield"))).toEqual(
new BrsString("")
);
});
});
});
16 changes: 16 additions & 0 deletions test/e2e/BrsComponents.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const { execute } = require("../../lib/");
const { createMockStreams, resourceFile, allArgs } = require("./E2ETests");
const lolex = require("lolex");
const path = require("path");

describe("end to end brightscript functions", () => {
let outputStreams;
Expand Down Expand Up @@ -734,4 +735,19 @@ describe("end to end brightscript functions", () => {
"",
]);
});

test("components/roAppInfo.brs", async () => {
outputStreams.root = path.join(__dirname, "resources", "conditional-compilation");

await execute([resourceFile("components", "roAppInfo.brs")], outputStreams);
expect(allArgs(outputStreams.stdout.write).filter((arg) => arg !== "\n")).toEqual([
"dev",
"true",
"3.1.2",
"Some title",
"subtitle",
"34c6fceca75e456f25e7e99531e2425c6c1de443",
"Some text",
]);
});
});
12 changes: 12 additions & 0 deletions test/e2e/resources/components/roAppInfo.brs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
sub main()
appInfo = createObject("roAppInfo")

print appInfo.getID()
print appInfo.isDev()
print appInfo.getVersion()
print appInfo.getTitle()
print appInfo.getSubtitle()
print appInfo.getDevID()
print appInfo.getValue("some_field")

end sub
Loading

0 comments on commit 4d24b7c

Please sign in to comment.