Skip to content

Commit 737cfe7

Browse files
committed
QScripts v1.2.3
- bugfix: package dependencies reloading was not working properly - added 'pkgparentmodname' variable
1 parent 00e22c4 commit 737cfe7

4 files changed

Lines changed: 72 additions & 56 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ See also:
6767
* `$basename$`: This variable is expanded to the base name of the current dependency line
6868
* `$env:EnvVariableName$`: `EnvVariableName` is expanded to its environment variable value if it exists or left unexpanded otherwise
6969
* `$pkgbase$`: Specify a package base directory. Can be used as part of a dependency file path.
70-
* `$pkgmodname$`: This is mainly used inside the `reload` directive. It replaces the path seperators with dots (which works nicely as a fully qualified module name). Please see the [package dependency](test_scripts/pkg-dependency/README.md) example file.
70+
* `$pkgparentmodname$` and `$pkgmodname$`: These are mainly used inside the `reload` directive. They help with proper [package dependency](test_scripts/pkg-dependency/README.md) reloading.
7171
* `$ext$`: This resolves to the plugin suffix and extension ("64.dll", ".so", "64.dylib", etc.). See the trigger native deps files for reference.
7272

7373
## Using QScripts with trigger files

qscripts.cpp

Lines changed: 52 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ struct qscripts_chooser_t: public plugmod_t, public chooser_t
141141
if (ctx.main_file)
142142
{
143143
ctx.pkg_base = val;
144+
expand_file_name(ctx.pkg_base, ctx);
144145
make_abs_path(ctx.pkg_base, ctx.base_dir.c_str(), true);
145146
}
146147
continue;
@@ -170,6 +171,7 @@ struct qscripts_chooser_t: public plugmod_t, public chooser_t
170171
// From here on, the *line* variable is an expandable string leading to a script file
171172
ctx.script_file = line;
172173
expand_file_name(line, ctx);
174+
normalize_path_sep(line);
173175

174176
// Skip dependency scripts that (do not|no longer) exist
175177
script_info_t dep_script;
@@ -225,14 +227,38 @@ struct qscripts_chooser_t: public plugmod_t, public chooser_t
225227
bool is_monitor_active() const { return m_b_filemon_timer_active; }
226228
bool is_filemon_timer_installed() const { return m_filemon_timer != nullptr; }
227229

228-
// Dynamic string expansion
229-
// ------------------------
230-
// basename Returns the basename of the input file
231-
// env:Variable_Name Expands the 'Variable_Name'
232-
// pkgbase Sets the current pkgbase path
233-
// pkgmodname Expands the file name using the pckbase into the form: 'module.submodule1.submodule2'
234-
// ext Addon suffix including bitness and extension (example: 64.dll, .so, 64.so, .dylib, etc.)
235-
void expand_string(qstring &input, qstring &output, const expand_ctx_t& ctx)
230+
std::string expand_pkgmodname(const expand_ctx_t& ctx)
231+
{
232+
auto dep_file = selected_script.has_dep(ctx.script_file.c_str());
233+
qstring pkg_base = dep_file == nullptr ? selected_script.pkg_base : dep_file->pkg_base;
234+
235+
// If the script file is in the package base, then replace the path separators with '.'
236+
if (strncmp(ctx.script_file.c_str(), pkg_base.c_str(), pkg_base.length()) == 0)
237+
{
238+
qstring s = ctx.script_file.c_str() + pkg_base.length() + 1;
239+
s.replace(SDIRCHAR, ".");
240+
// Drop the extension too
241+
auto idx = s.rfind('.');
242+
if (idx != -1)
243+
s.resize(idx);
244+
245+
return s.c_str();
246+
}
247+
return "";
248+
}
249+
250+
// Dynamic string expansion Description
251+
// ------------------------ -----------
252+
// basename Returns the basename of the input file
253+
// env:Variable_Name Expands the 'Variable_Name'
254+
// pkgbase Sets the current pkgbase path
255+
// pkgmodname Expands the file name using the pkgbase into the form: 'module.submodule1.submodule2'
256+
// pkgparentmodname Expands the file name using the pkgbase into the form up to the parent module: 'module.submodule1'
257+
// ext Add-on suffix including bitness and extension (example: 64.dll, .so, 64.so, .dylib, etc.)
258+
void expand_string(
259+
qstring &input,
260+
qstring &output,
261+
const expand_ctx_t& ctx)
236262
{
237263
output = std::regex_replace(
238264
input.c_str(),
@@ -243,22 +269,13 @@ struct qscripts_chooser_t: public plugmod_t, public chooser_t
243269

244270
if (strncmp(match1.c_str(), "pkgmodname", 10) == 0)
245271
{
246-
auto dep_file = selected_script.has_dep(ctx.script_file.c_str());
247-
qstring pkg_base = dep_file == nullptr ? selected_script.pkg_base : dep_file->pkg_base;
248-
249-
// If the script file is in the package base, then replace the path separators with '.'
250-
if (strncmp(ctx.script_file.c_str(), pkg_base.c_str(), pkg_base.length()) == 0)
251-
{
252-
qstring s = ctx.script_file.c_str() + pkg_base.length() + 1;
253-
s.replace(SDIRCHAR, ".");
254-
// Drop the extension too
255-
auto idx = s.rfind('.');
256-
if (idx != -1)
257-
s.resize(idx);
258-
259-
return s.c_str();
260-
}
261-
return "";
272+
return expand_pkgmodname(ctx);
273+
}
274+
else if (strncmp(match1.c_str(), "pkgparentmodname", 16) == 0)
275+
{
276+
std::string pkgmodname = expand_pkgmodname(ctx);
277+
size_t pos = pkgmodname.rfind('.');
278+
return pos == std::string::npos ? pkgmodname : pkgmodname.substr(0, pos);
262279
}
263280
else if (strncmp(match1.c_str(), "ext", 3) == 0)
264281
{
@@ -307,15 +324,21 @@ struct qscripts_chooser_t: public plugmod_t, public chooser_t
307324
qstring reload_cmd;
308325
expand_ctx_t ctx;
309326
ctx.script_file = script_file;
327+
ctx.pkg_base = dep_script_file.pkg_base;
310328
expand_string(dep_script_file.reload_cmd, reload_cmd, ctx);
311329

312330
if (!elang->eval_snippet(reload_cmd.c_str(), &err))
331+
{
332+
err.sprnt("failed to execute the reload directive for '%s' with command: %s!\n",
333+
script_file,
334+
reload_cmd.c_str());
313335
break;
336+
}
314337
return true;
315338
} while (false);
316339

317340
if (!silent)
318-
msg("QScripts failed to reload script file: '%s':\n%s", script_file, err.c_str());
341+
msg("QScripts failed to reload script file: '%s'\nReload command used: %s", script_file, err.c_str());
319342

320343
return false;
321344
}
@@ -339,7 +362,7 @@ struct qscripts_chooser_t: public plugmod_t, public chooser_t
339362
{
340363
auto script_file = script_info->file_path.c_str();
341364

342-
// First things first: always take the file's modification timestamp first so not to visit it again in the file monitor timer
365+
// First things first: always take the file's modification time-stamp first so not to visit it again in the file monitor timer
343366
if (!get_file_modification_time(script_file, &script_info->modified_time))
344367
{
345368
msg("Script file '%s' not found!\n", script_file);
@@ -392,7 +415,8 @@ struct qscripts_chooser_t: public plugmod_t, public chooser_t
392415
return exec_ok;
393416
}
394417

395-
enum {
418+
enum
419+
{
396420
OPTID_INTERVAL = 0x0001,
397421
OPTID_CLEARLOG = 0x0002,
398422
OPTID_SHOWNAME = 0x0004,
@@ -537,9 +561,8 @@ struct qscripts_chooser_t: public plugmod_t, public chooser_t
537561
qstring err;
538562
dep_script_changed = true;
539563
if ( dep_script.has_reload_directive()
540-
&& !execute_reload_directive(dep_script, err))
564+
&& !execute_reload_directive(dep_script, err, false))
541565
{
542-
msg("QScripts: warning: failed to execute reload directive: %s\n", err.c_str());
543566
brk = true;
544567
break;
545568
}
Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,18 @@
1-
This is an example where we have a dependency file that relies on a package.
1+
In this example, we're dealing with a dependency file for a package that's currently being developed. This setup is designed to ensure that modules from the package, `idapyx` in this instance, are automatically reloaded upon any changes.
22

3-
The package (supposedly) is under development and you want to have its modules be reloaded when they change.
3+
For this purpose, we can either explicitly specify the package's full path or refer to it through an environment variable, as demonstrated below:
44

5+
```plaintext
6+
# Define package base folder
7+
/pkgbase $env:idapyx$
8+
# Automatically reload the package's modules when they change
9+
/reload import importlib; from $pkgparentmodname$ import $basename$ as __qscripts_autoreload__; importlib.reload(__qscripts_autoreload__)
10+
# Specify the paths to the package modules that need to be reloaded if they change
11+
$pkgbase$/idapyx/bin/pe/rtfuncs.py
12+
$pkgbase$/idapyx/bin/pe/types.py
513
```
6-
# Non package dependencies first
7-
/reload import importlib;importlib.reload($basename$)
8-
dep.py
914

10-
# Package dependencies
11-
/pkgbase C:\Python38\Lib\site-packages
12-
/reload import importlib;import $pkgmodname$;importlib.reload($pkgmodname$)
13-
14-
$pkgbase$\PIL\BlpImagePlugin.py
15-
$pkgbase$\PIL\ContainerIO.py
16-
```
17-
18-
* We start by describing the local dependencies if any. Note the Python specific reload directive
19-
* Then we use `pkgbase` to define the package base. This will help with the reload directive
20-
* The reload directive uses the special variable `pkgmodname` that will use the package directory to formulate a Python module name
21-
* Finally, we define our dependencies relative to their package base folder using the variable `pkgbase`
15+
- The `/pkgbase` directive specifies the base path of the package, aiding in the module reload process.
16+
- The reload directive employs the `pkgmodparentname` variable to derive the Python parent module name based on the package directory.
17+
- Similarly, the reload directive utilizes the `basename` variable to determine the Python module name, again using the package directory.
18+
- Dependencies are specified relative to their package base folder, facilitated by the `pkgbase` variable.
Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
1-
# Non package dependencies first
2-
/reload import importlib;importlib.reload($basename$)
3-
dep.py
1+
# Package dependencies (here, we use the environment variable 'IDAPYX' that contains the package path
2+
/pkgbase $env:idapyx$
3+
/reload import importlib;from $pkgparentmodname$ import $basename$ as __qscripts_autoreload__;importlib.reload(__qscripts_autoreload__)
4+
$pkgbase$/idapyx/bin/pe/rtfuncs.py
5+
$pkgbase$/idapyx/bin/pe/types.py
46

5-
# Package dependencies
6-
/pkgbase C:\Python38\Lib\site-packages
7-
/reload import importlib;import $pkgmodname$;importlib.reload($pkgmodname$)
8-
9-
$pkgbase$\PIL\BlpImagePlugin.py
10-
$pkgbase$\PIL\ContainerIO.py

0 commit comments

Comments
 (0)