Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(stdlib): Implement roAppInfo #643

Merged
merged 11 commits into from
Apr 30, 2021
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", {
alimnios72 marked this conversation as resolved.
Show resolved Hide resolved
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 : "..";
alimnios72 marked this conversation as resolved.
Show resolved Hide resolved

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 @@ -150,6 +150,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>();
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ooooh returning an empty map is a great idea!

}

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 @@ -719,4 +720,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()
vbuchii marked this conversation as resolved.
Show resolved Hide resolved
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