Commit e9bce288 authored by Rafaël Carré's avatar Rafaël Carré

fix zsh completion

parent 510b8c25
/***************************************************************************** /*****************************************************************************
* zsh.cpp: create zsh completion rule for vlc * zsh.cpp: create zsh completion rule for vlc
***************************************************************************** *****************************************************************************
* Copyright © 2005-2008 the VideoLAN team * Copyright © 2005-2011 the VideoLAN team
* $Id$
* *
* Authors: Sigmund Augdal Helberg <dnumgis@videolan.org> * Authors: Sigmund Augdal Helberg <dnumgis@videolan.org>
Rafaël Carré <funman@videolanorg>
* *
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
...@@ -21,21 +21,26 @@ ...@@ -21,21 +21,26 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/ *****************************************************************************/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <stdio.h> #include <stdio.h>
#include <map> #include <map>
#include <string> #include <string>
#include <sstream>
#include <utility> #include <utility>
#include <iostream> #include <iostream>
#include <algorithm> #include <algorithm>
typedef std::multimap<std::string, std::string> mumap;
typedef std::multimap<int, std::string> mcmap;
typedef std::pair<std::string, std::string> mpair; typedef std::pair<std::string, std::string> mpair;
typedef std::multimap<std::string, std::string> mumap;
mumap mods;
typedef std::pair<int, std::string> mcpair; typedef std::pair<int, std::string> mcpair;
typedef std::multimap<int, std::string> mcmap;
mcmap mods2;
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <vlc_common.h> #include <vlc_common.h>
#include <vlc/vlc.h> #include <vlc/vlc.h>
...@@ -45,418 +50,251 @@ typedef std::pair<int, std::string> mcpair; ...@@ -45,418 +50,251 @@ typedef std::pair<int, std::string> mcpair;
#undef __PLUGIN__ #undef __PLUGIN__
#include <../src/modules/modules.h> #include <../src/modules/modules.h>
void ParseModules( mumap &mods, mcmap &mods2 ); static void ReplaceChars(char *str)
void PrintModuleList( mumap &mods, mcmap &mods2 );
void ParseOption( module_config_t *p_item, mumap &mods, mcmap &mods2 );
void PrintOption( char *psz_option, char i_short, char *psz_exlusive,
char *psz_text, char *psz_longtext, char *psz_args );
int main( int i_argc, const char **ppsz_argv )
{ {
mumap mods; if (str) {
mcmap mods2; char *parser;
/* Create a libvlc structure */ while ((parser = strchr(str, ':'))) *parser=';' ;
const char *argv[i_argc + 1]; while ((parser = strchr(str, '"'))) *parser='\'' ;
argv[0] = "vlc"; while ((parser = strchr(str, '`'))) *parser='\'' ;
for( int i = 0; i < i_argc; i++ )
argv[i+1] = ppsz_argv[i];
libvlc_instance_t *p_libvlc_instance = libvlc_new(i_argc+1, argv);
if( !p_libvlc_instance )
{
return 1;
} }
printf("#compdef vlc cvlc rvlc svlc mvlc qvlc nvlc\n\n"
"#This file is autogenerated by zsh.cpp\n"
"typeset -A opt_args\n"
"local context state line ret=1\n"
"local modules\n\n" );
PrintModuleList( mods, mods2 );
printf( "_arguments -S -s \\\n" );
ParseModules( mods, mods2 );
printf( " \"(--module)-p[print help on module]:print help on module:($modules)\"\\\n" );
printf( " \"(-p)--module[print help on module]:print help on module:($modules)\"\\\n" );
printf( " \"(--help)-h[print help]\"\\\n" );
printf( " \"(-h)--help[print help]\"\\\n" );
printf( " \"(--longhelp)-H[print detailed help]\"\\\n" );
printf( " \"(-H)--longhelp[print detailed help]\"\\\n" );
printf( " \"(--list)-l[print a list of available modules]\"\\\n" );
printf( " \"(-l)--list[print a list of available modules]\"\\\n" );
printf( " \"--reset-config[reset the current config to the default values]\"\\\n" );
printf( " \"--config[use alternate config file]\"\\\n" );
printf( " \"--reset-plugins-cache[resets the current plugins cache]\"\\\n" );
printf( " \"--version[print version information]\"\\\n" );
printf( " \"*:Playlist item:->mrl\" && ret=0\n\n" );
printf( "case $state in\n" );
printf( " mrl)\n" );
printf( " _alternative 'files:file:_files' 'urls:URL:_urls' && ret=0\n" );
printf( " ;;\n" );
printf( "esac\n\n" );
printf( "return ret\n" );
libvlc_release( p_libvlc_instance );
return 0;
} }
void ParseModules( mumap &mods, mcmap &mods2 ) static void PrintOption(const module_config_t *item, const std::string &opt,
const std::string &excl, const std::string &args)
{ {
module_t **p_list; char *longtext = item->psz_longtext;
module_t *p_module; char *text = item->psz_text;
module_config_t *p_item; char i_short = item->i_short;
int i_index; ReplaceChars(longtext);
int i_items; ReplaceChars(text);
size_t i_modules;
/* List the plugins */
p_list = module_list_get(&i_modules);
if( !p_list ) return;
for( i_index = 0; i_index < i_modules; i_index++ )
{
p_module = p_list[i_index];
/* Exclude empty plugins (submodules don't have config options, they if (!longtext || strchr(longtext, '\n') || strchr(longtext, '('))
* are stored in the parent module) */ longtext = text;
if( p_module->parent )
continue;
// p_item = ((module_t *)p_module->p_parent)->p_config;
else
p_item = p_module->p_config;
// printf( " #%s\n", p_module->psz_longname ); printf(" \"");
if( !p_item ) continue;
i_items = 0;
do
{
if( CONFIG_ITEM(p_item->i_type) )
ParseOption( p_item, mods, mods2 );
#if 0
else if( p_item->i_type == CONFIG_CATEGORY )
printf( " #Category %d\n", p_item->i_value );
else if( p_item->i_type == CONFIG_SUBCATEGORY )
printf( " #Subcategory %d\n", p_item->i_value );
#endif
}
while( ++i_items < p_module->confsize && p_item++ );
} const char *args_c = args.empty() ? "" : "=";
module_list_free( p_list ); if (i_short) {
} printf("(-%c", i_short);
void PrintModuleList( mumap &mods, mcmap &mods2 ) if (!excl.empty())
{ printf("%s", excl.c_str());
module_t **p_list = NULL;
module_t *p_module;
int i_index;
int i_items;
size_t i_modules;
/* List the plugins */
p_list = module_list_get(&i_modules);
if( !p_list ) return;
printf( "modules=\"" );
for( i_index = 0; i_index < i_modules; i_index++ )
{
p_module = p_list[i_index];
/* Exclude empty plugins (submodules don't have config options, they printf(")--%s%s[%s]", opt.c_str(), args_c, text);
* are stored in the parent module) */
if( strcmp( p_module->psz_object_name, "main" ) ) if (!args.empty())
{ printf(":%s:%s", longtext, args.c_str());
mods.insert( mpair( p_module->psz_capability,
p_module->psz_object_name ) );
module_config_t *p_config = p_module->p_config;
i_items = 0;
if( p_config ) do
{
/* Hack: required subcategory is stored in i_min */
if( p_config->i_type == CONFIG_SUBCATEGORY )
{
mods2.insert( mcpair( p_config->value.i,
p_module->psz_object_name ) );
}
} while( i_items++ < p_module->i_config_items && p_config++ );
if( p_module->parent )
continue;
printf( "%s ", p_module->psz_object_name );
}
printf("\"\\\n \"(--%s%s)-%c", opt.c_str(), excl.c_str(), i_short);
} else {
if (!excl.empty())
printf("(%s)", excl.c_str());
printf("--%s", opt.c_str());
if (!excl.empty())
printf("%s", args_c);
} }
printf( "\"\n\n" );
module_list_free( p_list ); printf("[%s]", text);
return; if (!args.empty())
printf( ":%s:%s", longtext, args.c_str());
puts( "\"\\");
} }
void ParseOption( module_config_t *p_item, mumap &mods, mcmap &mods2 ) static void ParseOption(const module_config_t *item)
{ {
char *psz_arguments = NULL; std::string excl, args;
char *psz_exclusive; std::string list;
char *psz_option; std::pair<mcmap::iterator, mcmap::iterator> range;
char *psz_name; std::pair<mumap::iterator, mumap::iterator> range_mod;
char *psz_text;
char *psz_longtext;
#define DUP( x ) strdup( x ? x : "" )
//Skip deprecated options if (item->b_removed)
if( p_item->b_removed )
return; return;
switch( p_item->i_type ) switch(item->i_type)
{ {
case CONFIG_ITEM_MODULE: case CONFIG_ITEM_MODULE:
{ range_mod = mods.equal_range(item->psz_type);
std::pair<mumap::iterator, mumap::iterator> range = mods.equal_range( p_item->psz_type ); list = (*range_mod.first).second;
std::string list = (*range.first).second; if (range_mod.first != range_mod.second) {
if( range.first != range.second ) while (range_mod.first++ != range_mod.second)
{ list += " " + range_mod.first->second;
while( range.first++ != range.second ) args = std::string("(") + list + ")";
{
list = list.append( " " );
list = list.append( range.first->second );
}
asprintf( &psz_arguments, "(%s)", list.c_str() );
}
} }
break; break;
case CONFIG_ITEM_MODULE_CAT: case CONFIG_ITEM_MODULE_CAT:
{ range = mods2.equal_range(item->min.i);
std::pair<mcmap::iterator, mcmap::iterator> range = list = (*range.first).second;
mods2.equal_range( p_item->min.i ); if (range.first != range.second) {
std::string list = (*range.first).second; while (range.first++ != range.second)
if( range.first != range.second ) list += " " + range.first->second;
{ args = std::string("(") + list + ")";
while( range.first++ != range.second )
{
list = list.append( " " );
list = list.append( range.first->second );
}
asprintf( &psz_arguments, "(%s)", list.c_str() );
}
}
break;
case CONFIG_ITEM_MODULE_LIST_CAT:
{
std::pair<mcmap::iterator, mcmap::iterator> range =
mods2.equal_range( p_item->min.i );
std::string list = "_values -s , ";
list = list.append( p_item->psz_name );
while( range.first != range.second )
{
list = list.append( " '*" );
list = list.append( range.first->second );
list = list.append( "'" );
++range.first;
}
psz_arguments = strdup( list.c_str() );
} }
break; break;
case CONFIG_ITEM_STRING: case CONFIG_ITEM_MODULE_LIST_CAT:
if( p_item->i_list ) range = mods2.equal_range(item->min.i);
{ args = std::string("_values -s , ") + item->psz_name;
int i = p_item->i_list -1; while (range.first != range.second)
char *psz_list; args += " '*" + range.first++->second + "'";
if( p_item->ppsz_list_text )
asprintf( &psz_list, "%s\\:\\\"%s\\\"", p_item->ppsz_list[i],
p_item->ppsz_list_text[i] );
else
psz_list = strdup(p_item->ppsz_list[i]);
char *psz_list2;
while( i>1 )
{
if( p_item->ppsz_list_text )
asprintf( &psz_list2, "%s\\:\\\"%s\\\" %s", p_item->ppsz_list[i-1],
p_item->ppsz_list_text[i-1], psz_list );
else
asprintf( &psz_list2, "\\\"%s\\\" %s", p_item->ppsz_list[i-1],
psz_list );
free( psz_list );
psz_list = psz_list2;
i--;
}
if( p_item->ppsz_list_text )
asprintf( &psz_arguments, "((%s))", psz_list );
else
asprintf( &psz_arguments, "(%s)", psz_list );
free( psz_list );
}
break; break;
case CONFIG_ITEM_LOADFILE: case CONFIG_ITEM_LOADFILE:
case CONFIG_ITEM_SAVEFILE: case CONFIG_ITEM_SAVEFILE:
psz_arguments = strdup( "_files" ); args = "_files";
break; break;
case CONFIG_ITEM_DIRECTORY: case CONFIG_ITEM_DIRECTORY:
psz_arguments = strdup( "_files -/" ); args = "_files -/";
break; break;
case CONFIG_ITEM_STRING:
case CONFIG_ITEM_INTEGER: case CONFIG_ITEM_INTEGER:
if( p_item->i_list ) if (item->i_list == 0)
{ break;
int i = p_item->i_list -1;
char *psz_list;
if( p_item->ppsz_list_text )
asprintf( &psz_list, "%d\\:\\\"%s\\\"", p_item->pi_list[i],
p_item->ppsz_list_text[i] );
else
psz_list = strdup(p_item->ppsz_list[i]);
char *psz_list2;
while( i > 0 )
{
if( p_item->ppsz_list_text )
asprintf( &psz_list2, "%d\\:\\\"%s\\\" %s", p_item->pi_list[i-1],
p_item->ppsz_list_text[i-1], psz_list );
else
asprintf( &psz_list2, "\\\"%s\\\" %s", p_item->ppsz_list[i-1],
psz_list );
free( psz_list ); for (int i = 0; i < item->i_list; i++) {
psz_list = psz_list2; std::string val;
i--; if (item->ppsz_list_text) {
const char *text = item->ppsz_list_text[i];
if (item->i_type == CONFIG_ITEM_INTEGER) {
std::stringstream s;
s << item->pi_list[i];
val = s.str() + "\\:\\\"" + text;
} else {
if (!item->ppsz_list[i] || !text)
continue;
val = item->ppsz_list[i] + std::string("\\:\\\"") + text;
} }
if( p_item->ppsz_list_text ) } else
asprintf( &psz_arguments, "((%s))", psz_list ); val = std::string("\\\"") + item->ppsz_list[i];
else
asprintf( &psz_arguments, "(%s)", psz_list );
free( psz_list ); list = val + "\\\" " + list;
}
else if( p_item->min.i != 0 || p_item->max.i != 0 )
{
// p_control = new RangedIntConfigControl( p_this, p_item, parent );
}
else
{
// p_control = new IntegerConfigControl( p_this, p_item, parent );
} }
break;
case CONFIG_ITEM_KEY: if (item->ppsz_list_text)
// p_control = new KeyConfigControl( p_this, p_item, parent ); args = std::string("((") + list + "))";
break; else
args = std::string("(") + list + ")";
case CONFIG_ITEM_FLOAT:
// p_control = new FloatConfigControl( p_this, p_item, parent );
break; break;
case CONFIG_ITEM_BOOL: case CONFIG_ITEM_BOOL:
// p_control = new BoolConfigControl( p_this, p_item, parent ); excl = std::string("--no") + item->psz_name + " --no-" + item->psz_name;
asprintf( &psz_exclusive, "--no%s --no-%s", p_item->psz_name, PrintOption(item, item->psz_name, excl, args);
p_item->psz_name );
psz_name = DUP( p_item->psz_name ); excl = std::string("--no") + item->psz_name + " --" + item->psz_name;
psz_text = DUP( p_item->psz_text ); PrintOption(item, std::string("no-") + item->psz_name, excl, args);
psz_longtext = DUP( p_item->psz_longtext );
PrintOption( psz_name, p_item->i_short, psz_exclusive, excl = std::string("--no-")+ item->psz_name + " --" + item->psz_name;
psz_text, psz_longtext, psz_arguments ); PrintOption(item, std::string("no") + item->psz_name, excl, args);
free( psz_name );
free( psz_text );
free( psz_longtext );
free( psz_exclusive );
asprintf( &psz_exclusive, "--no%s --%s", p_item->psz_name,
p_item->psz_name );
asprintf( &psz_option, "no-%s", p_item->psz_name );
psz_text = DUP( p_item->psz_text );
psz_longtext = DUP( p_item->psz_longtext );
PrintOption( psz_option, p_item->i_short, psz_exclusive,
psz_text, psz_longtext, psz_arguments );
free( psz_text );
free( psz_longtext );
free( psz_exclusive );
free( psz_option );
asprintf( &psz_exclusive, "--no-%s --%s", p_item->psz_name,
p_item->psz_name );
asprintf( &psz_option, "no%s", p_item->psz_name );
psz_text = DUP( p_item->psz_text );
psz_longtext = DUP( p_item->psz_longtext );
PrintOption( psz_option, p_item->i_short, psz_exclusive,
psz_text, psz_longtext, psz_arguments );
free( psz_text );
free( psz_longtext );
free( psz_exclusive );
free( psz_option );
return; return;
case CONFIG_ITEM_KEY:
case CONFIG_SECTION: case CONFIG_SECTION:
// p_control = new SectionConfigControl( p_this, p_item, parent ); case CONFIG_ITEM_FLOAT:
break;
default: default:
break; break;
} }
psz_name = DUP( p_item->psz_name );
psz_text = DUP( p_item->psz_text ); PrintOption(item, item->psz_name, "", args);
psz_longtext = DUP( p_item->psz_longtext );
PrintOption( psz_name, p_item->i_short, NULL,
psz_text, psz_longtext, psz_arguments );
free( psz_name );
free( psz_text );
free( psz_longtext );
free( psz_arguments );
} }
void PrintOption( char *psz_option, char i_short, char *psz_exclusive, static void PrintModuleList()
char *psz_text, char *psz_longtext, char *psz_args )
{ {
char *foo; printf("vlc_modules=\"");
if( psz_text )
{
while( (foo = strchr( psz_text, ':' ))) *foo=';';
while( (foo = strchr( psz_text, '"' ))) *foo='\'';
while( (foo = strchr( psz_text, '`' ))) *foo='\'';
}
if( psz_longtext )
{
while( (foo = strchr( psz_longtext, ':' ))) *foo=';';
while( (foo = strchr( psz_longtext, '"' ))) *foo='\'';
while( (foo = strchr( psz_longtext, '`' ))) *foo='\'';
}
if( !psz_longtext ||
strchr( psz_longtext, '\n' ) ||
strchr( psz_longtext, '(' ) ) psz_longtext = psz_text;
if( i_short )
{
if( !psz_exclusive )
printf( " \"(-%c)--%s%s[%s]", i_short,
psz_option, psz_args?"=":"", psz_text );
else
printf( " \"(-%c%s)--%s%s[%s]", i_short, psz_exclusive,
psz_option, psz_args?"=":"", psz_text );
if( psz_args )
printf( ":%s:%s\"\\\n", psz_longtext, psz_args );
else
printf( "\"\\\n" );
printf( " \"(--%s%s)-%c[%s]", psz_option, psz_exclusive ? psz_exclusive : "", size_t modules = 0;
i_short, psz_text ); module_t **list = module_list_get(&modules);
if( psz_args )
printf( ":%s:%s\"\\\n", psz_longtext, psz_args ); if (!list || modules == 0)
else return;
printf( "\"\\\n" );
for (module_t **pmod = list; pmod < &list[modules]; pmod++) {
/* Exclude empty plugins (submodules don't have config options, they
* are stored in the parent module) */
module_t *mod = *pmod;
if (!strcmp(mod->pp_shortcuts[0], "main"))
continue;
const char *capability = mod->psz_capability ? mod->psz_capability : "";
mods.insert(mpair(capability, mod->pp_shortcuts[0]));
module_config_t *max = &mod->p_config[mod->i_config_items];
for (module_config_t *cfg = mod->p_config; cfg && cfg < max; cfg++)
if (cfg->i_type == CONFIG_SUBCATEGORY)
mods2.insert(mcpair(cfg->value.i, mod->pp_shortcuts[0]));
if (!mod->parent)
printf("%s ", mod->pp_shortcuts[0]);
} }
else puts("\"\n");
{ module_list_free(list);
if( psz_exclusive ) }
printf( " \"(%s)--%s%s[%s]", psz_exclusive, psz_option,
psz_args?"=":"", psz_text );
else
printf( " \"--%s[%s]", psz_option, psz_text );
if( psz_args ) static void ParseModules()
printf( ":%s:%s\"\\\n", psz_longtext, psz_args ); {
else size_t modules = 0;
printf( "\"\\\n" ); module_t **list = module_list_get(&modules);
if (!list || modules == 0)
return;
for (module_t **pmod = list; pmod < &list[modules]; pmod++) {
/* Exclude empty plugins (submodules don't have config options, they
* are stored in the parent module) */
module_t *mod = *pmod;
if (mod->parent)
continue;
module_config_t *max = mod->p_config + mod->confsize;
for (module_config_t *cfg = mod->p_config; cfg && cfg < max; cfg++)
if (CONFIG_ITEM(cfg->i_type))
ParseOption(cfg);
} }
module_list_free(list);
} }
int main(int argc, const char **argv)
{
libvlc_instance_t *libvlc = libvlc_new(argc, argv);
if (!libvlc)
return 1;
puts("#compdef vlc cvlc rvlc svlc mvlc qvlc nvlc\n"
"#This file is autogenerated by zsh.cpp"
"typeset -A opt_args"
"local context state line ret=1"
"local modules\n");
PrintModuleList();
puts("_arguments -S -s \\");
ParseModules();
puts(" \"(--module)-p[print help on module]:print help on module:($vlc_modules)\"\\");
puts(" \"(-p)--module[print help on module]:print help on module:($vlc_modules)\"\\");
puts(" \"(--help)-h[print help]\"\\");
puts(" \"(-h)--help[print help]\"\\");
puts(" \"(--longhelp)-H[print detailed help]\"\\");
puts(" \"(-H)--longhelp[print detailed help]\"\\");
puts(" \"(--list)-l[print a list of available modules]\"\\");
puts(" \"(-l)--list[print a list of available modules]\"\\");
puts(" \"--reset-config[reset the current config to the default values]\"\\");
puts(" \"--config[use alternate config file]\"\\");
puts(" \"--reset-plugins-cache[resets the current plugins cache]\"\\");
puts(" \"--version[print version information]\"\\");
puts(" \"*:Playlist item:->mrl\" && ret=0\n");
puts("case $state in");
puts(" mrl)");
puts(" _alternative 'files:file:_files' 'urls:URL:_urls' && ret=0");
puts(" ;;");
puts("esac\n");
puts("return ret");
libvlc_release(libvlc);
return 0;
}
...@@ -27,7 +27,7 @@ export VLC_PLUGIN_PATH ...@@ -27,7 +27,7 @@ export VLC_PLUGIN_PATH
function find_libvlc { function find_libvlc {
[ -z "$SUFFIX" ] && return 0 # linking will fail if lib isn't found [ -z "$SUFFIX" ] && return 0 # linking will fail if lib isn't found
for i in $BUILDDIR/src/.libs/libvlc.$SUFFIX $BUILDDIR/src/libvlc.$SUFFIX; do for i in $BUILDDIR/lib/.libs/libvlc.$SUFFIX $BUILDDIR/lib/libvlc.$SUFFIX; do
[ -e $i ] && LIBVLC=$i && return 0 [ -e $i ] && LIBVLC=$i && return 0
done done
return 1 return 1
...@@ -54,6 +54,7 @@ if ! find_libvlccore; then ...@@ -54,6 +54,7 @@ if ! find_libvlccore; then
fi fi
export LD_LIBRARY_PATH=$BUILDDIR/src/.libs export LD_LIBRARY_PATH=$BUILDDIR/src/.libs
CXXFLAGS="$CXXFLAGS -g -O0"
if [ -e ../../extras/contrib/config.mak -a ! "`grep HOST ../../extras/contrib/config.mak 2>/dev/null|awk '{print $3}'`" != "$HOST" ]; then if [ -e ../../extras/contrib/config.mak -a ! "`grep HOST ../../extras/contrib/config.mak 2>/dev/null|awk '{print $3}'`" != "$HOST" ]; then
CXXFLAGS="-I../../extras/contrib/include" CXXFLAGS="-I../../extras/contrib/include"
...@@ -77,7 +78,7 @@ Please press enter to verify that all the VLC modules are shown" ...@@ -77,7 +78,7 @@ Please press enter to verify that all the VLC modules are shown"
./zsh_gen -vv --list ./zsh_gen -vv --list
echo " echo "
If they are shown, press enter to see if you can debug the problem If they are shown, press enter to see if you can debug the problem
It will be reproduced by running \"./zsh_gen -vvv\"" It will be reproduced by running \"./zsh_gen -vv\""
read i read i
./zsh_gen --plugin-path=$BUILDDIR -vv ./zsh_gen --plugin-path=$BUILDDIR -vv
exit 1 exit 1
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment