├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── feature_request.md │ └── bug_report.md ├── workflows │ └── test.yml ├── FUNDING.yml ├── PULL_REQUEST_TEMPLATE.md └── CONTRIBUTING.md ├── shell.gif ├── src ├── Errors │ ├── ImbaMissingException.imba │ ├── InvalidLanguageException.imba │ ├── TypeScriptMissingException.imba │ └── index.imba ├── Runners │ ├── index.imba │ ├── TypeScriptRunner.imba │ └── ImbaRunner.imba ├── ContextHelpers.imba ├── Compilers │ ├── index.imba │ ├── TypeScriptCompiler.imba │ ├── ImbaCompiler.imba │ └── Compiler.imba ├── index.imba ├── UpdateNotifier.imba ├── Command.imba └── ImbaRepl.imba ├── types ├── ContextHelpers.d.ts ├── Errors │ ├── ImbaMissingException.d.ts │ ├── InvalidLanguageException.d.ts │ ├── TypeScriptMissingException.d.ts │ └── index.d.ts ├── Runners │ ├── index.d.ts │ ├── ImbaRunner.d.ts │ └── TypeScriptRunner.d.ts ├── Compilers │ ├── index.d.ts │ ├── ImbaCompiler.d.ts │ └── TypeScriptCompiler.d.ts ├── Command.d.ts ├── UpdateNotifier.d.ts ├── ImbaRepl.d.ts └── index.d.ts ├── test ├── bin │ ├── hello │ └── hello.imba ├── imba.runner.test.js ├── ts.runner.test.js ├── imba.compiler.test.js ├── ts.compiler.test.js ├── bin.repl.test.js ├── repl.test.js └── bin.runtime.test.js ├── .gitignore ├── bin ├── runtime └── repl ├── .editorconfig ├── tsconfig.json ├── scripts └── update.js ├── LICENSE ├── package.json └── README.md /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | -------------------------------------------------------------------------------- /shell.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/donaldp/imba-shell/HEAD/shell.gif -------------------------------------------------------------------------------- /src/Errors/ImbaMissingException.imba: -------------------------------------------------------------------------------- 1 | export default class ImbaMissingException < Error 2 | -------------------------------------------------------------------------------- /src/Errors/InvalidLanguageException.imba: -------------------------------------------------------------------------------- 1 | export default class InvalidLanguageException < Error 2 | -------------------------------------------------------------------------------- /types/ContextHelpers.d.ts: -------------------------------------------------------------------------------- 1 | export function exit(): never; 2 | export function clear(): void; 3 | -------------------------------------------------------------------------------- /src/Errors/TypeScriptMissingException.imba: -------------------------------------------------------------------------------- 1 | export default class TypeScriptMissingException < Error 2 | -------------------------------------------------------------------------------- /test/bin/hello: -------------------------------------------------------------------------------- 1 | const name = process.argv.slice(2)[0] ?? 'stranger' 2 | 3 | console.log "Hello {name}" 4 | -------------------------------------------------------------------------------- /test/bin/hello.imba: -------------------------------------------------------------------------------- 1 | const name = process.argv.slice(2)[0] ?? 'stranger' 2 | 3 | console.log "Hello {name}" 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /public 3 | /dist 4 | .env 5 | .env.backup 6 | npm-debug.log 7 | yarn-error.log 8 | *.tgz 9 | -------------------------------------------------------------------------------- /types/Errors/ImbaMissingException.d.ts: -------------------------------------------------------------------------------- 1 | export default ImbaMissingException; 2 | declare class ImbaMissingException extends Error { 3 | } 4 | -------------------------------------------------------------------------------- /types/Errors/InvalidLanguageException.d.ts: -------------------------------------------------------------------------------- 1 | export default InvalidLanguageException; 2 | declare class InvalidLanguageException extends Error { 3 | } 4 | -------------------------------------------------------------------------------- /types/Errors/TypeScriptMissingException.d.ts: -------------------------------------------------------------------------------- 1 | export default TypeScriptMissingException; 2 | declare class TypeScriptMissingException extends Error { 3 | } 4 | -------------------------------------------------------------------------------- /src/Runners/index.imba: -------------------------------------------------------------------------------- 1 | import ImbaRunner from './ImbaRunner' 2 | import TypeScriptRunner from './TypeScriptRunner' 3 | 4 | export { 5 | ImbaRunner 6 | TypeScriptRunner 7 | } 8 | -------------------------------------------------------------------------------- /types/Runners/index.d.ts: -------------------------------------------------------------------------------- 1 | import ImbaRunner from './ImbaRunner' 2 | import TypeScriptRunner from './TypeScriptRunner' 3 | 4 | export { 5 | ImbaRunner, 6 | TypeScriptRunner 7 | } 8 | -------------------------------------------------------------------------------- /src/ContextHelpers.imba: -------------------------------------------------------------------------------- 1 | export def exit 2 | console.log 'Exit: Goodbye' 3 | 4 | process.exit! 5 | 6 | export def clear 7 | process.stdout.write('\u001B[2J\u001B[0;0f') 8 | 9 | return 10 | -------------------------------------------------------------------------------- /src/Errors/index.imba: -------------------------------------------------------------------------------- 1 | import ImbaMissingException from './ImbaMissingException' 2 | import InvalidLanguageException from './InvalidLanguageException' 3 | 4 | export { 5 | ImbaMissingException 6 | InvalidLanguageException 7 | } 8 | -------------------------------------------------------------------------------- /bin/runtime: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const { Command } = require('../dist'); 4 | 5 | class RuntimeCommand extends Command { 6 | get isRuntime() { 7 | return true; 8 | } 9 | } 10 | 11 | (new RuntimeCommand).run(); 12 | -------------------------------------------------------------------------------- /src/Compilers/index.imba: -------------------------------------------------------------------------------- 1 | import Compiler from './Compiler' 2 | import ImbaCompiler from './ImbaCompiler' 3 | import TypeScriptCompiler from './TypeScriptCompiler' 4 | 5 | export { 6 | Compiler 7 | ImbaCompiler 8 | TypeScriptCompiler 9 | } -------------------------------------------------------------------------------- /types/Compilers/index.d.ts: -------------------------------------------------------------------------------- 1 | import Compiler from './Compiler' 2 | import ImbaCompiler from './ImbaCompiler' 3 | import TypeScriptCompiler from './TypeScriptCompiler' 4 | 5 | export { 6 | Compiler, 7 | ImbaCompiler, 8 | TypeScriptCompiler 9 | } 10 | -------------------------------------------------------------------------------- /types/Runners/ImbaRunner.d.ts: -------------------------------------------------------------------------------- 1 | export default ImbaRunner; 2 | declare class ImbaRunner { 3 | static get ext(): "" | ".cmd"; 4 | 5 | static get imba(): string; 6 | 7 | static instance(compiler?: boolean): string; 8 | 9 | static get version(): string; 10 | } 11 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | indent_style = tab 8 | indent_size = 4 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | 14 | [*.{yml,yaml}] 15 | indent_size = 2 -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: push 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v2 12 | - name: Install modules 13 | run: npm install 14 | - name: Run tests 15 | run: npm test 16 | -------------------------------------------------------------------------------- /types/Compilers/ImbaCompiler.d.ts: -------------------------------------------------------------------------------- 1 | export default ImbaCompiler; 2 | 3 | declare class ImbaCompiler { 4 | code: string; 5 | sessionId: string; 6 | 7 | static code(code: string, sessionId: string): ImbaCompiler; 8 | 9 | constructor(code: string, sessionId: string); 10 | 11 | get(): string; 12 | } 13 | -------------------------------------------------------------------------------- /types/Compilers/TypeScriptCompiler.d.ts: -------------------------------------------------------------------------------- 1 | export default TypeScriptCompiler; 2 | 3 | declare class TypeScriptCompiler { 4 | code: string; 5 | sessionId: string; 6 | 7 | static code(code: string, sessionId: string): TypeScriptCompiler; 8 | 9 | constructor(code: string, sessionId: string); 10 | 11 | get(): string; 12 | } 13 | -------------------------------------------------------------------------------- /types/Errors/index.d.ts: -------------------------------------------------------------------------------- 1 | import ImbaMissingException from './ImbaMissingException' 2 | import InvalidLanguageException from './InvalidLanguageException' 3 | import TypeScriptMissingException from './TypeScriptMissingException' 4 | 5 | export { 6 | ImbaMissingException, 7 | InvalidLanguageException, 8 | TypeScriptMissingException 9 | } 10 | -------------------------------------------------------------------------------- /src/Compilers/TypeScriptCompiler.imba: -------------------------------------------------------------------------------- 1 | import { transpile } from 'typescript' 2 | import Compiler from './Compiler' 3 | 4 | export default class TypeScriptCompiler < Compiler 5 | 6 | def get 7 | transpile(self.code.trim!, { 8 | module: 'CommonJS', 9 | target: 'ESNext', 10 | moduleResolution: 'node', 11 | esModuleInterop: true, 12 | }) 13 | -------------------------------------------------------------------------------- /types/Runners/TypeScriptRunner.d.ts: -------------------------------------------------------------------------------- 1 | import { TypeScriptMissingException } from '../Errors' 2 | 3 | declare class TypeScriptRunner { 4 | static get tsc(): string; 5 | 6 | /** 7 | * @throws {TypeScriptMissingException} 8 | */ 9 | static instance(): string; 10 | 11 | static get version(): string; 12 | } 13 | 14 | export default TypeScriptRunner 15 | -------------------------------------------------------------------------------- /test/imba.runner.test.js: -------------------------------------------------------------------------------- 1 | const { ImbaRunner } = require('../dist/Runners'); 2 | 3 | describe('src/Runners/ImbaRunner', () => { 4 | 5 | it('should return imba version.', () => { 6 | expect(ImbaRunner.version).toContain('.'); 7 | }); 8 | 9 | it('should return imba instance path.', () => { 10 | expect(ImbaRunner.instance()).toContain('node_modules'); 11 | }); 12 | 13 | }) 14 | -------------------------------------------------------------------------------- /test/ts.runner.test.js: -------------------------------------------------------------------------------- 1 | const { TypeScriptRunner } = require('../dist/Runners') 2 | 3 | describe('src/Runners/TypeScriptRunner', () => { 4 | 5 | it('should return typescript version.', () => { 6 | expect(TypeScriptRunner.version).toContain('.'); 7 | }); 8 | 9 | it('should return typescript instance path.', () => { 10 | expect(TypeScriptRunner.instance()).toContain('node_modules'); 11 | }); 12 | 13 | }) -------------------------------------------------------------------------------- /types/Command.d.ts: -------------------------------------------------------------------------------- 1 | export default Command; 2 | 3 | declare class Command { 4 | args: string[]; 5 | watch: boolean; 6 | get isRuntime(): boolean; 7 | enableWatcher(): string[]; 8 | printVersion(): void; 9 | displayHelp(): void; 10 | invalidCommand(): never; 11 | run(): any; 12 | exec(): void; 13 | createFallbackScript(): string; 14 | handle(...args: any[]): void; 15 | } 16 | -------------------------------------------------------------------------------- /types/UpdateNotifier.d.ts: -------------------------------------------------------------------------------- 1 | export default UpdateNotifier; 2 | declare class UpdateNotifier { 3 | package: string; 4 | directory: string; 5 | 6 | shouldFetchLatestVersion(): number | true; 7 | 8 | compareVersion(latestVersion: string): boolean | 0; 9 | 10 | fetchLatestVersion(): string | null; 11 | 12 | storeVersion(data: string): void; 13 | 14 | check(callback?: Function | boolean): void; 15 | } 16 | -------------------------------------------------------------------------------- /src/index.imba: -------------------------------------------------------------------------------- 1 | import * as Compilers from './Compilers' 2 | import * as ContextHelpers from './ContextHelpers' 3 | import * as Errors from './Errors' 4 | import * as Runners from './Runners' 5 | import Command from './Command' 6 | import ImbaRepl from './ImbaRepl' 7 | import UpdateNotifier from './UpdateNotifier' 8 | 9 | export { 10 | Command 11 | Compilers 12 | ContextHelpers 13 | Errors 14 | ImbaRepl 15 | Runners 16 | UpdateNotifier 17 | } 18 | -------------------------------------------------------------------------------- /test/imba.compiler.test.js: -------------------------------------------------------------------------------- 1 | const { ImbaCompiler } = require('../dist/Compilers'); 2 | 3 | describe('src/Compilers/ImbaCompiler', () => { 4 | 5 | it('should expect code to be of type string.', () => { 6 | expect(() => { 7 | ImbaCompiler.code(123).get() 8 | }).toThrow(TypeError); 9 | }); 10 | 11 | it('should compile Imba to JavaScript.', () => { 12 | const sessionId = String(new Date().valueOf()); 13 | 14 | expect(ImbaCompiler.code('console.log "Hello World!"', sessionId).get()).toContain('console.log("Hello World!")'); 15 | }); 16 | 17 | }) 18 | -------------------------------------------------------------------------------- /test/ts.compiler.test.js: -------------------------------------------------------------------------------- 1 | const { TypeScriptCompiler } = require('../dist/Compilers'); 2 | 3 | describe('src/Compilers/TypeScriptCompiler', () => { 4 | 5 | it('should expect code to be of type string.', () => { 6 | expect(() => { 7 | TypeScriptCompiler.code(123).get() 8 | }).toThrow(TypeError); 9 | }); 10 | 11 | it('should compile typescript to javascript.', () => { 12 | const sessionId = String(new Date().valueOf()); 13 | 14 | expect(TypeScriptCompiler.code('const name: string = "Donald";', sessionId).get()).toContain('const name = "Donald"'); 15 | }); 16 | 17 | }) 18 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // Change this to match your project 3 | "include": ["dist/*"], 4 | "compilerOptions": { 5 | // Tells TypeScript to read JS files, as 6 | // normally they are ignored as source files 7 | "allowJs": true, 8 | // Generate d.ts files 9 | "declaration": true, 10 | // This compiler run should 11 | // only output d.ts files 12 | "emitDeclarationOnly": true, 13 | // Types should go into this directory. 14 | // Removing this would place the .d.ts files 15 | // next to the .js files 16 | "outDir": "types" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /types/ImbaRepl.d.ts: -------------------------------------------------------------------------------- 1 | export default ImbaRepl; 2 | declare class ImbaRepl { 3 | ctxCallbacks: Array; 4 | cmdCallbacks: Array; 5 | update: boolean|Function; 6 | language: string; 7 | prompt: string; 8 | historyPath: string; 9 | 10 | constructor(language?: string, prompt?: string, historyPath?: string); 11 | 12 | registerCallback(callback: Function): ImbaRepl; 13 | 14 | registerCommand(name: string, callback: Function): ImbaRepl; 15 | 16 | shouldUpdate(callback?: Function): ImbaRepl; 17 | 18 | run(options?: object): Promise; 19 | } 20 | -------------------------------------------------------------------------------- /src/Compilers/ImbaCompiler.imba: -------------------------------------------------------------------------------- 1 | import { execSync } from 'child_process' 2 | import { name } from '../../package.json' 3 | import { ImbaRunner, TypeScriptRunner } from '../Runners' 4 | import Compiler from './Compiler' 5 | import fs from 'fs' 6 | import os from 'os' 7 | import path from 'path' 8 | 9 | export default class ImbaCompiler < Compiler 10 | def get 11 | fs.writeFileSync(path.join(self.directory, self.sessionId), self.code.trim!) 12 | 13 | const results\Buffer = execSync("{ImbaRunner.instance(true)} {path.join(self.directory, self.sessionId)} --platform=node --format=cjs --print") 14 | 15 | fs.rmSync(path.join(self.directory, self.sessionId)) 16 | 17 | results.toString! 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F680Feature request" 3 | about: Suggest an idea for Formidable 4 | --- 5 | 6 | 7 | 8 | ### Description 9 | 10 | 11 | 12 | ### Why 13 | 14 | 15 | 16 | 17 | 18 | ### Possible Implementation & Open Questions 19 | 20 | 21 | 22 | 23 | 24 | ### Is this something you're interested in working on? 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/Runners/TypeScriptRunner.imba: -------------------------------------------------------------------------------- 1 | import { execSync } from 'child_process' 2 | import fs from 'fs' 3 | import TypeScriptMissingException from '../Errors/TypeScriptMissingException' 4 | import path from 'path' 5 | 6 | export default class TypeScriptRunner 7 | 8 | static get tsc 9 | const local\string = path.join(process.cwd!, 'node_modules', '.bin', 'tsc') 10 | const onboard\string = path.join(__dirname, '..', 'node_modules', '.bin', 'tsc') 11 | 12 | fs.existsSync(local) ? local : onboard 13 | 14 | static def instance 15 | const file = self.tsc 16 | 17 | if !fs.existsSync(file) 18 | throw new TypeScriptMissingException "tsc not found at {file}" 19 | 20 | file 21 | 22 | static get version 23 | execSync("{self.instance!} -v").toString!.trim!.split(' ')[1] 24 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [donaldp] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 14 | -------------------------------------------------------------------------------- /bin/repl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const { Command, ImbaRepl } = require('../dist'); 4 | const imba = require('imba'); 5 | const os = require('os'); 6 | const path = require('path'); 7 | 8 | class ReplCommand extends Command { 9 | /** 10 | * @param {string} language 11 | */ 12 | handle(language) { 13 | const repl = new ImbaRepl(language, '>>> ', path.join(os.homedir(), '.imba_shell_history')); 14 | 15 | if (language == 'imba') { 16 | /** load imba. */ 17 | repl.registerCallback((ctx) => { 18 | Object.keys(imba).forEach((key) => { 19 | if (!ctx[key]) { 20 | ctx[key] = imba[key]; 21 | } 22 | }); 23 | }); 24 | } 25 | 26 | repl.shouldUpdate().run({ ignoreUndefined: true, useColors: language == 'typescript' }); 27 | } 28 | } 29 | 30 | (new ReplCommand).run(); 31 | -------------------------------------------------------------------------------- /src/Compilers/Compiler.imba: -------------------------------------------------------------------------------- 1 | import { name } from '../../package.json' 2 | import fs from 'fs' 3 | import os from 'os' 4 | import path from 'path' 5 | 6 | export default class Compiler 7 | 8 | prop directory\string = path.join os.tmpdir!, ".{name}" 9 | prop code\string 10 | prop sessionId\string 11 | 12 | def constructor code\string, sessionId\string 13 | if typeof code !== 'string' 14 | throw new TypeError 'Expected code to be a String.' 15 | 16 | if typeof sessionId !== 'string' 17 | throw new TypeError 'Expected sessionId to be a String.' 18 | 19 | if !fs.existsSync(self.directory) 20 | fs.mkdirSync(self.directory, { recursive: true }) 21 | 22 | self.code = code 23 | self.sessionId = sessionId 24 | 25 | static def code code\string, sessionId\string 26 | new self code, sessionId 27 | -------------------------------------------------------------------------------- /scripts/update.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | 3 | const updates = { 4 | '(0, import_path.dirname)((0, import_path.resolve)("src/Runners/ImbaRunner.imba"))': '__dirname', 5 | '(0, import_path2.dirname)((0, import_path2.resolve)("src/Runners/ImbaRunner.imba"))': '__dirname', 6 | '(0, import_path3.dirname)((0, import_path3.resolve)("src/Runners/TypeScriptRunner.imba"))': '__dirname', 7 | '(0, import_path4.dirname)((0, import_path4.resolve)("src/Runners/TypeScriptRunner.imba"))': '__dirname' 8 | } 9 | 10 | const persist = (file) => { 11 | let content = fs.readFileSync(file, 'utf8').toString() 12 | 13 | Object.keys(updates).forEach((key) => { 14 | content = content.replace(key, updates[key]) 15 | 16 | fs.writeFileSync(file, content) 17 | }) 18 | } 19 | 20 | persist('dist/index.js') 21 | persist('dist/Runners/index.js') 22 | -------------------------------------------------------------------------------- /src/Runners/ImbaRunner.imba: -------------------------------------------------------------------------------- 1 | import { execSync } from 'child_process' 2 | import fs from 'fs' 3 | import ImbaMissingException from '../Errors/ImbaMissingException' 4 | import path from 'path' 5 | 6 | export default class ImbaRunner 7 | 8 | static get ext 9 | process.platform === 'win32' ? '.cmd' : '' 10 | 11 | static get imba 12 | const local\string = path.join(process.cwd!, 'node_modules', '.bin', 'imba') 13 | const onboard\string = path.join(__dirname, '..', 'node_modules', '.bin', 'imba') 14 | 15 | fs.existsSync(local) ? local : onboard 16 | 17 | static def instance compiler\Boolean = false 18 | const file = self.imba + (compiler ? 'c' : '') + self.ext 19 | 20 | if !fs.existsSync(file) 21 | throw new ImbaMissingException "Imba not found at {file}" 22 | 23 | file 24 | 25 | static get version 26 | execSync("{self.instance(true)} -v").toString!.trim! 27 | -------------------------------------------------------------------------------- /test/bin.repl.test.js: -------------------------------------------------------------------------------- 1 | const { execSync } = require('child_process'); 2 | 3 | describe('bin/Runtime', () => { 4 | 5 | it('should run repl.', () => { 6 | expect(execSync('./bin/repl').toString()).toContain('>>>') 7 | }) 8 | 9 | it('should return Imba Shell version.', () => { 10 | expect(execSync('./bin/repl --version').toString()).toContain('Imba Shell v'); 11 | expect(execSync('./bin/repl -v').toString()).toContain('Imba Shell v'); 12 | }); 13 | 14 | it('should return Imba Shell help.', () => { 15 | expect(execSync('./bin/repl --help').toString()).toContain('Usage:'); 16 | expect(execSync('./bin/repl -h').toString()).toContain('Usage:'); 17 | }); 18 | 19 | it("should output error if option doesn't exist.", () => { 20 | expect(() => { 21 | try { 22 | execSync('./bin/repl --random'); 23 | } catch { 24 | throw new Error; 25 | } 26 | }).toThrowError(); 27 | }); 28 | 29 | }) 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 - 2025 Donald Pakkies 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /test/repl.test.js: -------------------------------------------------------------------------------- 1 | const { ImbaRepl } = require('../dist'); 2 | 3 | describe('src/ImbaRepl', () => { 4 | 5 | it('should create a repl instance.', () => { 6 | expect(new ImbaRepl()).toBeInstanceOf(ImbaRepl); 7 | 8 | expect(new ImbaRepl('imba', 'imba> ')).toBeInstanceOf(ImbaRepl); 9 | 10 | expect(new ImbaRepl('imba', 'imba> ', '.imba_history')).toBeInstanceOf(ImbaRepl); 11 | }); 12 | 13 | it('should expect repl language to be of type string.', () => { 14 | expect(() => { 15 | new ImbaRepl([]) 16 | }).toThrow(TypeError); 17 | }); 18 | 19 | it('should expect repl language to be "imba" or "typescript".', () => { 20 | expect(() => { 21 | new ImbaRepl('random') 22 | }).toThrow(Error); 23 | }); 24 | 25 | it('should expect repl prompt to be of type string.', () => { 26 | expect(() => { 27 | new ImbaRepl(null, []) 28 | }).toThrow(TypeError); 29 | }); 30 | 31 | it('should expect repl options to be of type object.', () => { 32 | expect(async () => { 33 | await new ImbaRepl().run('fail'); 34 | }).rejects.toThrowError(new TypeError('Expected repl options to be an Object.')); 35 | }); 36 | 37 | it('should expect repl callbacks to be valid.', () => { 38 | expect(() => { 39 | new ImbaRepl().registerCallback('invalid callback') 40 | }).toThrow(TypeError); 41 | }); 42 | 43 | }) 44 | -------------------------------------------------------------------------------- /types/index.d.ts: -------------------------------------------------------------------------------- 1 | declare const _exports: any; 2 | export = _exports; 3 | export var Command: { 4 | new (): { 5 | args: string[]; 6 | watch: any; 7 | readonly isRuntime: boolean; 8 | enableWatcher(): string[]; 9 | printVersion(): void; 10 | displayHelp(): void; 11 | invalidCommand(): never; 12 | run(): any; 13 | exec(): void; 14 | createFallbackScript(): string; 15 | handle(): any; 16 | }; 17 | }; 18 | export var ImbaRepl: { 19 | new (language?: string, prompt?: string, historyPath?: any): { 20 | ctxCallbacks: any; 21 | cmdCallbacks: any; 22 | update: any; 23 | language: string; 24 | prompt: string; 25 | historyPath: any; 26 | registerCallback(callback: any): any; 27 | registerCommand(name2: any, callback: any): any; 28 | shouldUpdate(callback?: any): any; 29 | run(options?: {}): Promise; 30 | }; 31 | }; 32 | export var UpdateNotifier: { 33 | new (): { 34 | package: any; 35 | directory: any; 36 | shouldFetchLatestVersion(): number | true; 37 | compareVersion(latestVersion: any): boolean | 0; 38 | fetchLatestVersion(): any; 39 | storeVersion(data: any): any; 40 | check(callback?: any): any; 41 | }; 42 | }; 43 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ## Description 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | ## How Has This Been Tested? 14 | 15 | 16 | 17 | 18 | 19 | ## Types of changes 20 | 21 | 22 | 23 | - [ ] Bug fix (non-breaking change which fixes an issue) 24 | - [ ] New feature (non-breaking change which adds functionality) 25 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) 26 | - [ ] This change requires a documentation update 27 | 28 | ## Checklist: 29 | 30 | 31 | 32 | 33 | 34 | - [ ] I have read the **CONTRIBUTING** document. 35 | - [ ] I have updated/added documentation affected by my changes. 36 | - [ ] I have added tests to cover my changes. 37 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F41BBug report" 3 | about: Something isn't working right 4 | --- 5 | 6 | 7 | 8 | ### imba-shell version: x.x.x 9 | 10 | 11 | 12 | ### Details 13 | 14 | 19 | 20 | ### Expected Behavior 21 | 22 | 23 | 24 | ### Actual Behavior 25 | 26 | 27 | 28 | ### Possible Fix 29 | 30 | 31 | 32 |
Additional Info 33 | 34 | ### Your Environment 35 | 36 | 37 | 38 | - Environment name and version (e.g. Windows 10, node.js 8.1): 39 | - Operating System and version (Mac or Linux): 40 | - Useful link to screenshot or any other information: 41 | 42 | ### Steps to Reproduce 43 | 44 | 45 | 46 | 47 | 48 | 1. first... 49 | 2. 50 | 3. 51 | 4. 52 | 53 | ### Stack Trace 54 | 55 | 56 | 57 |
58 | -------------------------------------------------------------------------------- /test/bin.runtime.test.js: -------------------------------------------------------------------------------- 1 | const { execSync } = require('child_process'); 2 | 3 | describe('bin/Runtime', () => { 4 | 5 | it('should return Imba Shell version.', () => { 6 | expect(execSync('./bin/runtime --version').toString()).toContain('Imba Shell v'); 7 | expect(execSync('./bin/runtime -v').toString()).toContain('Imba Shell v'); 8 | }); 9 | 10 | it('should return Imba Shell help.', () => { 11 | expect(execSync('./bin/runtime --help').toString()).toContain('Usage:'); 12 | expect(execSync('./bin/runtime -h').toString()).toContain('Usage:'); 13 | }); 14 | 15 | it("should output error if option doesn't exist.", () => { 16 | expect(() => { 17 | try { 18 | execSync('./bin/runtime --random'); 19 | } catch { 20 | throw new Error; 21 | } 22 | }).toThrowError(); 23 | }); 24 | 25 | it('should execute Imba script.', () => { 26 | expect(execSync('./bin/runtime ./test/bin/hello.imba').toString()).toContain('Hello stranger'); 27 | expect(execSync('./bin/runtime ./test/bin/hello.imba Donald').toString()).toContain('Hello Donald'); 28 | expect(execSync('./bin/runtime ./test/bin/hello Donald').toString()).toContain('Hello Donald'); 29 | }); 30 | 31 | it('should output help if script name is missing.', () => { 32 | expect(execSync('./bin/runtime').toString()).toContain('Usage:'); 33 | }); 34 | 35 | it("should output error if script doesn't exist.", () => { 36 | expect(() => { 37 | try { 38 | execSync('./bin/runtime ./random.imba'); 39 | } catch { 40 | throw new Error; 41 | } 42 | }).toThrowError(); 43 | }); 44 | 45 | }) 46 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "imba-shell", 3 | "version": "0.5.3", 4 | "description": "Interactive debugger and REPL for Imba.", 5 | "keywords": [ 6 | "imba", 7 | "imba-shell", 8 | "shell", 9 | "repl", 10 | "debugger" 11 | ], 12 | "author": "Donald Pakkies", 13 | "license": "MIT", 14 | "main": "dist/index.js", 15 | "types": "types/index.d.ts", 16 | "bugs": "https://github.com/donaldp/imba-shell/issues", 17 | "homepage": "https://github.com/donaldp/imba-shell", 18 | "repository": "https://github.com/donaldp/imba-shell", 19 | "scripts": { 20 | "build": "npm run build:entry && npm run build:compilers && npm run build:errors && npm run build:runners && npm run build:update", 21 | "build:entry": "imba build src/index.imba --platform node -o dist -s -M", 22 | "build:compilers": "imba build src/Compilers/index.imba --bundle --platform node -o dist/Compilers -s -M", 23 | "build:errors": "imba build src/Errors/index.imba --bundle --platform node -o dist/Errors -s -M", 24 | "build:runners": "imba build src/Runners/index.imba --bundle --platform node -o dist/Runners -s -M", 25 | "build:update": "node ./scripts/update.js", 26 | "test": "jest --roots test", 27 | "test:watch": "jest --watchAll --roots test", 28 | "prepare": "npm run build && npm run test" 29 | }, 30 | "bin": { 31 | "imba-r": "./bin/runtime", 32 | "imba-runtime": "./bin/runtime", 33 | "imba-shell": "./bin/repl", 34 | "imbar": "./bin/runtime", 35 | "imbas": "./bin/repl", 36 | "ir": "./bin/runtime" 37 | }, 38 | "exports": { 39 | ".": "./dist/index.js", 40 | "./Compilers": "./dist/Compilers/index.js", 41 | "./Errors": "./dist/Errors/index.js", 42 | "./Runners": "./dist/Runners/index.js" 43 | }, 44 | "dependencies": { 45 | "imba": "^2.0.0-alpha.245", 46 | "typescript": "^5.8.3" 47 | }, 48 | "devDependencies": { 49 | "@types/jest": "^29.5.14", 50 | "@types/node": "^22.14.1", 51 | "jest": "^29.7.0" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/UpdateNotifier.imba: -------------------------------------------------------------------------------- 1 | import { name, version } from '../package.json' 2 | import fs from 'fs' 3 | import https from 'https' 4 | import os from 'os' 5 | import path from 'path' 6 | import type Version from '../types/Version' 7 | 8 | export default class UpdateNotifier 9 | 10 | prop package\string = "https://registry.npmjs.org/-/package/{name}/dist-tags" 11 | prop directory\string = path.join os.homedir!, ".{name}" 12 | 13 | def constructor 14 | if !fs.existsSync(self.directory) then fs.mkdirSync self.directory 15 | 16 | if self.shouldFetchLatestVersion! 17 | self.fetchLatestVersion! 18 | 19 | def shouldFetchLatestVersion 20 | const file = path.join self.directory, 'latest.json' 21 | 22 | if !fs.existsSync file then return true 23 | 24 | const fileDate = fs.statSync(file).mtime 25 | const currentDate = new Date 26 | 27 | const _FILE_DATE\Number = Date.UTC fileDate.getFullYear!, fileDate.getMonth!, fileDate.getDate! 28 | const _CURRENT_DATE\Number = Date.UTC currentDate.getFullYear!, currentDate.getMonth!, currentDate.getDate! 29 | const _MS_PER_DAY = 1000 * 60 * 60 * 24 30 | 31 | const shouldRefresh = Math.floor (_CURRENT_DATE - _FILE_DATE) / _MS_PER_DAY > 0 32 | 33 | if shouldRefresh then fs.unlinkSync file 34 | 35 | shouldRefresh 36 | 37 | def compareVersion latestVersion\string 38 | if version.trim! == latestVersion.trim! then return 0 39 | 40 | latestVersion.localeCompare(version) == 1 41 | 42 | def fetchLatestVersion 43 | const request = https.get self.package 44 | 45 | request.on 'response' do(response) 46 | if response.statusCode !== 200 then return 47 | 48 | let data = '' 49 | 50 | response.on 'data' do(chunk) data += chunk 51 | 52 | response.on 'end' do self.storeVersion data 53 | 54 | request.end! 55 | 56 | request.on 'error' do return 57 | 58 | def storeVersion data\string 59 | const latestPath = path.join(self.directory, 'latest.json') 60 | 61 | fs.writeFileSync(latestPath, data) 62 | 63 | def check callback\Function|boolean = null 64 | if !fs.existsSync(path.join(self.directory, 'latest.json')) then return 65 | 66 | const response\Version = JSON.parse(fs.readFileSync(path.join(self.directory, 'latest.json')).toString!) 67 | 68 | if !self.compareVersion(response.latest) then return 69 | 70 | if callback && typeof callback == 'function' 71 | response.current = version 72 | 73 | return callback response 74 | 75 | const repeat = do(char\string) char.repeat(name.length * 2) 76 | 77 | console.log '┌─────────────────────────────────────────────────────' + repeat('─') + '─┐' 78 | console.log '│ ' + repeat(' ') + '│' 79 | console.log "│ New version available: v{response.latest} (current: v{version}) " + repeat(' ') + '│' 80 | console.log "│ Run \u001B[32mnpm install -g {name}\u001B[0m or \u001B[32myarn global add {name}\u001B[0m to update! │" 81 | console.log '│ ' + repeat(' ') + '│' 82 | console.log '└──────────────────────────────────────────────────────' + repeat('─') + '┘' 83 | 84 | null 85 | -------------------------------------------------------------------------------- /src/Command.imba: -------------------------------------------------------------------------------- 1 | import { existsSync, copyFileSync, unlinkSync } from 'fs' 2 | import { ImbaRunner } from './Runners' 3 | import { join, dirname, basename } from 'path' 4 | import { spawnSync } from 'child_process' 5 | import { version } from '../package.json' 6 | 7 | export default class Command 8 | 9 | prop args\string[] = [] 10 | 11 | prop watch\boolean = false 12 | 13 | get isRuntime 14 | false 15 | 16 | def constructor 17 | self.args = process.argv.slice(2) 18 | 19 | def enableWatcher 20 | self.watch = true 21 | 22 | self.args = self.args.filter do(arg) !['-w', '--watch'].includes(arg) 23 | 24 | def printVersion 25 | console.log "Imba Shell v{version} (imba {ImbaRunner.version})" 26 | 27 | def displayHelp 28 | if self.isRuntime 29 | console.log "\x1b[32mUsage:\x1b[0m\n [options] [\x1b[2m