@ -11,435 +11,382 @@
# include "list.h"
# include "log.h"
enum criteria_type { // *must* keep in sync with criteria_strings[]
CRIT_APP_ID ,
CRIT_CLASS ,
CRIT_CON_ID ,
CRIT_CON_MARK ,
CRIT_FLOATING ,
CRIT_ID ,
CRIT_INSTANCE ,
CRIT_TILING ,
CRIT_TITLE ,
CRIT_URGENT ,
CRIT_WINDOW_ROLE ,
CRIT_WINDOW_TYPE ,
CRIT_WORKSPACE ,
CRIT_LAST
} ;
static const char * const criteria_strings [ CRIT_LAST ] = {
[ CRIT_APP_ID ] = " app_id " ,
[ CRIT_CLASS ] = " class " ,
[ CRIT_CON_ID ] = " con_id " ,
[ CRIT_CON_MARK ] = " con_mark " ,
[ CRIT_FLOATING ] = " floating " ,
[ CRIT_ID ] = " id " ,
[ CRIT_INSTANCE ] = " instance " ,
[ CRIT_TILING ] = " tiling " ,
[ CRIT_TITLE ] = " title " ,
[ CRIT_URGENT ] = " urgent " , // either "latest" or "oldest" ...
[ CRIT_WINDOW_ROLE ] = " window_role " ,
[ CRIT_WINDOW_TYPE ] = " window_type " ,
[ CRIT_WORKSPACE ] = " workspace "
} ;
bool criteria_is_empty ( struct criteria * criteria ) {
return ! criteria - > title
& & ! criteria - > app_id
& & ! criteria - > class
& & ! criteria - > instance
& & ! criteria - > con_mark
& & ! criteria - > con_id
& & ! criteria - > id
& & ! criteria - > window_role
& & ! criteria - > window_type
& & ! criteria - > floating
& & ! criteria - > tiling
& & ! criteria - > urgent
& & ! criteria - > workspace ;
}
/**
* A single criteria token ( ie . value / regex pair ) ,
* e . g . ' class = " some class regex " ' .
*/
struct crit_token {
enum criteria_type type ;
pcre * regex ;
char * raw ;
} ;
void criteria_destroy ( struct criteria * criteria ) {
pcre_free ( criteria - > title ) ;
pcre_free ( criteria - > app_id ) ;
pcre_free ( criteria - > class ) ;
pcre_free ( criteria - > instance ) ;
pcre_free ( criteria - > con_mark ) ;
pcre_free ( criteria - > window_role ) ;
free ( criteria - > workspace ) ;
free ( criteria - > raw ) ;
free ( criteria ) ;
}
static void free_crit_token ( struct crit_token * crit ) {
pcre_free ( crit - > regex ) ;
free ( crit - > raw ) ;
free ( crit ) ;
static int regex_cmp ( const char * item , const pcre * regex ) {
return pcre_exec ( regex , NULL , item , strlen ( item ) , 0 , 0 , NULL , 0 ) ;
}
static void free_crit_tokens ( list_t * crit_tokens ) {
for ( int i = 0 ; i < crit_tokens - > length ; i + + ) {
free_crit_token ( crit_tokens - > items [ i ] ) ;
static bool criteria_matches_view ( struct criteria * criteria ,
struct sway_view * view ) {
if ( criteria - > title ) {
const char * title = view_get_title ( view ) ;
if ( ! title | | regex_cmp ( title , criteria - > title ) ! = 0 ) {
return false ;
}
}
list_free ( crit_tokens ) ;
}
// Extracts criteria string from its brackets. Returns new (duplicate)
// substring.
static char * criteria_from ( const char * arg ) {
char * criteria = NULL ;
if ( * arg = = ' [ ' ) {
criteria = strdup ( arg + 1 ) ;
} else {
criteria = strdup ( arg ) ;
if ( criteria - > app_id ) {
const char * app_id = view_get_app_id ( view ) ;
if ( ! app_id | | regex_cmp ( app_id , criteria - > app_id ) ! = 0 ) {
return false ;
}
}
int last = strlen ( criteria ) - 1 ;
if ( criteria [ last ] = = ' ] ' ) {
criteria [ last ] = ' \0 ' ;
if ( criteria - > class ) {
const char * class = view_get_class ( view ) ;
if ( ! class | | regex_cmp ( class , criteria - > class ) ! = 0 ) {
return false ;
}
}
return criteria ;
}
// Return instances of c found in str.
static int countchr ( char * str , char c ) {
int found = 0 ;
for ( int i = 0 ; str [ i ] ; i + + ) {
if ( str [ i ] = = c ) {
+ + found ;
if ( criteria - > instance ) {
const char * instance = view_get_instance ( view ) ;
if ( ! instance | | regex_cmp ( instance , criteria - > instance ) ! = 0 ) {
return false ;
}
}
return found ;
}
// criteria_str is e.g. '[class="some class regex" instance="instance name"]'.
//
// Will create array of pointers in buf, where first is duplicate of given
// string (must be freed) and the rest are pointers to names and values in the
// base string (every other, naturally). argc will be populated with the length
// of buf.
//
// Returns error string or NULL if successful.
static char * crit_tokens ( int * argc , char * * * buf ,
const char * const criteria_str ) {
wlr_log ( L_DEBUG , " Parsing criteria: '%s' " , criteria_str ) ;
char * base = criteria_from ( criteria_str ) ;
char * head = base ;
char * namep = head ; // start of criteria name
char * valp = NULL ; // start of value
// We're going to place EOS markers where we need to and fill up an array
// of pointers to the start of each token (either name or value).
int pairs = countchr ( base , ' = ' ) ;
int max_tokens = pairs * 2 + 1 ; // this gives us at least enough slots
char * * argv = * buf = calloc ( max_tokens , sizeof ( char * ) ) ;
argv [ 0 ] = base ; // this needs to be freed by caller
bool quoted = true ;
* argc = 1 ; // uneven = name, even = value
while ( * head & & * argc < max_tokens ) {
if ( namep ! = head & & * ( head - 1 ) = = ' \\ ' ) {
// escaped character: don't try to parse this
} else if ( * head = = ' = ' & & namep ! = head ) {
if ( * argc % 2 ! = 1 ) {
// we're not expecting a name
return strdup ( " Unable to parse criteria: "
" Found out of place equal sign " ) ;
} else {
// name ends here
char * end = head ; // don't want to rewind the head
while ( * ( end - 1 ) = = ' ' ) {
- - end ;
}
* end = ' \0 ' ;
if ( * ( namep ) = = ' ' ) {
namep = strrchr ( namep , ' ' ) + 1 ;
}
argv [ * argc ] = namep ;
* argc + = 1 ;
}
} else if ( * head = = ' " ' ) {
if ( * argc % 2 ! = 0 ) {
// we're not expecting a value
return strdup ( " Unable to parse criteria: "
" Found quoted value where it was not expected " ) ;
} else if ( ! valp ) { // value starts here
valp = head + 1 ;
quoted = true ;
} else {
// value ends here
argv [ * argc ] = valp ;
* argc + = 1 ;
* head = ' \0 ' ;
valp = NULL ;
namep = head + 1 ;
}
} else if ( * argc % 2 = = 0 & & * head ! = ' ' ) {
// parse unquoted values
if ( ! valp ) {
quoted = false ;
valp = head ; // value starts here
}
} else if ( valp & & ! quoted & & * head = = ' ' ) {
// value ends here
argv [ * argc ] = valp ;
* argc + = 1 ;
* head = ' \0 ' ;
valp = NULL ;
namep = head + 1 ;
if ( criteria - > con_mark ) {
// TODO
return false ;
}
if ( criteria - > con_id ) { // Internal ID
if ( ! view - > swayc | | view - > swayc - > id ! = criteria - > con_id ) {
return false ;
}
head + + ;
}
// catch last unquoted value if needed
if ( valp & & ! quoted & & ! * head ) {
argv [ * argc ] = valp ;
* argc + = 1 ;
if ( criteria - > id ) { // X11 window ID
uint32_t x11_window_id = view_get_x11_window_id ( view ) ;
if ( ! x11_window_id | | x11_window_id ! = criteria - > id ) {
return false ;
}
}
return NULL ;
if ( criteria - > window_role ) {
// TODO
}
if ( criteria - > window_type ) {
uint32_t type = view_get_window_type ( view ) ;
if ( ! type | | type ! = criteria - > window_type ) {
return false ;
}
}
if ( criteria - > floating ) {
// TODO
return false ;
}
if ( criteria - > tiling ) {
// TODO
}
if ( criteria - > urgent ) {
// TODO
return false ;
}
if ( criteria - > workspace ) {
if ( ! view - > swayc ) {
return false ;
}
struct sway_container * ws = container_parent ( view - > swayc , C_WORKSPACE ) ;
if ( ! ws | | strcmp ( ws - > name , criteria - > workspace ) ! = 0 ) {
return false ;
}
}
return true ;
}
// Returns error string on failure or NULL otherwise.
static char * parse_criteria_name ( enum criteria_type * type , char * name ) {
* type = CRIT_LAST ;
for ( int i = 0 ; i < CRIT_LAST ; i + + ) {
if ( strcmp ( criteria_strings [ i ] , name ) = = 0 ) {
* type = ( enum criteria_type ) i ;
break ;
list_t * criteria_for_view ( struct sway_view * view , enum criteria_type types ) {
list_t * criterias = config - > criteria ;
list_t * matches = create_list ( ) ;
for ( int i = 0 ; i < criterias - > length ; + + i ) {
struct criteria * criteria = criterias - > items [ i ] ;
if ( ( criteria - > type & types ) & & criteria_matches_view ( criteria , view ) ) {
list_add ( matches , criteria ) ;
}
}
if ( * type = = CRIT_LAST ) {
const char * fmt = " Criteria type '%s' is invalid or unsupported. " ;
int len = strlen ( name ) + strlen ( fmt ) - 1 ;
char * error = malloc ( len ) ;
snprintf ( error , len , fmt , name ) ;
return error ;
} else if ( * type = = CRIT_URGENT | | * type = = CRIT_WINDOW_ROLE | |
* type = = CRIT_WINDOW_TYPE ) {
// (we're just being helpful here)
const char * fmt = " \" %s \" criteria currently unsupported, "
" no window will match this " ;
int len = strlen ( fmt ) + strlen ( name ) - 1 ;
char * error = malloc ( len ) ;
snprintf ( error , len , fmt , name ) ;
return error ;
return matches ;
}
struct match_data {
struct criteria * criteria ;
list_t * matches ;
} ;
static void criteria_get_views_iterator ( struct sway_container * container ,
void * data ) {
struct match_data * match_data = data ;
if ( container - > type = = C_VIEW ) {
if ( criteria_matches_view ( match_data - > criteria , container - > sway_view ) ) {
list_add ( match_data - > matches , container - > sway_view ) ;
}
}
return NULL ;
}
list_t * criteria_get_views ( struct criteria * criteria ) {
list_t * matches = create_list ( ) ;
struct match_data data = {
. criteria = criteria ,
. matches = matches ,
} ;
container_for_each_descendant_dfs ( & root_container ,
criteria_get_views_iterator , & data ) ;
return matches ;
}
// The error pointer is used for parsing functions, and saves having to pass it
// as an argument in several places.
char * error = NULL ;
// Returns error string on failure or NULL otherwise.
static char * generate_regex ( pcre * * regex , char * value ) {
static bool generate_regex ( pcre * * regex , char * value ) {
const char * reg_err ;
int offset ;
* regex = pcre_compile ( value , PCRE_UTF8 | PCRE_UCP , & reg_err , & offset , NULL ) ;
if ( ! * regex ) {
const char * fmt = " Regex compilation (for '%s') failed: %s " ;
const char * fmt = " Regex compilation for '%s' failed: %s" ;
int len = strlen ( fmt ) + strlen ( value ) + strlen ( reg_err ) - 3 ;
char * error = malloc ( len ) ;
error = malloc ( len ) ;
snprintf ( error , len , fmt , value , reg_err ) ;
return error ;
return false ;
}
return NULL ;
}
// Test whether the criterion corresponds to the currently focused window
static bool crit_is_focused ( const char * value ) {
return ! strcmp ( value , " focused " ) | | ! strcmp ( value , " __focused__ " ) ;
return true ;
}
// Populate list with crit_tokens extracted from criteria string, returns error
// string or NULL if successful.
char * extract_crit_tokens ( list_t * tokens , const char * const criteria ) {
int argc ;
char * * argv = NULL , * error = NULL ;
if ( ( error = crit_tokens ( & argc , & argv , criteria ) ) ) {
goto ect_cleanup ;
static bool parse_token ( struct criteria * criteria , char * name , char * value ) {
// Require value, unless token is floating or tiled
if ( ! value & & ( strcmp ( name , " title " ) = = 0
| | strcmp ( name , " app_id " ) = = 0
| | strcmp ( name , " class " ) = = 0
| | strcmp ( name , " instance " ) = = 0
| | strcmp ( name , " con_id " ) = = 0
| | strcmp ( name , " con_mark " ) = = 0
| | strcmp ( name , " window_role " ) = = 0
| | strcmp ( name , " window_type " ) = = 0
| | strcmp ( name , " id " ) = = 0
| | strcmp ( name , " urgent " ) = = 0
| | strcmp ( name , " workspace " ) = = 0 ) ) {
const char * fmt = " Token '%s' requires a value " ;
int len = strlen ( fmt ) + strlen ( name ) - 1 ;
error = malloc ( len ) ;
snprintf ( error , len , fmt , name ) ;
return false ;
}
for ( int i = 1 ; i + 1 < argc ; i + = 2 ) {
char * name = argv [ i ] , * value = argv [ i + 1 ] ;
struct crit_token * token = calloc ( 1 , sizeof ( struct crit_token ) ) ;
token - > raw = strdup ( value ) ;
if ( ( error = parse_criteria_name ( & token - > type , name ) ) ) {
free_crit_token ( token ) ;
goto ect_cleanup ;
} else if ( token - > type = = CRIT_URGENT | | crit_is_focused ( value ) ) {
wlr_log ( L_DEBUG , " %s -> \" %s \" " , name , value ) ;
list_add ( tokens , token ) ;
} else if ( ( error = generate_regex ( & token - > regex , value ) ) ) {
free_crit_token ( token ) ;
goto ect_cleanup ;
if ( strcmp ( name , " title " ) = = 0 ) {
generate_regex ( & criteria - > title , value ) ;
} else if ( strcmp ( name , " app_id " ) = = 0 ) {
generate_regex ( & criteria - > app_id , value ) ;
} else if ( strcmp ( name , " class " ) = = 0 ) {
generate_regex ( & criteria - > class , value ) ;
} else if ( strcmp ( name , " instance " ) = = 0 ) {
generate_regex ( & criteria - > instance , value ) ;
} else if ( strcmp ( name , " con_id " ) = = 0 ) {
char * endptr ;
criteria - > con_id = strtoul ( value , & endptr , 10 ) ;
if ( * endptr ! = 0 ) {
error = strdup ( " The value for 'con_id' should be numeric " ) ;
}
} else if ( strcmp ( name , " con_mark " ) = = 0 ) {
generate_regex ( & criteria - > con_mark , value ) ;
} else if ( strcmp ( name , " window_role " ) = = 0 ) {
generate_regex ( & criteria - > window_role , value ) ;
} else if ( strcmp ( name , " window_type " ) = = 0 ) {
// TODO: This is a string but will be stored as an enum or integer
} else if ( strcmp ( name , " id " ) = = 0 ) {
char * endptr ;
criteria - > id = strtoul ( value , & endptr , 10 ) ;
if ( * endptr ! = 0 ) {
error = strdup ( " The value for 'id' should be numeric " ) ;
}
} else if ( strcmp ( name , " floating " ) = = 0 ) {
criteria - > floating = true ;
} else if ( strcmp ( name , " tiling " ) = = 0 ) {
criteria - > tiling = true ;
} else if ( strcmp ( name , " urgent " ) = = 0 ) {
if ( strcmp ( value , " latest " ) = = 0 ) {
criteria - > urgent = ' l ' ;
} else if ( strcmp ( value , " oldest " ) = = 0 ) {
criteria - > urgent = ' o ' ;
} else {
wlr_log ( L_DEBUG , " %s -> /%s/ " , name , value ) ;
list_add ( tokens , token ) ;
error =
strdup ( " The value for 'urgent' must be 'latest' or 'oldest' " ) ;
}
} else if ( strcmp ( name , " workspace " ) = = 0 ) {
criteria - > workspace = strdup ( value ) ;
} else {
const char * fmt = " Token '%s' is not recognized " ;
int len = strlen ( fmt ) + strlen ( name ) - 1 ;
error = malloc ( len ) ;
snprintf ( error , len , fmt , name ) ;
}
ect_cleanup :
free ( argv [ 0 ] ) ; // base string
free ( argv ) ;
return error ;
}
static int regex_cmp ( const char * item , const pcre * regex ) {
return pcre_exec ( regex , NULL , item , strlen ( item ) , 0 , 0 , NULL , 0 ) ;
}
// test a single view if it matches list of criteria tokens (all of them).
static bool criteria_test ( struct sway_container * cont , list_t * tokens ) {
if ( cont - > type ! = C_CONTAINER & & cont - > type ! = C_VIEW ) {
if ( error ) {
return false ;
}
int matches = 0 ;
for ( int i = 0 ; i < tokens - > length ; i + + ) {
struct crit_token * crit = tokens - > items [ i ] ;
switch ( crit - > type ) {
case CRIT_CLASS :
{
const char * class = view_get_class ( cont - > sway_view ) ;
if ( ! class ) {
break ;
}
if ( crit - > regex & & regex_cmp ( class , crit - > regex ) = = 0 ) {
matches + + ;
}
break ;
}
case CRIT_CON_ID :
{
char * endptr ;
size_t crit_id = strtoul ( crit - > raw , & endptr , 10 ) ;
if ( * endptr = = 0 & & cont - > id = = crit_id ) {
+ + matches ;
}
break ;
}
case CRIT_CON_MARK :
// TODO
break ;
case CRIT_FLOATING :
// TODO
break ;
case CRIT_ID :
// TODO
break ;
case CRIT_APP_ID :
{
const char * app_id = view_get_app_id ( cont - > sway_view ) ;
if ( ! app_id ) {
break ;
}
if ( crit - > regex & & regex_cmp ( app_id , crit - > regex ) = = 0 ) {
matches + + ;
}
break ;
}
case CRIT_INSTANCE :
{
const char * instance = view_get_instance ( cont - > sway_view ) ;
if ( ! instance ) {
break ;
}
if ( crit - > regex & & regex_cmp ( instance , crit - > regex ) = = 0 ) {
matches + + ;
}
break ;
}
case CRIT_TILING :
// TODO
break ;
case CRIT_TITLE :
{
const char * title = view_get_title ( cont - > sway_view ) ;
if ( ! title ) {
break ;
}
if ( crit - > regex & & regex_cmp ( title , crit - > regex ) = = 0 ) {
matches + + ;
}
break ;
}
case CRIT_URGENT :
// TODO "latest" or "oldest"
break ;
case CRIT_WINDOW_ROLE :
// TODO
break ;
case CRIT_WINDOW_TYPE :
// TODO
break ;
case CRIT_WORKSPACE :
// TODO
break ;
default :
sway_abort ( " Invalid criteria type (%i) " , crit - > type ) ;
break ;
}
}
return matches = = tokens - > length ;
return true ;
}
int criteria_cmp ( const void * a , const void * b ) {
if ( a = = b ) {
return 0 ;
} else if ( ! a ) {
return - 1 ;
} else if ( ! b ) {
return 1 ;
static void skip_spaces ( char * * head ) {
while ( * * head = = ' ' ) {
+ + * head ;
}
const struct criteria * crit_a = a , * crit_b = b ;
int cmp = lenient_strcmp ( crit_a - > cmdlist , crit_b - > cmdlist ) ;
if ( cmp ! = 0 ) {
return cmp ;
}
return lenient_strcmp ( crit_a - > crit_raw , crit_b - > crit_raw ) ;
}
void free_criteria ( struct criteria * crit ) {
if ( crit - > tokens ) {
free_crit_tokens ( crit - > tokens ) ;
}
if ( crit - > cmdlist ) {
free ( crit - > cmdlist ) ;
// Remove escaping slashes from value
static void unescape ( char * value ) {
if ( ! strchr ( value , ' \\ ' ) ) {
return ;
}
if ( crit - > crit_raw ) {
free ( crit - > crit_raw ) ;
}
free ( crit ) ;
}
bool criteria_any ( struct sway_container * cont , list_t * criteria ) {
for ( int i = 0 ; i < criteria - > length ; i + + ) {
struct criteria * bc = criteria - > items [ i ] ;
if ( criteria_test ( cont , bc - > tokens ) ) {
return true ;
char * copy = calloc ( strlen ( value ) + 1 , 1 ) ;
char * readhead = value ;
char * writehead = copy ;
while ( * readhead ) {
if ( * readhead = = ' \\ ' & &
( * ( readhead + 1 ) = = ' " ' | | * ( readhead + 1 ) = = ' \\ ' ) ) {
// skip the slash
+ + readhead ;
}
* writehead = * readhead ;
+ + writehead ;
+ + readhead ;
}
return false ;
strcpy ( value , copy ) ;
free ( copy ) ;
}
list_t * criteria_for ( struct sway_container * cont ) {
list_t * criteria = config - > criteria , * matches = create_list ( ) ;
for ( int i = 0 ; i < criteria - > length ; i + + ) {
struct criteria * bc = criteria - > items [ i ] ;
if ( criteria_test ( cont , bc - > tokens ) ) {
list_add ( matches , bc ) ;
/**
* Parse a raw criteria string such as [ class = " foo " instance = " bar " ] into a
* criteria struct .
*
* If errors are found , NULL will be returned and the error argument will be
* populated with an error string .
*/
struct criteria * criteria_parse ( char * raw , char * * error_arg ) {
free ( error ) ;
error = NULL ;
char * head = raw ;
skip_spaces ( & head ) ;
if ( * head ! = ' [ ' ) {
* error_arg = strdup ( " No criteria " ) ;
return NULL ;
}
+ + head ;
struct criteria * criteria = calloc ( sizeof ( struct criteria ) , 1 ) ;
char * name = NULL , * value = NULL ;
bool in_quotes = false ;
while ( * head & & * head ! = ' ] ' ) {
skip_spaces ( & head ) ;
// Parse token name
char * namestart = head ;
while ( ( * head > = ' a ' & & * head < = ' z ' ) | | * head = = ' _ ' ) {
+ + head ;
}
name = calloc ( head - namestart + 1 , 1 ) ;
strncpy ( name , namestart , head - namestart ) ;
// Parse token value
skip_spaces ( & head ) ;
value = NULL ;
if ( * head = = ' = ' ) {
+ + head ;
skip_spaces ( & head ) ;
if ( * head = = ' " ' ) {
in_quotes = true ;
+ + head ;
}
char * valuestart = head ;
if ( in_quotes ) {
while ( * head & & ( * head ! = ' " ' | | * ( head - 1 ) = = ' \\ ' ) ) {
+ + head ;
}
if ( ! * head ) {
* error_arg = strdup ( " Quote mismatch in criteria " ) ;
goto cleanup ;
}
} else {
while ( * head & & * head ! = ' ' & & * head ! = ' ] ' ) {
+ + head ;
}
}
value = calloc ( head - valuestart + 1 , 1 ) ;
strncpy ( value , valuestart , head - valuestart ) ;
if ( in_quotes ) {
+ + head ;
in_quotes = false ;
}
unescape ( value ) ;
}
wlr_log ( L_DEBUG , " Found pair: %s=%s " , name , value ) ;
if ( ! parse_token ( criteria , name , value ) ) {
* error_arg = error ;
goto cleanup ;
}
skip_spaces ( & head ) ;
free ( name ) ;
free ( value ) ;
name = NULL ;
value = NULL ;
}
return matches ;
}
struct list_tokens {
list_t * list ;
list_t * tokens ;
} ;
static void container_match_add ( struct sway_container * container ,
struct list_tokens * list_tokens ) {
if ( criteria_test ( container , list_tokens - > tokens ) ) {
list_add ( list_tokens - > list , container ) ;
if ( * head ! = ' ] ' ) {
* error_arg = strdup ( " No closing brace found in criteria " ) ;
goto cleanup ;
}
}
list_t * container_for_crit_tokens ( list_t * tokens ) {
struct list_tokens list_tokens =
( struct list_tokens ) { create_list ( ) , tokens } ;
if ( criteria_is_empty ( criteria ) ) {
* error_arg = strdup ( " Criteria is empty " ) ;
goto cleanup ;
}
container_for_each_descendant_dfs ( & root_container ,
( void ( * ) ( struct sway_container * , void * ) ) container_match_add ,
& list_tokens ) ;
+ + head ;
int len = head - raw ;
criteria - > raw = calloc ( len + 1 , 1 ) ;
strncpy ( criteria - > raw , raw , len ) ;
return criteria ;
// TODO look in the scratchpad
return list_tokens . list ;
cleanup :
free ( name ) ;
free ( value ) ;
criteria_destroy ( criteria ) ;
return NULL ;
}