Files
fastfetch/src/fastfetch.c
T

894 lines
30 KiB
C
Raw Normal View History

2021-02-18 22:25:36 +01:00
#include "fastfetch.h"
#include "common/commandoption.h"
#include "common/io/io.h"
2023-06-11 00:51:29 +08:00
#include "common/jsonconfig.h"
2023-08-21 09:22:03 +08:00
#include "detection/version/version.h"
2023-02-01 15:56:25 +01:00
#include "util/stringUtils.h"
#include "util/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
2022-10-13 18:12:54 +08:00
#ifdef WIN32
#include "util/windows/getline.h"
#endif
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);
for (FFModuleBaseInfo** modules = ffModuleInfos[toupper(command[0]) - 'A']; *modules; ++modules)
{
FFModuleBaseInfo* baseInfo = *modules;
if (ffStrbufIgnCaseEqualS(&type, baseInfo->name))
{
if (baseInfo->printHelpFormat)
baseInfo->printHelpFormat();
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
2024-03-04 10:22:29 +08:00
puts("\n" FASTFETCH_DATATEXT_HELP_FOOTER);
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
2023-11-20 16:37:59 +08:00
return true;
}
2023-11-02 14:06:53 +08:00
}
}
2023-11-20 16:37:59 +08:00
return false;
}
static void printCommandHelp(const char* command)
{
if(command == NULL)
printFullHelp();
2023-08-24 14:36:32 +08:00
else if(ffStrEqualsIgnCase(command, "color"))
2022-12-12 13:43:10 +01:00
puts(FASTFETCH_DATATEXT_HELP_COLOR);
else if(ffStrEqualsIgnCase(command, "format"))
2022-12-12 13:43:10 +01:00
puts(FASTFETCH_DATATEXT_HELP_FORMAT);
2023-11-20 16:37:59 +08:00
else if(isalpha(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
{
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
}
}
static void listAvailableLogos(void)
2021-04-25 11:48:25 +02: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
}
static void listConfigPaths(void)
2023-01-02 16:51:11 +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);
if (!exists)
{
ffStrbufSubstrBefore(folder, folder->length - (uint32_t) strlen("jsonc"));
ffStrbufAppendS(folder, "conf");
exists = ffPathExists(folder->chars, FF_PATHTYPE_FILE);
}
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
}
}
static void listDataPaths(void)
2023-01-08 01:41:09 +01: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)
{
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
printf("%s:%s\n", (*modules)->name, (*modules)->description);
}
}
}
static void parseOption(FFdata* data, const char* key, const char* value);
2021-04-25 11:48:25 +02:00
static bool parseJsoncFile(const char* path)
2023-06-10 21:34:41 +08:00
{
2024-04-30 09:15:57 +08:00
assert(!instance.state.configDoc);
2023-06-10 21:34:41 +08:00
{
2024-04-30 09:15:57 +08:00
yyjson_read_err error;
instance.state.configDoc = yyjson_read_file(path, YYJSON_READ_ALLOW_COMMENTS | YYJSON_READ_ALLOW_TRAILING_COMMAS | YYJSON_READ_ALLOW_INF_AND_NAN, NULL, &error);
if (!instance.state.configDoc)
{
2024-04-30 09:15:57 +08:00
if (error.code != YYJSON_READ_ERROR_FILE_OPEN)
{
fprintf(stderr, "Error: failed to parse JSON config file `%s` at pos %zu: %s\n", path, error.pos, error.msg);
exit(477);
}
return false;
}
}
{
const char* error = NULL;
yyjson_val* const root = yyjson_doc_get_root(instance.state.configDoc);
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)) ||
(error = ffOptionsParseLibraryJsonConfig(&instance.config.library, root)) ||
false
) {
fprintf(stderr, "JsonConfig Error: %s\n", error);
2023-06-10 21:34:41 +08:00
exit(477);
}
}
return true;
2023-06-10 21:34:41 +08:00
}
static bool parseConfigFile(FFdata* data, const char* path)
2021-04-25 11:48:25 +02:00
{
FF_AUTO_CLOSE_FILE FILE* file = fopen(path, "r");
2022-08-22 12:45:20 +02:00
if(file == NULL)
return false;
2022-08-21 21:08:11 +02:00
char* line = NULL;
2021-04-25 11:48:25 +02:00
size_t len = 0;
ssize_t read;
FF_STRBUF_AUTO_DESTROY unescaped = ffStrbufCreate();
2021-04-25 11:48:25 +02:00
2022-08-21 21:08:11 +02:00
while ((read = getline(&line, &len, file)) != -1)
2021-04-25 11:48:25 +02:00
{
2022-08-21 21:08:11 +02:00
char* lineStart = line;
char* lineEnd = line + read - 1;
//Trim line left
while(isspace(*lineStart))
++lineStart;
2021-04-25 11:48:25 +02:00
2022-08-21 21:08:11 +02:00
//Continue if line is empty or a comment
if(*lineStart == '\0' || *lineStart == '#')
2021-04-25 11:48:25 +02:00
continue;
2022-08-21 21:08:11 +02:00
//Trim line right
while(lineEnd > lineStart && isspace(*lineEnd))
--lineEnd;
*(lineEnd + 1) = '\0';
char* valueStart = strchr(lineStart, ' ');
2021-04-25 11:48:25 +02:00
2022-08-21 21:08:11 +02:00
//If the line has no white space, it is only a key
if(valueStart == NULL)
2021-04-25 11:48:25 +02:00
{
parseOption(data, lineStart, NULL);
2021-04-25 11:48:25 +02:00
continue;
}
2022-08-21 21:08:11 +02:00
//separate the key from the value
2021-04-25 11:48:25 +02:00
*valueStart = '\0';
++valueStart;
2022-08-21 21:08:11 +02:00
//Trim space of value left
while(isspace(*valueStart))
2021-04-25 11:48:25 +02:00
++valueStart;
//If we want whitespace in values, we need to quote it. This is done to keep consistency with shell.
2022-08-21 21:08:11 +02:00
if((*valueStart == '"' || *valueStart == '\'') && *valueStart == *lineEnd && lineEnd > valueStart)
2021-04-25 11:48:25 +02:00
{
2022-08-21 21:08:11 +02:00
++valueStart;
2022-08-22 12:14:08 +02:00
*lineEnd = '\0';
2022-08-21 21:08:11 +02:00
--lineEnd;
2021-04-25 11:48:25 +02:00
}
if (strchr(valueStart, '\\'))
{
// Unescape all `\x`s
const char* value = valueStart;
while(*value != '\0')
{
if(*value != '\\')
{
ffStrbufAppendC(&unescaped, *value);
++value;
continue;
}
++value;
switch(*value)
{
case 'n': ffStrbufAppendC(&unescaped, '\n'); break;
case 't': ffStrbufAppendC(&unescaped, '\t'); break;
case 'e': ffStrbufAppendC(&unescaped, '\e'); break;
case '\\': ffStrbufAppendC(&unescaped, '\\'); break;
default:
ffStrbufAppendC(&unescaped, '\\');
ffStrbufAppendC(&unescaped, *value);
break;
}
++value;
}
parseOption(data, lineStart, unescaped.chars);
ffStrbufClear(&unescaped);
}
else
{
parseOption(data, lineStart, valueStart);
}
2021-04-25 11:48:25 +02:00
}
2022-08-21 21:08:11 +02:00
if(line != NULL)
free(line);
2022-08-22 12:45:20 +02:00
return true;
2021-04-25 11:48:25 +02:00
}
static void generateConfigFile(bool force, const char* filePath)
{
if (!filePath)
2023-10-26 16:34:38 +08:00
{
ffStrbufSet(&instance.state.genConfigPath, (FFstrbuf*) ffListGet(&instance.state.platform.configDirs, 0));
ffStrbufAppendS(&instance.state.genConfigPath, "fastfetch/config.jsonc");
2023-10-26 16:34:38 +08:00
}
else
{
if (ffStrEqualsIgnCase(filePath, "conf") || ffStrEqualsIgnCase(filePath, "jsonc"))
{
fputs("Error: specifying file type is no longer supported\n", stderr);
exit(477);
}
ffStrbufSetS(&instance.state.genConfigPath, filePath);
}
if (!force && ffPathExists(instance.state.genConfigPath.chars, FF_PATHTYPE_ANY))
{
fprintf(stderr, "Error: file `%s` exists. Use `--gen-config-force` to overwrite\n", instance.state.genConfigPath.chars);
exit(477);
}
}
static void optionParseConfigFile(FFdata* data, const char* key, const char* value)
2021-04-25 11:48:25 +02: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)
{
fprintf(stderr, "Error: usage: %s <config>\n", key);
2021-04-25 11:48:25 +02:00
exit(413);
}
uint32_t fileNameLen = (uint32_t) strlen(value);
if(fileNameLen == 0)
{
fprintf(stderr, "Error: usage: %s <config>\n", key);
exit(413);
}
if (ffStrEqualsIgnCase(value, "none"))
return;
bool isJsonConfig = fileNameLen > strlen(".jsonc") && strcasecmp(value + fileNameLen - strlen(".jsonc"), ".jsonc") == 0;
2021-04-25 11:48:25 +02:00
2022-08-22 12:45:20 +02:00
//Try to load as an absolute path
if(isJsonConfig ? parseJsoncFile(value) : parseConfigFile(data, value))
2021-04-25 11:48:25 +02:00
return;
2022-08-22 12:45:20 +02:00
{
2024-01-23 13:14:53 +08:00
bool success = isJsonConfig ? parseJsoncFile(value) : parseConfigFile(data, value);
if(success)
return;
}
//Try to load as a relative path
2023-01-15 20:03:28 +01:00
2024-01-23 13:14:53 +08:00
FF_STRBUF_AUTO_DESTROY absolutePath = ffStrbufCreateA(128);
FF_LIST_FOR_EACH(FFstrbuf, path, instance.state.platform.dataDirs)
2021-04-25 11:48:25 +02:00
{
2023-01-15 20:03:28 +01:00
//We need to copy it, because if a config file loads a config file, the value of path must be unchanged
ffStrbufSet(&absolutePath, path);
ffStrbufAppendS(&absolutePath, "fastfetch/presets/");
ffStrbufAppendS(&absolutePath, value);
2022-08-22 12:45:20 +02:00
bool success = isJsonConfig ? parseJsoncFile(absolutePath.chars) : parseConfigFile(data, absolutePath.chars);
2023-12-19 15:26:51 +08:00
if (!success)
{
ffStrbufAppendS(&absolutePath, ".jsonc");
success = parseJsoncFile(absolutePath.chars);
}
2021-04-25 11:48:25 +02:00
2023-01-08 01:41:09 +01:00
if(success)
return;
2021-04-25 11:48:25 +02:00
}
{
//Try exe path
ffStrbufSet(&absolutePath, &instance.state.platform.exePath);
ffStrbufSubstrBeforeLastC(&absolutePath, '/');
ffStrbufAppendS(&absolutePath, "/presets/");
ffStrbufAppendS(&absolutePath, value);
bool success = isJsonConfig ? parseJsoncFile(absolutePath.chars) : parseConfigFile(data, absolutePath.chars);
2023-12-19 15:26:51 +08:00
if (!success)
{
ffStrbufAppendS(&absolutePath, ".jsonc");
success = parseJsoncFile(absolutePath.chars);
}
if(success)
return;
}
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);
}
static void printVersion()
2023-08-21 09:22:03 +08:00
{
FFVersionResult result = {};
ffDetectVersion(&result);
printf("%s %s%s%s (%s)\n", result.projectName, result.version, result.versionTweak, result.debugMode ? "-debug" : "", result.architecture);
}
2023-01-19 14:57:54 +08:00
2023-11-10 15:05:09 +08:00
static void parseCommand(FFdata* data, char* key, char* value)
{
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);
}
else if(ffStrEqualsIgnCase(key, "-v") || ffStrEqualsIgnCase(key, "--version"))
{
printVersion();
2022-03-20 17:12:52 +01:00
exit(0);
}
else if(ffStrEqualsIgnCase(key, "--version-raw"))
{
2022-03-20 13:16:04 +01:00
puts(FASTFETCH_PROJECT_VERSION);
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-");
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"))
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);
}
2023-09-22 15:00:58 +08:00
else if(ffStrStartsWithIgnCase(key, "--list-"))
{
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"))
listConfigPaths();
2023-09-22 15:00:58 +08:00
else if(ffStrEqualsIgnCase(subkey, "data-paths"))
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-11-10 15:05:09 +08:00
fprintf(stderr, "Error: unsupported list option: %s\n", key);
exit(415);
}
2023-08-12 09:40:37 +08:00
2023-11-10 15:05:09 +08:00
exit(0);
}
else if(ffStrEqualsIgnCase(key, "--gen-config"))
generateConfigFile(false, value);
else if(ffStrEqualsIgnCase(key, "--gen-config-force"))
generateConfigFile(true, value);
else if(ffStrEqualsIgnCase(key, "-c") || ffStrEqualsIgnCase(key, "--load-config") || ffStrEqualsIgnCase(key, "--config"))
optionParseConfigFile(data, key, value);
2023-08-29 11:13:55 +08:00
else if(ffStrEqualsIgnCase(key, "--format"))
{
switch (ffOptionParseEnum(key, value, (FFKeyValuePair[]) {
{ "default", 0},
{ "json", 1 },
{},
}))
{
case 0:
if (instance.state.resultDoc)
{
yyjson_mut_doc_free(instance.state.resultDoc);
instance.state.resultDoc = NULL;
}
break;
case 1:
if (!instance.state.resultDoc)
{
instance.state.resultDoc = yyjson_mut_doc_new(NULL);
yyjson_mut_doc_set_root(instance.state.resultDoc, yyjson_mut_arr(instance.state.resultDoc));
}
break;
}
}
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)
{
if(ffStrEqualsIgnCase(key, "--set") || ffStrEqualsIgnCase(key, "--set-keyless"))
2023-11-10 15:05:09 +08:00
{
FF_STRBUF_AUTO_DESTROY customValueStr = ffStrbufCreate();
ffOptionParseString(key, value, &customValueStr);
uint32_t index = ffStrbufFirstIndexC(&customValueStr, '=');
if(index == 0 || index == customValueStr.length)
{
fprintf(stderr, "Error: usage: %s <key>=<str>\n", key);
exit(477);
}
FF_STRBUF_AUTO_DESTROY customKey = ffStrbufCreateNS(index, customValueStr.chars);
FFCustomValue* customValue = NULL;
FF_LIST_FOR_EACH(FFCustomValue, x, data->customValues)
{
if(ffStrbufEqual(&x->key, &customKey))
{
ffStrbufDestroy(&x->key);
ffStrbufDestroy(&x->value);
customValue = x;
break;
}
}
if(!customValue) customValue = (FFCustomValue*) ffListAdd(&data->customValues);
ffStrbufInitMove(&customValue->key, &customKey);
ffStrbufSubstrAfter(&customValueStr, index);
ffStrbufInitMove(&customValue->value, &customValueStr);
customValue->printKey = key[5] == '\0';
}
else 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) ||
ffOptionsParseDisplayCommandLine(&instance.config.display, key, value) ||
ffOptionsParseLibraryCommandLine(&instance.config.library, key, value) ||
ffParseModuleOptions(key, value)
) {}
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);
}
}
static void parseConfigFiles(FFdata* data)
2022-07-21 14:37:13 +02:00
{
if (__builtin_expect(instance.state.genConfigPath.length == 0, true))
2023-06-10 21:34:41 +08:00
{
FF_LIST_FOR_EACH(FFstrbuf, dir, instance.state.platform.configDirs)
{
uint32_t dirLength = dir->length;
2023-06-10 21:34:41 +08:00
ffStrbufAppendS(dir, "fastfetch/config.jsonc");
bool success = parseJsoncFile(dir->chars);
ffStrbufSubstrBefore(dir, dirLength);
if (success) return;
}
2023-06-10 21:34:41 +08:00
}
FF_LIST_FOR_EACH(FFstrbuf, dir, instance.state.platform.configDirs)
{
uint32_t dirLength = dir->length;
ffStrbufAppendS(dir, "fastfetch/config.conf");
bool success = parseConfigFile(data, dir->chars);
ffStrbufSubstrBefore(dir, dirLength);
if (success) return;
}
}
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))
{
for(int i = 1; i < argc; i++)
{
const char* key = argv[i];
2023-11-10 15:05:09 +08:00
if(*key == '\0')
continue; // has been handled by parseCommand
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`
2021-11-18 18:25:24 +01:00
strcasecmp(argv[i], "--separator-string") != 0 // Separator string can start with a -
)) {
2023-11-10 15:05:09 +08:00
parser(data, argv[i], NULL);
}
else
{
2023-11-10 15:05:09 +08:00
parser(data, argv[i], argv[i + 1]);
++i;
}
}
2021-02-27 20:31:54 +01:00
}
2023-10-25 10:18:07 +08:00
static void run(FFdata* data)
{
const bool useJsonConfig = data->structure.length == 0 && instance.state.configDoc;
if (useJsonConfig)
2023-10-26 15:39:28 +08:00
ffPrintJsonConfig(true /* prepare */, instance.state.resultDoc);
else
ffPrepareCommandOption(data);
2022-09-29 17:14:50 +08:00
ffStart();
2023-06-13 11:08:49 +08:00
#if defined(_WIN32)
2023-10-26 10:26:15 +08:00
if (!instance.config.display.noBuffer) fflush(stdout);
#endif
if (useJsonConfig)
2023-10-26 15:39:28 +08:00
ffPrintJsonConfig(false, instance.state.resultDoc);
2023-06-11 00:51:29 +08:00
else
2023-10-26 15:39:28 +08:00
ffPrintCommandOption(data, instance.state.resultDoc);
2023-06-11 00:51:29 +08:00
2023-08-29 11:13:55 +08:00
if (instance.state.resultDoc)
yyjson_mut_write_fp(stdout, instance.state.resultDoc, YYJSON_WRITE_INF_AND_NAN_AS_NULL | YYJSON_WRITE_PRETTY_TWO_SPACES | YYJSON_WRITE_NEWLINE_AT_END, NULL, NULL);
else
ffFinish();
}
static void writeConfigFile(FFdata* data, const FFstrbuf* filename)
2023-10-25 10:18:07 +08:00
{
yyjson_mut_doc* doc = yyjson_mut_doc_new(NULL);
yyjson_mut_val* root = yyjson_mut_obj(doc);
yyjson_mut_doc_set_root(doc, root);
yyjson_mut_obj_add_str(doc, root, "$schema", "https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json");
ffOptionsGenerateLogoJsonConfig(&instance.config.logo, doc);
ffOptionsGenerateDisplayJsonConfig(&instance.config.display, doc);
ffOptionsGenerateGeneralJsonConfig(&instance.config.general, doc);
ffOptionsGenerateLibraryJsonConfig(&instance.config.library, doc);
ffMigrateCommandOptionToJsonc(data, doc);
if (ffStrbufEqualS(filename, "-"))
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);
else
{
size_t len;
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);
if (!str)
{
printf("Error: failed to generate config file\n");
exit(1);
}
if (ffWriteFileData(filename->chars, len, str))
printf("The generated config file has been written in `%s`\n", filename->chars);
else
{
printf("Error: failed to write file in `%s`\n", filename->chars);
exit(1);
}
}
yyjson_mut_doc_free(doc);
2023-10-25 10:18:07 +08:00
}
2023-11-10 15:05:09 +08:00
int main(int argc, char** argv)
{
ffInitInstance();
//Data stores things only needed for the configuration of fastfetch
FFdata data = {
.structure = ffStrbufCreate(),
.customValues = ffListCreate(sizeof(FFCustomValue)),
.configLoaded = false,
};
2022-09-22 22:02:58 +02:00
2023-11-10 15:05:09 +08:00
parseArguments(&data, argc, argv, parseCommand);
if(!data.configLoaded && !getenv("NO_CONFIG"))
parseConfigFiles(&data);
2023-11-10 15:05:09 +08:00
parseArguments(&data, argc, argv, (void*) parseOption);
if (__builtin_expect(instance.state.genConfigPath.length == 0, true))
2023-10-25 10:18:07 +08:00
run(&data);
else
writeConfigFile(&data, &instance.state.genConfigPath);
2022-09-22 22:02:58 +02:00
2022-09-22 21:28:20 +08:00
ffStrbufDestroy(&data.structure);
FF_LIST_FOR_EACH(FFCustomValue, customValue, data.customValues)
{
ffStrbufDestroy(&customValue->key);
ffStrbufDestroy(&customValue->value);
}
ffListDestroy(&data.customValues);
ffDestroyInstance();
2021-02-18 22:25:36 +01:00
}