-
Notifications
You must be signed in to change notification settings - Fork 123
Conversation
I'm not sure about the
The linear search for the correct sub-command is really not how the sub-command parsing was meant to work. The reason, the Key * lookupKey = keyNew (CLI_BASE_KEY, KEY_END);
Key * commandKey = ksLookup (options, commandKey, 0);
while (keyGetValueSize (commandKey) > 1) // empty string has size 1, because of zero-terminator
{
keyAddBaseName (lookupKey, keyString (commandKey));
// TODO: process intermediate options of command defined by current commandKey
commandKey = ksLookup (options, commandKey, 0);
}
// TODO: execute the command defined by commandKey This approach naturally handles nested commands, it has a well defined and obvious interpretation for things like With this in mind, I think the commands should be defined by two // The first KeySet defines the spec passed to elektraGetOpts
KeySet * commandsSpec = ksNew (1,
keyNew ("spec:" CLI_BASE_KEY "/version", KEY_META, "description", "Print version info.", KEY_META, "opt", "V", KEY_META, "opt/long", "version", KEY_META, "opt/arg", "none", KEY_END),
keyNew ("spec:" CLI_BASE_KEY "/get", KEY_META, "description", "Get the value of an individual key.", KEY_META, "command", "get", KEY_END),
keyNew ("spec:" CLI_BASE_KEY "/get/all", KEY_META, "description", "Consider all of the keys", KEY_META, "opt", "a", KEY_META, "opt/long", "all", KEY_META, "opt/arg", "none", KEY_END),
KS_END);
// The second KeySet defines the functions for the commands that could be executed
KeySet * commands = ksNew (1,
keyNew (CLI_BASE_KEY "/get/exec", KEY_FUNC, execGet, KEY_END),
keyNew (CLI_BASE_KEY "/get/checkargs", KEY_FUNC, checkGetArgs, KEY_END),
KS_END);
Then you can do something like this, whenever you need a function specific to a command: // Note: error handling omitted
typedef int (*execFunc) (KeySet * options, Key * errorKey);
// assume commandKey is currently e.g. `CLI_BASE_KEY "/get"`
keyAddBaseName (commandKey, "exec");
Key * execKey = ksLookup (commands, commandKey, 0);
execFunc fnExec = *(execFunc *) keyValue (execKey);
keySetBaseName (commandKey, NULL);
// Now we can call the function
fnExec (options, errorKey); You could also store something like libelektra/src/libs/elektra/kdb.c Lines 385 to 398 in 5f0895b
Then the whole pointer casting and function accessing stuff might seem a bit more natural libelektra/src/libs/elektra/kdb.c Lines 1436 to 1448 in 5f0895b
Another advantage is that when you add a new field to the struct, you'll have to explicitly define a value for every command. With the "functions pointers/values directly in key values" approach from above, you'd always have to handle the case of a missing key. And of course you'll only have to do one key name change & lookup per command instead of one for every function you need. I do have some other more specific notes on parts of the code, but I'll hold off on a full review, until we've decided on a general structure. |
Thanks for the feedback @kodebach ! For your questions:
#define COMMAND_NAME "meta"
command metaSubcommands[] = {
{ "ls", addMetaLsSpec, checkMetaLsArgs, execMetaLs },
};
...
void addMetaSpec (KeySet * spec)
{
ksAppendKey (spec, keyNew (COMMAND_SPEC_KEY (COMMAND_NAME), KEY_META, "description", "Get the value of an individual key.",
KEY_META, "command", COMMAND_NAME, KEY_END));
for (unsigned long i = 0; i < sizeof (subcommands) / sizeof (subcommands[0]); ++i)
{
subcommands[i].addSpec (spec);
}
}
...
int execMeta(KeySet * options, Key * errorKey) {
const char * subcommand = keyString (ksLookupByName (options, CLI_BASE_KEY "/" COMMAND_NAME, 0));
for (unsigned long i = 0; i < sizeof (metaSubcommands) / sizeof (metaSubcommands[0]); ++i)
{
if (elektraStrCmp (subcommand, metaSubcommands[i].name) == 0)
{
return metaSubcommands[i].exec (options, errorKey);
}
}
}
#define COMMAND_NAME "meta/ls"
...
void addMetaLsSpec (KeySet * spec)
{
ksAppendKey (spec, keyNew (COMMAND_SPEC_KEY (COMMAND_NAME), KEY_META, "description", "Get all meta information of an individual key.",
KEY_META, "command", "ls", KEY_END));
ADD_BASIC_OPTIONS (spec, COMMAND_SPEC_KEY (COMMAND_NAME))
}
... So, I'd use a |
Hm, yeah since you are using I think @markus2330 might have had this "2 kdbGet & gopts" approach in mind all along for the new
I guess that would work yes, but it's a bit annoying IMO since every command with further sub-commands would have to duplicate the logic for sub-commands. I think the basic decision we have to make here is: Do we want
|
I think we need separation because separately implemented commands (in different processes) should look&feel as if they are internal commands (also be aware of common config like profiles, bookmarks etc.) @hannes99 can you also describe or give a implementation sketch how external commands could have specifications and process arguments? E.g. with the already discussed Disclaimer: I didn't read the full discussion. |
If we use However, this would mean we need to properly mount the spec of I also agree that there should be some level of separation. But there should also a good level of cohesion, since we are trying to develop one tool after all. Maybe we can find a way to automatically read the entire spec from separate config files (one per command). I could live with splitting the spec into multiple INI ( |
@markus2330 that could look something like this:
/**
* @file
*
* @brief
*
* @copyright BSD License (see LICENSE.md or https://www.libelektra.org)
*/
#include <gen.h>
#include <command.h>
#include <kdbassert.h>
#include <kdbease.h>
#include <kdberrors.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#define COMMAND_NAME "gen"
#define EXTERNAL_PROGRAM "/usr/bin/echo"
#define GET_OPTION_KEY(options, name) GET_OPT_KEY (options, COMMAND_BASE_KEY (COMMAND_NAME) "/" name)
#define GET_OPTION(options, name) GET_OPT (options, COMMAND_BASE_KEY (COMMAND_NAME) "/" name)
void addGenSpec (KeySet * spec)
{
ksAppendKey (spec, keyNew (COMMAND_SPEC_KEY (COMMAND_NAME), KEY_META, "description", "Elektra's code-generator", KEY_META,
"command", COMMAND_NAME, KEY_END));
ksAppendKey (spec, keyNew (COMMAND_SPEC_KEY (COMMAND_NAME) "/templatename", KEY_META, "description", "The name of the key",
KEY_META, "args", "indexed", KEY_META, "args/index", "0", KEY_END));
ksAppendKey (spec, keyNew (COMMAND_SPEC_KEY (COMMAND_NAME) "/parentkey", KEY_META, "description", "The name of the key", KEY_META,
"args", "indexed", KEY_META, "args/index", "1", KEY_END));
ksAppendKey (spec, keyNew (COMMAND_SPEC_KEY (COMMAND_NAME) "/outputname", KEY_META, "description", "The name of the key", KEY_META,
"args", "indexed", KEY_META, "args/index", "2", KEY_END));
ksAppendKey (spec, keyNew (COMMAND_SPEC_KEY (COMMAND_NAME) "/parameters/#", KEY_META, "description",
"a list of parameters, the supported parameters depend on the template", KEY_META, "args", "remaining",
KEY_END));
ksAppendKey (spec, keyNew (COMMAND_SPEC_KEY (COMMAND_NAME) "/inputfile", KEY_META, "description",
"Load a file with plugin instead of accessing the KDB", KEY_META, "opt", "F", KEY_META, "opt/arg/help",
"<plugin>=<file>", KEY_META, "opt/long", "inputfile", KEY_META, "opt/arg", "required", KEY_END));
ADD_BASIC_OPTIONS (spec, COMMAND_SPEC_KEY (COMMAND_NAME))
}
int execGen (KeySet * options, Key * errorKey)
{
Key * inFileKey = GET_OPTION_KEY (options, "inputfile");
const char * template = GET_OPTION (options, "templatename");
const char * parentkey = GET_OPTION (options, "parentkey");
const char * output = GET_OPTION (options, "outputname");
int argvIndex = 0;
const char * genArgs[64] = {
EXTERNAL_PROGRAM,
};
if (inFileKey != NULL)
{
genArgs[++argvIndex] = "-F";
genArgs[++argvIndex] = keyString (inFileKey);
}
if (GET_OPTION_KEY(options, "verbose") != NULL)
{
genArgs[++argvIndex] = "-v";
}
if (GET_OPTION_KEY (options, "debug") != NULL)
{
genArgs[++argvIndex] = "-d";
}
...
genArgs[++argvIndex] = template;
genArgs[++argvIndex] = parentkey;
genArgs[++argvIndex] = output;
Key * parametersParent = GET_OPTION_KEY (options, "parameters");
KeySet * params = elektraArrayGet (parametersParent, options);
Key * cur = NULL;
for (elektraCursor it = 0; it < ksGetSize (params); ++it)
{
cur = ksAtCursor (params, it);
genArgs[++argvIndex] = keyString (cur);
}
ksDel (params);
genArgs[++argvIndex] = NULL;
int status;
EXEC_EXT (EXTERNAL_PROGRAM, genArgs, &status);
if (status != WEXITSTATUS (status))
{
ELEKTRA_SET_INTERNAL_ERRORF (errorKey, "'%s' did not terminate properly", EXTERNAL_PROGRAM);
return -1;
}
}
int checkGenArgs (ELEKTRA_UNUSED KeySet * options, ELEKTRA_UNUSED Key * errorKey)
{
// could check here if GET_OPTION(options, "inputfile") matches "<plugin>=<file>"
return 0;
}
...
#define GET_OPT_KEY(options, key) ksLookupByName (options, "proc:" key, 0)
#define EXEC_EXT(prog, argv, status) \
pid_t extPid; \
int timeout = 1000; \
if (0 == (extPid = fork ())) \
{ \
if (-1 == execve (prog, (char **) (argv), NULL)) \
{ \
perror ("child process execve failed [%m]"); \
return -1; \
} \
} \
while (0 == waitpid (extPid, status, WNOHANG)) \
{ \
if (--timeout < 0) \
{ \
perror ("timeout"); \
return -1; \
} \
sleep (1); \
}
... in ...
{ "gen", addGenSpec, execGen, checkGenArgs },
... We could probably also catch
Side noteThe help message of |
Would then the compiled bin rely on specs in external config files? The implementations of commands depend on a specific specification for options and args, I feel like allowing the changing of those might not be a good thing. I think the spec should be baked into to final binary so commands can be sure the spec is as they expect. Or am I missing something? @kodebach |
That's one of the things I tried to solve with the very much failed
If I understand correctly, for this to work, we need to keep this separate libelektra/src/tools/kdb/cmdline.cpp Line 298 in 14d6500
To make that work properly, we should really use However, for that to work, the But the best idea would probably be to extend the /**
* Sets up a contract for use with kdbOpen() that configures
* the gopts plugin to use a hard coded specification instead of the one loaded from the KDB.
* This is useful for applications that are very dependent on having a correct spec, or which must run standalone without creation of mountpoints.
*
* @param contract The KeySet into which the contract will be written.
* @param spec The spec:/ keys that gopts will pass to elektraGetOpts.
*
* @retval -1 if @p contract is NULL
* @retval 0 on success
*/
int elektraGOptsSpecContract (KeySet * contract, KeySet * spec) For defining the spec, I'd personally still prefer having a single file with the entire spec for But I'm also fine with separating the spec into sections for each command, if it is done in a more readable way than what is currently proposed. Maybe something similar to what we do with plugin contracts (i.e. /***** start of main.c *****/
#define KDB_SPEC_INCLUDE
KeySet * spec = ksNew (1,
keyNew ("spec:" CLI_BASE_KEY "/verbose", KEY_META, "description", "Explain what is happening.", KEY_META, "opt", "v", KEY_META, "opt/long", "verbose", KEY_META, "opt/arg", "none", KEY_END),
// ... other options for `kdb` itself ...
#include "get.spec_incl.c"
#include "set.spec_incl.c"
// ...
KS_END
);
#undef KDB_SPEC_INCLUDE
/***** start of get.spec_incl.c *****/
#ifndef KDB_SPEC_INCLUDE
static KeySet * spec()
{
return ksNew (1,
#endif
keyNew ("spec:" CLI_BASE_KEY "/get", KEY_META, "description", "Get the value of an individual key.", KEY_META, "command", COMMAND_NAME, KEY_END),
keyNew ("spec:" CLI_BASE_KEY "/all", KEY_META, "description", "Consider all of the keys", KEY_META, "opt", "a", KEY_META, "opt/long", "all", KEY_META, "opt/arg", "none", KEY_END),
keyNew ("spec:" CLI_BASE_KEY "/name", KEY_META, "description", "The name of the key", KEY_META, "args", "indexed", KEY_META, "args/index", "0", KEY_END),
#ifndef KDB_SPEC_INCLUDE
KS_END
);
}
#endif So the idea is there one main file that directly refers to the other parts of the spec (which could in turn refer to further file e.g. for
|
I guess I don't see the benefit of splitting the spec and implementation of a command. Does it not make sense to have one command as one file containing its spec and implementation? We could use |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, already had this reviewed a week ago but GitHub did not post it...
Your questions are now very detailed to the implementation. I would suggest that you first have a deeper look at the user interface as described in doc/help. Read over all of it to get a gist of the requirements. What needs to be changed (due to bugs or due to the new "sub command" interfaces)? What changes to you propose to make the interface nicer? Please create a separate PR (against new-backend. Later this one should also be against new-backend.) where you modify the docs with changes you propose. (In new-backend we can also merge these changes.)
src/tools/kdb/CMakeLists.txt
Outdated
@@ -1,15 +1,17 @@ | |||
include (LibAddMacros) | |||
|
|||
file (GLOB HDR_FILES *.hpp gen/*.hpp gen/*/*.hpp) | |||
file (GLOB HDR_FILES *.h *.hpp gen/*.hpp gen/*/*.hpp) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you plan to gradually replace commands? If not, it is probably makes sense to only include .h and .c.
src/tools/kdb/CMakeLists.txt
Outdated
list (REMOVE_ITEM SRC_FILES "${CMAKE_CURRENT_SOURCE_DIR}/gen/templates/collect.cpp") | ||
set (SOURCES ${SRC_FILES} ${HDR_FILES}) | ||
|
||
add_compile_definitions(CLI_BASE_KEY="/cli/kdb") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please keep the same base key /sw/elektra/kdb/#0/current
(the legacy /sw/kdb/profile/
can be removed!). I don't see why it should be done via a compile definition? At most it can be in a header but it is not really something anyone would change.
#include <kdb.h> | ||
|
||
#define ADD_BASIC_OPTIONS(baseSpec, baseKeyName) \ | ||
ksAppendKey (baseSpec, keyNew (baseKeyName "/debug", KEY_META, "description", \ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This must be externally accessible (by other tools and also by kdb get spec:/sw/elektra/kdb/#0/current
etc):
- Either you use the specload plugin (which probably needs some love to work robustly), then your approach more-or-less works as is. Or
- We use an external config file to be mounted to
spec:/sw/elektra/kdb/#0/current
. Then the (whole) spec should be in a.ni
file.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@hannes99 I think this should answer the need for gopts
. If you don't use kdbGet
for reading the spec within kdb
, can't really be accessible externally.
We use an external config file to be mounted to
spec:/sw/elektra/kdb/#0/current
I really wouldn't recommend that. Depending on the spec being correctly set up can be a problem in any application. But if the core tool kdb
breaks, because a mountpoint got screwed up it would be extremely bad.
the (whole) spec should be in a
.ni
file.
Defining the spec as a ni
plugin format file would be better than ksNew
/keyNew
, but if we want to bake it into the executable we'll run into bootstrap problems. I think this should only be future improvement, i.e. translate the spec via ni
and c
plugins during build.
src/tools/kdb/command.h
Outdated
KEY_META, "opt", "n", KEY_META, "opt/long", "no-newline", KEY_META, "opt/arg", "none", KEY_END)); | ||
|
||
|
||
#define GET_OPT(options, key) keyString (ksLookupByName (options, "proc:" key, 0)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only cascading lookups should be needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only cascading lookups should be needed.
With the current setup it doesn't matter, since elektraGetOpts
only produces proc:/
keys. But with gopts
using anything other than a cascading lookup here, is a straight-up bug.
|
||
|
||
#define GET_OPT(options, key) keyString (ksLookupByName (options, "proc:" key, 0)) | ||
#define OR(value, def) \ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This macro name is imho even for a private header file too short. Please prefix all macros with ELEKTRA_ to be consistent with our coding style. See doc/CODING.md and doc/DESIGN.md
src/tools/kdb/get.c
Outdated
char * buffer = elektraMalloc (binSize); | ||
if (buffer == NULL) | ||
{ | ||
ELEKTRA_SET_RESOURCE_ERROR (errorKey, "could not allocate memory for key bin value"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
continue after this error?
printf ("Did not find key '%s'", name); | ||
ret = 11; | ||
} | ||
printf ("\n"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't be there if nothing was printed?
if (options & KDB_O_CALLBACK) printf ("KDB_O_CALLBACK"); | ||
} | ||
|
||
const char * getCascadingName (const char * str) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is probably also useful somewhere else? Then put it to some shared code. Otherwise make it static.
} | ||
|
||
|
||
Key * printTrace (ELEKTRA_UNUSED KeySet * ks, Key * key, Key * found, elektraLookupFlags options) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you somehow mark code that you took 1:1 (only cout -> printf or similar) (or mark changes)? This would make the review easier.
src/tools/kdb/main.c
Outdated
/** | ||
* @file | ||
* | ||
* @brief |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please always add a very short description of the file (e.g. contains main routine with error handling)
There is already one reason above (externally accessible spec). Other reasons are:
|
@hannes99 is there any open question? |
c756e73
to
c309c9e
Compare
@hannes99 Please don't forget to target new-backend with this PR. |
Hey, @kodebach. Do you have an idea why here(in libelektra/src/libs/elektra/kdb.c Lines 625 to 648 in 3e9f5ed
plugins is empty when the mountpoint uses specload (which causes kdbOpen to fail)? It works on the master branch, but on new-backend it doesn't. Unfortunately I was not able to figure out what changes might cause this.
The mountpoints config seems fine(?)
and it was mounted with |
No they're not. That's one of the big changes in
I assume the For the system:/elektra/mountpoints/spec:\/sw\/elektra\/kdb\/current/plugins/noresolver/name = noresolver
system:/elektra/mountpoints/spec:\/sw\/elektra\/kdb\/current/plugins/specload/name = specload
system:/elektra/mountpoints/spec:\/sw\/elektra\/kdb\/current/plugins/specload/config/app = /home/hannes/git/libelektra/cmake-build-debug/bin/kdb
system:/elektra/mountpoints/spec:\/sw\/elektra\/kdb\/current/plugins/backend/name = backend
sytem:/elektra/mountpoints/spec:\/sw\/elektra\/kdb\/current/definition/path = specload.eqd
sytem:/elektra/mountpoints/spec:\/sw\/elektra\/kdb\/current/definition/positions/get/resolver = noresolver
sytem:/elektra/mountpoints/spec:\/sw\/elektra\/kdb\/current/definition/positions/get/storage = specload
sytem:/elektra/mountpoints/spec:\/sw\/elektra\/kdb\/current/definition/positions/set/resolver = noresolver
sytem:/elektra/mountpoints/spec:\/sw\/elektra\/kdb\/current/definition/positions/set/storage = specload
sytem:/elektra/mountpoints/spec:\/sw\/elektra\/kdb\/current/definition/positions/set/commit = noresolver
sytem:/elektra/mountpoints/spec:\/sw\/elektra\/kdb\/current/definition/positions/set/rollback = noresolver |
Ohhh, thanks! Works now. Not sure if already written somewhere, but having mounpoints created with the old |
I don't think it's written explicitly somewhere, but it's known. I guess we should have some sort of migration script that transforms the old config to new one. I'll see what I can come up with. Shouldn't be too hard to do. |
Something else, if the spec(mounted with |
You can tell which namespace a Key * found = ksLookup (ks, "/foo", 0);
if (keyGetNamespace (found) == KEY_NS_PROC) {
// key is in proc namespace
} Another solution would be to just rename everything from Key * currentParent = keyNew ("proc:/sw/elektra/#0/current", KEY_END);
Key * profileParent = keyNew ("proc:/sw/elektra/#0", KEY_END);
keyAddBaseName (profileParent, profile);
ksRename (ks, currentParent, profileParent); Then you can just do a cascading lookup with But both of this solutions have drawbacks, so I'd say: Make it work for I'm not even sure we do really need the profiles concept at all (@markus2330 what do you think?). If we do want profiles, it might be worth thinking about whether they should be handled more generally, so that other application can use this mechanism too. |
c309c9e
to
e66a778
Compare
short changelist:
|
54df663
to
55693e4
Compare
This pull request fixes 1 alert when merging 55693e4 into 3e9f5ed - view on LGTM.com fixed alerts:
|
jenkins build libelektra please |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good so far, just a few notes. One important thing: When dealing with key names, always prefer using the key*
APIs, since they do a lot of validation to make sure the name is valid. Only do direct string manipulation when there is no other way of doing things or you definitely cannot do things wrong (e.g. your getCascadingName
is fine)
Thanks for the feedback @kodebach ! |
7641926
to
54b4a85
Compare
This pull request fixes 1 alert when merging 54b4a85 into 3e9f5ed - view on LGTM.com fixed alerts:
|
Similar to gopts, profiles shouldn't be hardcoded in the kdb application. Instead we should use an improvement of the profile plugin https://www.libelektra.org/plugins/profile (what is missing that it somehow works together with gopts: I created #4481). |
and add command.h containing helpful macros and a struct def that is needed later
... to command.c for resolving bookmarks in keynames.
... and remove cpp implementation
... and remove cpp implementation
... and remove cpp implementation
... and remove cpp implementation
... and remove cpp implementation
.. and remove cpp implementation
54b4a85
to
14f7e01
Compare
I'm open to feedback on the general structure of the new CLI, the earlier the better.
Issue: #4431
Basics
(added as entry in
doc/news/_preparation_next_release.md
whichcontains
_(my name)_
)Please always add something to the release notes.
(first line should have
module: short statement
syntax)close #X
, are in the commit messages.doc/news/_preparation_next_release.md
scripts/dev/reformat-all
Checklist
(not in the PR description)
Review
Labels