annotate src/vamp-plugin-sdk-2.4/rdf/generator/vamp-rdf-template-generator.cpp @ 83:ae30d91d2ffe

Replace these with versions built using an older toolset (so as to avoid ABI compatibilities when linking on Ubuntu 14.04 for packaging purposes)
author Chris Cannam
date Fri, 07 Feb 2020 11:51:13 +0000
parents b7bda433d832
children
rev   line source
Chris@12 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@12 2
Chris@12 3 #include <vamp-hostsdk/PluginHostAdapter.h>
Chris@12 4 #include <vamp-hostsdk/PluginChannelAdapter.h>
Chris@12 5 #include <vamp-hostsdk/PluginInputDomainAdapter.h>
Chris@12 6 #include <vamp-hostsdk/PluginLoader.h>
Chris@12 7 #include <vamp/vamp.h>
Chris@12 8
Chris@12 9 #include <iostream>
Chris@12 10 #include <fstream>
Chris@12 11 #include <sstream>
Chris@12 12
Chris@12 13 #include <cmath>
Chris@12 14 #include <cstdlib>
Chris@12 15 #include <cstring>
Chris@12 16
Chris@12 17 #include <cstdlib>
Chris@12 18 #include <cstring>
Chris@12 19
Chris@12 20 using std::cout;
Chris@12 21 using std::cin;
Chris@12 22 using std::cerr;
Chris@12 23 using std::getline;
Chris@12 24 using std::endl;
Chris@12 25 using std::string;
Chris@12 26 using std::vector;
Chris@12 27 using std::ofstream;
Chris@12 28 using std::ios;
Chris@12 29
Chris@12 30 using Vamp::HostExt::PluginLoader;
Chris@12 31 using Vamp::Plugin;
Chris@12 32
Chris@12 33 //???
Chris@12 34 string programURI = "http://www.vamp-plugins.org/doap.rdf#template-generator";
Chris@12 35
Chris@12 36 void usage()
Chris@12 37 {
Chris@12 38 cerr << endl;
Chris@12 39 cerr << "vamp-rdf-template-generator: Create a skeleton RDF description file describing" << endl;
Chris@12 40 cerr << "a Vamp plugin library using the Vamp ontology." << endl;
Chris@12 41 cerr << endl;
Chris@12 42 cerr << "Usage:" << endl;
Chris@12 43 cerr << " vamp-rdf-template-generator -i vamp:soname[:plugin] [vamp:soname[:plugin] ...]" << endl;
Chris@12 44 cerr << " vamp-rdf-template-generator PLUGIN_BASE_URI [ -m YOUR_URI ] [vamp:]soname[:plugin] [[vamp:]soname[:plugin] ...]" << endl;
Chris@12 45 cerr << endl;
Chris@12 46 cerr << "Example:" << endl;
Chris@12 47 cerr << " vamp-rdf-template-generator http://vamp-plugins.org/rdf/plugins/ vamp-example-plugins" << endl;
Chris@12 48 cerr << endl;
Chris@12 49 exit(2);
Chris@12 50 }
Chris@12 51
Chris@12 52 template <class T>
Chris@12 53 inline string to_string (const T& t)
Chris@12 54 {
Chris@12 55 std::stringstream ss;
Chris@12 56 ss << t;
Chris@12 57 return ss.str();
Chris@12 58 }
Chris@12 59
Chris@12 60 string describe_namespaces(string pluginBundleBaseURI, string libname)
Chris@12 61 {
Chris@12 62 string res=\
Chris@12 63 "@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .\n\
Chris@12 64 @prefix xsd: <http://www.w3.org/2001/XMLSchema#> .\n\
Chris@12 65 @prefix vamp: <http://purl.org/ontology/vamp/> .\n\
Chris@12 66 @prefix plugbase: <"+pluginBundleBaseURI+libname+"#> .\n\
Chris@12 67 @prefix owl: <http://www.w3.org/2002/07/owl#> .\n\
Chris@12 68 @prefix dc: <http://purl.org/dc/elements/1.1/> .\n\
Chris@12 69 @prefix af: <http://purl.org/ontology/af/> .\n\
Chris@12 70 @prefix foaf: <http://xmlns.com/foaf/0.1/> .\n\
Chris@12 71 @prefix cc: <http://web.resource.org/cc/> .\n\
Chris@12 72 @prefix : <#> .\n\n";
Chris@12 73
Chris@12 74 return res;
Chris@12 75 }
Chris@12 76
Chris@12 77 string describe_doc(string describerURI, string pluginBundleBaseURI,
Chris@12 78 string libname)
Chris@12 79 {
Chris@12 80 string res=\
Chris@12 81 "<> a vamp:PluginDescription ;\n";
Chris@12 82 if (describerURI != "") {
Chris@12 83 res += " foaf:maker <"+describerURI+"> ;\n";
Chris@12 84 }
Chris@12 85 res += "\
Chris@12 86 foaf:maker <"+programURI+"> ;\n\
Chris@12 87 foaf:primaryTopic <"+pluginBundleBaseURI+libname+"> .\n\n";
Chris@12 88 return res;
Chris@12 89 }
Chris@12 90
Chris@12 91
Chris@12 92 string describe_library(string libname, vector<Plugin *> plugins)
Chris@12 93 {
Chris@12 94 string res=\
Chris@12 95 ":"+libname+" a vamp:PluginLibrary ;\n\
Chris@12 96 vamp:identifier \""+libname+"\" ";
Chris@12 97
Chris@12 98 for (size_t i = 0; i < plugins.size(); ++i) {
Chris@12 99 res += " ; \n\
Chris@12 100 vamp:available_plugin plugbase:"+plugins[i]->getIdentifier();
Chris@12 101 }
Chris@12 102
Chris@12 103 res += " ; \n\
Chris@12 104 # foaf:page <Place more-information HTML page URL here and uncomment> ;\n\
Chris@12 105 .\n\n";
Chris@12 106 return res;
Chris@12 107 }
Chris@12 108
Chris@12 109 string describe_plugin(Plugin* plugin)
Chris@12 110 {
Chris@12 111 string res=\
Chris@12 112 "plugbase:"+plugin->getIdentifier()+" a vamp:Plugin ;\n\
Chris@12 113 dc:title \""+plugin->getName()+"\" ;\n\
Chris@12 114 vamp:name \""+plugin->getName()+"\" ;\n\
Chris@12 115 dc:description \"\"\""+plugin->getDescription()+"\"\"\" ;\n\
Chris@12 116 foaf:maker [ foaf:name \""+plugin->getMaker()+"\" ] ; # FIXME could give plugin author's URI here\n\
Chris@12 117 dc:rights \"\"\""+plugin->getCopyright()+"\"\"\" ;\n\
Chris@12 118 # cc:license <Place plugin license URI here and uncomment> ; \n\
Chris@12 119 vamp:identifier \""+plugin->getIdentifier()+"\" ;\n\
Chris@12 120 vamp:vamp_API_version vamp:api_version_"+to_string(plugin->getVampApiVersion())+" ;\n\
Chris@12 121 owl:versionInfo \""+to_string(plugin->getPluginVersion())+"\" ;\n";
Chris@12 122 if (plugin->getInputDomain() == Vamp::Plugin::FrequencyDomain)
Chris@12 123 res+=" vamp:input_domain vamp:FrequencyDomain ;\n\n";
Chris@12 124 else
Chris@12 125 res+=" vamp:input_domain vamp:TimeDomain ;\n";
Chris@12 126
Chris@12 127
Chris@12 128 Plugin::ParameterList params = plugin->getParameterDescriptors();
Chris@12 129 if (!params.empty()) res+="\n";
Chris@12 130 for (Plugin::ParameterList::const_iterator i = params.begin(); i != params.end(); i++)
Chris@12 131 res+=" vamp:parameter plugbase:"+plugin->getIdentifier()+"_param_"+(*i).identifier+" ;\n";
Chris@12 132 if (!params.empty()) res+="\n";
Chris@12 133
Chris@12 134 Plugin::OutputList outputs = plugin->getOutputDescriptors();
Chris@12 135 for (Plugin::OutputList::const_iterator i = outputs.begin(); i!= outputs.end(); i++)
Chris@12 136 res+=" vamp:output plugbase:"+plugin->getIdentifier()+"_output_"+(*i).identifier+" ;\n";
Chris@12 137 res+=" .\n";
Chris@12 138
Chris@12 139 return res;
Chris@12 140 }
Chris@12 141
Chris@12 142 string describe_param(Plugin *plugin, Plugin::ParameterDescriptor p)
Chris@12 143 {
Chris@12 144
Chris@12 145 //FIXME: dc:format and vamp:unit are the same???
Chris@12 146 //Should be a QUantizedParameter also a Parameter??
Chris@12 147 if(p.isQuantized){
Chris@12 148 string res=\
Chris@12 149 "plugbase:"+plugin->getIdentifier()+"_param_"+p.identifier+" a vamp:QuantizedParameter ;\n\
Chris@12 150 vamp:identifier \""+p.identifier+"\" ;\n\
Chris@12 151 dc:title \""+p.name+"\" ;\n\
Chris@12 152 dc:format \""+p.unit+"\" ;\n\
Chris@12 153 vamp:min_value "+to_string(p.minValue)+" ;\n\
Chris@12 154 vamp:max_value "+to_string(p.maxValue)+" ;\n\
Chris@12 155 vamp:unit \""+p.unit+"\" ;\n\
Chris@12 156 vamp:quantize_step "+to_string(p.quantizeStep)+" ;\n\
Chris@12 157 vamp:default_value "+to_string(p.defaultValue)+" ;\n\
Chris@12 158 vamp:value_names (";
Chris@12 159
Chris@12 160 unsigned int i;
Chris@12 161 for (i=0; i+1 < p.valueNames.size(); i++)
Chris@12 162 res+=" \""+p.valueNames[i]+"\"";
Chris@12 163 if (i < p.valueNames.size())
Chris@12 164 res+=" \""+p.valueNames[i]+"\"";
Chris@12 165 res+=");\n";
Chris@12 166
Chris@12 167 res+=" .\n";
Chris@12 168
Chris@12 169 return res;
Chris@12 170
Chris@12 171 }else{
Chris@12 172 string res=\
Chris@12 173 "plugbase:"+plugin->getIdentifier()+"_param_"+p.identifier+" a vamp:Parameter ;\n\
Chris@12 174 vamp:identifier \""+p.identifier+"\" ;\n\
Chris@12 175 dc:title \""+p.name+"\" ;\n\
Chris@12 176 dc:format \""+p.unit+"\" ;\n\
Chris@12 177 vamp:min_value "+to_string(p.minValue)+" ;\n\
Chris@12 178 vamp:max_value "+to_string(p.maxValue)+" ;\n\
Chris@12 179 vamp:unit \""+p.unit+"\" ;\n\
Chris@12 180 vamp:default_value "+to_string(p.defaultValue)+" ;\n\
Chris@12 181 vamp:value_names (";
Chris@12 182
Chris@12 183 unsigned int i;
Chris@12 184 for (i=0; i+1 < p.valueNames.size(); i++)
Chris@12 185 res+=" \""+p.valueNames[i]+"\"";
Chris@12 186 if (i < p.valueNames.size())
Chris@12 187 res+=" \""+p.valueNames[i]+"\"";
Chris@12 188 res+=");\n";
Chris@12 189
Chris@12 190 res+=" .\n";
Chris@12 191
Chris@12 192 return res;
Chris@12 193
Chris@12 194 }
Chris@12 195 }
Chris@12 196
Chris@12 197 string describe_output(Plugin *plugin, Plugin::OutputDescriptor o)
Chris@12 198 {
Chris@12 199
Chris@12 200 //we need to distinguish here between different output types:
Chris@12 201
Chris@12 202 //Quantize or not
Chris@12 203 //KnownExtents or not
Chris@12 204 //Data output classification:
Chris@12 205 //DenseOutput
Chris@12 206 //SparseOutput
Chris@12 207 //TrackLevelOutput
Chris@12 208
Chris@12 209
Chris@12 210 // SparseOutput: variable sample rate. Events are not evenly
Chris@12 211 // spaced so we need to record the time associated with the event
Chris@12 212 // as it its not ensured that we have an event after the next one
Chris@12 213 // (but there is not time to set the duration, it has to be
Chris@12 214 // calculated as the different between 2 different events). The
Chris@12 215 // timestamp must be read.
Chris@12 216
Chris@12 217 string res;
Chris@12 218
Chris@12 219 if (o.sampleType == Plugin::OutputDescriptor::VariableSampleRate ||
Chris@12 220 !o.hasFixedBinCount)
Chris@12 221 {
Chris@12 222
Chris@12 223 res=\
Chris@12 224 "plugbase:"+plugin->getIdentifier()+"_output_"+o.identifier+" a vamp:SparseOutput ;\n\
Chris@12 225 vamp:identifier \""+o.identifier+"\" ;\n\
Chris@12 226 dc:title \""+o.name+"\" ;\n\
Chris@12 227 dc:description \"\"\""+o.description+"\"\"\" ;\n\
Chris@12 228 vamp:fixed_bin_count \""+(o.hasFixedBinCount == 1 ? "true" : "false")+"\" ;\n\
Chris@12 229 vamp:unit \""+(o.unit)+"\" ;\n";
Chris@12 230
Chris@12 231
Chris@12 232 //another type of output
Chris@12 233 if(o.isQuantized){
Chris@12 234
Chris@12 235 res+=" a vamp:QuantizedOutput ;\n";
Chris@12 236 res+=" vamp:quantize_step "+to_string(o.quantizeStep)+" ;\n";
Chris@12 237 }
Chris@12 238
Chris@12 239 //and yet another type
Chris@12 240 if(o.hasKnownExtents){
Chris@12 241
Chris@12 242 res+=" a vamp:KnownExtentsOutput ;\n";
Chris@12 243 res+=" vamp:min_value "+to_string(o.minValue)+" ;\n";
Chris@12 244 res+=" vamp:max_value "+to_string(o.maxValue)+" ;\n";
Chris@12 245 }
Chris@12 246
Chris@12 247 // FIXME ? Bin names may vary based on plugin setup, so including them here might be misleading...
Chris@12 248 if (o.hasFixedBinCount)
Chris@12 249 {
Chris@12 250 res+=" vamp:bin_count "+to_string(o.binCount)+" ;\n";
Chris@12 251
Chris@12 252 bool haveBinNames = false;
Chris@12 253 for (unsigned int i=0; i < o.binNames.size(); i++) {
Chris@12 254 if (o.binNames[i] != "") {
Chris@12 255 haveBinNames = true;
Chris@12 256 break;
Chris@12 257 }
Chris@12 258 }
Chris@12 259
Chris@12 260 if (haveBinNames) {
Chris@12 261 res+=" vamp:bin_names (";
Chris@12 262
Chris@12 263 unsigned int i;
Chris@12 264 for (i=0; i+1 < o.binNames.size(); i++)
Chris@12 265 res+=" \""+o.binNames[i]+"\"";
Chris@12 266 if (i < o.binNames.size())
Chris@12 267 res+=" \""+o.binNames[i]+"\"";
Chris@12 268 res+=");\n";
Chris@12 269 }
Chris@12 270 }
Chris@12 271
Chris@12 272 res+=" vamp:sample_type vamp:VariableSampleRate ;\n";
Chris@12 273 if (o.sampleRate > 0.0f)
Chris@12 274 res+=" vamp:sample_rate "+to_string(o.sampleRate)+" ;\n";
Chris@12 275
Chris@12 276 }
Chris@12 277
Chris@12 278 //If we do not have SparseOutput, then we have DenseOutput. TrackLevelOutput can not be inferred from the plugin directly without actually
Chris@12 279 //running the plugin.
Chris@12 280 else{
Chris@12 281
Chris@12 282 res=\
Chris@12 283 "plugbase:"+plugin->getIdentifier()+"_output_"+o.identifier+" a vamp:DenseOutput ;\n\
Chris@12 284 vamp:identifier \""+o.identifier+"\" ;\n\
Chris@12 285 dc:title \""+o.name+"\" ;\n\
Chris@12 286 dc:description \"\"\""+o.description+"\"\"\" ;\n\
Chris@12 287 vamp:fixed_bin_count \""+(o.hasFixedBinCount == 1 ? "true" : "false")+"\" ;\n\
Chris@12 288 vamp:unit \""+(o.unit)+"\" ;\n";
Chris@12 289
Chris@12 290
Chris@12 291 //another type of output
Chris@12 292 if(o.isQuantized){
Chris@12 293
Chris@12 294 res+=" a vamp:QuantizedOutput ;\n";
Chris@12 295 res+=" vamp:quantize_step "+to_string(o.quantizeStep)+" ;\n";
Chris@12 296 }
Chris@12 297
Chris@12 298 //and yet another type
Chris@12 299 if(o.hasKnownExtents){
Chris@12 300
Chris@12 301 res+=" a vamp:KnownExtentsOutput ;\n";
Chris@12 302 res+=" vamp:min_value "+to_string(o.minValue)+" ;\n";
Chris@12 303 res+=" vamp:max_value "+to_string(o.maxValue)+" ;\n";
Chris@12 304 }
Chris@12 305
Chris@12 306 // FIXME ? Bin names may vary based on plugin setup, so including them here might be misleading...
Chris@12 307 if (o.hasFixedBinCount)
Chris@12 308 {
Chris@12 309 res+=" vamp:bin_count "+to_string(o.binCount)+" ;\n";
Chris@12 310
Chris@12 311 bool haveBinNames = false;
Chris@12 312 for (unsigned int i=0; i < o.binNames.size(); i++) {
Chris@12 313 if (o.binNames[i] != "") {
Chris@12 314 haveBinNames = true;
Chris@12 315 break;
Chris@12 316 }
Chris@12 317 }
Chris@12 318
Chris@12 319 if (haveBinNames) {
Chris@12 320 res+=" vamp:bin_names (";
Chris@12 321
Chris@12 322 unsigned int i;
Chris@12 323 for (i=0; i+1 < o.binNames.size(); i++)
Chris@12 324 res+=" \""+o.binNames[i]+"\"";
Chris@12 325 if (i < o.binNames.size())
Chris@12 326 res+=" \""+o.binNames[i]+"\"";
Chris@12 327 res+=");\n";
Chris@12 328 }
Chris@12 329 }
Chris@12 330
Chris@12 331 else if (o.sampleType == Plugin::OutputDescriptor::FixedSampleRate)
Chris@12 332 {
Chris@12 333 res+=" vamp:sample_type vamp:FixedSampleRate ;\n";
Chris@12 334 res+=" vamp:sample_rate "+to_string(o.sampleRate)+" ;\n";
Chris@12 335 }
Chris@12 336 else if (o.sampleType == Plugin::OutputDescriptor::OneSamplePerStep)
Chris@12 337 res+=" vamp:sample_type vamp:OneSamplePerStep ;\n";
Chris@12 338 else
Chris@12 339 {
Chris@12 340 cerr<<"Incomprehensible sampleType for output descriptor "+o.identifier<<" !"<<endl;
Chris@12 341 exit(1);
Chris@12 342 }
Chris@12 343 }
Chris@12 344
Chris@12 345 //There is no way to know this in advance, but we can use the km a bit for this.
Chris@12 346 res+="# vamp:computes_event_type <Place event type URI here and uncomment> ;\n";
Chris@12 347 res+="# vamp:computes_feature <Place feature attribute URI here and uncomment> ;\n";
Chris@12 348 res+="# vamp:computes_signal_type <Place signal type URI here and uncomment> ;\n";
Chris@12 349 res+=" .\n";
Chris@12 350
Chris@12 351 return res;
Chris@12 352 }
Chris@12 353
Chris@12 354 string describe(vector<Plugin *> plugins, string pluginBundleBaseURI,
Chris@12 355 string describerURI, string libname)
Chris@12 356 {
Chris@12 357 string res = describe_namespaces(pluginBundleBaseURI, libname);
Chris@12 358
Chris@12 359 res += describe_doc(describerURI, pluginBundleBaseURI, libname);
Chris@12 360
Chris@12 361 res += describe_library(libname, plugins);
Chris@12 362
Chris@12 363 for (size_t i = 0; i < plugins.size(); ++i) {
Chris@12 364
Chris@12 365 Plugin *plugin = plugins[i];
Chris@12 366
Chris@12 367 res += describe_plugin(plugin);
Chris@12 368
Chris@12 369 Plugin::ParameterList params = plugin->getParameterDescriptors();
Chris@12 370 for (Plugin::ParameterList::const_iterator i = params.begin(); i != params.end(); i++)
Chris@12 371 res += describe_param(plugin, *i);
Chris@12 372
Chris@12 373 Plugin::OutputList outputs = plugin->getOutputDescriptors();
Chris@12 374 for (Plugin::OutputList::const_iterator i = outputs.begin(); i!= outputs.end(); i++)
Chris@12 375 res += describe_output(plugin, *i);
Chris@12 376 }
Chris@12 377
Chris@12 378 return res;
Chris@12 379 }
Chris@12 380
Chris@12 381 int main(int argc, char **argv)
Chris@12 382 {
Chris@12 383 if (argc < 3) usage();
Chris@12 384
Chris@12 385 bool interactive = false;
Chris@12 386 if (!strcmp(argv[1], "-i")) interactive = true;
Chris@12 387
Chris@12 388 if (!interactive && argc < 3) usage();
Chris@12 389
Chris@12 390 string pluginBundleBaseURI, describerURI;
Chris@12 391
Chris@12 392 int argidx = 2;
Chris@12 393
Chris@12 394 if (!interactive) {
Chris@12 395 pluginBundleBaseURI = argv[1];
Chris@12 396 if (!strcmp(argv[2], "-m")) {
Chris@12 397 if (argc < 5) usage();
Chris@12 398 describerURI = argv[3];
Chris@12 399 argidx = 4;
Chris@12 400 }
Chris@12 401 } else {
Chris@12 402 cerr << "Please enter the base URI for the plugin bundle : ";
Chris@12 403 getline(cin, pluginBundleBaseURI);
Chris@12 404 cerr << "Please enter your URI (empty to omit) : ";
Chris@12 405 getline(cin, describerURI);
Chris@12 406 }
Chris@12 407
Chris@12 408 vector<Plugin *> plugins;
Chris@12 409 string libname;
Chris@12 410
Chris@12 411 PluginLoader *loader = PluginLoader::getInstance();
Chris@12 412
Chris@12 413 while (argidx < argc) {
Chris@12 414
Chris@12 415 string pluginName = argv[argidx];
Chris@12 416
Chris@12 417 if (pluginName.substr(0, 5) == "vamp:") {
Chris@12 418 pluginName = pluginName.substr(5);
Chris@12 419 }
Chris@12 420
Chris@12 421 string mylibname = pluginName.substr(0, pluginName.find(':'));
Chris@12 422
Chris@12 423 if (libname == "") libname = mylibname;
Chris@12 424 else if (libname != mylibname) {
Chris@12 425 cerr << "ERROR: All plugins specified on command line must originate in the same library" << endl;
Chris@12 426 exit(1);
Chris@12 427 }
Chris@12 428
Chris@12 429 if (mylibname == pluginName) { // pluginName is a library, not a plugin
Chris@12 430
Chris@12 431 PluginLoader::PluginKeyList list = loader->listPlugins();
Chris@12 432 for (size_t i = 0; i < list.size(); ++i) {
Chris@12 433 string thislibname = list[i].substr(0, list[i].find(':'));
Chris@12 434 if (thislibname != mylibname) continue;
Chris@12 435 Plugin *plugin = loader->loadPlugin(list[i], 44100);
Chris@12 436 if (!plugin) {
Chris@12 437 cerr << "ERROR: Plugin \"" << list[i] << "\" could not be loaded" << endl;
Chris@12 438 exit(1);
Chris@12 439 }
Chris@12 440 plugins.push_back(plugin);
Chris@12 441 }
Chris@12 442
Chris@12 443 if (plugins.empty()) {
Chris@12 444 cerr << "ERROR: Plugin library \"" << mylibname << "\" does not exist, could not be opened, or contains no plugins" << endl;
Chris@12 445 exit(1);
Chris@12 446 }
Chris@12 447
Chris@12 448 } else { // pluginName is a plugin
Chris@12 449
Chris@12 450 Plugin *plugin = loader->loadPlugin(pluginName, size_t(44100));
Chris@12 451 if (!plugin) {
Chris@12 452 cerr << "ERROR: Plugin \"" << pluginName << "\" could not be loaded" << endl;
Chris@12 453 exit(1);
Chris@12 454 }
Chris@12 455 plugins.push_back(plugin);
Chris@12 456 }
Chris@12 457
Chris@12 458 ++argidx;
Chris@12 459 }
Chris@12 460
Chris@12 461 cout << describe(plugins, pluginBundleBaseURI, describerURI, libname) << endl;
Chris@12 462
Chris@12 463 return 0;
Chris@12 464 }
Chris@12 465
Chris@12 466