Skip to content

Commit d1b35f3

Browse files
authored
Merge pull request #5 from logos-co/improve_lm_binary
Supressed debug outputs when running LM command; improve default behaviour and experience
2 parents f7ee69d + 40feaf0 commit d1b35f3

File tree

6 files changed

+223
-33
lines changed

6 files changed

+223
-33
lines changed

README.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,20 @@ nix build .#all
3232

3333
## Using the `lm` Binary
3434

35-
Show plugin metadata:
35+
Show both metadata and methods (default):
36+
```bash
37+
lm /path/to/plugin.dylib
38+
lm /path/to/plugin.dylib --json
39+
lm /path/to/plugin.dylib --debug
40+
```
41+
42+
Show plugin metadata only:
3643
```bash
3744
lm metadata /path/to/plugin.dylib
3845
lm metadata /path/to/plugin.dylib --json
3946
```
4047

41-
Show plugin methods:
48+
Show plugin methods only:
4249
```bash
4350
lm methods /path/to/plugin.dylib
4451
lm methods /path/to/plugin.dylib --json

cmd/main.cpp

Lines changed: 207 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
#include <iostream>
88
#include <vector>
99
#include <string>
10+
#include <unistd.h>
11+
#include <fcntl.h>
1012

1113
#include "logos_module.h"
1214
#include "module_metadata.h"
@@ -18,29 +20,67 @@ QTextStream err(stderr);
1820

1921
const char* VERSION = "0.1.0";
2022

23+
// Global flag for debug mode
24+
static bool g_debugMode = false;
25+
26+
// Custom Qt message handler to suppress debug/info messages unless in debug mode
27+
void customMessageHandler(QtMsgType type, const QMessageLogContext& context, const QString& msg) {
28+
if (!g_debugMode && (type == QtDebugMsg || type == QtInfoMsg)) {
29+
// Suppress debug and info messages when not in debug mode
30+
return;
31+
}
32+
33+
// For warnings and errors, or when in debug mode, use default handling
34+
QByteArray localMsg = msg.toLocal8Bit();
35+
const char* file = context.file ? context.file : "";
36+
const char* function = context.function ? context.function : "";
37+
38+
switch (type) {
39+
case QtDebugMsg:
40+
fprintf(stderr, "Debug: %s (%s:%u, %s)\n", localMsg.constData(), file, context.line, function);
41+
break;
42+
case QtInfoMsg:
43+
fprintf(stderr, "Info: %s (%s:%u, %s)\n", localMsg.constData(), file, context.line, function);
44+
break;
45+
case QtWarningMsg:
46+
fprintf(stderr, "Warning: %s (%s:%u, %s)\n", localMsg.constData(), file, context.line, function);
47+
break;
48+
case QtCriticalMsg:
49+
fprintf(stderr, "Critical: %s (%s:%u, %s)\n", localMsg.constData(), file, context.line, function);
50+
break;
51+
case QtFatalMsg:
52+
fprintf(stderr, "Fatal: %s (%s:%u, %s)\n", localMsg.constData(), file, context.line, function);
53+
abort();
54+
}
55+
}
56+
2157
void printVersion() {
2258
out << "lm (Logos Module) version " << VERSION << Qt::endl;
2359
}
2460

2561
void printUsage() {
2662
out << "lm - Logos Module Inspector\n"
2763
<< "\n"
28-
<< "Usage: lm <command> [options] <plugin-path>\n"
64+
<< "Usage: lm [command] [options] <plugin-path>\n"
2965
<< "\n"
3066
<< "Commands:\n"
67+
<< " (default) Show both metadata and methods (when no command specified)\n"
3168
<< " metadata Show plugin metadata (name, version, description, etc.)\n"
3269
<< " methods Show plugin methods and signatures\n"
3370
<< "\n"
3471
<< "Options:\n"
3572
<< " --json Output in JSON format\n"
73+
<< " --debug Show debug output from plugin loading\n"
3674
<< " --help, -h Show help information\n"
3775
<< " --version, -v Show version information\n"
3876
<< "\n"
3977
<< "Examples:\n"
78+
<< " lm /path/to/plugin.so\n"
79+
<< " lm /path/to/plugin.so --json\n"
4080
<< " lm metadata /path/to/plugin.so\n"
4181
<< " lm methods /path/to/plugin.so\n"
4282
<< " lm metadata /path/to/plugin.so --json\n"
43-
<< " lm methods /path/to/plugin.so --json\n";
83+
<< " lm methods /path/to/plugin.so --json --debug\n";
4484
}
4585

4686
void printCommandHelp(const QString& command) {
@@ -51,15 +91,17 @@ void printCommandHelp(const QString& command) {
5191
<< "type, and dependencies.\n"
5292
<< "\n"
5393
<< "Options:\n"
54-
<< " --json Output in JSON format\n";
94+
<< " --json Output in JSON format\n"
95+
<< " --debug Show debug output from plugin loading\n";
5596
} else if (command == "methods") {
5697
out << "Usage: lm methods [options] <plugin-path>\n"
5798
<< "\n"
5899
<< "Show all methods exposed by the plugin via Qt's meta-object system.\n"
59100
<< "Displays method name, signature, return type, and parameters.\n"
60101
<< "\n"
61102
<< "Options:\n"
62-
<< " --json Output in JSON format\n";
103+
<< " --json Output in JSON format\n"
104+
<< " --debug Show debug output from plugin loading\n";
63105
}
64106
}
65107

@@ -137,26 +179,22 @@ int cmdMetadata(const QString& pluginPath, bool jsonOutput) {
137179
return 1;
138180
}
139181

140-
QString errorString;
141-
LogosModule plugin = LogosModule::loadFromPath(absolutePath, &errorString);
142-
143-
if (!plugin.isValid()) {
144-
err << "Error: Failed to load plugin: " << errorString << Qt::endl;
182+
auto metadata = LogosModule::extractMetadata(absolutePath);
183+
if (!metadata) {
184+
err << "Error: Failed to extract metadata from: " << pluginPath << Qt::endl;
145185
return 1;
146186
}
147187

148-
const ModuleMetadata& metadata = plugin.metadata();
149-
150188
if (jsonOutput) {
151-
printMetadataJson(metadata);
189+
printMetadataJson(*metadata);
152190
} else {
153-
printMetadataHuman(metadata);
191+
printMetadataHuman(*metadata);
154192
}
155193

156194
return 0;
157195
}
158196

159-
int cmdMethods(const QString& pluginPath, bool jsonOutput) {
197+
int cmdMethods(const QString& pluginPath, bool jsonOutput, bool debugOutput) {
160198
QFileInfo fileInfo(pluginPath);
161199
QString absolutePath = fileInfo.absoluteFilePath();
162200

@@ -166,7 +204,36 @@ int cmdMethods(const QString& pluginPath, bool jsonOutput) {
166204
}
167205

168206
QString errorString;
169-
LogosModule plugin = LogosModule::loadFromPath(absolutePath, &errorString);
207+
LogosModule plugin;
208+
209+
if (!debugOutput) {
210+
// Redirect stdout and stderr to /dev/null during plugin loading
211+
int stdout_copy = dup(STDOUT_FILENO);
212+
int stderr_copy = dup(STDERR_FILENO);
213+
214+
int devnull = open("/dev/null", O_WRONLY);
215+
if (devnull != -1) {
216+
dup2(devnull, STDOUT_FILENO);
217+
dup2(devnull, STDERR_FILENO);
218+
close(devnull);
219+
}
220+
221+
// Load the plugin (this may trigger constructor output)
222+
plugin = LogosModule::loadFromPath(absolutePath, &errorString);
223+
224+
// Restore stdout and stderr
225+
if (stdout_copy != -1) {
226+
dup2(stdout_copy, STDOUT_FILENO);
227+
close(stdout_copy);
228+
}
229+
if (stderr_copy != -1) {
230+
dup2(stderr_copy, STDERR_FILENO);
231+
close(stderr_copy);
232+
}
233+
} else {
234+
// Debug mode: load normally without suppression
235+
plugin = LogosModule::loadFromPath(absolutePath, &errorString);
236+
}
170237

171238
if (!plugin.isValid()) {
172239
err << "Error: Failed to load plugin: " << errorString << Qt::endl;
@@ -188,9 +255,100 @@ int cmdMethods(const QString& pluginPath, bool jsonOutput) {
188255
return 0;
189256
}
190257

258+
int cmdInfo(const QString& pluginPath, bool jsonOutput, bool debugOutput) {
259+
QFileInfo fileInfo(pluginPath);
260+
QString absolutePath = fileInfo.absoluteFilePath();
261+
262+
if (!fileInfo.exists()) {
263+
err << "Error: Plugin file not found: " << pluginPath << Qt::endl;
264+
return 1;
265+
}
266+
267+
if (jsonOutput) {
268+
// For JSON output, combine metadata and methods into a single object
269+
auto metadata = LogosModule::extractMetadata(absolutePath);
270+
if (!metadata) {
271+
err << "Error: Failed to extract metadata from: " << pluginPath << Qt::endl;
272+
return 1;
273+
}
274+
275+
// Load plugin for methods
276+
QString errorString;
277+
LogosModule plugin;
278+
279+
if (!debugOutput) {
280+
// Suppress output during loading
281+
int stdout_copy = dup(STDOUT_FILENO);
282+
int stderr_copy = dup(STDERR_FILENO);
283+
284+
int devnull = open("/dev/null", O_WRONLY);
285+
if (devnull != -1) {
286+
dup2(devnull, STDOUT_FILENO);
287+
dup2(devnull, STDERR_FILENO);
288+
close(devnull);
289+
}
290+
291+
plugin = LogosModule::loadFromPath(absolutePath, &errorString);
292+
293+
if (stdout_copy != -1) {
294+
dup2(stdout_copy, STDOUT_FILENO);
295+
close(stdout_copy);
296+
}
297+
if (stderr_copy != -1) {
298+
dup2(stderr_copy, STDERR_FILENO);
299+
close(stderr_copy);
300+
}
301+
} else {
302+
plugin = LogosModule::loadFromPath(absolutePath, &errorString);
303+
}
304+
305+
if (!plugin.isValid()) {
306+
err << "Error: Failed to load plugin: " << errorString << Qt::endl;
307+
return 1;
308+
}
309+
310+
// Build combined JSON object
311+
QJsonObject combined;
312+
313+
QJsonObject metadataObj;
314+
metadataObj["name"] = metadata->name;
315+
metadataObj["version"] = metadata->version;
316+
metadataObj["description"] = metadata->description;
317+
metadataObj["author"] = metadata->author;
318+
metadataObj["type"] = metadata->type;
319+
320+
QJsonArray deps;
321+
for (const QString& dep : metadata->dependencies) {
322+
deps.append(dep);
323+
}
324+
metadataObj["dependencies"] = deps;
325+
326+
combined["metadata"] = metadataObj;
327+
combined["methods"] = LogosModule::getMethodsAsJson(plugin.instance());
328+
329+
QJsonDocument doc(combined);
330+
out << doc.toJson(QJsonDocument::Indented);
331+
} else {
332+
// For human-readable output, print metadata then methods with a separator
333+
int result = cmdMetadata(pluginPath, false);
334+
if (result != 0) {
335+
return result;
336+
}
337+
338+
out << "\n";
339+
340+
return cmdMethods(pluginPath, false, debugOutput);
341+
}
342+
343+
return 0;
344+
}
345+
191346
int main(int argc, char* argv[]) {
192347
QCoreApplication app(argc, argv);
193348

349+
// Install custom message handler to suppress debug output by default
350+
qInstallMessageHandler(customMessageHandler);
351+
194352
std::vector<std::string> args;
195353
for (int i = 1; i < argc; ++i) {
196354
args.push_back(argv[i]);
@@ -213,24 +371,40 @@ int main(int argc, char* argv[]) {
213371
return 0;
214372
}
215373

216-
std::string command = firstArg;
374+
std::string command;
375+
bool defaultMode = false;
217376
bool jsonOutput = false;
377+
bool debugOutput = false;
218378
QString pluginPath;
219379

220-
if (command != "metadata" && command != "methods") {
221-
err << "Error: Unknown command '" << QString::fromStdString(command) << "'\n"
222-
<< "\nRun 'lm --help' to see available commands.\n";
380+
// Check if first arg is a command or a plugin path
381+
if (firstArg == "metadata" || firstArg == "methods") {
382+
command = firstArg;
383+
} else if (firstArg[0] != '-') {
384+
// First arg is not a command and not an option, treat as plugin path
385+
defaultMode = true;
386+
pluginPath = QString::fromStdString(firstArg);
387+
} else {
388+
err << "Error: Unknown option '" << QString::fromStdString(firstArg) << "'" << Qt::endl;
223389
return 1;
224390
}
225391

226-
for (size_t i = 1; i < args.size(); ++i) {
392+
// Parse remaining arguments
393+
size_t startIdx = defaultMode ? 1 : 1; // Start after command or plugin path
394+
for (size_t i = startIdx; i < args.size(); ++i) {
227395
std::string arg = args[i];
228396

229397
if (arg == "--help" || arg == "-h") {
230-
printCommandHelp(QString::fromStdString(command));
398+
if (defaultMode) {
399+
printUsage();
400+
} else {
401+
printCommandHelp(QString::fromStdString(command));
402+
}
231403
return 0;
232404
} else if (arg == "--json") {
233405
jsonOutput = true;
406+
} else if (arg == "--debug") {
407+
debugOutput = true;
234408
} else if (arg[0] == '-') {
235409
err << "Error: Unknown option '" << QString::fromStdString(arg) << "'" << Qt::endl;
236410
return 1;
@@ -246,14 +420,23 @@ int main(int argc, char* argv[]) {
246420

247421
if (pluginPath.isEmpty()) {
248422
err << "Error: Missing plugin path" << Qt::endl;
249-
err << "\nUsage: lm " << QString::fromStdString(command) << " [options] <plugin-path>" << Qt::endl;
423+
if (defaultMode) {
424+
err << "\nUsage: lm [options] <plugin-path>" << Qt::endl;
425+
} else {
426+
err << "\nUsage: lm " << QString::fromStdString(command) << " [options] <plugin-path>" << Qt::endl;
427+
}
250428
return 1;
251429
}
252430

253-
if (command == "metadata") {
431+
// Set global debug flag
432+
g_debugMode = debugOutput;
433+
434+
if (defaultMode) {
435+
return cmdInfo(pluginPath, jsonOutput, debugOutput);
436+
} else if (command == "metadata") {
254437
return cmdMetadata(pluginPath, jsonOutput);
255438
} else if (command == "methods") {
256-
return cmdMethods(pluginPath, jsonOutput);
439+
return cmdMethods(pluginPath, jsonOutput, debugOutput);
257440
}
258441

259442
return 0;

flake.nix

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@
3737
# lm binary package
3838
lm = bin;
3939

40+
# Additional aliases for the binary
41+
cli = bin;
42+
bin = bin;
43+
cmd = bin;
44+
4045
# logos-module library
4146
lib = lib;
4247

0 commit comments

Comments
 (0)