File indexing completed on 2024-04-21 04:02:54

0001 /***************************************************************************
0002                           ccmdprocessor.cpp  -  command processor
0003     This file is a part of KMuddy distribution.
0004                              -------------------
0005     begin                : Pi Jul 5 2002
0006     copyright            : (C) 2002 by Tomas Mecir
0007     email                : kmuddy@kmuddy.com
0008  ***************************************************************************/
0009 
0010 /***************************************************************************
0011  *                                                                         *
0012  *   This program is free software; you can redistribute it and/or modify  *
0013  *   it under the terms of the GNU General Public License as published by  *
0014  *   the Free Software Foundation; either version 2 of the License, or     *
0015  *   (at your option) any later version.                                   *
0016  *                                                                         *
0017  ***************************************************************************/
0018 
0019 #include "ccmdprocessor.h"
0020 
0021 #include "cexpresolver.h"
0022 #include "cglobalsettings.h"
0023 #include "cmacromanager.h"
0024 #include "cvariablelist.h"
0025 
0026 #include <KLocalizedString>
0027 
0028 #include <map>
0029 
0030 class cExpCache {
0031   public:
0032     cExpCache ();
0033     ~cExpCache ();
0034     arith_exp *expression (const QString &src);
0035     void addExpression (const QString &src, arith_exp *exp);
0036     void clear ();
0037   protected:
0038     std::map<QString, arith_exp *> cache;
0039 };
0040 
0041 
0042 cExpCache::cExpCache ()
0043 {
0044 }
0045 
0046 cExpCache::~cExpCache ()
0047 {
0048   clear ();
0049 }
0050 
0051 arith_exp *cExpCache::expression (const QString &src)
0052 {
0053   if (cache.count (src))
0054     return cache[src];
0055   return nullptr;
0056 }
0057 
0058 void cExpCache::addExpression (const QString &src, arith_exp *exp)
0059 {
0060   // clear the cache if it's grown way too big
0061   // it's unlikely that anyone will ever hit this limit, but just in case ...
0062   if (cache.size() > 10000) clear();
0063   
0064   if (cache.count (src)) delete cache[src];
0065   cache[src] = exp;
0066 }
0067 
0068 void cExpCache::clear ()
0069 {
0070   std::map<QString, arith_exp *>::iterator it;
0071   for (it = cache.begin(); it != cache.end(); ++it)
0072     delete it->second;
0073   cache.clear ();
0074 }
0075 
0076 
0077 cCmdProcessor::cCmdProcessor (int sess) : cActionBase ("cmdprocessor", sess)
0078 {
0079   focusstr = ":";
0080   resolver = new cExpResolver (sess);
0081   expcache = new cExpCache;
0082   addGlobalEventHandler ("global-settings-changed", 50, PT_NOTHING);
0083 }
0084 
0085 cCmdProcessor::~cCmdProcessor()
0086 {
0087   removeGlobalEventHandler ("global-settings-changed");
0088   delete resolver;
0089   delete expcache;
0090 }
0091 
0092 void cCmdProcessor::eventNothingHandler (QString event, int /*session*/)
0093 {
0094   if (event == "global-settings-changed") {
0095     setFocusCommandString (cGlobalSettings::self()->getString ("str-focus"));
0096   }
0097 }
0098 
0099 void cCmdProcessor::setFocusCommandString (QString str)
0100 {
0101   focusstr = str.trimmed();
0102 }
0103 
0104 void cCmdProcessor::processCommand (const QString &command, cCmdQueue *queue)
0105 {
0106   QString cmd = command;  // we need to modify the command ...
0107 
0108   // expand internal scripting and variables, then send the command ...
0109   // also process command focusing if needed
0110   // internal scripting must be expanded first, because it can contain variable names, and
0111   // expandVariables would therefore mess things up ...
0112   expandInternalScripting (cmd, queue);
0113   expandVariables (cmd, queue);
0114 
0115   // focusing command?
0116   bool focused = false;
0117   int pos;
0118   if ((pos = isFocusCommand (cmd)) != -1) {
0119     focused = true;
0120     if (processFocusCommand (cmd, pos) == -1)
0121       focused = false;
0122   }
0123   
0124   if (!focused)
0125     // send the command !
0126     invokeEvent ("send-command", sess(), cmd);
0127 }
0128 
0129 void cCmdProcessor::expandInternalScripting (QString &command, cCmdQueue *queue)
0130 {
0131   // require profile connection ...
0132   if (!settings()) return;
0133 
0134   resolver->setQueue (queue);
0135   if (command.indexOf ('[') == -1)  // no internal scripting in this command ...
0136     return;
0137   
0138   QString res, script;
0139   bool changed = false;
0140   // walk through the command and expand [] sequences as scripting, putting the result into
0141   // the "res" string
0142   int len = command.length();
0143   bool backslash = false, in = false, instring = false;
0144   for (int i = 0; i < len; ++i) {
0145     char ch = command[i].toLatin1();
0146     if (backslash) {
0147       backslash = false;
0148       if (in)
0149         script += command[i];
0150       else
0151         res += command[i];
0152       continue;
0153     }
0154     if (ch == '\\') {
0155       backslash = true;
0156       if (in)
0157         script += command[i];
0158       else
0159         res += command[i];
0160       continue;
0161     }
0162     if (!in) {
0163       if (ch == '[') {
0164         in = true;
0165         instring = false;
0166         script = "";
0167         continue;
0168       }
0169       // add the letter to the result
0170       res += command[i];
0171     }
0172     else {
0173       if (instring) {
0174         script += command[i];
0175         if (ch == '"')
0176           instring = false;
0177       } else {
0178         if (ch == ']')
0179         {
0180           // end of script - evaluate it, assuming it's not empty
0181           script = script.trimmed ();
0182           if (script.length() > 0) {
0183             bool ok = true;
0184             cValue val = eval (script, queue, ok);
0185             if (ok) {
0186               res += val.asString ();
0187               changed = true;
0188             } else {
0189               // invokeEvent ("message", sess(), i18n ("Error in the script. Sending as-is."));
0190               res += QChar('[') + script + QChar(']');
0191             }
0192           } else {
0193             res += QChar('[') + script + QChar(']');
0194           }
0195           script = "";
0196           in = false;
0197         }
0198         else
0199           script += command[i];
0200       }
0201     }
0202   }
0203   
0204   if (in)  // unterminated script = no script
0205     res += QChar ('[') + script;
0206 
0207   // if there was some change, update the command !
0208   if (changed)
0209     command = res;
0210 }
0211 
0212 cValue cCmdProcessor::eval (const QString &e, cCmdQueue *queue, bool &ok)
0213 {
0214   resolver->setQueue (queue);
0215   ok = true;
0216 
0217   // end of script - evaluate it, assuming it's not empty
0218   QString script = e.trimmed ();
0219   if (script.length() == 0)
0220     return cValue::empty();
0221 
0222   // try to fetch expression from the cache
0223   arith_exp *exp = expcache->expression (script);
0224   // expression is not in cache - need to create, compile and add to the cache
0225   if (!exp) {
0226     exp = new arith_exp;
0227     ok = exp->compile (script);
0228     if (ok)
0229       expcache->addExpression (script, exp);
0230     else
0231       delete exp;  // wrong expression - get rid of it
0232   }
0233   if (ok)
0234     return exp->evaluate (resolver);
0235   else
0236     return cValue::empty();
0237 }
0238 
0239 void cCmdProcessor::expandVariables (QString &command, cCmdQueue *queue)
0240 {
0241   cVariableList *vl = dynamic_cast<cVariableList *>(object ("variables"));
0242   if (vl)
0243     command = vl->expandVariables (command, true, queue);
0244 }
0245 
0246 int cCmdProcessor::isFocusCommand (const QString &command)
0247 {
0248   int pos;
0249   if ((focusstr.length() > 0) && (command.startsWith(focusstr))) {
0250     QString cmd = command.trimmed(); //removes leading/trailing spaces
0251     if ((pos = cmd.indexOf(focusstr, focusstr.length())) == -1)
0252       return -1; //return false if a second focustr can't be found
0253     return pos;
0254   }
0255   return -1; //should not get to here
0256 }
0257 
0258 int cCmdProcessor::processFocusCommand (const QString &text, int pos)
0259 {
0260   QString window, command;
0261   window = command = text;
0262 
0263   window.remove(pos, window.length());
0264   window.remove(0,focusstr.length()); //remove first focusstr
0265 
0266   command.remove(0,pos);
0267   command.remove(0,focusstr.length()); //remove next
0268 
0269   if ( (command.length() < 1) || (window == focusstr) || (window.length() < 1))
0270     return -1; //if nothing was passed after 2nd focusstr, return
0271 
0272   invokeEvent ("focus-change", 0, window, command);
0273 
0274   return 0;
0275 }
0276 
0277 void cCmdProcessor::processMacro (const QString &mname, const QString &params, cCmdQueue *queue)
0278 {
0279   cMacroManager *macros = dynamic_cast<cMacroManager *>(object ("macros", 0));
0280   if (!macros) return;
0281   
0282   if (!settings()) // -> not a profile-based connection
0283   {
0284     //exclaim that macros are not available
0285     invokeEvent ("message", sess(), i18n ("Sorry, but macro calls are only available for profile connections."));
0286     return;
0287   }
0288 
0289   QString mnamel = mname.toLower ();
0290   QString pars = params;  // needed to get rid of the "const" modifier
0291   
0292   expandInternalScripting (pars, queue);
0293   // we don't expand variables - macros must do that themselves
0294   // TODO: maybe allow the macro to specify whether it needs unexpanded vars ?
0295   if (!macros->callMacro (mnamel, pars, sess(), queue))
0296     invokeEvent ("message", sess(), i18n ("This macro does not exist."));
0297     
0298 }
0299