-
Notifications
You must be signed in to change notification settings - Fork 175
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[script] Add script to create a new module
This adds a helper script to reduce the boilerplate of creating a new module. It creates the module structure and stubs for all necessary files/directories in the module. The script takes a directory path as an argument, verifies that it doesn't already exist and can be written to, deduces the module name from the last part of the path, and then creates all necessary files doing the minimum of what needs to be done. The module contains a root page and single endpoint named "some_api" as an example. If the --tsx option is specified, a stub tsx index is created as well. Note: SQL is contained in the module's directory rather than the LORIS SQL directory because the script doesn't know if it's a project or LORIS module being created (and if project, where SQL is tracked.)
- Loading branch information
Showing
1 changed file
with
359 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,359 @@ | ||
#!/usr/bin/env php | ||
<?php declare(strict_types=1); | ||
/** | ||
* PHP Version 8 | ||
* | ||
* @author Dave MacFarlane <[email protected]> | ||
* @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3 | ||
* @link https://www.github.com/aces/Loris/ | ||
*/ | ||
|
||
require_once 'generic_includes.php'; | ||
|
||
$flagsEnd = 0; | ||
$flags = getopt("hj", ['jsx', 'help'], $flagsEnd); | ||
|
||
|
||
$jsx = isset($flags['j']) || isset($flags['jsx']); | ||
$help = isset($flags['h']) || isset($flags['help']); | ||
|
||
if ($help) { | ||
usage(); | ||
exit(0); | ||
} | ||
if ($argc !== $flagsEnd+1) { | ||
usage(); | ||
exit(1); | ||
} | ||
$mdir = $argv[$flagsEnd]; | ||
|
||
if (file_exists($mdir)) { | ||
fwrite(STDERR, "$mdir already exists, can not create new module."); | ||
exit(2); | ||
} | ||
|
||
$path = pathinfo($mdir); | ||
if (!empty($path['extension'])) { | ||
fwrite(STDERR, "$mdir should not have a file extension"); | ||
exit(2); | ||
} | ||
|
||
$mname = $path['filename']; | ||
if (!is_writable($path['dirname'])) { | ||
fwrite(STDERR, "Can not create $mname, $path[dirname] is not writeable."); | ||
exit(2); | ||
} | ||
|
||
if (mkdir($mdir) === false) { | ||
fwrite(STDERR, "Can not create directory $mdir"); | ||
exit(2); | ||
|
||
} | ||
if (mkdir($mdir . DIRECTORY_SEPARATOR . "SQL") === false) { | ||
fwrite(STDERR, "Can not create directory $mdir/SQL"); | ||
exit(2); | ||
|
||
} | ||
if (mkdir($mdir . DIRECTORY_SEPARATOR . "php") === false) { | ||
fwrite(STDERR, "Can not create directory $mdir/php"); | ||
exit(2); | ||
|
||
} | ||
|
||
if (mkdir($mdir . DIRECTORY_SEPARATOR . "test") === false) { | ||
fwrite(STDERR, "Can not create directory $mdir/test"); | ||
exit(2); | ||
} | ||
|
||
if (mkdir($mdir . DIRECTORY_SEPARATOR . "help") === false) { | ||
fwrite(STDERR, "Can not create directory $mdir/help"); | ||
exit(2); | ||
} | ||
|
||
|
||
writeFile( | ||
$mdir . DIRECTORY_SEPARATOR . "README.md", | ||
<<<EOF | ||
# $mname | ||
## Purpose | ||
[Short Module purpose description goes here] | ||
## Intended Users | ||
[List intended users of module] | ||
## Scope | ||
[List intended scope of the module] | ||
NOT in scope: | ||
[List possible misinterpretations of scope of module that | ||
are not intended] | ||
## Permissions | ||
The $mname module uses the following permissions. Any one of them | ||
is sufficient to have access to the module. | ||
[List module permissions with a brief description of what each does.] | ||
## Configurations | ||
[List configurations that affect the behaviour of the module. | ||
If necessary, include subheadings for things like database configurations, | ||
filesystem configurations, etc] | ||
## Interactions with LORIS | ||
[List ways that this module interacts with other modules in LORIS if applicable. | ||
If there are no interactions, delete this section] | ||
EOF | ||
); | ||
|
||
writeFile( | ||
$mdir . DIRECTORY_SEPARATOR . "SQL" | ||
. DIRECTORY_SEPARATOR . date("Y-m-d-") . $mname . '.sql', | ||
<<<EOF | ||
INSERT INTO modules (Name, Active) VALUES ('$mname', 'Y'); | ||
/* | ||
INSERT INTO permissions (code, description, moduleID, action) | ||
SELECT '$mname','Access module $mname', m.ID, 'View' | ||
FROM modules m WHERE Name='$mname'; | ||
*/ | ||
/* | ||
CREATE TABLE $mname ( | ||
... | ||
); | ||
*/ | ||
EOF | ||
); | ||
|
||
writeFile( | ||
$mdir . DIRECTORY_SEPARATOR . "php" | ||
. DIRECTORY_SEPARATOR . "module.class.inc", | ||
<<<EOF | ||
<?php declare(strict_types=1); | ||
namespace LORIS\\$mname; | ||
/** | ||
* Module descriptor class for the $mname module. | ||
*/ | ||
class Module extends \Module { | ||
public function getLongName() : string { | ||
// XXX: Return the long, human-readable name of the module here | ||
return "$mname"; | ||
} | ||
} | ||
EOF | ||
); | ||
|
||
writeFile( | ||
$mdir . DIRECTORY_SEPARATOR . "php" | ||
. DIRECTORY_SEPARATOR . "$mname.class.inc", | ||
<<<EOF | ||
<?php declare(strict_types=1); | ||
namespace LORIS\\$mname; | ||
/** | ||
* This class is the main entry point for the module $mname. | ||
* It handles incoming requests to the root page of the module. | ||
*/ | ||
class $mname extends \NDB_Page { | ||
public \$skipTemplate = true; | ||
} | ||
EOF | ||
); | ||
|
||
writeFile( | ||
$mdir . DIRECTORY_SEPARATOR. "php" | ||
. DIRECTORY_SEPARATOR . "some_api.class.inc", | ||
<<<EOF | ||
<?php declare(strict_types=1); | ||
namespace LORIS\\$mname; | ||
use \Psr\Http\Message\ServerRequestInterface; | ||
use \Psr\Http\Message\ResponseInterface; | ||
/** | ||
* This class is an example API endpoint for the module. It | ||
* handles incoming requests to the module url LORIS/$mname/some_api | ||
* and returns JSON. | ||
* | ||
* You may copy/rename it as necessary, or if not required delete | ||
* it. | ||
*/ | ||
class some_api extends \LORIS\Http\Endpoint { | ||
public function _hasAccess(\User \$user) : bool { | ||
// XXX: Add permission checks here if necessary. | ||
return true; | ||
} | ||
public function handle(ServerRequestInterface \$request) : ResponseInterface | ||
{ | ||
// XXX: Do something with the incoming request here. | ||
return new \LORIS\Http\Response\JSON\OK(["success" => "Alright."]); | ||
} | ||
} | ||
EOF | ||
); | ||
|
||
writeFile( | ||
$mdir . DIRECTORY_SEPARATOR. "help" | ||
. DIRECTORY_SEPARATOR . "$mname.md", | ||
<<<EOF | ||
# $mname Help | ||
User help text goes here. | ||
EOF | ||
); | ||
|
||
writeFile( | ||
$mdir . DIRECTORY_SEPARATOR. "test" | ||
. DIRECTORY_SEPARATOR . "TestPlan.md", | ||
<<<EOF | ||
## $mname test plan | ||
[List of steps to be taken to test the module. If necessary, split | ||
into subsections for different features] | ||
EOF | ||
); | ||
|
||
writeFile( | ||
$mdir . DIRECTORY_SEPARATOR. "test" | ||
. DIRECTORY_SEPARATOR . "{$mname}Test.php", | ||
<<<EOF | ||
<?php | ||
// XXX This should be made relative to __DIR__ | ||
require_once "test/integrationtests/LorisIntegrationTest.class.inc"; | ||
class {$mname}Test extends LorisIntegrationTest { | ||
function testPageDoesLoad() | ||
{ | ||
\$this->safeGet(\$this->url . "/$mname/"); | ||
\$bodyText = \$this->safeFindElement( | ||
WebDriverBy::cssSelector("#breadcrumbs") | ||
)->getText(); | ||
\$this->assertStringContainsString("$mname", \$bodyText); | ||
\$this->assertStringNotContainsString( | ||
"You do not have access to this page.", | ||
\$bodyText | ||
); | ||
\$this->assertStringNotContainsString( | ||
"An error occured while loading the page.", | ||
\$bodyText | ||
); | ||
} | ||
function testPageDoesNotLoadWithoutPermissions() | ||
{ | ||
// Without permissions | ||
\$this->setupPermissions(array('')); | ||
\$this->safeGet( | ||
\$this->url . "/$mname/" | ||
); | ||
\$errorText = \$this->safeFindElement( | ||
WebDriverBy::cssSelector("body") | ||
)->getText(); | ||
\$this->assertStringContainsString( | ||
"You do not have access to this page.", | ||
\$errorText | ||
); | ||
} | ||
} | ||
EOF | ||
); | ||
|
||
if ($jsx) { | ||
if (mkdir($mdir . DIRECTORY_SEPARATOR . "jsx") === false) { | ||
fwrite(STDERR, "Can not create directory $mdir/help"); | ||
exit(2); | ||
} | ||
writeFile( | ||
$mdir . DIRECTORY_SEPARATOR. "jsx" | ||
. DIRECTORY_SEPARATOR . "index.tsx", | ||
<<<EOF | ||
import {createRoot} from 'react-dom/client'; | ||
function Index({}) : React.ReactElement { | ||
return <div>Welcome to $mname!</div>; | ||
} | ||
declare const loris: any; | ||
window.addEventListener('load', () => { | ||
const element = document.getElementById('lorisworkspace'); | ||
if (!element) { | ||
throw new Error('Missing lorisworkspace'); | ||
} | ||
const root = createRoot(element); | ||
root.render( | ||
<Index BaseURL={loris.BaseURL} /> | ||
); | ||
}); | ||
EOF | ||
); | ||
} | ||
|
||
|
||
print <<<EOF | ||
Successfully created stub for module $mname in $mdir. | ||
Next steps: | ||
- Adjust permissions in SQL patch in $mname/SQL directory | ||
- Add any necessary CREATE TABLE or other SQL statements to SQL patch | ||
- Source SQL patch into your development database | ||
- Update documentation in README.md | ||
- Update module long name in $mname/php/module.class.inc | ||
- Write test plan for module in $mname/test/TestPlan.md | ||
- Write automated tests for module in $mname/test | ||
- Adjust module as needed (create new endpoints, modify JSX, etc) | ||
- Write user help content for module in $mname/help. | ||
- If dealing with candidate data, add candidate profile widget | ||
in module getWidgets function | ||
EOF; | ||
if ($jsx) { | ||
print "\n- Update webpack.config.js to ensure module entrypoint is compiled"; | ||
} | ||
|
||
/** | ||
* Prints help text for this tool. | ||
* | ||
* @return void | ||
*/ | ||
function usage(): void | ||
{ | ||
global $argv; | ||
print <<<ENDHELP | ||
usage: $argv[0] [--jsx] moduledirectory | ||
Options: | ||
--jsx Add stub jsx file | ||
-h/--help Show this screen | ||
ENDHELP; | ||
|
||
} | ||
|
||
/** | ||
* Writes a file to the filesystem and exits if there is an error | ||
* @param string $filename - The filename to write | ||
* @param string $content - The content to put into the file | ||
* | ||
* @return void | ||
*/ | ||
function writeFile(string $filename, string $content) | ||
{ | ||
if (file_put_contents($filename, $content) === false) { | ||
fwrite(STDERR, "Could not write $filename"); | ||
exit(2); | ||
} | ||
} | ||
|