diff --git a/cli/rpgparse/index.ts b/cli/rpgparse/index.ts new file mode 100644 index 00000000..e15b0e97 --- /dev/null +++ b/cli/rpgparse/index.ts @@ -0,0 +1,129 @@ +import glob from "glob"; +import Parser from "../../language/parser"; +import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs"; +import path from "path"; + +main(); + +function printHelp() { + console.log(` +Usage: "" [options] + +Options: + -f, --files Specify the files to scan (required) + -c, --cwd Specify the current working directory + -d, --outdir Specify the output directory + -o, --output Specify the output file + -h, --help Display this help message + `); +} + +async function main() { + const parms = process.argv.slice(2); + + let cwd = process.cwd(); + let scanGlob = `**/*.{SQLRPGLE,sqlrpgle,RPGLE,rpgle}`; + let filesProvided: boolean = false; + let output: string = undefined; + let outDir: string = undefined; + + for (let i = 0; i < parms.length; i++) { + switch (parms[i]) { + case `-f`: + case `--files`: + scanGlob = parms[i + 1]; + filesProvided = true; + i++; + break; + case `-c`: + case `--cwd`: + cwd = parms[i + 1]; + i++; + break; + + case `-d`: + case `--outdir`: + outDir = parms[i + 1]; + i++; + break; + + case `-o`: + case `--output`: + output = parms[i + 1]; + i++; + break; + + case `-h`: + case `--help`: + printHelp(); + process.exit(0); + } + } + + if (!filesProvided) { + console.log(`files not provided`); + process.exit(1); + } + + let parser: Parser = new Parser(); + let files: string[]; + + try { + files = getFiles(cwd, scanGlob); + } catch (e) { + error(e.message || e); + process.exit(1); + } + + for (const file of files) { + try { + const content = readFileSync(file, { encoding: `utf-8` }); + + if ( + content.length > 6 && + content.substring(0, 6).toLowerCase() === `**free` + ) { + const docs = await parser.getDocs(file, content, { + withIncludes: true, + collectReferences: true, + }); + + const filterdDocs = JSON.stringify(docs.filterCache(), null, 2); + + if (output) { + const outputPath = path.join(outDir ?? cwd, output); + try { + if (outDir) { + if (!existsSync(outDir)) { + mkdirSync(outDir, { recursive: true }); + } + } + writeFileSync(outputPath, filterdDocs, { + encoding: `utf-8`, + flag: `w`, + }); + } catch (err) { + console.log(err); + } + } else { + console.log(filterdDocs); + } + } + } catch (e) { + error(`failed to parse ${file}: ${e.message || e}`); + process.exit(1); + } + } +} // end of main + +function getFiles(cwd: string, globPath: string): string[] { + return glob.sync(globPath, { + cwd, + absolute: true, + nocase: true, + }); +} + +function error(line: string) { + process.stdout.write(line + `\n`); +} diff --git a/cli/rpgparse/package.json b/cli/rpgparse/package.json new file mode 100644 index 00000000..d56f0e56 --- /dev/null +++ b/cli/rpgparse/package.json @@ -0,0 +1,22 @@ +{ + "name": "@code4i/rpgparse-cli", + "version": "1.0.0", + "description": "A CLI tool for parsing RPG files", + "bin": { + "rpgparse": "./dist/index.js" + }, + "scripts": { + "webpack:dev": "webpack --mode none --config ./webpack.config.js", + "webpack": "webpack --mode production --config ./webpack.config.js", + "test": "webpack --mode none --config ./webpack.config.js && node ./dist/index.js", + "build": "tsc", + "start": "node dist/index.js" + }, + "dependencies": { + }, + "devDependencies": { + "typescript": "^4.8.4" + }, + "author": "Code4i", + "license": "MIT" +} diff --git a/cli/rpgparse/test/copy1.rpgle b/cli/rpgparse/test/copy1.rpgle new file mode 100644 index 00000000..973db342 --- /dev/null +++ b/cli/rpgparse/test/copy1.rpgle @@ -0,0 +1,5 @@ +**FREE + +Dcl-Pr theExtProcedure ExtProc; + theNewValue Char(20); +End-Pr; \ No newline at end of file diff --git a/cli/rpgparse/test/test.rpgle b/cli/rpgparse/test/test.rpgle new file mode 100644 index 00000000..bb19667c --- /dev/null +++ b/cli/rpgparse/test/test.rpgle @@ -0,0 +1,28 @@ +**FREE +Ctl-Opt Main(MainLine); +/// ------------------------------------- +// Main +/// ------------------------------------- +Dcl-Proc MainLine; + Dcl-Pi MainLine Extpgm('MAINTLINE'); + Iof Char(1); + End-Pi; + Dcl-S myString Varchar(20); + + myString = CvtToMixed(myString); +End-Proc; + +/// ------------------------------------- +// CvtToMixed +// Convert the passed string to mixed case or +// what is normally called Title case. +// @param Source string +// @return Title cased string +/// ------------------------------------- +Dcl-Proc CvtToMixed; + Dcl-Pi CvtToMixed Extpgm('MAINTLINE'); + theString Varchar(100); + End-Pi; + + return theString; +End-Proc; \ No newline at end of file diff --git a/cli/rpgparse/tsconfig.json b/cli/rpgparse/tsconfig.json new file mode 100644 index 00000000..5d62abd3 --- /dev/null +++ b/cli/rpgparse/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "ES2019", + "noImplicitAny": false, + "noUnusedParameters": false, + "strict": false, + "allowJs": true, + "outDir": "./dist", + "esModuleInterop": true, + "sourceMap": true + } + } \ No newline at end of file diff --git a/cli/rpgparse/webpack.config.js b/cli/rpgparse/webpack.config.js new file mode 100644 index 00000000..af01baa3 --- /dev/null +++ b/cli/rpgparse/webpack.config.js @@ -0,0 +1,27 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// @ts-nocheck + +'use strict'; + +const withDefaults = require(`../../shared.webpack.config`); +const path = require(`path`); +const webpack = require(`webpack`); + +module.exports = withDefaults({ + context: path.join(__dirname), + entry: { + extension: `./index.ts`, + }, + output: { + filename: `index.js`, + path: path.join(__dirname, `dist`) + }, + // Other stuff + plugins: [ + new webpack.BannerPlugin({banner: `#! /usr/bin/env node`, raw: true}) + ] +}); \ No newline at end of file diff --git a/language/models/cache.ts b/language/models/cache.ts index 00d67268..761cd4b3 100644 --- a/language/models/cache.ts +++ b/language/models/cache.ts @@ -100,6 +100,19 @@ export default class Cache { return (lines.length >= 1 ? lines[0] : 0); } + /** + * Filters the cache to include only indicators that have references. + * + * @returns {Cache} A new Cache instance with filtered indicators. + */ + filterCache(): Cache { + const filteredIndicators = this.indicators.filter(indicator => indicator.references.length > 0); + return new Cache({ + ...this, + indicators: filteredIndicators + }); + } + find(name: string, includeProcedure?: string): Declaration|undefined { name = name.toUpperCase();