Skip to content

Commit ea92ad3

Browse files
committed
Command: adds parallel and useStdErr options
Fixes #2045
1 parent 7aaad5e commit ea92ad3

File tree

9 files changed

+124
-32
lines changed

9 files changed

+124
-32
lines changed

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,7 @@ set(LIBFASTFETCH_SRC
389389
src/detection/chassis/chassis.c
390390
src/detection/cpu/cpu.c
391391
src/detection/cpuusage/cpuusage.c
392+
src/detection/command/command.c
392393
src/detection/disk/disk.c
393394
src/detection/diskio/diskio.c
394395
src/detection/displayserver/displayserver.c

doc/json_schema.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2101,6 +2101,16 @@
21012101
"description": "Set the command text to be executed",
21022102
"type": "string"
21032103
},
2104+
"useStdErr": {
2105+
"description": "Set if stderr should be used instead of stdout for command output",
2106+
"type": "boolean",
2107+
"default": false
2108+
},
2109+
"parallel": {
2110+
"description": "Set if the command should be executed in parallel with other commands\nMight improve performance when using multiple commands, but may cause issues with some commands",
2111+
"type": "boolean",
2112+
"default": false
2113+
},
21042114
"key": {
21052115
"$ref": "#/$defs/key"
21062116
},

presets/examples/25.jsonc

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,8 @@
174174
"keyIcon": "", // Custom icon override
175175
"key": "│{#red}│ {icon} Rust │{$4}│{#keys}│{$2}",
176176
"text": "rustc --version",
177-
"format": "rustc {~6,13}" // Print 6th to 13th characters (version number)
177+
"format": "rustc {~6,13}", // Print 6th to 13th characters (version number)
178+
"parallel": true // Enable parallel execution for performance
178179
},
179180
{
180181
"type": "command",
@@ -194,28 +195,32 @@
194195
"keyIcon": "",
195196
"key": "│{#red}│ {icon} Clang │{$4}│{#keys}│{$2}",
196197
"text": "clang --version | findstr version", // Finds the line with "version"
197-
"format": "clang {~-6}" // Prints the last 6 characters (version number)
198+
"format": "clang {~-6}", // Prints the last 6 characters (version number)
199+
"parallel": true
198200
},
199201
{
200202
"type": "command",
201203
"keyIcon": "",
202204
"key": "│{#red}│ {icon} NodeJS │{$4}│{#keys}│{$2}",
203205
"text": "node --version",
204-
"format": "node {~1}" // {~1} removes first character (v)
206+
"format": "node {~1}", // {~1} removes first character (v)
207+
"parallel": true
205208
},
206209
{
207210
"type": "command",
208211
"keyIcon": "",
209212
"key": "│{#red}│ {icon} Go │{$4}│{#keys}│{$2}",
210213
"text": "go version | cut -d' ' -f3",
211-
"format": "go {~2}" // {~2} removes first 2 characters (go)
214+
"format": "go {~2}", // {~2} removes first 2 characters (go)
215+
"parallel": true
212216
},
213217
{
214218
"type": "command",
215219
"keyIcon": "",
216220
"key": "│{#red}│ {icon} Zig │{$4}│{#keys}│{$2}",
217221
"text": "zig version",
218-
"format": "zig {}"
222+
"format": "zig {}",
223+
"parallel": true
219224
},
220225
{
221226
"type": "editor",
@@ -226,7 +231,8 @@
226231
"keyIcon": "󰊢",
227232
"key": "│{#red}│ {icon} Git │{$4}│{#keys}│{$2}",
228233
"text": "git version",
229-
"format": "git {~12}"
234+
"format": "git {~12}",
235+
"parallel": true
230236
},
231237
{
232238
"type": "font",

src/common/jsonconfig.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,16 @@ static void prepareModuleJsonObject(const char* type, yyjson_val* module)
158158
ffPrepareCPUUsage();
159159
break;
160160
}
161+
case 'c': case 'C': {
162+
if (ffStrEqualsIgnCase(type, FF_COMMAND_MODULE_NAME))
163+
{
164+
__attribute__((__cleanup__(ffDestroyCommandOptions))) FFCommandOptions options;
165+
ffInitCommandOptions(&options);
166+
if (module) ffCommandModuleInfo.parseJsonObject(&options, module);
167+
ffPrepareCommand(&options);
168+
}
169+
break;
170+
}
161171
case 'd': case 'D': {
162172
if (ffStrEqualsIgnCase(type, FF_DISKIO_MODULE_NAME))
163173
{

src/detection/command/command.c

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
#include "common/processing.h"
2+
#include "util/FFstrbuf.h"
3+
#include "detection/command/command.h"
4+
5+
typedef struct FFCommandResultBundle
6+
{
7+
FFProcessHandle handle;
8+
const char* error;
9+
} FFCommandResultBundle;
10+
11+
// FIFO, non-thread-safe list of running commands
12+
static FFlist commandQueue = {
13+
.elementSize = sizeof(FFCommandResultBundle),
14+
};
15+
16+
static const char* spawnProcess(FFCommandOptions* options, FFProcessHandle* handle)
17+
{
18+
if (options->text.length == 0)
19+
return "No command text specified";
20+
21+
return ffProcessSpawn(options->param.length ? (char* const[]){
22+
options->shell.chars,
23+
options->param.chars,
24+
options->text.chars,
25+
NULL
26+
} : (char* const[]){
27+
options->shell.chars,
28+
options->text.chars,
29+
NULL
30+
}, options->useStdErr, handle);
31+
}
32+
33+
bool ffPrepareCommand(FFCommandOptions* options)
34+
{
35+
if (!options->parallel) return false;
36+
37+
FFCommandResultBundle* bundle = ffListAdd(&commandQueue);
38+
bundle->error = spawnProcess(options, &bundle->handle);
39+
40+
return true;
41+
}
42+
43+
const char* ffDetectCommand(FFCommandOptions* options, FFstrbuf* result)
44+
{
45+
FFCommandResultBundle bundle;
46+
if (!options->parallel)
47+
bundle.error = spawnProcess(options, &bundle.handle);
48+
else
49+
ffListShift(&commandQueue, &bundle);
50+
51+
if (bundle.error)
52+
return bundle.error;
53+
54+
bundle.error = ffProcessReadOutput(&bundle.handle, result);
55+
if (bundle.error)
56+
return bundle.error;
57+
58+
ffStrbufTrimRightSpace(result);
59+
return NULL;
60+
}

src/detection/command/command.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#pragma once
2+
3+
#include "fastfetch.h"
4+
#include "modules/command/option.h"
5+
6+
const char* ffDetectCommand(FFCommandOptions* options, FFstrbuf* result);

src/modules/command/command.c

Lines changed: 21 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,20 @@
11
#include "common/printing.h"
22
#include "common/jsonconfig.h"
3-
#include "common/processing.h"
43
#include "modules/command/command.h"
5-
#include "util/stringUtils.h"
4+
#include "detection/command/command.h"
65

76
bool ffPrintCommand(FFCommandOptions* options)
87
{
98
FF_STRBUF_AUTO_DESTROY result = ffStrbufCreate();
10-
const char* error = ffProcessAppendStdOut(&result, options->param.length ? (char* const[]){
11-
options->shell.chars,
12-
options->param.chars,
13-
options->text.chars,
14-
NULL
15-
} : (char* const[]){
16-
options->shell.chars,
17-
options->text.chars,
18-
NULL
19-
});
9+
const char* error = ffDetectCommand(options, &result);
2010

21-
if(error)
11+
if (error)
2212
{
2313
ffPrintError(FF_COMMAND_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", error);
2414
return false;
2515
}
2616

27-
if(!result.length)
17+
if (!result.length)
2818
{
2919
ffPrintError(FF_COMMAND_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "No result generated");
3020
return false;
@@ -72,6 +62,18 @@ void ffParseCommandJsonObject(FFCommandOptions* options, yyjson_val* module)
7262
continue;
7363
}
7464

65+
if (unsafe_yyjson_equals_str(key, "useStdErr"))
66+
{
67+
options->useStdErr = yyjson_get_bool(val);
68+
continue;
69+
}
70+
71+
if (unsafe_yyjson_equals_str(key, "parallel"))
72+
{
73+
options->parallel = yyjson_get_bool(val);
74+
continue;
75+
}
76+
7577
ffPrintError(FF_COMMAND_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key));
7678
}
7779
}
@@ -81,25 +83,16 @@ void ffGenerateCommandJsonConfig(FFCommandOptions* options, yyjson_mut_doc* doc,
8183
ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs);
8284

8385
yyjson_mut_obj_add_strbuf(doc, module, "shell", &options->shell);
84-
8586
yyjson_mut_obj_add_strbuf(doc, module, "param", &options->param);
86-
8787
yyjson_mut_obj_add_strbuf(doc, module, "text", &options->text);
88+
yyjson_mut_obj_add_bool(doc, module, "useStdErr", options->useStdErr);
89+
yyjson_mut_obj_add_bool(doc, module, "parallel", options->parallel);
8890
}
8991

9092
bool ffGenerateCommandJsonResult(FF_MAYBE_UNUSED FFCommandOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module)
9193
{
9294
FF_STRBUF_AUTO_DESTROY result = ffStrbufCreate();
93-
const char* error = ffProcessAppendStdOut(&result, options->param.length ? (char* const[]){
94-
options->shell.chars,
95-
options->param.chars,
96-
options->text.chars,
97-
NULL
98-
} : (char* const[]){
99-
options->shell.chars,
100-
options->text.chars,
101-
NULL
102-
});
95+
const char* error = ffDetectCommand(options, &result);
10396

10497
if(error)
10598
{
@@ -137,6 +130,8 @@ void ffInitCommandOptions(FFCommandOptions* options)
137130
#endif
138131
);
139132
ffStrbufInit(&options->text);
133+
options->useStdErr = false;
134+
options->parallel = false;
140135
}
141136

142137
void ffDestroyCommandOptions(FFCommandOptions* options)

src/modules/command/command.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
#define FF_COMMAND_MODULE_NAME "Command"
66

7+
bool ffPrepareCommand(FFCommandOptions* options);
8+
79
bool ffPrintCommand(FFCommandOptions* options);
810
void ffInitCommandOptions(FFCommandOptions* options);
911
void ffDestroyCommandOptions(FFCommandOptions* options);

src/modules/command/option.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ typedef struct FFCommandOptions
99
FFstrbuf shell;
1010
FFstrbuf param;
1111
FFstrbuf text;
12+
bool useStdErr;
13+
bool parallel;
1214
} FFCommandOptions;
1315

1416
static_assert(sizeof(FFCommandOptions) <= FF_OPTION_MAX_SIZE, "FFCommandOptions size exceeds maximum allowed size");

0 commit comments

Comments
 (0)