Browse Source

ksession: Validate parg using PTYPE

Serj Kalichev 2 years ago
parent
commit
a05e1732ed

+ 3 - 6
klish/kcontext.h

@@ -30,12 +30,9 @@ FAUX_HIDDEN bool_t kcontext_set_sym(kcontext_t *context, ksym_t *sym);
 kpargv_t *kcontext_pargv(const kcontext_t *context);
 FAUX_HIDDEN bool_t kcontext_set_pargv(kcontext_t *context, kpargv_t *pargv);
 // Parent pargv object
-kpargv_t *kcontext_parent_pargv(const kcontext_t *context);
-FAUX_HIDDEN bool_t kcontext_set_pargv(kcontext_t *context, kpargv_t *parent_pargv);
-// Parg to validate
-kparg_t *kcontext_parg_to_validate(const kcontext_t *context);
-FAUX_HIDDEN bool_t kcontext_set_parg_to_validate(kcontext_t *context,
-	kparg_t *parg_to_validate);
+const kpargv_t *kcontext_parent_pargv(const kcontext_t *context);
+FAUX_HIDDEN bool_t kcontext_set_parent_pargv(kcontext_t *context,
+	const kpargv_t *parent_pargv);
 // Action iterator
 faux_list_node_t *kcontext_action_iter(const kcontext_t *context);
 FAUX_HIDDEN bool_t kcontext_set_action_iter(kcontext_t *context, faux_list_node_t *action_iter);

+ 1 - 1
klish/khelper.h

@@ -89,7 +89,7 @@
 	assert(subobj); \
 	if (!subobj) \
 		return BOOL_FALSE; \
-	if (!faux_list_add(inst->nested, subobj)) \
+	if (!faux_list_add(inst->nested, (void *)subobj)) \
 		return BOOL_FALSE; \
 	return BOOL_TRUE; \
 }

+ 9 - 4
klish/kpargv.h

@@ -44,10 +44,10 @@ C_DECL_BEGIN
 
 // Parg
 
-kparg_t *kparg_new(kentry_t *entry, const char *value);
+kparg_t *kparg_new(const kentry_t *entry, const char *value);
 void kparg_free(kparg_t *parg);
 
-kentry_t *kparg_entry(const kparg_t *parg);
+const kentry_t *kparg_entry(const kparg_t *parg);
 bool_t kparg_set_value(kparg_t *parg, const char *value);
 const char *kparg_value(const kparg_t *parg);
 
@@ -76,6 +76,11 @@ bool_t kpargv_set_purpose(kpargv_t *pargv, kpargv_purpose_e purpose);
 // Last argument
 bool_t kpargv_set_last_arg(kpargv_t *pargv, const char *last_arg);
 const char *kpargv_last_arg(const kpargv_t *pargv);
+// Candidate parg
+bool_t kpargv_set_candidate_parg(kpargv_t *pargv, kparg_t *candidate_parg);
+kparg_t *kpargv_candidate_parg(const kpargv_t *pargv);
+bool_t kpargv_accept_candidate_parg(kpargv_t *pargv);
+bool_t kpargv_decline_candidate_parg(kpargv_t *pargv);
 
 // Pargs
 faux_list_t *kpargv_pargs(const kpargv_t *pargv);
@@ -89,11 +94,11 @@ kparg_t *kpargv_entry_exists(const kpargv_t *pargv, const void *entry);
 
 // Completions
 faux_list_t *kpargv_completions(const kpargv_t *pargv);
-bool_t kpargv_add_completions(kpargv_t *pargv, kentry_t *completion);
+bool_t kpargv_add_completions(kpargv_t *pargv, const kentry_t *completion);
 ssize_t kpargv_completions_len(const kpargv_t *pargv);
 bool_t kpargv_completions_is_empty(const kpargv_t *pargv);
 kpargv_completions_node_t *kpargv_completions_iter(const kpargv_t *pargv);
-kentry_t *kpargv_completions_each(kpargv_completions_node_t **iter);
+const kentry_t *kpargv_completions_each(kpargv_completions_node_t **iter);
 
 // Debug
 bool_t kpargv_debug(const kpargv_t *pargv);

+ 2 - 2
klish/kpath.h

@@ -19,10 +19,10 @@ C_DECL_BEGIN
 
 // Level
 
-klevel_t *klevel_new(kentry_t *entry);
+klevel_t *klevel_new(const kentry_t *entry);
 void klevel_free(klevel_t *level);
 
-kentry_t *klevel_entry(const klevel_t *level);
+const kentry_t *klevel_entry(const klevel_t *level);
 
 // Path
 

+ 4 - 3
klish/ksession.h

@@ -35,10 +35,11 @@ kpargv_t *ksession_parse_for_completion(ksession_t *session,
 	const char *raw_line);
 kexec_t *ksession_parse_for_exec(ksession_t *session, const char *raw_line,
 	faux_error_t *error);
-kexec_t *ksession_parse_for_local_exec(kentry_t *entry);
+kexec_t *ksession_parse_for_local_exec(ksession_t *session,
+	const kentry_t *entry, const kpargv_t *parent_pargv);
 
-bool_t ksession_exec_locally(ksession_t *session, kentry_t *entry,
-	int *retcode, const char **out);
+bool_t ksession_exec_locally(ksession_t *session, const kentry_t *entry,
+	kpargv_t *parent_pargv, int *retcode, const char **out);
 
 C_DECL_END
 

+ 3 - 9
klish/ksession/kcontext.c

@@ -19,8 +19,7 @@ struct kcontext_s {
 	int retcode;
 	kplugin_t *plugin;
 	kpargv_t *pargv;
-	kpargv_t *parent_pargv; // Parent
-	kparg_t *parg_to_validate; // Parent
+	const kpargv_t *parent_pargv; // Parent
 	faux_list_node_t *action_iter; // Current action
 	ksym_t *sym;
 	int stdin;
@@ -54,12 +53,8 @@ KGET(context, kpargv_t *, pargv);
 FAUX_HIDDEN KSET(context, kpargv_t *, pargv);
 
 // Parent pargv
-KGET(context, kpargv_t *, parent_pargv);
-FAUX_HIDDEN KSET(context, kpargv_t *, parent_pargv);
-
-// Parg to validate
-KGET(context, kparg_t *, parg_to_validate);
-FAUX_HIDDEN KSET(context, kparg_t *, parg_to_validate);
+KGET(context, const kpargv_t *, parent_pargv);
+FAUX_HIDDEN KSET(context, const kpargv_t *, parent_pargv);
 
 // Action iterator
 KGET(context, faux_list_node_t *, action_iter);
@@ -101,7 +96,6 @@ kcontext_t *kcontext_new(kcontext_type_e type)
 	context->plugin = NULL;
 	context->pargv = NULL;
 	context->parent_pargv = NULL; // Don't free
-	context->parg_to_validate = NULL; // Don't free
 	context->action_iter = NULL;
 	context->sym = NULL;
 	context->stdin = -1;

+ 3 - 3
klish/ksession/klevel.c

@@ -11,15 +11,15 @@
 #include <klish/kentry.h>
 
 struct klevel_s {
-	kentry_t *entry;
+	const kentry_t *entry;
 };
 
 
 // ENTRY
-KGET(level, kentry_t *, entry);
+KGET(level, const kentry_t *, entry);
 
 
-klevel_t *klevel_new(kentry_t *entry)
+klevel_t *klevel_new(const kentry_t *entry)
 {
 	klevel_t *level = NULL;
 

+ 3 - 3
klish/ksession/kparg.c

@@ -15,20 +15,20 @@
 
 
 struct kparg_s {
-	kentry_t *entry;
+	const kentry_t *entry;
 	char *value;
 };
 
 
 // Entry
-KGET(parg, kentry_t *, entry);
+KGET(parg, const kentry_t *, entry);
 
 // Value
 KSET_STR(parg, value);
 KGET_STR(parg, value);
 
 
-kparg_t *kparg_new(kentry_t *entry, const char *value)
+kparg_t *kparg_new(const kentry_t *entry, const char *value)
 {
 	kparg_t *parg = NULL;
 

+ 35 - 3
klish/ksession/kpargv.c

@@ -21,6 +21,7 @@ struct kpargv_s {
 	bool_t continuable; // Last argument can be expanded
 	kpargv_purpose_e purpose; // Exec/Completion/Help
 	char *last_arg;
+	kparg_t *candidate_parg; // Don't free
 };
 
 // Status
@@ -47,6 +48,10 @@ KSET(pargv, kpargv_purpose_e, purpose);
 KSET_STR(pargv, last_arg);
 KGET_STR(pargv, last_arg);
 
+// Level
+KGET(pargv, kparg_t *, candidate_parg);
+KSET(pargv, kparg_t *, candidate_parg);
+
 // Pargs
 KGET(pargv, faux_list_t *, pargs);
 KADD_NESTED(pargv, kparg_t *, pargs);
@@ -57,11 +62,11 @@ KNESTED_EACH(pargv, kparg_t *, pargs);
 
 // Completions
 KGET(pargv, faux_list_t *, completions);
-KADD_NESTED(pargv, kentry_t *, completions);
+KADD_NESTED(pargv, const kentry_t *, completions);
 KNESTED_LEN(pargv, completions);
 KNESTED_IS_EMPTY(pargv, completions);
 KNESTED_ITER(pargv, completions);
-KNESTED_EACH(pargv, kentry_t *, completions);
+KNESTED_EACH(pargv, const kentry_t *, completions);
 
 
 static int kpargv_completions_compare(const void *first, const void *second)
@@ -98,6 +103,7 @@ kpargv_t *kpargv_new()
 	pargv->continuable = BOOL_FALSE;
 	pargv->purpose = KPURPOSE_EXEC;
 	pargv->last_arg = NULL;
+	pargv->candidate_parg = NULL;
 
 	// Parsed arguments list
 	pargv->pargs = faux_list_new(FAUX_LIST_UNSORTED, FAUX_LIST_NONUNIQUE,
@@ -194,6 +200,32 @@ const char *kpargv_status_str(const kpargv_t *pargv)
 }
 
 
+bool_t kpargv_accept_candidate_parg(kpargv_t *pargv)
+{
+	kparg_t *candidate = NULL;
+
+	assert(pargv);
+	if (!pargv)
+		return BOOL_FALSE;
+	if (!(candidate = pargv->candidate_parg))
+		return BOOL_FALSE;
+	pargv->candidate_parg = NULL;
+
+	return kpargv_add_pargs(pargv, candidate);
+}
+
+
+bool_t kpargv_decline_candidate_parg(kpargv_t *pargv)
+{
+	assert(pargv);
+	if (!pargv)
+		return BOOL_FALSE;
+	pargv->candidate_parg = NULL;
+
+	return BOOL_TRUE;
+}
+
+
 bool_t kpargv_debug(const kpargv_t *pargv)
 {
 #ifdef PARGV_DEBUG
@@ -220,7 +252,7 @@ bool_t kpargv_debug(const kpargv_t *pargv)
 
 	// Completions
 	if (!kpargv_completions_is_empty(pargv)) {
-		kentry_t *completion = NULL;
+		const kentry_t *completion = NULL;
 		kpargv_completions_node_t *citer = kpargv_completions_iter(pargv);
 		printf("Completions (%s):\n", kpargv_last_arg(pargv));
 		while ((completion = kpargv_completions_each(&citer)))

+ 4 - 4
klish/ksession/ksession.c

@@ -42,7 +42,7 @@ KSET_BOOL(session, done);
 ksession_t *ksession_new(const kscheme_t *scheme, const char *start_entry)
 {
 	ksession_t *session = NULL;
-	kentry_t *entry = NULL;
+	const kentry_t *entry = NULL;
 	const char *entry_to_search = NULL;
 	klevel_t *level = NULL;
 
@@ -175,8 +175,8 @@ static bool_t action_stdout_ev(faux_eloop_t *eloop, faux_eloop_type_e type,
 }
 
 
-bool_t ksession_exec_locally(ksession_t *session, kentry_t *entry,
-	int *retcode, const char **out)
+bool_t ksession_exec_locally(ksession_t *session, const kentry_t *entry,
+	kpargv_t *parent_pargv, int *retcode, const char **out)
 {
 	kexec_t *exec = NULL;
 	faux_eloop_t *eloop = NULL;
@@ -189,7 +189,7 @@ bool_t ksession_exec_locally(ksession_t *session, kentry_t *entry,
 		return BOOL_FALSE;
 
 	// Parsing
-	exec = ksession_parse_for_local_exec(entry);
+	exec = ksession_parse_for_local_exec(session, entry, parent_pargv);
 	if (!exec)
 		return BOOL_FALSE;
 

+ 65 - 37
klish/ksession/ksession_parse.c

@@ -17,39 +17,56 @@
 #include <klish/ksession.h>
 
 
-static bool_t ksession_validate_arg(kentry_t *entry, const char *arg)
+static bool_t ksession_validate_arg(ksession_t *session, kpargv_t *pargv)
 {
-	const char *str = NULL;
+	const char *out = NULL;
+	int retcode = -1;
+	const kentry_t *ptype_entry = NULL;
+	kparg_t *candidate = NULL;
 
-	assert(entry);
-	if (!entry)
+	assert(session);
+	if (!session)
+		return BOOL_FALSE;
+	assert(pargv);
+	if (!pargv)
+		return BOOL_FALSE;
+	candidate = kpargv_candidate_parg(pargv);
+	if (!candidate)
+		return BOOL_FALSE;
+	ptype_entry = kentry_nested_by_purpose(kparg_entry(candidate),
+		KENTRY_PURPOSE_PTYPE);
+	if (!ptype_entry)
+		return BOOL_FALSE;
+
+	if (!ksession_exec_locally(session, ptype_entry, pargv,
+		&retcode, &out)) {
 		return BOOL_FALSE;
-	assert(arg);
-	if (!arg)
+	}
+
+	if (retcode != 0)
 		return BOOL_FALSE;
 
-	// Temporary test code that implements COMMAND i.e. it compares argument
-	// to ENTRY's 'name' or 'value'. Later it will be removed by real code.
-	str = kentry_value(entry);
-	if (!str)
-		str = kentry_name(entry);
-	if (faux_str_casecmp(str, arg) == 0)
-			return BOOL_TRUE;
+	if (!faux_str_is_empty(out))
+		kparg_set_value(candidate, out);
 
-	return BOOL_FALSE;
+	return BOOL_TRUE;
 }
 
 
-static kpargv_status_e ksession_parse_arg(kentry_t *current_entry,
-	faux_argv_node_t **argv_iter, kpargv_t *pargv, bool_t entry_is_command)
+static kpargv_status_e ksession_parse_arg(ksession_t *session,
+	const kentry_t *current_entry, faux_argv_node_t **argv_iter,
+	kpargv_t *pargv, bool_t entry_is_command)
 {
-	kentry_t *entry = current_entry;
+	const kentry_t *entry = current_entry;
 	kentry_mode_e mode = KENTRY_MODE_NONE;
 	kpargv_status_e retcode = KPARSE_INPROGRESS; // For ENTRY itself
 	kpargv_status_e rc = KPARSE_NOTFOUND; // For nested ENTRYs
 	faux_argv_node_t *saved_argv_iter = NULL;
 	kpargv_purpose_e purpose = KPURPOSE_NONE;
 
+//fprintf(stderr, "PARSE: name=%s, ref=%s, arg=%s\n",
+//kentry_name(entry), kentry_ref_str(entry), faux_argv_current(*argv_iter));
+
 	assert(current_entry);
 	if (!current_entry)
 		return KPARSE_ERROR;
@@ -81,9 +98,7 @@ static kpargv_status_e ksession_parse_arg(kentry_t *current_entry,
 	// Container can't be a candidate.
 	} else if (!kentry_container(entry)) {
 		const char *current_arg = NULL;
-
-//printf("arg: %s, entry: %s\n", *argv_iter ? faux_argv_current(*argv_iter) : "<empty>",
-//	kentry_name(entry));
+		kparg_t *parg = NULL;
 
 		// When purpose is COMPLETION or HELP then fill completion list.
 		// Additionally if it's last continuable argument then lie to
@@ -112,9 +127,10 @@ static kpargv_status_e ksession_parse_arg(kentry_t *current_entry,
 
 		// Validate argument
 		current_arg = faux_argv_current(*argv_iter);
-		if (ksession_validate_arg(entry, current_arg)) {
-			kparg_t *parg = kparg_new(entry, current_arg);
-			kpargv_add_pargs(pargv, parg);
+		parg = kparg_new(entry, current_arg);
+		kpargv_set_candidate_parg(pargv, parg);
+		if (ksession_validate_arg(session, pargv)) {
+			kpargv_accept_candidate_parg(pargv);
 			// Command is an ENTRY with ACTIONs or NAVigation
 			if (kentry_actions_len(entry) > 0)
 				kpargv_set_command(pargv, entry);
@@ -123,6 +139,8 @@ static kpargv_status_e ksession_parse_arg(kentry_t *current_entry,
 		} else {
 			// It's not a container and is not validated so
 			// no chance to find anything here.
+			kpargv_decline_candidate_parg(pargv);
+			kparg_free(parg);
 			return KPARSE_NOTFOUND;
 		}
 	}
@@ -139,20 +157,27 @@ static kpargv_status_e ksession_parse_arg(kentry_t *current_entry,
 	if (KENTRY_MODE_EMPTY == mode)
 		return retcode;
 
+	// Following code (SWITCH or SEQUENCE cases) sometimes don's set rc.
+	// It happens when entry has nested entries but purposes of all entries
+	// are not COMMON so they will be ignored. So return code of function
+	// will be the code of ENTRY itself processing.
+	rc = retcode;
+
 	// SWITCH mode
 	// Entries within SWITCH can't has 'min'/'max' else than 1.
 	// So these attributes will be ignored. Note SWITCH itself can have
 	// 'min'/'max'.
 	if (KENTRY_MODE_SWITCH == mode) {
 		kentry_entrys_node_t *iter = kentry_entrys_iter(entry);
-		kentry_t *nested = NULL;
+		const kentry_t *nested = NULL;
 
 		while ((nested = kentry_entrys_each(&iter))) {
+//printf("SWITCH arg: %s, entry %s\n", *argv_iter ? faux_argv_current(*argv_iter) : "<empty>", kentry_name(nested));
 			// Ignore entries with non-COMMON purpose.
 			if (kentry_purpose(nested) != KENTRY_PURPOSE_COMMON)
 				continue;
-//printf("SWITCH arg: %s, entry %s\n", *argv_iter ? faux_argv_current(*argv_iter) : "<empty>", kentry_name(nested));
-			rc = ksession_parse_arg(nested, argv_iter, pargv, BOOL_FALSE);
+			rc = ksession_parse_arg(session, nested, argv_iter,
+				pargv, BOOL_FALSE);
 //printf("%s\n", kpargv_status_decode(rc));
 			// If some arguments was consumed then we will not check
 			// next SWITCH's entries in any case.
@@ -169,13 +194,14 @@ static kpargv_status_e ksession_parse_arg(kentry_t *current_entry,
 	} else if (KENTRY_MODE_SEQUENCE == mode) {
 		kentry_entrys_node_t *iter = kentry_entrys_iter(entry);
 		kentry_entrys_node_t *saved_iter = iter;
-		kentry_t *nested = NULL;
+		const kentry_t *nested = NULL;
 
 		while ((nested = kentry_entrys_each(&iter))) {
 			kpargv_status_e nrc = KPARSE_NOTFOUND;
 			size_t num = 0;
 			size_t min = kentry_min(nested);
 
+//fprintf(stderr, "SEQ arg: %s, entry %s\n", *argv_iter ? faux_argv_current(*argv_iter) : "<empty>", kentry_name(nested));
 			// Ignore entries with non-COMMON purpose.
 			if (kentry_purpose(nested) != KENTRY_PURPOSE_COMMON)
 				continue;
@@ -185,10 +211,9 @@ static kpargv_status_e ksession_parse_arg(kentry_t *current_entry,
 			// Try to match argument and current entry
 			// (from 'min' to 'max' times)
 			for (num = 0; num < kentry_max(nested); num++) {
-//printf("SEQ arg: %s, entry %s\n", *argv_iter ? faux_argv_current(*argv_iter) : "<empty>", kentry_name(nested));
-				nrc = ksession_parse_arg(nested, argv_iter,
-					pargv, BOOL_FALSE);
-//printf("%s\n", kpargv_status_decode(nrc));
+				nrc = ksession_parse_arg(session, nested,
+					argv_iter, pargv, BOOL_FALSE);
+//fprintf(stderr, "%s: %s\n", kentry_name(nested), kpargv_status_decode(nrc));
 				if (nrc != KPARSE_INPROGRESS)
 					break;
 			}
@@ -265,14 +290,14 @@ kpargv_t *ksession_parse_line(ksession_t *session, const faux_argv_t *argv,
 	levels_iterr = kpath_iterr(path);
 	level_found = kpath_len(path);
 	while ((level = kpath_eachr(&levels_iterr))) {
-		kentry_t *current_entry = klevel_entry(level);
+		const kentry_t *current_entry = klevel_entry(level);
 		// Ignore entries with non-COMMON purpose. These entries are for
 		// special processing and will be ignored here.
 		if (kentry_purpose(current_entry) != KENTRY_PURPOSE_COMMON)
 			continue;
 		// Parsing
-		pstatus = ksession_parse_arg(current_entry, &argv_iter, pargv,
-			BOOL_FALSE);
+		pstatus = ksession_parse_arg(session, current_entry, &argv_iter,
+			pargv, BOOL_FALSE);
 		if (pstatus != KPARSE_NOTFOUND)
 			break;
 		// NOTFOUND but some args were parsed.
@@ -512,12 +537,13 @@ kexec_t *ksession_parse_for_exec(ksession_t *session, const char *raw_line,
 }
 
 
-kexec_t *ksession_parse_for_local_exec(kentry_t *entry)
+kexec_t *ksession_parse_for_local_exec(ksession_t *session,
+	const kentry_t *entry, const kpargv_t *parent_pargv)
 {
 	faux_argv_node_t *argv_iter = NULL;
 	kpargv_t *pargv = NULL;
 	kexec_t *exec = NULL;
-	faux_argv_t *argv = faux_argv_new();
+	faux_argv_t *argv = NULL;
 	kcontext_t *context = NULL;
 	kpargv_status_e pstatus = KPARSE_NONE;
 	const char *line = NULL; // TODO: Must be 'line' field of ENTRY
@@ -539,7 +565,8 @@ kexec_t *ksession_parse_for_local_exec(kentry_t *entry)
 	kpargv_set_continuable(pargv, faux_argv_is_continuable(argv));
 	kpargv_set_purpose(pargv, KPURPOSE_EXEC);
 
-	pstatus = ksession_parse_arg(entry, &argv_iter, pargv, BOOL_TRUE);
+	pstatus = ksession_parse_arg(session, entry, &argv_iter, pargv,
+		BOOL_TRUE);
 	// Parsing problems
 	if ((pstatus != KPARSE_INPROGRESS) || (argv_iter != NULL)) {
 		kexec_free(exec);
@@ -551,6 +578,7 @@ kexec_t *ksession_parse_for_local_exec(kentry_t *entry)
 	context = kcontext_new(KCONTEXT_PLUGIN_ACTION);
 	assert(context);
 	kcontext_set_pargv(context, pargv);
+	kcontext_set_parent_pargv(context, parent_pargv);
 	kexec_add_contexts(exec, context);
 
 	faux_argv_free(argv);

+ 1 - 0
klish/ktp/ktpd_session.c

@@ -127,6 +127,7 @@ static bool_t ktpd_session_process_cmd(ktpd_session_t *ktpd, faux_msg_t *msg)
 	error = faux_error_new();
 
 	rc = ktpd_session_exec(ktpd, line, &retcode, error);
+	faux_str_free(line);
 	if (ktpd->exec) {
 		faux_error_free(error);
 		return BOOL_TRUE; // Continue and wait for ACTION

+ 1 - 0
plugins/klish/Makefile.am

@@ -7,6 +7,7 @@ kplugin_klish_la_LIBADD = libklish.la
 
 kplugin_klish_la_SOURCES += \
 	plugins/klish/plugin_init.c \
+	plugins/klish/ptypes.c \
 	plugins/klish/misc.c
 #	plugins/klish/hook_access.c \
 #	plugins/klish/hook_config.c \

+ 4 - 1
plugins/klish/plugin_init.c

@@ -25,10 +25,13 @@ int kplugin_klish_init(kcontext_t *context)
 	plugin = kcontext_plugin(context);
 	assert(plugin);
 
+	// Misc
 	kplugin_add_syms(plugin, ksym_new("nop", klish_nop));
 	kplugin_add_syms(plugin, ksym_new("tsym", klish_tsym));
 
-//	fprintf(stderr, "Plugin 'klish' init\n");
+	// PTYPEs
+	kplugin_add_syms(plugin, ksym_new("COMMAND", klish_ptype_COMMAND));
+
 	context = context; // Happy compiler
 
 	return 0;

+ 4 - 0
plugins/klish/private.h

@@ -14,6 +14,10 @@ C_DECL_BEGIN
 int klish_nop(kcontext_t *context);
 int klish_tsym(kcontext_t *context);
 
+// PTYPEs
+int klish_ptype_COMMAND(kcontext_t *context);
+
+
 C_DECL_END