Augeas wrapper added.
authorJán Kupec <jkupec@suse.cz>
Thu, 19 Mar 2009 16:17:36 +0000 (17:17 +0100)
committerJán Kupec <jkupec@suse.cz>
Thu, 19 Mar 2009 16:17:36 +0000 (17:17 +0100)
- Might be adjusted to take zypp/zypper.conf lense and file as input
  (as soon as the new API is ready for this) and moved to libzypp
- FindAugeas.cmake to be moved to libzypp
- Must be adjusted to take root argument
- zypper.aug can be improved to be more relaxed

CMakeLists.txt
cmake/modules/FindAugeas.cmake [new file with mode: 0644]
src/CMakeLists.txt
src/utils/Augeas.cc [new file with mode: 0644]
src/utils/Augeas.h [new file with mode: 0644]
src/utils/zypper.aug [new file with mode: 0644]

index b8a52bd..d0d87c0 100644 (file)
@@ -42,6 +42,14 @@ ELSE( READLINE_FOUND )
   MESSAGE( FATAL_ERROR "readline not found" )
 ENDIF( READLINE_FOUND )
 
+FIND_PACKAGE( Augeas REQUIRED )
+IF( AUGEAS_FOUND )
+  INCLUDE_DIRECTORIES(${AUGEAS_INCLUDE_DIR})
+ELSE( AUGEAS_FOUND )
+  MESSAGE( FATAL_ERROR "augeas not found" )
+ENDIF( AUGEAS_FOUND )
+
+
 ADD_SUBDIRECTORY( src )
 ADD_SUBDIRECTORY( po )
 ADD_SUBDIRECTORY( doc )
diff --git a/cmake/modules/FindAugeas.cmake b/cmake/modules/FindAugeas.cmake
new file mode 100644 (file)
index 0000000..277102e
--- /dev/null
@@ -0,0 +1,32 @@
+# Find augeas library and tool
+#
+
+if(AUGEAS_INCLUDE_DIR AND AUGEAS_LIBRARY)
+        # Already in cache, be silent
+        set(AUGEAS_FIND_QUIETLY TRUE)
+endif(AUGEAS_INCLUDE_DIR AND AUGEAS_LIBRARY)
+
+set(AUGEAS_LIBRARY)
+set(AUGEAS_INCLUDE_DIR)
+
+FIND_PATH(AUGEAS_INCLUDE_DIR augeas.h
+  /usr/include
+  /usr/local/include
+)
+
+FIND_LIBRARY(AUGEAS_LIBRARY NAMES augeas
+  PATHS
+  /usr/lib
+  /usr/lib64
+  /usr/local/lib
+  /usr/local/lib64
+)
+
+if(AUGEAS_INCLUDE_DIR AND AUGEAS_LIBRARY)
+   MESSAGE( STATUS "augeas found: includes in ${AUGEAS_INCLUDE_DIR}, library in ${AUGEAS_LIBRARY}")
+   set(AUGEAS_FOUND TRUE)
+else(AUGEAS_INCLUDE_DIR AND AUGEAS_LIBRARY)
+   MESSAGE( STATUS "augeas not found")
+endif(AUGEAS_INCLUDE_DIR AND AUGEAS_LIBRARY)
+
+MARK_AS_ADVANCED(AUGEAS_INCLUDE_DIR AUGEAS_LIBRARY)
index 599273f..c968e9a 100644 (file)
@@ -60,6 +60,7 @@ SET( zypper_out_SRCS
 )
 
 SET( zypper_utils_HEADERS
+  utils/Augeas.h
   utils/colors.h
   utils/getopt.h
   utils/messages.h
@@ -70,6 +71,7 @@ SET( zypper_utils_HEADERS
 )
 
 SET( zypper_utils_SRCS
+  utils/Augeas.cc
   utils/colors.cc
   utils/getopt.cc
   utils/messages.cc
@@ -81,7 +83,7 @@ SET( zypper_utils_SRCS
 )
 
 ADD_EXECUTABLE( zypper ${zypper_SRCS} ${zypper_out_SRCS} ${zypper_utils_SRCS} )
-TARGET_LINK_LIBRARIES( zypper ${ZYPP_LIBRARY} ${READLINE_LIBRARY} )
+TARGET_LINK_LIBRARIES( zypper ${ZYPP_LIBRARY} ${READLINE_LIBRARY} -laugeas ${AUGEAS_LIBRARY} )
 
 INSTALL(
   TARGETS zypper
@@ -135,3 +137,8 @@ INSTALL(
   DESTINATION ${SYSCONFDIR}/logrotate.d
 )
 
+# augeas lens
+INSTALL(
+  FILES utils/zypper.aug
+  DESTINATION ${INSTALL_PREFIX}/share/zypper
+)
diff --git a/src/utils/Augeas.cc b/src/utils/Augeas.cc
new file mode 100644 (file)
index 0000000..2056866
--- /dev/null
@@ -0,0 +1,161 @@
+/*---------------------------------------------------------------------------*\
+                          ____  _ _ __ _ __  ___ _ _
+                         |_ / || | '_ \ '_ \/ -_) '_|
+                         /__|\_, | .__/ .__/\___|_|
+                             |__/|_|  |_|
+\*---------------------------------------------------------------------------*/
+#include <iostream>
+
+#include "zypp/base/Logger.h"
+#include "Zypper.h"
+#include "utils/Augeas.h"
+
+using namespace zypp;
+using namespace std;
+
+Augeas::Augeas()
+  : _augeas(NULL), _got_global_zypper_conf(false), _got_user_zypper_conf(false)
+{
+  MIL << "Going to read zypper config using Augeas..." << endl;
+
+  //! \todo use specified root dir
+  _augeas = ::aug_init(NULL, "/usr/share/zypper", AUG_NO_STDINC);
+
+  if (_augeas == NULL)
+    ZYPP_THROW(Exception(_("Cannot initialize configuration file parser.")));
+
+
+  _got_global_zypper_conf =
+    ::aug_get(_augeas, "/files/etc/zypp/zypper.conf", NULL) != 0;
+
+  const char *value[1] = {};
+  string error;
+  if (::aug_get(_augeas, "/augeas/files/etc/zypp/zypper.conf/error/message", value))
+    error = value[0];
+
+  _homedir = ::getenv("HOME");
+  if (_homedir.empty())
+    WAR << "Cannot figure out user's home directory." << endl;
+  else
+  {
+    //! \todo cherry-pick this file as soon as the API allows it
+    string user_zypper_conf = "/files" + _homedir + "/.zypper.conf";
+    _got_user_zypper_conf =
+      ::aug_get(_augeas, user_zypper_conf.c_str(), NULL) != 0;
+  }
+
+  if (!_got_global_zypper_conf && !_got_user_zypper_conf)
+  {
+    if (error.empty())
+      ZYPP_THROW(Exception(
+          _("No configuration file exists or could be parsed.")));
+    else
+    {
+      string msg = _("Error parsing zypper.conf:") + string("\n") + error;
+      ZYPP_THROW(Exception(msg));
+    }
+  }
+
+  MIL << "Done reading conf files:" << endl;
+  MIL << "user conf read: " << (_got_user_zypper_conf ? "yes" : "no") << endl;
+  MIL << "global conf read: " << (_got_global_zypper_conf ? "yes" : "no") << endl;
+}
+
+// ---------------------------------------------------------------------------
+
+Augeas::~Augeas()
+{
+  if (_augeas != NULL)
+    ::aug_close(_augeas);
+}
+
+// ---------------------------------------------------------------------------
+
+static string global_option_path(
+    const string & section, const string & option)
+{
+  return "/files/etc/zypp/zypper.conf/" + section + "/*/" + option;
+}
+
+static string user_option_path(
+    const string & section, const string & option, const string & homedir)
+{
+  return "/files" + homedir + "/.zypper.conf/" + section + "/*/" + option;
+}
+
+// ---------------------------------------------------------------------------
+
+string Augeas::get(const string & augpath) const
+{
+  const char *value[1] = {};
+  _last_get_result = ::aug_get(_augeas, augpath.c_str(), value);
+  if (_last_get_result)
+  {
+    MIL << "Got " << augpath << " = " << value[0] << endl;
+    return value[0];
+  }
+  else if (_last_get_result == 0)
+    DBG << "No match for " << augpath << endl;
+  else
+    DBG << "Multiple matches for " << augpath << endl;
+
+  return string();
+}
+
+// ---------------------------------------------------------------------------
+
+string Augeas::getOption(const string & option) const
+{
+  vector<string> opt;
+  str::split(option, back_inserter(opt), "/");
+
+  if (opt.size() != 2 || opt[0].empty() || opt[1].empty())
+  {
+    ERR << "invalid option " << option << endl;
+    return string();
+  }
+
+  string augpath_u = user_option_path(opt[0], opt[1], _homedir);
+  string result = get(augpath_u);
+  if (_last_get_result && !isCommented(opt[0], opt[1], false))
+    return result;
+
+  string augpath_g = global_option_path(opt[0], opt[1]);
+  result = get(augpath_g);
+  if (_last_get_result && !isCommented(opt[0], opt[1], true))
+    return result;
+
+  return string();
+}
+
+// ---------------------------------------------------------------------------
+
+bool Augeas::isCommented(
+    const string & section, const string & option, bool global) const
+{
+  Pathname path(global ?
+      global_option_path(section, option) :
+      user_option_path(section, option, _homedir));
+
+  path = path.dirname() + "/commented";
+  if (::aug_get(_augeas, path.c_str(), NULL))
+    return true;
+
+  return false;
+}
+
+// ---------------------------------------------------------------------------
+
+bool Augeas::isCommented(const string & option, bool global) const
+{
+  vector<string> opt;
+  str::split(option, back_inserter(opt), "/");
+
+  if (opt.size() != 2 || opt[0].empty() || opt[1].empty())
+  {
+    ERR << "invalid option " << option << endl;
+    return false;
+  }
+
+  return isCommented(opt[0], opt[1], global);
+}
diff --git a/src/utils/Augeas.h b/src/utils/Augeas.h
new file mode 100644 (file)
index 0000000..1a3621d
--- /dev/null
@@ -0,0 +1,52 @@
+/*---------------------------------------------------------------------------*\
+                          ____  _ _ __ _ __  ___ _ _
+                         |_ / || | '_ \ '_ \/ -_) '_|
+                         /__|\_, | .__/ .__/\___|_|
+                             |__/|_|  |_|
+\*---------------------------------------------------------------------------*/
+
+#ifndef ZYPPER_UTIL_AUGEAS_H_
+#define ZYPPER_UTIL_AUGEAS_H_
+
+#include <string>
+
+extern "C"
+{
+  #include <augeas.h>
+}
+
+#include "zypp/base/NonCopyable.h"
+
+/**
+ * Zypper's wrapper around Augeas.
+ */
+class Augeas : private zypp::base::NonCopyable
+{
+public:
+  Augeas();
+  ~Augeas();
+
+  std::string get(const std::string & augpath) const;
+
+  std::string getOption(const std::string & option) const;
+  bool isCommented(const std::string & option, bool global) const;
+  void comment(const std::string & option);
+  void uncomment(const std::string & option);
+
+  ::augeas * getAugPtr()
+  { return _augeas; }
+
+private:
+  bool isCommented(const std::string & section, const std::string & option,
+      bool global) const;
+
+private:
+  ::augeas * _augeas;
+  std::string _homedir;
+  bool _got_global_zypper_conf;
+  bool _got_user_zypper_conf;
+
+  mutable bool _last_get_result;
+};
+
+#endif /* ZYPPER_UTIL_AUGEAS_H_ */
diff --git a/src/utils/zypper.aug b/src/utils/zypper.aug
new file mode 100644 (file)
index 0000000..9bacad4
--- /dev/null
@@ -0,0 +1,93 @@
+(* Lens for parsing Zypper configuration file.
+
+About: License
+  This file is licensed under the GPLv2+, like the rest of Zypper.
+*)
+
+module ZYpper =
+  autoload xfm
+
+  (* ****************( primitives )*************** *)
+  (* These are taken from the official util.aug *)
+
+  (*
+  Variable: eol
+    Delete end of line, including optional trailing whitespace
+  *)
+  let eol = del /[ \t]*\n/ "\n"
+
+  (*
+  Variable: del_str
+    Delete a string and default to it
+
+  Parameters:
+     s:string - the string to delete and default to
+  *)
+  let del_str (s:string) = del s s
+
+  (*
+  Variable: del_opt_ws
+    Delete optional whitespace
+  *)
+  let del_opt_ws = del /[ \t]*/
+
+  (* Matches an empty line and creates a node for it *)
+  let empty = [ eol ]
+  
+  (* Deletes optional whitespace and stores the rest 'till the end of line *)
+  let store_to_eol = del_opt_ws " " . store /([^ \t\n].*[^ \t\n])/ 
+
+  (*
+    Keyword regex.
+    Allows alphanumericals and '.' and '_'. Must start with a letter
+    and end with a letter or number.
+  *)
+  let kw_re = /[a-zA-Z][a-zA-Z0-9\._]*[a-zA-Z0-9]/
+
+  (* ****************( section )*************** *)
+
+  (* Matches one line of ## description and creates a node for it *)
+  let description = [ label "description" . del /##/ "##" . store_to_eol? . eol ]
+
+  (*
+    Matches '#' and whitespace, creates a 'commented' note for it.
+    Used in 'option' to mark the option as commented.
+  *)
+  let commented  = [ label "commented" . del /#[ \t]*/ "# " ]
+
+  (* Matches key=value, creates a new node out of key and stores the value *)
+  let kv = [ key kw_re . del /[ \t]*=[ \t]*/ " = " . store /[^# \t\n][^#\n]*/ ]
+
+  (*
+    An option consists of ## description, and an (optionally commented)
+    key=value pair.
+
+    This could be relaxed a bit in the future to allow more commented
+    keys, arbitrary comments, etc.
+  *)
+  let option = [ seq "option" . description* . commented? . kv . del_str "\n" ]
+
+  (* ****************( section )*************** *)
+
+  (* Matches section [title] and creates a new tree node out of it *)
+  let section_title = del_str "[" . key /[^] \t\n\/]+/ . del_str "]" . eol
+
+  (* Section with it's contests *)
+  let section = [ section_title . (option | empty)* ]
+
+  (* Optional comments in the anonymous section (start of the file). *)
+  let section_anon = [ label "anon" . ( description | empty )+ ]
+
+
+  (* The lens matching and mapping the whole file *)
+  let lns = section_anon . section+
+
+  (*
+    Filter for the xfm transformation.
+    This is for system-wide zypper.conf only. ~/.zypper.conf must be
+    read on-demand only.
+  *)
+  let filter = (incl "/etc/zypp/zypper.conf")
+
+  (* Transfrom files matching 'filter' using lens 'lns' *)
+  let xfm = transform lns filter