2021-02-18 22:25:36 +01:00
|
|
|
#include "fastfetch.h"
|
2026-01-15 15:03:38 +08:00
|
|
|
#include "common/ffdata.h"
|
2023-08-21 09:22:03 +08:00
|
|
|
#include "detection/version/version.h"
|
2025-04-14 10:28:01 +08:00
|
|
|
#include "logo/logo.h"
|
2026-01-06 09:48:38 +08:00
|
|
|
#include "common/commandoption.h"
|
|
|
|
|
#include "common/init.h"
|
|
|
|
|
#include "common/io.h"
|
|
|
|
|
#include "common/jsonconfig.h"
|
|
|
|
|
#include "common/time.h"
|
|
|
|
|
#include "common/stringUtils.h"
|
|
|
|
|
#include "common/mallocHelper.h"
|
2023-07-28 14:12:48 +08:00
|
|
|
#include "fastfetch_datatext.h"
|
2021-02-18 22:25:36 +01:00
|
|
|
|
2022-06-18 14:25:31 +02:00
|
|
|
#include <stdlib.h>
|
2022-08-21 21:08:11 +02:00
|
|
|
#include <ctype.h>
|
2021-04-09 14:07:05 +02:00
|
|
|
#include <string.h>
|
2021-02-22 16:52:51 +01:00
|
|
|
|
2024-12-11 16:12:03 +08:00
|
|
|
static void printCommandFormatHelpJson(void)
|
|
|
|
|
{
|
|
|
|
|
yyjson_mut_doc* doc = yyjson_mut_doc_new(NULL);
|
|
|
|
|
yyjson_mut_val* root = yyjson_mut_obj(doc);
|
|
|
|
|
yyjson_mut_doc_set_root(doc, root);
|
|
|
|
|
|
|
|
|
|
for (uint32_t i = 0; i <= 'Z' - 'A'; ++i)
|
|
|
|
|
{
|
|
|
|
|
for (FFModuleBaseInfo** modules = ffModuleInfos[i]; *modules; ++modules)
|
|
|
|
|
{
|
|
|
|
|
FFModuleBaseInfo* baseInfo = *modules;
|
|
|
|
|
if (!baseInfo->formatArgs.count) continue;
|
|
|
|
|
|
|
|
|
|
FF_STRBUF_AUTO_DESTROY type = ffStrbufCreateS(baseInfo->name);
|
|
|
|
|
ffStrbufLowerCase(&type);
|
|
|
|
|
ffStrbufAppendS(&type, "Format");
|
|
|
|
|
|
|
|
|
|
yyjson_mut_val* obj = yyjson_mut_obj(doc);
|
|
|
|
|
if (yyjson_mut_obj_add(root, yyjson_mut_strbuf(doc, &type), obj))
|
|
|
|
|
{
|
2025-04-04 18:59:15 +08:00
|
|
|
FF_STRBUF_AUTO_DESTROY content = ffStrbufCreateF("Output format of the module `%s`. See Wiki for formatting syntax\n", baseInfo->name);
|
2024-12-11 16:12:03 +08:00
|
|
|
for (unsigned i = 0; i < baseInfo->formatArgs.count; i++)
|
|
|
|
|
{
|
|
|
|
|
const FFModuleFormatArg* arg = &baseInfo->formatArgs.args[i];
|
|
|
|
|
ffStrbufAppendF(&content, " %u. {%s}: %s\n", i + 1, arg->name, arg->desc);
|
|
|
|
|
}
|
|
|
|
|
ffStrbufTrimRight(&content, '\n');
|
|
|
|
|
yyjson_mut_obj_add_strbuf(doc, obj, "description", &content);
|
|
|
|
|
yyjson_mut_obj_add_str(doc, obj, "type", "string");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
yyjson_mut_write_fp(stdout, doc, YYJSON_WRITE_PRETTY, NULL, NULL);
|
|
|
|
|
putchar('\n');
|
|
|
|
|
yyjson_mut_doc_free(doc);
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-07 13:40:40 +08:00
|
|
|
static void printCommandFormatHelp(const char* command)
|
2021-03-19 20:57:07 +01:00
|
|
|
{
|
2023-10-07 13:40:40 +08:00
|
|
|
FF_STRBUF_AUTO_DESTROY type = ffStrbufCreateNS((uint32_t) (strlen(command) - strlen("-format")), command);
|
2024-12-11 10:47:02 +08:00
|
|
|
ffStrbufLowerCase(&type);
|
2023-10-07 13:40:40 +08:00
|
|
|
for (FFModuleBaseInfo** modules = ffModuleInfos[toupper(command[0]) - 'A']; *modules; ++modules)
|
|
|
|
|
{
|
|
|
|
|
FFModuleBaseInfo* baseInfo = *modules;
|
|
|
|
|
if (ffStrbufIgnCaseEqualS(&type, baseInfo->name))
|
|
|
|
|
{
|
2024-12-11 10:47:02 +08:00
|
|
|
if (baseInfo->formatArgs.count > 0)
|
|
|
|
|
{
|
2025-08-21 23:56:14 +08:00
|
|
|
FF_STRBUF_AUTO_DESTROY variable = ffStrbufCreate();
|
|
|
|
|
printf("-- In config file: { \"type\": \"%s\", \"format\": \"{<format-variable>}\" }\n", type.chars);
|
2024-12-11 10:47:02 +08:00
|
|
|
printf("Sets the format string for %s output.\n", baseInfo->name);
|
2025-08-21 23:56:14 +08:00
|
|
|
puts("To see how a format string is constructed, take a look at https://github.com/fastfetch-cli/fastfetch/wiki/Format-String-Guide.");
|
|
|
|
|
puts("The following variables are passed:");
|
2024-12-11 10:47:02 +08:00
|
|
|
|
|
|
|
|
for (unsigned i = 0; i < baseInfo->formatArgs.count; i++)
|
|
|
|
|
{
|
|
|
|
|
const FFModuleFormatArg* arg = &baseInfo->formatArgs.args[i];
|
2025-08-21 23:56:14 +08:00
|
|
|
ffStrbufSetF(&variable, "{%s}", arg->name);
|
|
|
|
|
printf("%20s: %s\n", variable.chars, arg->desc);
|
2024-12-11 10:47:02 +08:00
|
|
|
}
|
|
|
|
|
}
|
2023-10-07 13:40:40 +08:00
|
|
|
else
|
|
|
|
|
fprintf(stderr, "Error: Module '%s' doesn't support output formatting\n", baseInfo->name);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-03-19 20:57:07 +01:00
|
|
|
|
2023-10-07 13:40:40 +08:00
|
|
|
fprintf(stderr, "Error: Module '%s' is not supported\n", type.chars);
|
2021-03-11 22:15:48 +01:00
|
|
|
}
|
|
|
|
|
|
2023-11-20 16:37:59 +08:00
|
|
|
static void printFullHelp()
|
2021-02-20 19:32:57 +01:00
|
|
|
{
|
2023-11-20 16:37:59 +08:00
|
|
|
fputs("Fastfetch is a neofetch-like tool for fetching system information and displaying them in a pretty way\n\n", stdout);
|
|
|
|
|
if (!instance.config.display.pipe)
|
2023-11-20 22:04:03 +08:00
|
|
|
fputs("\e[1;4mUsage:\e[m \e[1mfastfetch\e[m \e[3m<?options>\e[m\n\n", stdout);
|
2023-11-20 16:37:59 +08:00
|
|
|
else
|
|
|
|
|
fputs("Usage: fastfetch <?options>\n\n", stdout);
|
|
|
|
|
|
|
|
|
|
yyjson_doc* doc = yyjson_read(FASTFETCH_DATATEXT_JSON_HELP, strlen(FASTFETCH_DATATEXT_JSON_HELP), YYJSON_READ_NOFLAG);
|
|
|
|
|
assert(doc);
|
|
|
|
|
yyjson_val *groupKey, *flagArr;
|
|
|
|
|
size_t groupIdx, groupMax;
|
|
|
|
|
yyjson_obj_foreach(yyjson_doc_get_root(doc), groupIdx, groupMax, groupKey, flagArr)
|
2023-11-02 14:06:53 +08:00
|
|
|
{
|
2023-11-02 14:44:18 +08:00
|
|
|
if (!instance.config.display.pipe)
|
2023-11-20 22:04:03 +08:00
|
|
|
fputs("\e[1;4m", stdout);
|
2023-11-20 16:37:59 +08:00
|
|
|
printf("%s options:", yyjson_get_str(groupKey));
|
|
|
|
|
if (!instance.config.display.pipe)
|
2023-11-20 22:04:03 +08:00
|
|
|
fputs("\e[m", stdout);
|
2023-11-20 16:37:59 +08:00
|
|
|
putchar('\n');
|
|
|
|
|
|
|
|
|
|
yyjson_val* flagObj;
|
|
|
|
|
size_t flagIdx, flagMax;
|
|
|
|
|
yyjson_arr_foreach(flagArr, flagIdx, flagMax, flagObj)
|
2023-11-02 14:06:53 +08:00
|
|
|
{
|
2023-11-20 16:37:59 +08:00
|
|
|
yyjson_val* shortKey = yyjson_obj_get(flagObj, "short");
|
|
|
|
|
if (shortKey)
|
|
|
|
|
{
|
|
|
|
|
fputs(" ", stdout);
|
|
|
|
|
if (!instance.config.display.pipe)
|
2023-11-20 22:04:03 +08:00
|
|
|
fputs("\e[1m", stdout);
|
2023-11-20 16:37:59 +08:00
|
|
|
printf("-%s", yyjson_get_str(shortKey));
|
|
|
|
|
if (!instance.config.display.pipe)
|
2023-11-20 22:04:03 +08:00
|
|
|
fputs("\e[m", stdout);
|
2023-11-20 16:37:59 +08:00
|
|
|
fputs(", ", stdout);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
fputs(" ", stdout);
|
|
|
|
|
}
|
|
|
|
|
yyjson_val* longKey = yyjson_obj_get(flagObj, "long");
|
|
|
|
|
assert(longKey);
|
|
|
|
|
if (!instance.config.display.pipe)
|
2023-11-20 22:04:03 +08:00
|
|
|
fputs("\e[1m", stdout);
|
2023-11-20 16:37:59 +08:00
|
|
|
printf("--%s", yyjson_get_str(longKey));
|
|
|
|
|
if (!instance.config.display.pipe)
|
2023-11-20 22:04:03 +08:00
|
|
|
fputs("\e[m", stdout);
|
2023-11-02 14:06:53 +08:00
|
|
|
|
2023-11-20 16:37:59 +08:00
|
|
|
yyjson_val* argObj = yyjson_obj_get(flagObj, "arg");
|
|
|
|
|
if (argObj)
|
2023-11-02 14:06:53 +08:00
|
|
|
{
|
2023-11-20 16:37:59 +08:00
|
|
|
yyjson_val* typeKey = yyjson_obj_get(argObj, "type");
|
|
|
|
|
assert(typeKey);
|
|
|
|
|
yyjson_val* optionalKey = yyjson_obj_get(argObj, "optional");
|
|
|
|
|
bool optional = optionalKey && yyjson_get_bool(optionalKey);
|
|
|
|
|
putchar(' ');
|
|
|
|
|
if (!instance.config.display.pipe)
|
2023-11-20 22:51:01 +08:00
|
|
|
fputs("\e[3m", stdout);
|
2023-11-20 16:37:59 +08:00
|
|
|
printf("<%s%s>", optional ? "?" : "", yyjson_get_str(typeKey));
|
|
|
|
|
if (!instance.config.display.pipe)
|
2023-11-20 22:04:03 +08:00
|
|
|
fputs("\e[m", stdout);
|
2023-11-20 16:37:59 +08:00
|
|
|
}
|
2023-11-02 14:06:53 +08:00
|
|
|
|
2023-11-20 16:37:59 +08:00
|
|
|
yyjson_val* descKey = yyjson_obj_get(flagObj, "desc");
|
|
|
|
|
assert(descKey);
|
|
|
|
|
if (yyjson_is_arr(descKey))
|
|
|
|
|
{
|
|
|
|
|
if (instance.config.display.pipe)
|
|
|
|
|
putchar(':');
|
|
|
|
|
|
|
|
|
|
yyjson_val* descStr;
|
|
|
|
|
size_t descIdx, descMax;
|
|
|
|
|
yyjson_arr_foreach(descKey, descIdx, descMax, descStr)
|
2023-11-02 14:06:53 +08:00
|
|
|
{
|
2023-11-20 16:37:59 +08:00
|
|
|
if (!instance.config.display.pipe)
|
|
|
|
|
printf("\e[46G%s\n", yyjson_get_str(descStr));
|
|
|
|
|
else
|
|
|
|
|
printf(" %s", yyjson_get_str(descStr));
|
2023-11-02 14:06:53 +08:00
|
|
|
}
|
2023-11-20 16:37:59 +08:00
|
|
|
if (instance.config.display.pipe)
|
|
|
|
|
putchar('\n');
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (!instance.config.display.pipe)
|
|
|
|
|
fputs("\e[46G", stdout);
|
|
|
|
|
else
|
|
|
|
|
fputs(": ", stdout);
|
|
|
|
|
puts(yyjson_get_str(descKey));
|
2023-11-02 14:06:53 +08:00
|
|
|
}
|
|
|
|
|
}
|
2023-11-20 16:37:59 +08:00
|
|
|
|
|
|
|
|
putchar('\n');
|
|
|
|
|
}
|
|
|
|
|
yyjson_doc_free(doc);
|
2023-11-22 09:32:38 +08:00
|
|
|
|
2025-04-04 18:59:15 +08:00
|
|
|
puts("\n\
|
2025-08-04 15:08:30 +08:00
|
|
|
Command flags are not case sensitive. E.g. `--print-logos` is equal to `--Print-Logos`\n\
|
2025-04-04 18:59:15 +08:00
|
|
|
If a value starts with a ?, it is optional. An optional boolean value defaults to true if not specified.\n\
|
|
|
|
|
More detailed help messages for each options can be printed with `-h <option_without_dash_prefix>`\n\
|
2025-08-04 15:08:30 +08:00
|
|
|
For detailed information on logo options, module configuration, and formatting, visit:\n\
|
|
|
|
|
https://github.com/fastfetch-cli/fastfetch/wiki/Configuration");
|
2023-11-20 16:37:59 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool printSpecificCommandHelp(const char* command)
|
|
|
|
|
{
|
|
|
|
|
yyjson_doc* doc = yyjson_read(FASTFETCH_DATATEXT_JSON_HELP, strlen(FASTFETCH_DATATEXT_JSON_HELP), YYJSON_READ_NOFLAG);
|
|
|
|
|
assert(doc);
|
|
|
|
|
yyjson_val *groupKey, *flagArr;
|
|
|
|
|
size_t groupIdx, groupMax;
|
|
|
|
|
yyjson_obj_foreach(yyjson_doc_get_root(doc), groupIdx, groupMax, groupKey, flagArr)
|
|
|
|
|
{
|
|
|
|
|
yyjson_val* flagObj;
|
|
|
|
|
size_t flagIdx, flagMax;
|
|
|
|
|
yyjson_arr_foreach(flagArr, flagIdx, flagMax, flagObj)
|
2023-11-02 14:06:53 +08:00
|
|
|
{
|
2023-11-20 16:37:59 +08:00
|
|
|
yyjson_val* pseudo = yyjson_obj_get(flagObj, "pseudo");
|
|
|
|
|
if (pseudo && yyjson_get_bool(pseudo))
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
yyjson_val* longKey = yyjson_obj_get(flagObj, "long");
|
|
|
|
|
assert(longKey);
|
|
|
|
|
if (ffStrEqualsIgnCase(command, yyjson_get_str(longKey)))
|
|
|
|
|
{
|
2023-11-21 16:39:38 +08:00
|
|
|
puts(yyjson_get_str(yyjson_obj_get(flagObj, "desc")));
|
|
|
|
|
|
|
|
|
|
printf("%10s: ", "Usage");
|
|
|
|
|
yyjson_val* shortKey = yyjson_obj_get(flagObj, "short");
|
|
|
|
|
if (shortKey)
|
|
|
|
|
{
|
|
|
|
|
if (!instance.config.display.pipe)
|
|
|
|
|
fputs("\e[1m", stdout);
|
|
|
|
|
printf("-%s", yyjson_get_str(shortKey));
|
|
|
|
|
if (!instance.config.display.pipe)
|
|
|
|
|
fputs("\e[m", stdout);
|
|
|
|
|
fputs(", ", stdout);
|
|
|
|
|
}
|
|
|
|
|
if (!instance.config.display.pipe)
|
|
|
|
|
fputs("\e[1m", stdout);
|
|
|
|
|
printf("--%s", yyjson_get_str(longKey));
|
|
|
|
|
if (!instance.config.display.pipe)
|
|
|
|
|
fputs("\e[m", stdout);
|
|
|
|
|
|
|
|
|
|
yyjson_val* argObj = yyjson_obj_get(flagObj, "arg");
|
|
|
|
|
if (argObj)
|
|
|
|
|
{
|
|
|
|
|
yyjson_val* typeKey = yyjson_obj_get(argObj, "type");
|
|
|
|
|
assert(typeKey);
|
|
|
|
|
yyjson_val* optionalKey = yyjson_obj_get(argObj, "optional");
|
|
|
|
|
bool optional = optionalKey && yyjson_get_bool(optionalKey);
|
|
|
|
|
putchar(' ');
|
|
|
|
|
if (!instance.config.display.pipe)
|
|
|
|
|
fputs("\e[3m", stdout);
|
|
|
|
|
printf("<%s%s>", optional ? "?" : "", yyjson_get_str(typeKey));
|
|
|
|
|
if (!instance.config.display.pipe)
|
|
|
|
|
fputs("\e[m", stdout);
|
|
|
|
|
putchar('\n');
|
|
|
|
|
|
|
|
|
|
yyjson_val* defaultKey = yyjson_obj_get(argObj, "default");
|
|
|
|
|
if (defaultKey)
|
|
|
|
|
{
|
|
|
|
|
if (ffStrEqualsIgnCase(yyjson_get_str(typeKey), "structure"))
|
|
|
|
|
printf("%10s: %s\n", "Default", FASTFETCH_DATATEXT_STRUCTURE);
|
|
|
|
|
else if (yyjson_is_bool(defaultKey))
|
|
|
|
|
printf("%10s: %s\n", "Default", yyjson_get_bool(defaultKey) ? "true" : "false");
|
|
|
|
|
else if (yyjson_is_num(defaultKey))
|
2023-11-22 15:36:48 +08:00
|
|
|
printf("%10s: %d\n", "Default", yyjson_get_int(defaultKey));
|
2023-11-21 16:39:38 +08:00
|
|
|
else if (yyjson_is_str(defaultKey))
|
|
|
|
|
printf("%10s: %s\n", "Default", yyjson_get_str(defaultKey));
|
|
|
|
|
else
|
|
|
|
|
printf("%10s: Unknown\n", "Default");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
yyjson_val* enumKey = yyjson_obj_get(argObj, "enum");
|
|
|
|
|
if (enumKey)
|
|
|
|
|
{
|
|
|
|
|
printf("%10s:\n", "Options");
|
|
|
|
|
yyjson_val *optKey, *optVal;
|
|
|
|
|
size_t optIdx, optMax;
|
|
|
|
|
yyjson_obj_foreach(enumKey, optIdx, optMax, optKey, optVal)
|
|
|
|
|
printf("%12s: %s\n", yyjson_get_str(optKey), yyjson_get_str(optVal));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
putchar('\n');
|
|
|
|
|
|
|
|
|
|
yyjson_val* remarkKey = yyjson_obj_get(flagObj, "remark");
|
|
|
|
|
if (remarkKey)
|
2024-02-14 22:07:08 +08:00
|
|
|
{
|
|
|
|
|
if (yyjson_is_str(remarkKey))
|
|
|
|
|
printf("%10s: %s\n", "Remark", yyjson_get_str(remarkKey));
|
|
|
|
|
else if (yyjson_is_arr(remarkKey) && yyjson_arr_size(remarkKey) > 0)
|
|
|
|
|
{
|
|
|
|
|
yyjson_val* remarkStr;
|
|
|
|
|
size_t remarkIdx, remarkMax;
|
|
|
|
|
yyjson_arr_foreach(remarkKey, remarkIdx, remarkMax, remarkStr)
|
|
|
|
|
{
|
|
|
|
|
if (remarkIdx == 0)
|
|
|
|
|
printf("%10s: %s\n", "Remark", yyjson_get_str(remarkStr));
|
|
|
|
|
else
|
|
|
|
|
printf(" %s\n", yyjson_get_str(remarkStr));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-11-21 16:39:38 +08:00
|
|
|
|
2024-05-20 16:45:23 +08:00
|
|
|
yyjson_doc_free(doc);
|
2023-11-20 16:37:59 +08:00
|
|
|
return true;
|
|
|
|
|
}
|
2023-11-02 14:06:53 +08:00
|
|
|
}
|
|
|
|
|
}
|
2024-05-20 16:45:23 +08:00
|
|
|
|
|
|
|
|
yyjson_doc_free(doc);
|
2023-11-20 16:37:59 +08:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void printCommandHelp(const char* command)
|
|
|
|
|
{
|
|
|
|
|
if(command == NULL)
|
|
|
|
|
printFullHelp();
|
2024-12-11 16:12:03 +08:00
|
|
|
else if(ffStrEqualsIgnCase(command, "format-json"))
|
|
|
|
|
printCommandFormatHelpJson();
|
2024-05-05 07:10:51 +08:00
|
|
|
else if(ffCharIsEnglishAlphabet(command[0]) && ffStrEndsWithIgnCase(command, "-format")) // <module>-format
|
2023-10-07 13:40:40 +08:00
|
|
|
printCommandFormatHelp(command);
|
2023-11-20 16:37:59 +08:00
|
|
|
else if(!printSpecificCommandHelp(command))
|
2023-10-07 13:40:40 +08:00
|
|
|
fprintf(stderr, "Error: No specific help for command '%s' provided\n", command);
|
2021-02-20 19:32:57 +01:00
|
|
|
}
|
|
|
|
|
|
2023-11-13 16:41:10 +08:00
|
|
|
static void listAvailablePresets(bool pretty)
|
2021-04-25 11:48:25 +02:00
|
|
|
{
|
2023-06-24 10:59:53 +08:00
|
|
|
FF_LIST_FOR_EACH(FFstrbuf, path, instance.state.platform.dataDirs)
|
2021-04-25 11:48:25 +02:00
|
|
|
{
|
2023-01-28 17:19:45 +01:00
|
|
|
ffStrbufAppendS(path, "fastfetch/presets/");
|
2023-11-13 16:41:10 +08:00
|
|
|
ffListFilesRecursively(path->chars, pretty);
|
2021-04-25 11:48:25 +02:00
|
|
|
}
|
2024-05-24 09:18:48 +08:00
|
|
|
|
2024-10-15 10:31:37 +08:00
|
|
|
if (instance.state.platform.exePath.length)
|
|
|
|
|
{
|
|
|
|
|
FF_STRBUF_AUTO_DESTROY absolutePath = ffStrbufCreateCopy(&instance.state.platform.exePath);
|
|
|
|
|
ffStrbufSubstrBeforeLastC(&absolutePath, '/');
|
|
|
|
|
ffStrbufAppendS(&absolutePath, "/presets/");
|
|
|
|
|
ffListFilesRecursively(absolutePath.chars, pretty);
|
|
|
|
|
}
|
2021-04-25 11:48:25 +02:00
|
|
|
}
|
|
|
|
|
|
2023-06-24 10:59:53 +08:00
|
|
|
static void listAvailableLogos(void)
|
2021-04-25 11:48:25 +02:00
|
|
|
{
|
2023-06-24 10:59:53 +08:00
|
|
|
FF_LIST_FOR_EACH(FFstrbuf, path, instance.state.platform.dataDirs)
|
2023-01-08 01:41:09 +01:00
|
|
|
{
|
2023-01-28 17:19:45 +01:00
|
|
|
ffStrbufAppendS(path, "fastfetch/logos/");
|
2023-11-13 16:41:10 +08:00
|
|
|
ffListFilesRecursively(path->chars, true);
|
2023-01-08 01:41:09 +01:00
|
|
|
}
|
2021-04-25 11:48:25 +02:00
|
|
|
}
|
|
|
|
|
|
2023-06-24 10:59:53 +08:00
|
|
|
static void listConfigPaths(void)
|
2023-01-02 16:51:11 +08:00
|
|
|
{
|
2023-06-24 10:59:53 +08:00
|
|
|
FF_LIST_FOR_EACH(FFstrbuf, folder, instance.state.platform.configDirs)
|
2023-01-02 16:51:11 +08:00
|
|
|
{
|
2023-06-10 21:34:41 +08:00
|
|
|
bool exists = false;
|
2023-12-21 09:52:15 +08:00
|
|
|
uint32_t length = folder->length + (uint32_t) strlen("fastfetch") + 1 /* trailing slash */;
|
2023-06-10 21:34:41 +08:00
|
|
|
ffStrbufAppendS(folder, "fastfetch/config.jsonc");
|
|
|
|
|
exists = ffPathExists(folder->chars, FF_PATHTYPE_FILE);
|
2023-11-14 09:58:42 +08:00
|
|
|
ffStrbufSubstrBefore(folder, length);
|
2023-06-10 21:34:41 +08:00
|
|
|
printf("%s%s\n", folder->chars, exists ? " (*)" : "");
|
2023-01-02 16:51:11 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-24 10:59:53 +08:00
|
|
|
static void listDataPaths(void)
|
2023-01-08 01:41:09 +01:00
|
|
|
{
|
2023-06-24 10:59:53 +08:00
|
|
|
FF_LIST_FOR_EACH(FFstrbuf, folder, instance.state.platform.dataDirs)
|
2023-01-08 01:41:09 +01:00
|
|
|
{
|
|
|
|
|
ffStrbufAppendS(folder, "fastfetch/");
|
|
|
|
|
puts(folder->chars);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-16 14:21:24 +08:00
|
|
|
static void listModules(bool pretty)
|
2023-08-20 20:51:55 +08:00
|
|
|
{
|
|
|
|
|
unsigned count = 0;
|
|
|
|
|
for (int i = 0; i <= 'Z' - 'A'; ++i)
|
|
|
|
|
{
|
|
|
|
|
for (FFModuleBaseInfo** modules = ffModuleInfos[i]; *modules; ++modules)
|
|
|
|
|
{
|
|
|
|
|
++count;
|
2023-11-16 14:21:24 +08:00
|
|
|
if (pretty)
|
2024-01-22 18:50:11 +08:00
|
|
|
printf("%d)%s%-14s: %s\n", count, count > 9 ? " " : " ", (*modules)->name, (*modules)->description);
|
2023-11-16 14:21:24 +08:00
|
|
|
else
|
2023-11-17 14:12:17 +08:00
|
|
|
printf("%s:%s\n", (*modules)->name, (*modules)->description);
|
2023-08-20 20:51:55 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-15 15:03:38 +08:00
|
|
|
static bool parseJsoncFile(FFdata* data, const char* path, yyjson_read_flag flg)
|
2023-06-10 21:34:41 +08:00
|
|
|
{
|
2026-01-15 15:03:38 +08:00
|
|
|
assert(!data->configDoc);
|
2024-04-30 09:15:57 +08:00
|
|
|
|
2023-06-10 21:34:41 +08:00
|
|
|
{
|
2024-04-30 09:15:57 +08:00
|
|
|
yyjson_read_err error;
|
2026-01-15 15:03:38 +08:00
|
|
|
data->configDoc = path
|
2025-08-14 10:50:39 +08:00
|
|
|
? yyjson_read_file(path, flg, NULL, &error)
|
|
|
|
|
: yyjson_read_fp(stdin, flg, NULL, &error);
|
2026-01-15 15:03:38 +08:00
|
|
|
if (!data->configDoc)
|
2023-07-05 14:21:35 +08:00
|
|
|
{
|
2024-04-30 09:15:57 +08:00
|
|
|
if (error.code != YYJSON_READ_ERROR_FILE_OPEN)
|
|
|
|
|
{
|
2025-08-14 10:50:39 +08:00
|
|
|
if (path)
|
|
|
|
|
{
|
|
|
|
|
size_t row = 0, col = error.pos;
|
|
|
|
|
FF_STRBUF_AUTO_DESTROY content = ffStrbufCreate();
|
|
|
|
|
if (ffAppendFileBuffer(path, &content))
|
|
|
|
|
yyjson_locate_pos(content.chars, content.length, error.pos, &row, &col, NULL);
|
|
|
|
|
fprintf(stderr, "Error: failed to parse JSON config file `%s` at (%zu, %zu): %s\n", path, row, col, error.msg);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
fprintf(stderr, "Error: failed to parse JSON from stdin at %zu: %s\n", error.pos, error.msg);
|
|
|
|
|
|
2024-04-30 09:15:57 +08:00
|
|
|
exit(477);
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
const char* error = NULL;
|
|
|
|
|
|
2026-01-15 15:03:38 +08:00
|
|
|
yyjson_val* const root = yyjson_doc_get_root(data->configDoc);
|
2024-04-30 09:15:57 +08:00
|
|
|
if (!yyjson_is_obj(root))
|
|
|
|
|
error = "Invalid JSON config format. Root value must be an object";
|
|
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
error ||
|
|
|
|
|
(error = ffOptionsParseLogoJsonConfig(&instance.config.logo, root)) ||
|
|
|
|
|
(error = ffOptionsParseGeneralJsonConfig(&instance.config.general, root)) ||
|
|
|
|
|
(error = ffOptionsParseDisplayJsonConfig(&instance.config.display, root)) ||
|
|
|
|
|
false
|
|
|
|
|
) {
|
|
|
|
|
fprintf(stderr, "JsonConfig Error: %s\n", error);
|
2023-06-10 21:34:41 +08:00
|
|
|
exit(477);
|
|
|
|
|
}
|
2023-06-17 20:39:08 +08:00
|
|
|
}
|
2023-07-05 14:21:35 +08:00
|
|
|
|
|
|
|
|
return true;
|
2023-06-10 21:34:41 +08:00
|
|
|
}
|
|
|
|
|
|
2025-08-11 14:49:22 +08:00
|
|
|
|
2026-01-15 15:03:38 +08:00
|
|
|
static void generateConfigFile(FFdata* data, bool force, const char* filePath, bool fullConfig)
|
2023-01-02 12:43:11 +08:00
|
|
|
{
|
2026-01-15 15:03:38 +08:00
|
|
|
if (data->resultDoc)
|
|
|
|
|
{
|
|
|
|
|
fprintf(stderr, "Error: duplicated `--gen-config` or `--format json` flags found\n");
|
|
|
|
|
exit(477);
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-10 15:47:09 +08:00
|
|
|
if (!filePath)
|
2023-10-26 16:34:38 +08:00
|
|
|
{
|
2025-08-27 15:29:38 +08:00
|
|
|
if (instance.state.platform.configDirs.length == 0)
|
|
|
|
|
{
|
|
|
|
|
fprintf(stderr, "Error: No config directory found to generate config file in. Use --gen-config <path> to specify a path\n");
|
|
|
|
|
exit(477);
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-09 16:18:25 +08:00
|
|
|
FFstrbuf* configDir = FF_LIST_FIRST(FFstrbuf, instance.state.platform.configDirs);
|
2026-01-15 15:03:38 +08:00
|
|
|
ffStrbufEnsureFixedLengthFree(&data->genConfigPath, configDir->length + strlen("fastfetch/config.jsonc"));
|
|
|
|
|
ffStrbufSet(&data->genConfigPath, configDir);
|
|
|
|
|
ffStrbufAppendS(&data->genConfigPath, "fastfetch/config.jsonc");
|
2023-10-26 16:34:38 +08:00
|
|
|
}
|
2023-11-10 15:47:09 +08:00
|
|
|
else
|
2023-08-07 14:15:11 +08:00
|
|
|
{
|
2026-01-15 15:03:38 +08:00
|
|
|
ffStrbufSetS(&data->genConfigPath, filePath);
|
2023-08-07 14:15:11 +08:00
|
|
|
}
|
|
|
|
|
|
2026-01-15 15:03:38 +08:00
|
|
|
if (!force && ffPathExists(data->genConfigPath.chars, FF_PATHTYPE_ANY))
|
2023-01-02 12:43:11 +08:00
|
|
|
{
|
2026-01-15 15:03:38 +08:00
|
|
|
fprintf(stderr, "Error: file `%s` exists. Use `--gen-config%s-force` to overwrite\n", data->genConfigPath.chars, fullConfig ? "-full" : "");
|
2023-11-10 15:47:09 +08:00
|
|
|
exit(477);
|
2023-01-02 12:43:11 +08:00
|
|
|
}
|
2025-08-11 14:49:22 +08:00
|
|
|
|
2026-01-15 15:03:38 +08:00
|
|
|
data->docType = fullConfig ? FF_RESULT_DOC_TYPE_CONFIG_FULL : FF_RESULT_DOC_TYPE_CONFIG;
|
|
|
|
|
data->resultDoc = yyjson_mut_doc_new(NULL);
|
2023-01-02 12:43:11 +08:00
|
|
|
}
|
|
|
|
|
|
2023-06-24 10:59:53 +08:00
|
|
|
static void optionParseConfigFile(FFdata* data, const char* key, const char* value)
|
2021-04-25 11:48:25 +02:00
|
|
|
{
|
2023-11-16 16:41:08 +08:00
|
|
|
if (data->configLoaded)
|
|
|
|
|
{
|
|
|
|
|
fprintf(stderr, "Error: only one config file can be loaded\n");
|
|
|
|
|
exit(413);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
data->configLoaded = true;
|
|
|
|
|
|
2021-04-25 11:48:25 +02:00
|
|
|
if(value == NULL)
|
|
|
|
|
{
|
2023-11-16 16:41:08 +08:00
|
|
|
fprintf(stderr, "Error: usage: %s <config>\n", key);
|
2021-04-25 11:48:25 +02:00
|
|
|
exit(413);
|
|
|
|
|
}
|
2023-07-05 14:21:35 +08:00
|
|
|
|
2025-04-04 14:38:51 +08:00
|
|
|
if (value[0] == '\0' || ffStrEqualsIgnCase(value, "none"))
|
2023-11-16 16:41:08 +08:00
|
|
|
return;
|
|
|
|
|
|
2025-08-14 10:50:39 +08:00
|
|
|
if (value[0] == '-' && value[1] == '\0')
|
|
|
|
|
{
|
2026-01-15 15:03:38 +08:00
|
|
|
parseJsoncFile(data, NULL, false);
|
2025-08-14 10:50:39 +08:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2022-08-22 12:45:20 +02:00
|
|
|
//Try to load as an absolute path
|
|
|
|
|
|
2026-01-09 16:18:25 +08:00
|
|
|
FF_STRBUF_AUTO_DESTROY absolutePath = ffStrbufCreateA(128);
|
|
|
|
|
ffStrbufSetS(&absolutePath, value);
|
2025-04-04 14:38:51 +08:00
|
|
|
bool strictJson = ffStrbufEndsWithIgnCaseS(&absolutePath, ".json");
|
2025-08-19 10:25:52 +08:00
|
|
|
bool jsonc = !strictJson && ffStrbufEndsWithIgnCaseS(&absolutePath, ".jsonc");
|
|
|
|
|
bool json5 = !strictJson && !jsonc && ffStrbufEndsWithIgnCaseS(&absolutePath, ".json5");
|
|
|
|
|
bool needExtension = !strictJson && !jsonc && !json5;
|
2025-04-04 14:38:51 +08:00
|
|
|
if (needExtension)
|
|
|
|
|
ffStrbufAppendS(&absolutePath, ".jsonc");
|
2023-08-13 10:18:16 +08:00
|
|
|
|
2025-08-19 10:25:52 +08:00
|
|
|
yyjson_read_flag flag = strictJson
|
|
|
|
|
? 0
|
|
|
|
|
: jsonc
|
|
|
|
|
? YYJSON_READ_ALLOW_COMMENTS | YYJSON_READ_ALLOW_TRAILING_COMMAS
|
|
|
|
|
: YYJSON_READ_JSON5;
|
|
|
|
|
|
2026-01-15 15:03:38 +08:00
|
|
|
if (parseJsoncFile(data, absolutePath.chars, flag)) return;
|
2025-03-31 10:13:30 +08:00
|
|
|
|
2025-12-05 10:30:51 +08:00
|
|
|
//Try to load as a relative path with the config directory
|
|
|
|
|
|
|
|
|
|
FF_LIST_FOR_EACH(FFstrbuf, path, instance.state.platform.configDirs)
|
|
|
|
|
{
|
|
|
|
|
ffStrbufSet(&absolutePath, path);
|
|
|
|
|
ffStrbufAppendS(&absolutePath, "fastfetch/");
|
|
|
|
|
ffStrbufAppendS(&absolutePath, value);
|
|
|
|
|
if (needExtension)
|
|
|
|
|
ffStrbufAppendS(&absolutePath, ".jsonc");
|
|
|
|
|
|
2026-01-15 15:03:38 +08:00
|
|
|
if (parseJsoncFile(data, absolutePath.chars, flag)) return;
|
2025-12-05 10:30:51 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//Try to load as a preset
|
2025-04-05 08:37:18 +08:00
|
|
|
|
|
|
|
|
FF_LIST_FOR_EACH(FFstrbuf, path, instance.state.platform.dataDirs)
|
2021-04-25 11:48:25 +02:00
|
|
|
{
|
2025-04-05 08:37:18 +08:00
|
|
|
ffStrbufSet(&absolutePath, path);
|
|
|
|
|
ffStrbufAppendS(&absolutePath, "fastfetch/presets/");
|
|
|
|
|
ffStrbufAppendS(&absolutePath, value);
|
|
|
|
|
if (needExtension)
|
|
|
|
|
ffStrbufAppendS(&absolutePath, ".jsonc");
|
2022-08-22 12:45:20 +02:00
|
|
|
|
2026-01-15 15:03:38 +08:00
|
|
|
if (parseJsoncFile(data, absolutePath.chars, flag)) return;
|
2025-04-05 08:37:18 +08:00
|
|
|
}
|
2021-04-25 11:48:25 +02:00
|
|
|
|
2025-12-05 10:30:51 +08:00
|
|
|
//Try to load as a relative path with the directory of fastfetch binary, for Windows support
|
2025-03-31 10:13:30 +08:00
|
|
|
|
2025-04-05 08:37:18 +08:00
|
|
|
if (instance.state.platform.exePath.length)
|
|
|
|
|
{
|
2025-04-08 09:11:08 +08:00
|
|
|
uint32_t lastSlash = ffStrbufLastIndexC(&instance.state.platform.exePath, '/') + 1;
|
|
|
|
|
assert(lastSlash < instance.state.platform.exePath.length);
|
|
|
|
|
|
|
|
|
|
// Try {exePath}/
|
|
|
|
|
ffStrbufSetNS(&absolutePath, lastSlash, instance.state.platform.exePath.chars);
|
2025-04-05 08:37:18 +08:00
|
|
|
ffStrbufAppendS(&absolutePath, value);
|
|
|
|
|
if (needExtension)
|
|
|
|
|
ffStrbufAppendS(&absolutePath, ".jsonc");
|
2026-01-15 15:03:38 +08:00
|
|
|
if (parseJsoncFile(data, absolutePath.chars, flag)) return;
|
2023-08-24 14:29:35 +08:00
|
|
|
|
2025-04-08 09:11:08 +08:00
|
|
|
// Try {exePath}/presets/
|
|
|
|
|
ffStrbufSubstrBefore(&absolutePath, lastSlash);
|
|
|
|
|
ffStrbufAppendS(&absolutePath, "presets/");
|
|
|
|
|
ffStrbufAppendS(&absolutePath, value);
|
|
|
|
|
if (needExtension)
|
|
|
|
|
ffStrbufAppendS(&absolutePath, ".jsonc");
|
2026-01-15 15:03:38 +08:00
|
|
|
if (parseJsoncFile(data, absolutePath.chars, flag)) return;
|
2023-08-24 14:29:35 +08:00
|
|
|
}
|
|
|
|
|
|
2022-08-22 12:45:20 +02:00
|
|
|
//File not found
|
|
|
|
|
|
2021-04-25 11:48:25 +02:00
|
|
|
fprintf(stderr, "Error: couldn't find config: %s\n", value);
|
|
|
|
|
exit(414);
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-17 16:00:24 +08:00
|
|
|
static void printVersion()
|
2023-08-21 09:22:03 +08:00
|
|
|
{
|
2024-07-02 10:12:26 +08:00
|
|
|
FFVersionResult* result = &ffVersionResult;
|
|
|
|
|
printf("%s %s%s%s (%s)\n", result->projectName, result->version, result->versionTweak, result->debugMode ? "-debug" : "", result->architecture);
|
2023-08-17 16:00:24 +08:00
|
|
|
}
|
2023-01-19 14:57:54 +08:00
|
|
|
|
2026-01-15 15:03:38 +08:00
|
|
|
static void enableJsonOutput(FFdata* data)
|
2025-09-28 15:07:45 +08:00
|
|
|
{
|
2026-01-15 15:03:38 +08:00
|
|
|
if (data->resultDoc)
|
2025-09-28 15:07:45 +08:00
|
|
|
{
|
2026-01-15 15:03:38 +08:00
|
|
|
fprintf(stderr, "Error: duplicated `--gen-config` or `--format json` flags found\n");
|
|
|
|
|
exit(477);
|
2025-09-28 15:07:45 +08:00
|
|
|
}
|
2026-01-15 15:03:38 +08:00
|
|
|
|
|
|
|
|
data->resultDoc = yyjson_mut_doc_new(NULL);
|
|
|
|
|
data->docType = FF_RESULT_DOC_TYPE_JSON;
|
|
|
|
|
yyjson_mut_doc_set_root(data->resultDoc, yyjson_mut_arr(data->resultDoc));
|
2025-09-28 15:07:45 +08:00
|
|
|
}
|
|
|
|
|
|
2023-11-10 15:05:09 +08:00
|
|
|
static void parseCommand(FFdata* data, char* key, char* value)
|
2023-08-17 16:00:24 +08:00
|
|
|
{
|
|
|
|
|
if(ffStrEqualsIgnCase(key, "-h") || ffStrEqualsIgnCase(key, "--help"))
|
|
|
|
|
{
|
|
|
|
|
printCommandHelp(value);
|
|
|
|
|
exit(0);
|
|
|
|
|
}
|
2023-11-22 09:14:27 +08:00
|
|
|
if(ffStrEqualsIgnCase(key, "--help-raw"))
|
|
|
|
|
{
|
|
|
|
|
puts(FASTFETCH_DATATEXT_JSON_HELP);
|
|
|
|
|
exit(0);
|
|
|
|
|
}
|
2023-08-17 16:00:24 +08:00
|
|
|
else if(ffStrEqualsIgnCase(key, "-v") || ffStrEqualsIgnCase(key, "--version"))
|
|
|
|
|
{
|
|
|
|
|
printVersion();
|
2022-03-20 17:12:52 +01:00
|
|
|
exit(0);
|
|
|
|
|
}
|
2023-06-19 15:21:49 +08:00
|
|
|
else if(ffStrEqualsIgnCase(key, "--version-raw"))
|
2021-03-03 19:59:39 +01:00
|
|
|
{
|
2022-03-20 13:16:04 +01:00
|
|
|
puts(FASTFETCH_PROJECT_VERSION);
|
2021-03-03 19:59:39 +01:00
|
|
|
exit(0);
|
|
|
|
|
}
|
2023-09-22 15:00:58 +08:00
|
|
|
else if(ffStrStartsWithIgnCase(key, "--print-"))
|
2021-03-27 19:26:58 +01:00
|
|
|
{
|
2023-09-22 15:00:58 +08:00
|
|
|
const char* subkey = key + strlen("--print-");
|
2023-11-19 11:15:06 +08:00
|
|
|
if(ffStrEndsWithIgnCase(subkey, "structure"))
|
2022-10-22 13:02:04 +08:00
|
|
|
puts(FASTFETCH_DATATEXT_STRUCTURE);
|
2023-09-22 15:00:58 +08:00
|
|
|
else if(ffStrEqualsIgnCase(subkey, "logos"))
|
2023-06-24 10:59:53 +08:00
|
|
|
ffLogoBuiltinPrint();
|
2022-10-22 13:02:04 +08:00
|
|
|
else
|
2023-11-10 15:05:09 +08:00
|
|
|
{
|
|
|
|
|
fprintf(stderr, "Error: unsupported print option: %s\n", key);
|
|
|
|
|
exit(415);
|
|
|
|
|
}
|
|
|
|
|
exit(0);
|
2021-03-03 19:59:39 +01:00
|
|
|
}
|
2023-09-22 15:00:58 +08:00
|
|
|
else if(ffStrStartsWithIgnCase(key, "--list-"))
|
2021-03-03 19:59:39 +01:00
|
|
|
{
|
2023-09-22 15:00:58 +08:00
|
|
|
const char* subkey = key + strlen("--list-");
|
|
|
|
|
if(ffStrEqualsIgnCase(subkey, "modules"))
|
2023-11-16 14:21:24 +08:00
|
|
|
listModules(!value || !ffStrEqualsIgnCase(value, "autocompletion"));
|
2023-09-22 15:00:58 +08:00
|
|
|
else if(ffStrEqualsIgnCase(subkey, "presets"))
|
2023-11-16 14:21:24 +08:00
|
|
|
listAvailablePresets(!value || !ffStrEqualsIgnCase(value, "autocompletion"));
|
2023-09-22 15:00:58 +08:00
|
|
|
else if(ffStrEqualsIgnCase(subkey, "config-paths"))
|
2023-06-24 10:59:53 +08:00
|
|
|
listConfigPaths();
|
2023-09-22 15:00:58 +08:00
|
|
|
else if(ffStrEqualsIgnCase(subkey, "data-paths"))
|
2023-06-24 10:59:53 +08:00
|
|
|
listDataPaths();
|
2023-09-22 15:00:58 +08:00
|
|
|
else if(ffStrEqualsIgnCase(subkey, "features"))
|
2022-10-22 13:02:04 +08:00
|
|
|
ffListFeatures();
|
2023-09-22 15:00:58 +08:00
|
|
|
else if(ffStrEqualsIgnCase(subkey, "logos"))
|
2022-10-22 13:02:04 +08:00
|
|
|
{
|
2023-11-16 14:21:24 +08:00
|
|
|
if (value)
|
|
|
|
|
{
|
|
|
|
|
if (ffStrEqualsIgnCase(value, "autocompletion"))
|
|
|
|
|
ffLogoBuiltinListAutocompletion();
|
|
|
|
|
else if (ffStrEqualsIgnCase(value, "builtin"))
|
|
|
|
|
ffLogoBuiltinList();
|
|
|
|
|
else if (ffStrEqualsIgnCase(value, "custom"))
|
|
|
|
|
listAvailableLogos();
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
fprintf(stderr, "Error: unsupported logo type: %s\n", value);
|
|
|
|
|
exit(415);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
puts("Builtin logos:");
|
|
|
|
|
ffLogoBuiltinList();
|
|
|
|
|
puts("\nCustom logos:");
|
|
|
|
|
listAvailableLogos();
|
|
|
|
|
}
|
2022-10-22 13:02:04 +08:00
|
|
|
}
|
|
|
|
|
else
|
2023-07-03 16:22:16 +08:00
|
|
|
{
|
2023-11-10 15:05:09 +08:00
|
|
|
fprintf(stderr, "Error: unsupported list option: %s\n", key);
|
|
|
|
|
exit(415);
|
2023-07-03 16:22:16 +08:00
|
|
|
}
|
2023-08-12 09:40:37 +08:00
|
|
|
|
2023-11-10 15:05:09 +08:00
|
|
|
exit(0);
|
2023-07-03 16:22:16 +08:00
|
|
|
}
|
2023-06-19 15:21:49 +08:00
|
|
|
else if(ffStrEqualsIgnCase(key, "--gen-config"))
|
2026-01-15 15:03:38 +08:00
|
|
|
generateConfigFile(data, false, value, false);
|
2023-06-19 15:21:49 +08:00
|
|
|
else if(ffStrEqualsIgnCase(key, "--gen-config-force"))
|
2026-01-15 15:03:38 +08:00
|
|
|
generateConfigFile(data, true, value, false);
|
2025-08-11 14:49:22 +08:00
|
|
|
else if(ffStrEqualsIgnCase(key, "--gen-config-full"))
|
2026-01-15 15:03:38 +08:00
|
|
|
generateConfigFile(data, false, value, true);
|
2025-08-11 14:49:22 +08:00
|
|
|
else if(ffStrEqualsIgnCase(key, "--gen-config-full-force"))
|
2026-01-15 15:03:38 +08:00
|
|
|
generateConfigFile(data, true, value, true);
|
2025-08-14 10:42:15 +08:00
|
|
|
else if(ffStrEqualsIgnCase(key, "-c") || ffStrEqualsIgnCase(key, "--config"))
|
2023-11-16 16:41:08 +08:00
|
|
|
optionParseConfigFile(data, key, value);
|
2025-09-28 15:07:45 +08:00
|
|
|
else if(ffStrEqualsIgnCase(key, "-j") || ffStrEqualsIgnCase(key, "--json"))
|
2026-01-15 15:03:38 +08:00
|
|
|
{
|
|
|
|
|
if (ffOptionParseBoolean(value)) enableJsonOutput(data);
|
|
|
|
|
}
|
2023-08-29 11:13:55 +08:00
|
|
|
else if(ffStrEqualsIgnCase(key, "--format"))
|
|
|
|
|
{
|
2026-01-15 15:03:38 +08:00
|
|
|
if (!!ffOptionParseEnum(key, value, (FFKeyValuePair[]) {
|
2025-09-28 15:07:45 +08:00
|
|
|
{ "default", false},
|
|
|
|
|
{ "json", true },
|
2023-08-29 11:13:55 +08:00
|
|
|
{},
|
2026-01-15 15:03:38 +08:00
|
|
|
})) enableJsonOutput(data);
|
2023-08-29 11:13:55 +08:00
|
|
|
}
|
2025-11-05 10:00:31 +08:00
|
|
|
else if(ffStrEqualsIgnCase(key, "--dynamic-interval"))
|
|
|
|
|
instance.state.dynamicInterval = ffOptionParseUInt32(key, value); // seconds to milliseconds
|
2023-11-10 15:05:09 +08:00
|
|
|
else
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
// Don't parse it again in parseOption.
|
|
|
|
|
// This is necessary because parseOption doesn't understand this option and will result in an unknown option error.
|
|
|
|
|
key[0] = '\0';
|
|
|
|
|
if (value) value[0] = '\0';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void parseOption(FFdata* data, const char* key, const char* value)
|
|
|
|
|
{
|
2024-05-08 11:05:00 +08:00
|
|
|
if(ffStrEqualsIgnCase(key, "-s") || ffStrEqualsIgnCase(key, "--structure"))
|
2023-06-12 15:48:26 +08:00
|
|
|
ffOptionParseString(key, value, &data->structure);
|
2022-04-02 14:35:43 +02:00
|
|
|
|
2023-10-26 10:26:15 +08:00
|
|
|
else if(
|
|
|
|
|
ffOptionsParseGeneralCommandLine(&instance.config.general, key, value) ||
|
|
|
|
|
ffOptionsParseLogoCommandLine(&instance.config.logo, key, value) ||
|
2025-09-03 09:36:16 +08:00
|
|
|
ffOptionsParseDisplayCommandLine(&instance.config.display, key, value) ||
|
|
|
|
|
ffParseModuleOptions(key, value)
|
2023-10-26 10:26:15 +08:00
|
|
|
) {}
|
2023-08-17 16:00:24 +08:00
|
|
|
|
2026-01-15 10:35:01 +08:00
|
|
|
else if(ffStrEqualsIgnCase(key, "--structure-disabled"))
|
|
|
|
|
ffOptionParseString(key, value, &data->structureDisabled);
|
|
|
|
|
|
2021-03-03 19:59:39 +01:00
|
|
|
else
|
|
|
|
|
{
|
2021-04-03 11:39:59 +02:00
|
|
|
fprintf(stderr, "Error: unknown option: %s\n", key);
|
2021-03-05 23:00:28 +01:00
|
|
|
exit(400);
|
2021-03-03 19:59:39 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-15 15:03:38 +08:00
|
|
|
static void parseConfigFiles(FFdata* data)
|
2022-07-21 14:37:13 +02:00
|
|
|
{
|
2026-01-15 15:03:38 +08:00
|
|
|
if (__builtin_expect(data->genConfigPath.length == 0, true))
|
2023-06-10 21:34:41 +08:00
|
|
|
{
|
2023-11-14 09:58:42 +08:00
|
|
|
FF_LIST_FOR_EACH(FFstrbuf, dir, instance.state.platform.configDirs)
|
2023-10-23 13:39:07 +08:00
|
|
|
{
|
|
|
|
|
uint32_t dirLength = dir->length;
|
2023-06-10 21:34:41 +08:00
|
|
|
|
2023-10-23 13:39:07 +08:00
|
|
|
ffStrbufAppendS(dir, "fastfetch/config.jsonc");
|
2026-01-15 15:03:38 +08:00
|
|
|
bool success = parseJsoncFile(data, dir->chars, YYJSON_READ_ALLOW_COMMENTS | YYJSON_READ_ALLOW_TRAILING_COMMAS);
|
2025-08-19 10:25:52 +08:00
|
|
|
ffStrbufSubstrBefore(dir, dirLength);
|
|
|
|
|
if (success) return;
|
|
|
|
|
|
|
|
|
|
ffStrbufAppendS(dir, "fastfetch/config.json5");
|
2026-01-15 15:03:38 +08:00
|
|
|
success = parseJsoncFile(data, dir->chars, YYJSON_READ_JSON5);
|
2023-10-23 13:39:07 +08:00
|
|
|
ffStrbufSubstrBefore(dir, dirLength);
|
|
|
|
|
if (success) return;
|
|
|
|
|
}
|
2023-06-10 21:34:41 +08:00
|
|
|
}
|
2021-03-03 19:59:39 +01:00
|
|
|
}
|
|
|
|
|
|
2023-11-10 15:05:09 +08:00
|
|
|
static void parseArguments(FFdata* data, int argc, char** argv, void (*parser)(FFdata* data, char* key, char* value))
|
2021-03-03 19:59:39 +01:00
|
|
|
{
|
|
|
|
|
for(int i = 1; i < argc; i++)
|
|
|
|
|
{
|
2023-08-17 16:00:24 +08:00
|
|
|
const char* key = argv[i];
|
2023-11-10 15:05:09 +08:00
|
|
|
if(*key == '\0')
|
|
|
|
|
continue; // has been handled by parseCommand
|
|
|
|
|
|
2023-08-17 16:00:24 +08:00
|
|
|
if(*key != '-')
|
|
|
|
|
{
|
|
|
|
|
fprintf(stderr, "Error: invalid option: %s. An option must start with `-`\n", key);
|
|
|
|
|
exit(400);
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-18 18:25:24 +01:00
|
|
|
if(i == argc - 1 || (
|
2023-08-25 10:32:03 +08:00
|
|
|
argv[i + 1][0] == '-' &&
|
|
|
|
|
argv[i + 1][1] != '\0' && // `-` is used as an alias for `/dev/stdin`
|
2024-06-03 14:17:01 +08:00
|
|
|
!ffStrEqualsIgnCase(argv[i], "--separator-string") // Separator string can start with a -
|
2021-11-18 18:25:24 +01:00
|
|
|
)) {
|
2023-11-10 15:05:09 +08:00
|
|
|
parser(data, argv[i], NULL);
|
2021-03-03 19:59:39 +01:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2023-11-10 15:05:09 +08:00
|
|
|
parser(data, argv[i], argv[i + 1]);
|
2021-03-03 19:59:39 +01:00
|
|
|
++i;
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-02-27 20:31:54 +01:00
|
|
|
}
|
|
|
|
|
|
2023-10-25 10:18:07 +08:00
|
|
|
static void run(FFdata* data)
|
2021-03-03 19:59:39 +01:00
|
|
|
{
|
2026-01-15 15:03:38 +08:00
|
|
|
const bool useJsonConfig = data->structure.length == 0 && data->configDoc;
|
2023-08-18 13:28:07 +08:00
|
|
|
|
2023-10-23 13:39:07 +08:00
|
|
|
if (useJsonConfig)
|
2026-01-15 15:03:38 +08:00
|
|
|
ffPrintJsonConfig(data, true /* prepare */);
|
2023-08-18 13:28:07 +08:00
|
|
|
else
|
2025-09-19 14:51:31 +08:00
|
|
|
{
|
|
|
|
|
//If we don't have a custom structure, use the default one
|
|
|
|
|
if(data->structure.length == 0)
|
|
|
|
|
ffStrbufAppendS(&data->structure, FASTFETCH_DATATEXT_STRUCTURE); // Cannot use `ffStrbufSetStatic` here because we will modify the string
|
2023-10-23 13:39:07 +08:00
|
|
|
ffPrepareCommandOption(data);
|
2025-09-19 14:51:31 +08:00
|
|
|
}
|
2022-09-29 17:14:50 +08:00
|
|
|
|
2023-06-24 10:59:53 +08:00
|
|
|
ffStart();
|
2021-03-03 19:59:39 +01:00
|
|
|
|
2026-01-15 15:03:38 +08:00
|
|
|
if (!data->resultDoc)
|
|
|
|
|
ffLogoPrint();
|
|
|
|
|
|
2023-06-13 11:08:49 +08:00
|
|
|
#if defined(_WIN32)
|
2026-03-22 13:26:05 +08:00
|
|
|
if (!instance.config.display.noBuffer) fflush(stdout);
|
2023-01-12 00:11:25 +08:00
|
|
|
#endif
|
|
|
|
|
|
2025-11-05 10:00:31 +08:00
|
|
|
while (true)
|
|
|
|
|
{
|
|
|
|
|
if (useJsonConfig)
|
2026-01-15 15:03:38 +08:00
|
|
|
ffPrintJsonConfig(data, false);
|
2025-11-05 10:00:31 +08:00
|
|
|
else
|
2026-01-15 15:03:38 +08:00
|
|
|
ffPrintCommandOption(data);
|
2025-11-05 10:00:31 +08:00
|
|
|
|
|
|
|
|
if (instance.state.dynamicInterval > 0)
|
|
|
|
|
{
|
|
|
|
|
fflush(stdout);
|
|
|
|
|
ffTimeSleep(instance.state.dynamicInterval);
|
|
|
|
|
fputs("\e[H", stdout);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
break;
|
2026-01-16 20:02:03 +08:00
|
|
|
|
|
|
|
|
if (useJsonConfig)
|
|
|
|
|
ffPrintJsonConfig(data, true /* prepare */);
|
|
|
|
|
else
|
|
|
|
|
ffPrepareCommandOption(data);
|
2025-11-05 10:00:31 +08:00
|
|
|
}
|
2023-06-11 00:51:29 +08:00
|
|
|
|
2026-01-15 15:03:38 +08:00
|
|
|
if (data->resultDoc)
|
|
|
|
|
yyjson_mut_write_fp(stdout, data->resultDoc, YYJSON_WRITE_INF_AND_NAN_AS_NULL | YYJSON_WRITE_PRETTY_TWO_SPACES | YYJSON_WRITE_NEWLINE_AT_END, NULL, NULL);
|
2023-10-23 13:39:07 +08:00
|
|
|
else
|
2025-11-05 10:00:31 +08:00
|
|
|
{
|
|
|
|
|
if (instance.config.logo.printRemaining)
|
|
|
|
|
ffLogoPrintRemaining();
|
2023-10-23 13:39:07 +08:00
|
|
|
ffFinish();
|
2025-11-05 10:00:31 +08:00
|
|
|
}
|
2023-10-23 13:39:07 +08:00
|
|
|
}
|
|
|
|
|
|
2025-08-11 14:49:22 +08:00
|
|
|
static void writeConfigFile(FFdata* data)
|
2023-10-25 10:18:07 +08:00
|
|
|
{
|
2026-01-15 15:03:38 +08:00
|
|
|
const FFstrbuf* filename = &data->genConfigPath;
|
2025-08-11 14:49:22 +08:00
|
|
|
|
2026-01-15 15:03:38 +08:00
|
|
|
yyjson_mut_doc* doc = data->resultDoc;
|
2023-10-26 16:22:05 +08:00
|
|
|
yyjson_mut_val* root = yyjson_mut_obj(doc);
|
|
|
|
|
yyjson_mut_doc_set_root(doc, root);
|
2025-08-11 14:49:22 +08:00
|
|
|
yyjson_mut_obj_add_str(doc, root, "$schema", "https://github.com/fastfetch-cli/fastfetch/raw/master/doc/json_schema.json");
|
2023-10-26 16:22:05 +08:00
|
|
|
|
2026-01-15 15:03:38 +08:00
|
|
|
if (data->docType == FF_RESULT_DOC_TYPE_CONFIG_FULL)
|
2025-08-11 14:49:22 +08:00
|
|
|
{
|
2026-01-15 15:03:38 +08:00
|
|
|
ffOptionsGenerateLogoJsonConfig(data, &instance.config.logo);
|
|
|
|
|
ffOptionsGenerateDisplayJsonConfig(data, &instance.config.display);
|
|
|
|
|
ffOptionsGenerateGeneralJsonConfig(data, &instance.config.general);
|
2025-08-11 14:49:22 +08:00
|
|
|
}
|
2026-01-15 15:03:38 +08:00
|
|
|
ffMigrateCommandOptionToJsonc(data);
|
2023-10-26 16:22:05 +08:00
|
|
|
|
2024-04-07 20:41:01 +08:00
|
|
|
if (ffStrbufEqualS(filename, "-"))
|
2024-04-09 09:01:28 +08:00
|
|
|
yyjson_mut_write_fp(stdout, doc, YYJSON_WRITE_INF_AND_NAN_AS_NULL | YYJSON_WRITE_PRETTY_TWO_SPACES | YYJSON_WRITE_NEWLINE_AT_END, NULL, NULL);
|
2024-04-07 20:41:01 +08:00
|
|
|
else
|
2024-04-03 20:02:38 +08:00
|
|
|
{
|
2024-04-07 20:41:01 +08:00
|
|
|
size_t len;
|
2024-04-09 09:01:28 +08:00
|
|
|
FF_AUTO_FREE const char* str = yyjson_mut_write(doc, YYJSON_WRITE_INF_AND_NAN_AS_NULL | YYJSON_WRITE_PRETTY_TWO_SPACES | YYJSON_WRITE_NEWLINE_AT_END, &len);
|
2024-04-07 20:41:01 +08:00
|
|
|
if (!str)
|
|
|
|
|
{
|
|
|
|
|
printf("Error: failed to generate config file\n");
|
|
|
|
|
exit(1);
|
|
|
|
|
}
|
|
|
|
|
if (ffWriteFileData(filename->chars, len, str))
|
2025-08-23 20:37:29 +08:00
|
|
|
{
|
|
|
|
|
printf("✓ Configuration file generated: `%s`\n"
|
|
|
|
|
"* Tip: Use a JSON schema-aware editor for better editing experience\n"
|
|
|
|
|
"* Documentation: https://github.com/fastfetch-cli/fastfetch/wiki/Configuration\n", filename->chars);
|
|
|
|
|
}
|
2024-04-07 20:41:01 +08:00
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
printf("Error: failed to write file in `%s`\n", filename->chars);
|
|
|
|
|
exit(1);
|
|
|
|
|
}
|
2023-10-26 16:22:05 +08:00
|
|
|
}
|
2023-10-25 10:18:07 +08:00
|
|
|
}
|
|
|
|
|
|
2023-11-10 15:05:09 +08:00
|
|
|
int main(int argc, char** argv)
|
2023-10-23 13:39:07 +08:00
|
|
|
{
|
|
|
|
|
ffInitInstance();
|
2024-05-06 11:33:50 +08:00
|
|
|
atexit(ffDestroyInstance);
|
2023-10-23 13:39:07 +08:00
|
|
|
|
|
|
|
|
//Data stores things only needed for the configuration of fastfetch
|
|
|
|
|
FFdata data = {
|
|
|
|
|
.structure = ffStrbufCreate(),
|
2026-01-15 15:03:38 +08:00
|
|
|
.structureDisabled = ffStrbufCreate(),
|
|
|
|
|
.genConfigPath = ffStrbufCreate(),
|
2023-10-23 13:39:07 +08:00
|
|
|
};
|
2022-09-22 22:02:58 +02:00
|
|
|
|
2023-11-10 15:05:09 +08:00
|
|
|
parseArguments(&data, argc, argv, parseCommand);
|
2026-01-15 15:03:38 +08:00
|
|
|
if(instance.state.dynamicInterval && data.resultDoc)
|
2025-11-05 10:00:31 +08:00
|
|
|
{
|
|
|
|
|
fprintf(stderr, "Error: --dynamic-interval cannot be used with --json\n");
|
|
|
|
|
exit(400);
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-16 16:41:08 +08:00
|
|
|
if(!data.configLoaded && !getenv("NO_CONFIG"))
|
2026-01-15 15:03:38 +08:00
|
|
|
parseConfigFiles(&data);
|
2023-11-10 15:05:09 +08:00
|
|
|
parseArguments(&data, argc, argv, (void*) parseOption);
|
2023-10-23 13:39:07 +08:00
|
|
|
|
2026-01-15 15:03:38 +08:00
|
|
|
if (__builtin_expect(data.genConfigPath.length == 0, true))
|
2023-10-25 10:18:07 +08:00
|
|
|
run(&data);
|
2023-10-23 13:39:07 +08:00
|
|
|
else
|
2025-08-11 14:49:22 +08:00
|
|
|
writeConfigFile(&data);
|
2022-09-22 22:02:58 +02:00
|
|
|
|
2022-09-22 21:28:20 +08:00
|
|
|
ffStrbufDestroy(&data.structure);
|
2026-01-15 15:03:38 +08:00
|
|
|
ffStrbufDestroy(&data.structureDisabled);
|
|
|
|
|
yyjson_doc_free(data.configDoc);
|
|
|
|
|
yyjson_mut_doc_free(data.resultDoc);
|
|
|
|
|
ffStrbufDestroy(&data.genConfigPath);
|
2021-02-18 22:25:36 +01:00
|
|
|
}
|