Add simple sysconfig::write (bnc#766598)
authorMichael Andres <ma@suse.de>
Wed, 24 Oct 2012 08:39:56 +0000 (10:39 +0200)
committerMichael Andres <ma@suse.de>
Wed, 24 Oct 2012 08:39:56 +0000 (10:39 +0200)
tests/zypp/base/Sysconfig_test.cc
tests/zypp/base/data/Sysconfig/proxy
zypp/base/Sysconfig.cc
zypp/base/Sysconfig.h

index 610555a..dfbd98f 100644 (file)
@@ -1,5 +1,6 @@
 
 #include <iostream>
+#include <sstream>
 #include <fstream>
 #include <map>
 #include <string>
@@ -10,6 +11,7 @@
 #include "zypp/base/Exception.h"
 #include "zypp/TmpPath.h"
 #include "zypp/PathInfo.h"
+#include "zypp/ExternalProgram.h"
 
 #include "zypp/base/Sysconfig.h"
 
@@ -24,7 +26,7 @@ using namespace zypp;
 
 BOOST_AUTO_TEST_CASE(Sysconfig)
 {
-  Pathname file = DATADIR + "proxy";
+  Pathname file = DATADIR / "proxy";
   map<string,string> values = zypp::base::sysconfig::read(file);
   BOOST_CHECK_EQUAL( values.size(), 6 );
   BOOST_CHECK_EQUAL( values["PROXY_ENABLED"], "no");
@@ -32,3 +34,43 @@ BOOST_AUTO_TEST_CASE(Sysconfig)
   BOOST_CHECK_EQUAL( values["NO_PROXY"], "localhost, 127.0.0.1");
 }
 
+BOOST_AUTO_TEST_CASE(SysconfigWrite)
+{
+  Pathname file = DATADIR / "proxy";
+  filesystem::TmpFile tmpf( filesystem::TmpFile::makeSibling( file ) );
+  filesystem::copy( file, tmpf.path() );
+
+  BOOST_REQUIRE_THROW( zypp::base::sysconfig::writeStringVal( "/tmp/wrzlprmpf", "PROXY_ENABLED", "yes", "# fifi\n fofo\n" ),
+                      zypp::Exception );
+  BOOST_CHECK( zypp::base::sysconfig::writeStringVal( tmpf.path(), "PROXY_ENABLED", "yes", "# fifi\n fofo\n" ) );
+  BOOST_CHECK( !zypp::base::sysconfig::writeStringVal( tmpf.path(), "NEW1","12" ) );
+  BOOST_CHECK( zypp::base::sysconfig::writeStringVal( tmpf.path(), "NEW2","13", "# fifi\n# fofo" ) );
+  BOOST_CHECK( zypp::base::sysconfig::writeStringVal( tmpf.path(), "NEW3","13\"str\"", "fifi\nffofo" ) );
+
+  std::ostringstream s;
+  ExternalProgram( "diff -u " + file.asString() + " " + tmpf.path().asString() + " | tail -n +3" ) >> s;
+  BOOST_CHECK_EQUAL( s.str(),
+                    "@@ -8,7 +8,7 @@\n"
+                    " # This setting allows to turn the proxy on and off while\n"
+                    " # preserving the particular proxy setup.\n"
+                    " #\n"
+                    "-PROXY_ENABLED=\"no\"\n"
+                    "+PROXY_ENABLED=\"yes\"\n"
+                    " \n"
+                    " ## Type:\tstring\n"
+                    " ## Default:\t\"\"\n"
+                    "@@ -49,3 +49,11 @@\n"
+                    " # Example: NO_PROXY=\"www.me.de, do.main, localhost\"\n"
+                    " #\n"
+                    " NO_PROXY=\"localhost, 127.0.0.1\"\n"
+                    "+\n"
+                    "+# fifi\n"
+                    "+# fofo\n"
+                    "+NEW2=\"13\"\n"
+                    "+\n"
+                    "+# fifi\n"
+                    "+# ffofo\n"
+                    "+NEW3=\"13\\\"str\\\"\"\n"
+  );
+}
+
index 0924e45..f3a6b9c 100644 (file)
@@ -1,5 +1,5 @@
 ## Path:       Network/Proxy
-## Description:        
+## Description:
 ## Type:       yesno
 ## Default:    no
 ## Config:      kde,profiles
@@ -7,7 +7,7 @@
 # Enable a generation of the proxy settings to the profile.
 # This setting allows to turn the proxy on and off while
 # preserving the particular proxy setup.
-# 
+#
 PROXY_ENABLED="no"
 
 ## Type:       string
index 253c44d..0d11ec4 100644 (file)
 
 #include "zypp/base/Logger.h"
 #include "zypp/base/String.h"
+#include "zypp/base/StrMatcher.h"
+#include "zypp/base/IOStream.h"
+#include "zypp/base/InputStream.h"
 #include "zypp/Pathname.h"
+#include "zypp/PathInfo.h"
+#include "zypp/TmpPath.h"
 
 #include "zypp/base/Sysconfig.h"
 
@@ -24,13 +29,13 @@ using namespace zypp::base;
 
 namespace zypp {
   namespace base {
-
     namespace sysconfig {
+
       map<string,string> read( const Pathname & _path )
       {
        DBG << "Load '" << _path << "'" << endl;
        map<string,string> ret;
-      
+
        string line;
        ifstream in( _path.asString().c_str() );
        if ( in.fail() ) {
@@ -68,10 +73,83 @@ namespace zypp {
          } // not comment
 
        } // while getline
-  MIL << "done reading '" << _path << "'" << endl;
+       MIL << "done reading '" << _path << "'" << endl;
        return ret;
       }
 
+      bool write( const Pathname & path_r, const std::string & key_r, const std::string & val_r, const std::string & newcomment_r )
+      {
+       if ( key_r.empty() )
+       {
+         WAR << "Empty key in write " << path_r << endl;
+         return false;
+       }
+
+       PathInfo pi( path_r );
+       if ( ! pi.isFile() )
+         ZYPP_THROW( Exception( str::Str() << path_r << ": " << Errno(ENOENT) ) );
+       if ( ! pi.userMayRW() )
+         ZYPP_THROW( Exception( str::Str() << path_r << ": " << Errno(EACCES) ) );
+
+       bool found = false;
+       filesystem::TmpFile tmpf( filesystem::TmpFile::makeSibling( path_r ) );
+       {
+         StrMatcher matches( "^[ \t]*"+key_r+"[ \t]*=", Match::REGEX );
+         std::ofstream o( tmpf.path().c_str() );
+         iostr::forEachLine( InputStream( path_r ),
+                             [&]( int num_r, std::string line_r )->bool
+                             {
+                               if ( !found && matches( line_r ) )
+                               {
+                                 o << key_r << '=' << val_r << endl;
+                                 found = true;
+                                 MIL << path_r << ": " << key_r << '=' << val_r << " changed on line " << num_r << endl;
+                               }
+                               else
+                                 o << line_r << endl;
+                               return true;
+                             } );
+         if ( !found )
+         {
+           if ( newcomment_r.empty() )
+           {
+             WAR << path_r << ": " << key_r << '=' << val_r << " can not be added (no comment provided)." << endl;
+           }
+           else
+           {
+             std::vector<std::string> lines;
+             str::split( newcomment_r, std::back_inserter(lines), "\r\n" );
+             o << endl;
+             for ( auto line : lines )
+             {
+               if ( line[0] != '#' )
+                 o << "# ";
+               o << line << endl;
+             }
+             o << key_r << '=' << val_r << endl;
+             found = true;
+             MIL << path_r << ": " << key_r << '=' << val_r << " appended. " << endl;
+           }
+         }
+
+         if ( ! o )
+           ZYPP_THROW( Exception( str::Str() << tmpf.path() << ": " << Errno(EIO) ) );
+       }
+
+       // If everything is fine, exchange the files:
+       int res = exchange( tmpf.path(), path_r );
+       if ( res )
+       {
+         ZYPP_THROW( Exception( str::Str() << tmpf.path() << ": " << Errno(res) ) );
+       }
+       return found;
+      }
+
+      bool writeStringVal( const Pathname & path_r, const std::string & key_r, const std::string & val_r, const std::string & newcomment_r )
+      {
+       return write( path_r, key_r, str::Str() << '"' << str::escape( val_r, '"' )<< '"', newcomment_r );
+      }
+
     } // namespace sysconfig
   } // namespace base
 } // namespace zypp
index 5d511dc..c9ae7cb 100644 (file)
@@ -20,8 +20,51 @@ namespace zypp {
   namespace base {
     namespace sysconfig {
 
+      /** Read sysconfig file \a path_r and return <tt>(key,valye)</tt> pairs. */
       std::map<std::string,std::string> read( const Pathname & _path );
 
+      /** Add or change a value in sysconfig file \a path_r.
+       *
+       * If \a key_r already exists, only the \a val_r is changed accordingly.
+       *
+       * In case \a key_r is not yet present in the file, a new entry may be created
+       * at the end of the file, using the lines in \a newcomment_r as comment
+       * block. If \a newcomment_r is not provided or empty, a new value is not
+       * created and \c false is returned.
+       *
+       * \returns \c TRUE if an entry was changed or created.
+       *
+       * \throws Exception if \a path_r can not be read or written.
+       *
+       * \note \a val_r is written as it is. The caller is responsible for escaping and
+       * enclosing in '"', in case this is needed (\see \ref writeStringVal and \ref str::escape).
+       *
+       * \note Lines in \a newcomment_r which do not already start with a '#',
+       * are prefixes with "# ".
+       *
+       * \code
+       *  ## Type: string
+       *  ## Default: ""
+       *  #
+       *  # A multiline description of
+       *  # the options purpose.
+       *  #
+       *  KEY="value"
+       * \endcode
+       */
+      bool write( const Pathname & path_r, const std::string & key_r, const std::string & val_r,
+                 const std::string & newcomment_r = std::string() );
+
+      /** Convenience to add or change a string-value in sysconfig file \a path_r.
+       *
+       * \a val_r is expected to be a plain string value, so it is propery escaped and enclosed in
+       * double quotes before it is written to the sysconfig file \a path_r.
+       *
+       * \see \ref write
+       */
+      bool writeStringVal( const Pathname & path_r, const std::string & key_r, const std::string & val_r,
+                          const std::string & newcomment_r = std::string() );
+
     } // namespace sysconfig
   } // namespace base
 } // namespace zypp