Skip to content

Commit

Permalink
Merge pull request #1534 from ampli/hist-xdg
Browse files Browse the repository at this point in the history
link-parser's history file location control
  • Loading branch information
linas authored May 25, 2024
2 parents 5d5df94 + a14b465 commit d1b9d40
Show file tree
Hide file tree
Showing 9 changed files with 370 additions and 6 deletions.
2 changes: 1 addition & 1 deletion configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ CXXFLAGS="-D_DEFAULT_SOURCE ${CXXFLAGS}"
AX_GCC_BUILTIN(__builtin_types_compatible_p)
AC_C_TYPEOF

AC_CHECK_FUNCS(strndup strtok_r sigaction malloc_trim)
AC_CHECK_FUNCS(strndup strtok_r sigaction malloc_trim asprintf)
AC_CHECK_FUNCS(aligned_alloc posix_memalign _aligned_malloc)

# For the Wordgraph display code.
Expand Down
4 changes: 3 additions & 1 deletion link-parser/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ link_parser_SOURCES = link-parser.c \
parser-utilities.h \
parser-utilities.c \
command-line.h \
lg_readline.h
lg_readline.h \
lg_xdg.c \
lg_xdg.h

# -I$(top_builddir) to pick up autogened link-grammar/link-features.h
link_parser_CPPFLAGS = -I$(top_srcdir) -I$(top_builddir)
Expand Down
47 changes: 44 additions & 3 deletions link-parser/lg_readline.c
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@

#include "command-line.h"
#include "parser-utilities.h"
#include "lg_xdg.h"

extern Switch default_switches[];
static const Switch **sorted_names; /* sorted command names */
Expand All @@ -48,6 +49,46 @@ static wchar_t * prompt(EditLine *el)
return wc_prompt;
}

// lg_readline()is called via a chain of functions:
// fget_input_string -> get_line -> get_terminal_line -> lg_readline.
// To avoid changing all of them, this variable is static for now.
// FIXME: Move the call of find_history_filepath() to lg_readline(), and
// implement one of these:
// 1. Add the dictionary, argv[0] and prog to the call of each function.
// 2. Option 1 is cumbersome since the first 3 functions have numerous
// arguments. So replace them with a new struct "io_params".
static const char *history_file;

static const char history_file_basename[] = "_history";

void find_history_filepath(const char *dictname, const char *argv0,
const char *prog)
{
const char *xdg_prog = xdg_get_program_name(argv0);
if (NULL == xdg_prog) xdg_prog = prog;

// Prefix the history file name with a dict indication.
// To support sharing the history file by several similar dicts,
// use the dict name up to the first ":" if exists.
char *dictpref = strdup(dictname);
dictpref[strcspn(dictpref, ":")] = '\0';

// Find the location of the history file.
const char *hfile = xdg_make_path(XDG_BD_STATE, "%s/%s%s", xdg_prog,
dictpref, history_file_basename);
free(dictpref);

if (NULL == hfile)
{
prt_error("Warning: xdg_get_home(XDG_BD_STATE) failed; "
"input history will not be supported.\n");
history_file = strdup("dev/null");
return;
}

history_file = hfile;
}

/**
* Try to complete the wide string in \p input, whose length is \p len.
*
Expand Down Expand Up @@ -372,7 +413,6 @@ char *lg_readline(const char *mb_prompt)

if (!is_init)
{
#define HFILE ".lg_history"
is_init = true;

size_t sz = mbstowcs(NULL, mb_prompt, 0) + 4;
Expand All @@ -384,7 +424,7 @@ char *lg_readline(const char *mb_prompt)
history_w(hist, &ev, H_SETSIZE, 100);
history_w(hist, &ev, H_SETUNIQUE, 1);
el_wset(el, EL_HIST, history_w, hist);
history_w(hist, &ev, H_LOAD, HFILE);
history_w(hist, &ev, H_LOAD, history_file);

el_set(el, EL_SIGNAL, 1); /* Restore tty setting on returning to shell */

Expand All @@ -410,6 +450,7 @@ char *lg_readline(const char *mb_prompt)
{
el_end(el);
history_wend(hist);
free((void *)history_file);
free(wc_prompt);
wc_prompt = NULL;
hist = NULL;
Expand All @@ -421,7 +462,7 @@ char *lg_readline(const char *mb_prompt)
if (1 < numc)
{
history_w(hist, &ev, H_ENTER, wc_line);
history_w(hist, &ev, H_SAVE, HFILE);
history_w(hist, &ev, H_SAVE, history_file);
}
/* fwprintf(stderr, L"==> got %d %ls", numc, wc_line); */

Expand Down
10 changes: 9 additions & 1 deletion link-parser/lg_readline.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@
/* */
/***************************************************************************/

#ifdef HAVE_EDITLINE
#if HAVE_EDITLINE
char *lg_readline(const char *mb_prompt);
#endif /* HAVE_EDITLINE */

#if HAVE_WIDECHAR_EDITLINE
void find_history_filepath(const char *, const char *, const char *);
#else
// Defining here an empty inline function causes a mysterious ld
// error on MinGW.
#define find_history_filepath(a, b, c)
#endif /* HAVE_WIDECHAR_EDITLINE */
241 changes: 241 additions & 0 deletions link-parser/lg_xdg.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
/***************************************************************************/
/* Amir Plivatsky */
/* */
/* Use of the link grammar parsing system is subject to the terms of the */
/* license set forth in the LICENSE file included with this software. */
/* This license allows free redistribution and use in source and binary */
/* forms, with or without modification, subject to certain conditions. */
/* */
/***************************************************************************/

/**
* This file includes minimal implementation of the XDG
* specification version 0.8. See:
* https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
*
* It is supports just what needed to support the history file location.
*/

#if HAVE_WIDECHAR_EDITLINE
#include <ctype.h>
#include <stdarg.h>

#include "link-grammar/link-includes.h"
#include <sys/stat.h>
#include <stdbool.h>
#include <stdlib.h> // malloc etc.
#include <string.h>
#include <errno.h>

#include "lg_xdg.h"
#include "parser-utilities.h"

typedef struct
{
const char *rel_path;
const char *env_var;
} xdg_definition;;

xdg_definition xdg_def[] =
{
{ "/.local/state", "XDG_STATE_HOME" },
// Add more definitions if needed.
};

static bool is_empty(const char *path)
{
return path == NULL || path[0] == '\0';
}

static bool is_absolute_path(const char *path)
{
if (is_empty(path)) return false; // Empty path is not absolute

#if defined(_WIN32) || defined(__CYGWIN__)
// Check for Windows absolute paths.
if ((isalpha((unsigned char)path[0]) && path[1] == ':' &&
(path[2] == '\\' || path[2] == '/')) ||
(path[0] == '\\' && path[1] == '\\'))
{
return true;
}
#endif

// Check for POSIX absolute path.
if (path[0] == '/') return true;

return false; // Path is not absolute
}

static bool is_sep(int c)
{
#if defined(_WIN32) || defined(__CYGWIN__)
if (c == '\\') return true;
#endif
if (c == '/') return true;

return false;
}

/**
* Make directories for each directory component of \p path.
* If the last component is not ending with "/", it is considered
* a file name.
*/
static bool make_dirpath(const char *path)
{
char *dir = strdup(path);
struct stat sb;

#if defined(_WIN32) || defined(__CYGWIN__)
// Skip Windows UNC path \\X
if (is_sep(dir[0]) && is_sep(dir[1]))
{
const char *p;

// Start from the root or network share
for (p = dir + 2; *p != '\0'; p++)
if (is_sep(*p)) break;
if (*p == '\0') return true; // No further subdirectories
}
#endif

for (char *p = dir+1; '\0' != *p; p++)
{
char sep = *p;
if (is_sep(*p))
{
if (is_sep(p[-1])) continue; // Ignore directory separator sequences
*p = '\0'; // Now dir is the path up to this point
//prt_error("DEBUG: mkdir: '%s'\n", dir);
if (mkdir(dir, S_IRWXU) == -1)
{
int save_errno = errno;
if (errno == EEXIST)
{
if (stat(dir, &sb) != 0 || !S_ISDIR(sb.st_mode))
goto mkdir_error;
errno = 0;
}
mkdir_error:
if (errno != 0)
{
prt_error("Error: Cannot create directory '%s': %s.\n",
dir, strerror(save_errno));
free(dir);
return false;
}
}
*p = sep;
}
}

free(dir);
return true;
}

/**
* @brief Get the home directory for the given XDG base directory type.
*
* @param bd_type The XDG base directory type.
* @return const char* The home directory path (freed by caller).
*/
const char *xdg_get_home(xdg_basedir_type bd_type)
{
const char *def_suffix = xdg_def[bd_type].rel_path;
const char *evars[] = { xdg_def[bd_type].env_var, "HOME",
#if defined(_WIN32) || defined(__CYGWIN__)
"USERPROFILE"
#endif
};
char *dir;

size_t num_evars = sizeof(evars)/sizeof(evars[0]);
size_t i;
for (i = 0; i < num_evars; i++)
{
dir = getenv(evars[i]);
if (is_empty(dir)) continue;

if (is_absolute_path(dir)) break;
if (num_evars - 1 != i) // Avoid a double notification
{
prt_error("Warning: %s is not an absolute path (ignored).\n",
evars[i]);
}
}

if (!is_absolute_path(dir))
{
prt_error("Error: %s is not set or is not an absolute path.\n",
evars[num_evars - 1]);
return NULL;
}

char *def_dir;
// def_suffix is a directory - append '/' so make_dirpath() will create it.
int n = asprintf(&def_dir, "%s%s/", dir, (i > 0) ? def_suffix : "");
if (!make_dirpath(def_dir))
{
free(def_dir);
return NULL;
}
def_dir[n-1] = '\0'; // Remove the last '/'

return def_dir;
}

/**
* @brief Get the program name from the provided path.
*
* @param argv0 The path to the executable.
* @return const char* The program name.
*/
const char *xdg_get_program_name(const char *argv0)
{
if ((NULL == argv0) || ('\0' == argv0[0])) return NULL;

const char *basename = argv0;
for (const char *p = argv0; *p != '\0'; p++)
if (is_sep(*p)) basename = p + 1;

if (0 == strcmp(basename, "..")) return NULL;

return basename;
}


/**
* @brief Construct a full path within the specified XDG base directory.
*
* @param bd_type The XDG base directory type.
* @param fmt The format string for the path.
* @param ... Additional arguments for the format string.
* @return const char* The constructed full path (freed by caller).
*/

const char *xdg_make_path(xdg_basedir_type bd_typec, const char *fmt, ...)
{
const char *xdg_home = xdg_get_home(bd_typec);

if (NULL == xdg_home) return NULL;

char *xdg_fmt;
asprintf(&xdg_fmt, "%s/%s", xdg_home, fmt);

va_list filename_components;
va_start(filename_components, fmt);
char *xdg_filepath;
vasprintf(&xdg_filepath, xdg_fmt, filename_components);
va_end(filename_components);

free((void *)xdg_home);
free(xdg_fmt);
if (!make_dirpath(xdg_filepath))
{
free(xdg_filepath);
return NULL;
}
return xdg_filepath;
}
#endif /* HAVE_WIDECHAR_EDITLINE */
15 changes: 15 additions & 0 deletions link-parser/lg_xdg.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#ifndef _LG_XDG_H
#define _LG_XDG_H

#include "link-grammar/link-includes.h" // GNUC_PRINTF

typedef enum {
XDG_BD_STATE, // XDG_BD_CONFIG, XDG_BD_DATA, XDG_BD_CACHE
} xdg_basedir_type;

const char *xdg_get_home(xdg_basedir_type);
const char *xdg_get_program_name(const char *);
const char *xdg_make_path(xdg_basedir_type, const char *fmt, ...)
GNUC_PRINTF(2,3);

#endif //_LG_XDG_H
Loading

0 comments on commit d1b9d40

Please sign in to comment.