Avoid unsigned comparison with 0 warning.
[grantlee:haffmans-grantlee.git] / templates / lib / engine.cpp
1 /*
2   This file is part of the Grantlee template system.
3
4   Copyright (c) 2009,2010 Stephen Kelly <steveire@gmail.com>
5
6   This library is free software; you can redistribute it and/or
7   modify it under the terms of the GNU Lesser General Public
8   License as published by the Free Software Foundation; either version
9   2.1 of the Licence, or (at your option) any later version.
10
11   This library is distributed in the hope that it will be useful,
12   but WITHOUT ANY WARRANTY; without even the implied warranty of
13   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14   Lesser General Public License for more details.
15
16   You should have received a copy of the GNU Lesser General Public
17   License along with this library.  If not, see <http://www.gnu.org/licenses/>.
18
19 */
20
21 #include "engine.h"
22 #include "engine_p.h"
23
24 #include "exception.h"
25 #include "grantlee_config_p.h"
26 #include "grantlee_latin1literal_p.h"
27 #include "grantlee_version.h"
28 #include "scriptabletags.h"
29 #include "template_p.h"
30 #include "templateloader.h"
31
32 #include <QtCore/QCoreApplication>
33 #include <QtCore/QDir>
34 #include <QtCore/QPluginLoader>
35 #include <QtCore/QRegExp>
36 #include <QtCore/QTextStream>
37
38 using namespace Grantlee;
39
40 static const char __scriptableLibName[] = "grantlee_scriptabletags";
41
42 Engine::Engine( QObject *parent )
43     : QObject( parent ), d_ptr( new EnginePrivate( this ) )
44 {
45   d_ptr->m_defaultLibraries << QLatin1String( "grantlee_defaulttags" )
46                             << QLatin1String( "grantlee_loadertags" )
47                             << QLatin1String( "grantlee_defaultfilters" );
48
49   d_ptr->m_pluginDirs = QCoreApplication::instance()->libraryPaths();
50   d_ptr->m_pluginDirs << QString::fromLocal8Bit( GRANTLEE_PLUGIN_PATH );
51 }
52
53 Engine::~Engine()
54 {
55   qDeleteAll( d_ptr->m_scriptableLibraries );
56   d_ptr->m_libraries.clear();
57   delete d_ptr;
58 }
59
60 QList<AbstractTemplateLoader::Ptr> Engine::templateLoaders()
61 {
62   Q_D( Engine );
63   return d->m_loaders;
64 }
65
66 void Engine::addTemplateLoader( AbstractTemplateLoader::Ptr loader )
67 {
68   Q_D( Engine );
69   d->m_loaders << loader;
70 }
71
72 QPair<QString, QString> Engine::mediaUri( const QString &fileName ) const
73 {
74   Q_D( const Engine );
75   QListIterator<AbstractTemplateLoader::Ptr> it( d->m_loaders );
76
77   QPair<QString, QString> uri;
78   while ( it.hasNext() ) {
79     const AbstractTemplateLoader::Ptr loader = it.next();
80     uri = loader->getMediaUri( fileName );
81     if ( !uri.second.isEmpty() )
82       break;
83   }
84   return uri;
85 }
86
87 void Engine::setPluginPaths( const QStringList &dirs )
88 {
89   Q_D( Engine );
90   d->m_pluginDirs = dirs;
91 }
92
93 void Engine::addPluginPath( const QString &dir )
94 {
95   Q_D( Engine );
96   QStringList temp;
97   temp << dir;
98   temp << d->m_pluginDirs;
99   d->m_pluginDirs = temp;
100 }
101
102 void Engine::removePluginPath( const QString &dir )
103 {
104   Q_D( Engine );
105   d->m_pluginDirs.removeAll( dir );
106 }
107
108 QStringList Engine::pluginPaths() const
109 {
110   Q_D( const Engine );
111   return d->m_pluginDirs;
112 }
113
114 QStringList Engine::defaultLibraries() const
115 {
116   Q_D( const Engine );
117   return d->m_defaultLibraries;
118 }
119
120 void Engine::addDefaultLibrary( const QString &libName )
121 {
122   Q_D( Engine );
123   d->m_defaultLibraries << libName;
124 }
125
126 void Engine::removeDefaultLibrary( const QString &libName )
127 {
128   Q_D( Engine );
129   d->m_defaultLibraries.removeAll( libName );
130 }
131
132 template<uint v>
133 bool acceptableVersion(uint minorVersion)
134 {
135   return minorVersion >= v;
136 }
137
138 template<>
139 bool acceptableVersion<0>(uint)
140 {
141   return true;
142 }
143
144 void Engine::loadDefaultLibraries()
145 {
146   Q_D( Engine );
147   // Make sure we can load default scriptable libraries if we're supposed to.
148   if ( d->m_defaultLibraries.contains( QLatin1String( __scriptableLibName ) ) && !d->m_scriptableTagLibrary ) {
149     d->m_scriptableTagLibrary = new ScriptableTagLibrary( this );
150
151     // It would be better to load this as a plugin, but that is not currently possible with webkit/javascriptcore
152     // so we new the library directly.
153     // https://bugs.webkit.org/show_bug.cgi?id=38193
154 #if 0
155     d->loadCppLibrary( __scriptableLibName, GRANTLEE_VERSION_MINOR );
156     PluginPointer<TagLibraryInterface> library = d->loadCppLibrary( __scriptableLibName, GRANTLEE_VERSION_MINOR );
157     if ( !library )
158       throw Grantlee::Exception( TagSyntaxError, QLatin1String( "Could not load scriptable tags library" ) );
159 #endif
160   }
161
162   Q_FOREACH( const QString &libName, d->m_defaultLibraries ) {
163     if ( libName == QLatin1String( __scriptableLibName ) )
164       continue;
165
166     // already loaded by the engine.
167     if ( d->m_libraries.contains( libName ) )
168       continue;
169
170     // Warning. We load C++ plugins in this method, but not plugins written in QtScript.
171     // This should be a better situation in Grantlee 0.2 when the TagLibraryInterface
172     // can have shared pointers instead of raw pointers in its API. The whole scriptable
173     // thing likely needs to be redesigned too.
174     // The reason for explicitly not loading scripted plugins here is that they create new
175     // NodeFactory and Filter instances with each call to loadLibrary.
176     // NodeFactories are memory-managed by the Parser, as are Filters to an extent, because it
177     // creates shared pointers for Filters.
178     // Because this method loads the libraries but doesn't actually use them or send them
179     // back to the Parser the scriptable NodeFactories and Filters but be deleted immediately.
180     // The C++ plugins are different because although they are loaded, their NodeFactories
181     // and Filters are accessed only by the Parser, which manages them after that.
182     uint minorVersion = GRANTLEE_VERSION_MINOR;
183     while ( acceptableVersion<GRANTLEE_MIN_PLUGIN_VERSION>(minorVersion) ) {
184       // Although we don't use scripted libaries here, we need to recognize them being first
185       // in the search path and not load a c++ plugin of the same name in that case.
186       ScriptableLibraryContainer* scriptableLibrary = d->loadScriptableLibrary( libName, minorVersion );
187       if ( scriptableLibrary ) {
188         scriptableLibrary->clear();
189         break;
190       }
191
192       PluginPointer<TagLibraryInterface> library = d->loadCppLibrary( libName, minorVersion );
193       if ( minorVersion == 0)
194         break;
195       minorVersion--;
196       if ( library )
197         break;
198     }
199   }
200 }
201
202 TagLibraryInterface* Engine::loadLibrary( const QString &name )
203 {
204   Q_D( Engine );
205
206   if ( name == QLatin1String( __scriptableLibName ) )
207     return 0;
208
209   // already loaded by the engine.
210   if ( d->m_libraries.contains( name ) )
211     return d->m_libraries.value( name ).data();
212
213   uint minorVersion = GRANTLEE_VERSION_MINOR;
214   while ( acceptableVersion<GRANTLEE_MIN_PLUGIN_VERSION>(minorVersion) ) {
215     TagLibraryInterface* library = d->loadLibrary( name, minorVersion );
216     if ( library )
217       return library;
218     if (minorVersion == 0)
219       break;
220     minorVersion--;
221   }
222   throw Grantlee::Exception( TagSyntaxError, QString::fromLatin1( "Plugin library '%1' not found." ).arg( name ) );
223   return 0;
224 }
225
226 TagLibraryInterface* EnginePrivate::loadLibrary( const QString &name, uint minorVersion )
227 {
228   TagLibraryInterface* scriptableLibrary = loadScriptableLibrary( name, minorVersion );
229   if ( scriptableLibrary )
230     return scriptableLibrary;
231
232   // else this is not a scriptable library.
233
234   return loadCppLibrary( name, minorVersion ).data();
235 }
236
237 EnginePrivate::EnginePrivate( Engine *engine )
238   : q_ptr( engine ), m_scriptableTagLibrary( 0 ), m_smartTrimEnabled( false )
239 {
240 }
241
242 QString EnginePrivate::getScriptLibraryName( const QString &name, uint minorVersion ) const
243 {
244   int pluginIndex = 0;
245   const QString prefix = QLatin1Literal( "/grantlee/" )
246                        + QString::number( GRANTLEE_VERSION_MAJOR )
247                        + QLatin1Char( '.' )
248                        + QString::number( minorVersion )
249                        + QLatin1Char( '/' );
250   while ( m_pluginDirs.size() > pluginIndex ) {
251     const QString nextDir = m_pluginDirs.at( pluginIndex++ );
252     const QString libFileName = nextDir
253                               + prefix
254                               + name
255                               + QLatin1Literal( ".qs" );
256
257     const QFile file( libFileName );
258     if ( !file.exists() )
259       continue;
260     return libFileName;
261   }
262   QList<AbstractTemplateLoader::Ptr>::const_iterator it = m_loaders.constBegin();
263   const QList<AbstractTemplateLoader::Ptr>::const_iterator end = m_loaders.constEnd();
264   for ( ; it != end; ++it ) {
265     const QPair<QString, QString> pair = ( *it )->getMediaUri( prefix
266                                                             + name
267                                                             + QLatin1Literal( ".qs" ) );
268
269     if ( !pair.first.isEmpty() && !pair.second.isEmpty() ) {
270       return pair.first + pair.second;
271     }
272   }
273   return QString();
274 }
275
276 ScriptableLibraryContainer* EnginePrivate::loadScriptableLibrary( const QString &name, uint minorVersion )
277 {
278   if ( !m_scriptableTagLibrary )
279     return 0;
280
281 #if 0
282   if ( !m_libraries.contains( __scriptableLibName ) )
283     return 0;
284 #endif
285
286   const QString libFileName = getScriptLibraryName( name, minorVersion );
287
288   if ( libFileName.isEmpty() )
289     return 0;
290
291   if ( m_scriptableLibraries.contains( libFileName ) ) {
292     ScriptableLibraryContainer *library = m_scriptableLibraries.value( libFileName );
293     library->setNodeFactories( m_scriptableTagLibrary->nodeFactories( libFileName ) );
294     library->setFilters( m_scriptableTagLibrary->filters( libFileName ) );
295     return library;
296   }
297 #if 0
298   PluginPointer<TagLibraryInterface> scriptableTagLibrary = m_libraries.value( __scriptableLibName );
299 #endif
300
301   const QHash<QString, AbstractNodeFactory*> factories = m_scriptableTagLibrary->nodeFactories( libFileName );
302   const QHash<QString, Filter*> filters = m_scriptableTagLibrary->filters( libFileName );
303
304   ScriptableLibraryContainer *library = new ScriptableLibraryContainer( factories, filters );
305   m_scriptableLibraries.insert( libFileName, library );
306   return library;
307 }
308
309 PluginPointer<TagLibraryInterface> EnginePrivate::loadCppLibrary( const QString &name, uint minorVersion )
310 {
311   int pluginIndex = 0;
312   QString libFileName;
313
314   while ( m_pluginDirs.size() > pluginIndex ) {
315     const QString nextDir = m_pluginDirs.at( pluginIndex++ );
316     const QString pluginDirString = nextDir
317                                   + QLatin1Literal( "/grantlee/" )
318                                   + QString::number( GRANTLEE_VERSION_MAJOR )
319                                   + QLatin1Char( '.' )
320                                   + QString::number( minorVersion )
321                                   + QLatin1Char( '/' );
322
323     const QDir pluginDir( pluginDirString );
324
325     if ( !pluginDir.exists() )
326       continue;
327
328     const QStringList list = pluginDir.entryList( QStringList( name + QLatin1Char( '*' ) ) );
329
330     if ( list.isEmpty() )
331       continue;
332
333     QString pluginPath=pluginDir.absoluteFilePath( list.first() );
334     PluginPointer<TagLibraryInterface> plugin = PluginPointer<TagLibraryInterface>( pluginPath );
335
336     if ( plugin ) {
337 #ifdef __COVERAGESCANNER__
338       __coveragescanner_register_library(pluginPath.toLatin1().data());
339 #endif
340       m_libraries.insert( name, plugin );
341       return plugin;
342     }
343   }
344   return 0;
345 }
346
347 Template Engine::loadByName( const QString &name ) const
348 {
349   Q_D( const Engine );
350
351   QListIterator<AbstractTemplateLoader::Ptr> it( d->m_loaders );
352   while ( it.hasNext() ) {
353     const AbstractTemplateLoader::Ptr loader = it.next();
354
355     if ( !loader->canLoadTemplate( name ) )
356       continue;
357
358     const Template t = loader->loadByName( name, this );
359
360     if ( t ) {
361       return t;
362     }
363   }
364   Template t = Template( new TemplateImpl( this ) );
365   t->setObjectName( name );
366   t->d_ptr->m_error = TagSyntaxError;
367   t->d_ptr->m_errorString = QString::fromLatin1( "Template not found, %1" ).arg( name );
368   return t;
369 }
370
371 Template Engine::newTemplate( const QString &content, const QString &name ) const
372 {
373   Q_D( const Engine );
374   Template t = Template( new TemplateImpl( this, d->m_smartTrimEnabled ) );
375   t->setObjectName( name );
376   t->setContent( content );
377   return t;
378 }
379
380 void Engine::setSmartTrimEnabled( bool enabled )
381 {
382   Q_D( Engine );
383   d->m_smartTrimEnabled = enabled;
384 }
385
386 bool Engine::smartTrimEnabled() const
387 {
388   Q_D( const Engine );
389   return d->m_smartTrimEnabled;
390 }
391
392 #include "engine.moc"