# -*- coding: utf-8 -*- # Copyright 2008-2015 Calculate Ltd. http://www.calculate-linux.org # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import sys import os import stat import re import importlib from calculate.lib.cl_xml import xmlShare import types import random import string import time import glob import hashlib from fnmatch import fnmatch from math import sqrt from itertools import * from collections import OrderedDict from operator import lt, le, eq, ne, ge, gt from utils.common import _error, _warning, getTupleVersion, getPortageUidGid from utils.text import _u from utils.portage import (isPkgInstalled, reVerSplitToPV, EmergeLog, EmergeLogPackageTask, getPkgUses) from utils.content import PkgContents, checkContents, getCfgFiles, fillContents from utils.files import (getModeFile, listDirectory, removeDir, typeFile, scanDirectory, pathJoin, readFile, readLinesFile, process) from datavars import DataVarsError from calculate.lib.configparser import ConfigParser, NoSectionError from calculate.lib.cl_lang import setLocalTranslate, RegexpLocalization _ = lambda x: x setLocalTranslate('cl_lib3', sys.modules[__name__]) PORTAGEUID, PORTAGEGID = getPortageUidGid() class TemplatesError(Exception): """ Error on templates appling """ def catch_no_space_left(f): def wrapper(*args, **kw): try: return f(*args, **kw) except IOError as e: if e.errno == 28: raise TemplatesError(_("No space left on device")) raise return wrapper class _shareTermsFunction(object): """Общие аттрибуты для классов _terms и templateFunctions""" # Символы допустимые в скобках функции шаблона _reFunctionArgvInSquareBrackets = ("a-zA-Z0-9_:;<>=\!\|\{\}\^\$\?\(\)\[\]\-" "\+\,\*\/\.\'\"~\\\\ ") _reFunctionArgvText = "[%s]" % _reFunctionArgvInSquareBrackets # регулярное выражение для поиска функции в шаблоне _reFunctionText = "([a-zA-Z0-9\_\-]+)\((%s+|)\)" % _reFunctionArgvText class _terms(_error, _shareTermsFunction): """Вычисление условий применяемых в шаблонах """ # регулярное выражение для поиска функции в шаблоне _reFunction = re.compile(_shareTermsFunction._reFunctionText) # регулярное выражение для не версии _re_not_Version = re.compile("[^0-9\.]") # регулярное выражение не номер _re_not_Number = re.compile("[^0-9]") _suffixDict = {"pre": -2, "p": 0, "alpha": -4, "beta": -3, "rc": -1} _lenSuffixDict = len(_suffixDict) # Регулярное выражение для названия переменной _reRightName = re.compile("^(?:[a-z_\-]+\.)?(?:[a-zA-Z0-9_\-]+)$") # Регулярное выражение для сравниваемого значения _reDenyValue = re.compile("[^0-9a-zA-Z_/\.-]") # латинские буквы в нижнем регистре _letters = list(string.ascii_lowercase) # использует из других объектов objVar = None def _splitVersion(self, strVersion): """ Split version. Version, addition letter, list suffixes with version, revision. Examples: 3.0.0_beta2 ("3.0.0_beta2","",[],"") 3.0.0_beta2-r1 ("3.0.0_beta2","",[],"r1") 3.0.0_beta2a-r1 ("3.0.0_beta2","a",[],"r1") 3.0.0_beta2a_rc1-r1 ("3.0.0_beta2","a",[("rc","1")],"r1") 3.0.0_beta2a_rc1_p20111212-r1 ("3.0.0_beta2","a",[("rc1","1"),("p","20111212")],"r1") """ # get revision from version strWorkVersion, spl, rVersion = strVersion.rpartition("-") if rVersion == strVersion: strWorkVersion = rVersion rVersion = "" suffixes = [] # get suffixes from version while "_" in strWorkVersion: # 2.3_p45 ('2.3','_','p43') # 2.3_rc4_p45 ('2.3_rc4','_','p43') strWorkVersion, spl, suffix = strWorkVersion.rpartition("_") suffSplList = filter(lambda x: suffix.startswith(x), self._suffixDict.keys()) if suffSplList: suffSpl = suffSplList[0] lenSuffSpl = len(suffSpl) suffixVersion = suffix[lenSuffSpl:] suffixes.append((suffSpl, suffixVersion)) letters = "" numberVersion = strWorkVersion if numberVersion and numberVersion[-1:] in self._letters: letters = numberVersion[-1:] numberVersion = numberVersion[:-1] return numberVersion, letters, suffixes, rVersion def _notVersion(self, strVersion): """strVersion is not version - True""" numberVersion, letters, suffixes, rVersion = \ self._splitVersion(strVersion) if not numberVersion.strip(): return True if self._re_not_Version.search(numberVersion): return True if letters and not letters in self._letters: return True for suffix, suffixVersion in suffixes: if suffixVersion and self._re_not_Number.search(suffixVersion): return True if rVersion: if rVersion[0] != "r" or len(rVersion) == 1: return True if self._re_not_Number.search(rVersion[1:]): return True return False def _convertVers(self, verA, verB): """Конвертирование номеров версий для корректного сравнения """ def fillZero(elemA, elemB): # elemA, elemB = elemA[], elemB[] if len(elemA) > len(elemB): maxElemB = len(elemB) - 1 for i in range(len(elemA)): if i > maxElemB: elemB.append("0") else: maxElemA = len(elemA) - 1 for i in range(len(elemB)): if i > maxElemA: elemA.append("0") for i in range(len(elemB)): lenA = len(elemA[i]) lenB = len(elemB[i]) if lenA == lenB: pass elif lenA > lenB: res = lenA - lenB for z in range(res): elemB[i] = "0" + elemB[i] elif lenB > lenA: res = lenB - lenA for z in range(res): elemA[i] = "0" + elemA[i] def fillSuffix(elemA, elemB, sA, svA, sB, svB): if str(sA) or str(sB): svA, svB = map(lambda x: [x] if x else ['0'], (svA, svB)) fillZero(svA, svB) sA, sB = map(lambda x: x if x else 0, (sA, sB)) elemA.append(str(self._lenSuffixDict + sA)) elemA.extend(svA) elemB.append(str(self._lenSuffixDict + sB)) elemB.extend(svB) # Version, letters, suffix, suffixVersion, rVersion vA, lA, ssA, rvA = self._splitVersion(verA) vB, lB, ssB, rvB = self._splitVersion(verB) elemA = vA.split(".") elemB = vB.split(".") fillZero(elemA, elemB) if lA or lB: lA, lB = map(lambda x: x if x else '0', (lA, lB)) elemA.append(lA) elemB.append(lB) # dereferencing suffix in suffixes list ssA = map(lambda x: (self._suffixDict.get(x[0], 0), x[1]), ssA) ssB = map(lambda x: (self._suffixDict.get(x[0], 0), x[1]), ssB) for suffix, sufVer in reversed(ssA): if ssB: sB, svB = ssB.pop() else: sB, svB = "", "" fillSuffix(elemA, elemB, suffix, sufVer, sB, svB) while ssB: sB, svB = ssB.pop() fillSuffix(elemA, elemB, "", "", sB, svB) if rvA or rvB: rvA, rvB = map(lambda x: [x[1:]], (rvA, rvB)) fillZero(rvA, rvB) elemA += rvA elemB += rvB return ".".join(elemA), ".".join(elemB) def _equalTerm(self, term, textError, function=None): """Вычисление логических выражений для условий Для корректной работы в классе который наследует этот класс должен быть объявлен аттрибут self.objVar (объект для работы с переменными) function - функция для для обработки функций в заголовке блока """ rpl = lambda x: x.replace("@@", " ") trm = {"&&": "@@and@@", "||": "@@or@@"} dictRuleFunc = OrderedDict((("==", eq), ("!=", ne), (">=", ge), ("<=", le), (">", gt), ("<", lt))) rule = dictRuleFunc.keys() listEqual = [] for k in trm.keys(): if k in term: term = term.replace(k, trm[k]) trs = term.split("@@") listSplitOr = [] if "or" in trs: lst = [] for t in trs: if t != "or": lst.append(t) else: listSplitOr.append(lst) lst = [] if lst: listSplitOr.append(lst) else: listSplitOr = [trs] for trsAnd in listSplitOr: listEqual = [] for t in trsAnd: def search_rule(t, rule, prefix=""): for sepF in rule: if sepF in t: vals = list(t.partition(sepF)[::2]) if vals[0].endswith("\\"): return search_rule(vals[1], rule, prefix="%s%s%s" % ( prefix, vals[0], sepF)) return True, sepF, ["%s%s" % (prefix, vals[0]), vals[1]] return False, None, [] flagRule, sepF, vals = search_rule(t, rule) if flagRule: # проверка на допустимость названия переменной flagFunction = False if not self._reRightName.search(vals[0]): # проверка на допустимость функции flagError = True if callable(function): searchFunct = self._reFunction.search(vals[0]) if searchFunct: flagError = False flagFunction = True if flagError: self.setError( "'%s'" % rpl(term) + " " + _("incorrect")) self.setError(textError) return False # проверка на допустимость значения if self._reDenyValue.search(vals[1]): self.setError("'%s'" % rpl(term) + " " + _("incorrect")) self.setError(textError) return False flagIntTypeVar = None if flagFunction and callable(function): valVars = function("#-%s-#" % vals[0]) if valVars is False: self.setError( "'%s'" % rpl(term) + " " + _("incorrect")) self.setError(textError) return False if "load" == searchFunct.group(1) and \ re.search("\(\s*num\s*,", vals[0]): if valVars: try: valVars = int(valVars) except ValueError: self.setError("'%s'" % rpl(term) + " " + _("incorrect")) self.setError(textError) return False flagIntTypeVar = True else: flagIntTypeVar = False else: if valVars == "" and \ not self._notVersion(vals[1]): valVars = "0" elif vals[1] == "" and \ not self._notVersion(valVars): vals[1] = "0" else: try: valVars = self.objVar.Get(vals[0]) varTable = self.objVar.Get('cl_used_action') varTable.append((vals[0], vals[1])) if not valVars: valVars = "" except DataVarsError, e: raise TemplatesError("{header}\n{body}".format( header=textError, body=str(e))) # Номера версий для ini flagNotIniFunct = True # Два значения не пусты flagNotEmptyVals = not (valVars == "" and vals[1] == "") if flagFunction and flagNotEmptyVals and \ searchFunct.group(1) == "ini": # Проверка значения на версию if not self._notVersion(valVars) and \ not self._notVersion(vals[1]): verFile, verVar = self._convertVers(vals[1], valVars) res = dictRuleFunc[sepF](verVar, verFile) if res: listEqual.append(True) else: listEqual.append(False) break flagNotIniFunct = False # Cравниваем номера версий if flagNotIniFunct: if flagNotEmptyVals and \ ("_ver" in vals[0] or (flagFunction and searchFunct.group( 1) in ("pkg", "merge")) or (flagFunction and searchFunct.group( 1) == "load" and re.search("\(\s*ver\s*,", vals[0]))): # Проверка значения на версию if self._notVersion(vals[1]): self.setError("'%s'" % rpl(term) + " " + \ _("incorrect")) self.setError( _("This value is not a version")) return False # Проверка значения функции на версию if self._notVersion(valVars): self.setError("'%s'" % rpl(term) + " " + \ _("incorrect")) self.setError( _("The function value is not a version")) return False verFile, verVar = self._convertVers(vals[1], valVars) res = dictRuleFunc[sepF](verVar, verFile) if res: listEqual.append(True) else: listEqual.append(False) break else: if flagIntTypeVar is None: flagIntTypeVar = True try: valVars = int(valVars) except ValueError: flagIntTypeVar = False if flagIntTypeVar: if not vals[1].strip(): vals[1] = 0 try: valFile = int(vals[1]) valVar = valVars res = dictRuleFunc[sepF](valVar, valFile) if res: listEqual.append(True) else: listEqual.append(False) break except ValueError: flagIntTypeVar = False if not flagIntTypeVar: if sepF == "!=" or sepF == "==": if not vals[1].strip(): vals[1] = "" valFile = vals[1] valVar = valVars res = dictRuleFunc[sepF](valVar, valFile) if res: listEqual.append(True) else: listEqual.append(False) break else: if not flagNotEmptyVals: listEqual.append(False) break else: self.setError("'%s'" % rpl(term) + " " \ + _("incorrect")) self.setError(textError) return False else: if t == "and": if listEqual == [] or False in listEqual: listEqual = [False] break else: listEqual = [True] else: self.setError("'%s'" % rpl(term) + " " + _("incorrect")) self.setError(textError) return False if not (listEqual == [] or False in listEqual): break if listEqual == [] or False in listEqual: return False return True def splitParLine(self, linePar): """ Split params line """ def splitQuote(listPar, quoteSymbol): listTerm = map(lambda x: x + quoteSymbol, ("=", ">", "<")) flagQ = False mass = [] v = "" for i in listPar: if i.count(quoteSymbol) == 1: if flagQ and i.endswith(quoteSymbol): v = v + " " + i mass.append(v) v = "" flagQ = False elif filter(lambda x: x in i, listTerm): flagQ = True v = i else: mass.append(i) elif flagQ: v = v + " " + i else: mass.append(i) foundPar = list(set(mass) - set(listPar)) return not flagQ, filter(lambda x: not x in foundPar, mass), foundPar listPar = re.split("\s+", linePar) flagFoundQ = "'" in linePar flagFoundQQ = '"' in linePar if flagFoundQ and flagFoundQQ: flagQ, listSplQPar, listFoundQPar = splitQuote(listPar, "'") if flagQ: flagQQ, listSplQQPar, listFoundQQPar = splitQuote(listSplQPar, '"') if flagQQ: listPar = listSplQQPar + listFoundQPar + listFoundQQPar elif flagFoundQQ: flagQQ, listSplQQPar, listFoundQQPar = splitQuote(listPar, '"') if flagQQ: listPar = listSplQQPar + listFoundQQPar elif flagFoundQ: flagQ, listSplQPar, listFoundQPar = splitQuote(listPar, "'") if flagQ: listPar = listSplQPar + listFoundQPar if flagFoundQ: listQPar = [] for par in listPar: if par.endswith("'") and par.count("'") > 1: listQPar.append(par[:-1].replace("='", "=")) else: listQPar.append(par) listPar = listQPar if flagFoundQQ: listQQPar = [] for par in listPar: if par.endswith('"') and par.count('"') > 1: listQQPar.append(par[:-1].replace('="', '=')) else: listQQPar.append(par) listPar = listQQPar return listPar class fileHeader(_terms): """Обработка заголовков шаблонов и конфигурационных файлов """ # Допустимые параметры заголовка allowParam = ["format", "dotall", "multiline", "comment", "append", "force", "dconf", "link", "mirror", "symbolic", "chmod", "chown", "name", "path", "autoupdate", "protected", "run", "exec", "merge", "module", "env", "postmerge"] # Тип шаблона fileType = "" # Тип вставки шаблона typeAppend = "" # Возможные типы вставки шаблонов _fileAppend = "join", "before", "after", "replace", "remove", "skip", \ "patch", "clear" # Интерпретатор (#!/bin/bash) (#!/usr/bin/python) execStr = "" # Символ комментария comment = False # Выражение для поиска строки интерпретатора reExecStr = re.compile("^(#!.+\s)", re.M) # условные операторы terms = ('>', '<', '==', '!=', '>=', '<=') # параметры без значения listParNotVal = ("multiline", "dotall", "symbolic", "force", "mirror", "autoupdate", "protected") # Результат вычисления условия в заголовке headerTerm = True def __init__(self, templateName, text, comment=None, fileType=False, objVar=False, function=None, templateObj=None): self.body = text # Объект с переменными self.objVar = objVar # Параметры описанные в заголовке файла шаблона self.params = {} # некорректные параметры incorrectParams = [] # Поиск строки запустка (#!/bin/bash и.т. д) if comment or fileType != "bin": reExecRes = self.reExecStr.search(self.body) if reExecRes: self.execStr = self.body[reExecRes.start():reExecRes.end()] self.body = self.body[:reExecRes.start()] + \ self.body[reExecRes.end():] # Удаление Заголовка Calculate if comment: titleFirst = "Modified" # В случае текста XML if isinstance(comment, tuple) and len(comment) == 2: reCalcHeader = \ re.compile("\s*%s\s+%s.+\s+(.+\n)+%s\s?" \ % (comment[0], titleFirst, comment[1]), re.M | re.I) reS = reCalcHeader.search(self.body) if reS: self.body = self.body[:reS.start()] + self.body[reS.end():] else: reCalcHeader = re.compile( "\s*%s\-+\s+%s\s+%s.+\s+(%s.+\s+)+%s\-+\s?" \ % (comment, comment, titleFirst, comment, comment), re.M | re.I) reS = reCalcHeader.search(self.body) if reS: self.body = self.body[reS.end():] if fileType is not False: if fileType == "bin": self.params["format"] = fileType self.fileType = self._getType() self.typeAppend = self._getAppend() else: textLines = self.body.splitlines() if textLines: textLine = textLines[0] rePar = re.compile( "\s*#\s*calculate\s+\\\\?|\s*#\s*calculate\\\\?$", re.I) reP = rePar.search(textLine) if reP: reLns = re.compile(r"\A([^\\\n]*\\\n)+[^\n]*\n*", re.M) reLs = reLns.search(self.body) if reLs: reL = reLs paramLine = self.body[reP.end():reLs.end()] paramLine = paramLine.replace("\\", " ") else: reLn = re.compile("\n") reL = reLn.search(self.body) paramLine = textLine[reP.end():] if reL: self.body = self.body[reL.end():] else: self.body = "" paramLine = templateObj.applyFuncTemplate(paramLine, templateName) paramList = self.splitParLine(paramLine) if paramList: for i in paramList: foundTerm = False for term in self.terms: if term in i: foundTerm = True errorMsg = _("Incorrect template") + \ _( ": ") + templateName + "\n" + \ _( "template header not valid") + \ _(": ") + i if function: rezTerm = self._equalTerm(i, errorMsg, function) else: rezTerm = self._equalTerm(i, errorMsg) if not rezTerm: self.headerTerm = False break if not foundTerm: par = i.split("=") if len(par) == 1: if i in self.listParNotVal: self.params[i] = "True" else: if i.strip(): incorrectParams = {i} elif len(par) == 2: self.params[par[0]] = par[1] if par[0] == "env": try: importlib.import_module( "calculate.%s.variables" % par[1]) except (ImportError, AttributeError): self.headerTerm = False self.comment = self._getComment() self.fileType = self._getType() typeAppend = self._getAppend() if typeAppend: self.typeAppend = typeAppend else: self.headerTerm = False self.setError(_("incorrect header parameter: '%s'") \ % "append=%s" % self.params["append"]) if any(x in self.params for x in ('exec', 'run')): if 'exec' in self.params: self.execStr = "#!%s\n" % self.params['exec'] if 'run' in self.params: self.execStr = "#!%s\n" % self.params['run'] if "python" in self.execStr: self.execStr += "# -*- coding: utf-8 -*-\n" if not incorrectParams and self.params: incorrectParams = set(self.params.keys()) - set(self.allowParam) if incorrectParams: self.headerTerm = False self.setError(_("incorrect header parameter: '%s'") \ % " ".join(list(incorrectParams))) def _getType(self): """Выдать тип файла""" if "format" in self.params: return self.params["format"] else: return "raw" def _getAppend(self): """Выдать тип добавления файла""" if self.params.has_key("append"): if self.params["append"] in self._fileAppend: return self.params["append"] else: return False else: if self.fileType != "raw" and self.fileType != "bin" and \ self.fileType != "": if "format" in self.params and self.params["format"] in \ ("patch", "diff", "dconf"): self.params["append"] = "patch" return "patch" self.params["append"] = "join" return "join" self.params["append"] = "replace" return "replace" def _getComment(self): """Выдать символ комментария файла""" if self.params.has_key("comment"): if self.params["comment"] in ("xml", "XML"): return "" else: return self.params["comment"] else: return False class dirHeader(_terms): """Обработка заголовков шаблонов директорий """ # Допустимые параметры заголовка allowParam = ["append", "chmod", "chown", "name", "path", "autoupdate", "module", "env", "merge", "postmerge"] # Тип вставки шаблона typeAppend = "" # Возможные типы вставки шаблонов _fileAppend = "join", "remove", "skip", "clear" # условные операторы terms = ('>', '<', '==', '!=', '>=', '<=') # параметры без значения listParNotVal = ("symbolic", "force", "autoupdate") # Результат вычисления условия в заголовке headerTerm = True def __init__(self, templateName, text, objVar=False, function=None): self.body = text # Объект с переменными self.objVar = objVar # Параметры описанные в заголовке файла шаблона self.params = {} # некорректные параметры incorrectParams = set([]) textLines = text.splitlines() flagErrorBody = False if textLines: textLine = textLines[0] rePar = re.compile( "\s*#\s*calculate\s+\\\\?|\s*#\s*calculate\\\\?$", re.I) reP = rePar.search(textLine) if reP: reLns = re.compile(r"\A([^\\\n]*\\\n)+[^\n]*\n*", re.M) reLs = reLns.search(text) if reLs: reL = reLs paramLine = text[reP.end():reLs.end()] paramLine = paramLine.replace("\\", " ") else: reLn = re.compile("\n") reL = reLn.search(text) paramLine = textLine[reP.end():] if reL: self.body = text[reL.end():] else: self.body = "" if self.body.strip(): self.headerTerm = False self.setError(_("incorrect text in the template: '%s'") \ % self.body) flagErrorBody = True if not flagErrorBody: paramList = self.splitParLine(paramLine) if paramList: for i in paramList: foundTerm = False for term in self.terms: if term in i: foundTerm = True errorMsg = (_("Incorrect template") + _(": ") + templateName + "\n" + _( "template header not valid") + _( ": ") + i) if function: rezTerm = self._equalTerm(i, errorMsg, function) else: rezTerm = self._equalTerm(i, errorMsg) if not rezTerm: self.headerTerm = False break if not foundTerm: par = i.split("=") if len(par) == 1: if i in self.listParNotVal: self.params[i] = "True" else: if i.strip(): incorrectParams = {i} elif len(par) == 2: self.params[par[0]] = par[1] if par[0] == "env": try: importlib.import_module( "calculate.%s.variables" % par[1]) except (ImportError, AttributeError): self.headerTerm = False self.objVar.defaultModule = \ self.params['env'] typeAppend = self._getAppend() if typeAppend: self.typeAppend = typeAppend else: self.headerTerm = False self.setError(_("incorrect header parameter: '%s'") \ % "append=%s" % self.params["append"]) if not flagErrorBody: if not incorrectParams: incorrectParams = set(self.params.keys()) - set(self.allowParam) if incorrectParams: self.headerTerm = False self.setError(_("incorrect header parameter: '%s'") \ % " ".join(list(incorrectParams))) def _getAppend(self): """Выдать тип добавления директории""" if self.params.has_key("append"): if self.params["append"] in self._fileAppend: return self.params["append"] else: return False else: return "join" class blocText(object): """Разбиваем текст на блоки""" def splitTxtToBloc(self, text, openTxtBloc, closeTxtBloc, commentTxtBloc, sepField): """Делит текст на блоки (без заголовков) openTxtBloc - регулярное выражение для начала блока closeTxtBloc - регулярное выражение для конца блока commentTxtBloc - регулярное выражение - комментарий возвращает блоки текста """ blocs = [] level = 0 # Нахождение нескольких блоков в строке # разделители линий, разделителями могут быть ("","\n") sepsLines = [] # линии txtLines = [] # Исходные строки txtLinesSrc = text.splitlines() for line in txtLinesSrc: lineTmpA = line closeBl = False txtLinesTmp = [] commentSpl = commentTxtBloc.split(line) textLine = None commentLine = None if commentSpl[0].strip(): closeBl = True if len(commentSpl) > 1: commentBl = commentTxtBloc.search(line) textLine = commentSpl[0] commentLine = line[commentBl.start(0):] lineTmpA = textLine while closeBl: closeBl = sepField.search(lineTmpA) if closeBl: lineTmpB = lineTmpA[closeBl.end(0):] txtLinesTmp.append(lineTmpA[:closeBl.end(0)]) lineTmpA = lineTmpB if lineTmpA.strip(): txtLinesTmp.append(lineTmpA) # Если есть значение и комментарий в строке if textLine is not None: for l in txtLinesTmp: txtLines.append(l) sepsLines.append("") if not txtLinesTmp: txtLines.append(textLine) sepsLines.append("") txtLines.append(commentLine) sepsLines.append("\n") # Если есть несколько блоков в строке elif len(txtLinesTmp) > 1 and txtLinesTmp[1].strip(): lenTmpLines = len(txtLinesTmp) for l in range(lenTmpLines): txtLines.append(txtLinesTmp[l]) if l == lenTmpLines - 1: sepsLines.append("\n") else: sepsLines.append("") # Cтрока не преобразована else: txtLines.append(line) sepsLines.append("\n") # разбивание на блоки z = 0 bl = "" for i in txtLines: if commentTxtBloc.split(i)[0].strip() and openTxtBloc.search(i): level += len(openTxtBloc.split(i)) - 1 if commentTxtBloc.split(i)[0].strip() and closeTxtBloc.search(i): level -= len(closeTxtBloc.split(i)) - 1 bl += i + sepsLines[z] if level == 0: if bl: blocs.append(bl) bl = "" z += 1 # cоздание блоков с элементами не входящими в блоки realBlocs = [] z = 0 bl = "" for i in blocs: txtLines = i.splitlines() if len(txtLines) > 0: line = txtLines[0] else: line = i if commentTxtBloc.split(i)[0].strip() and openTxtBloc.search(line): if bl: realBlocs.append(bl) bl = "" realBlocs.append(i) else: bl += i z += 1 if bl: realBlocs.append(bl) if level == 0: if text and text[-1] != "\n": tmpBlocs = realBlocs.pop() tmpBlocs = tmpBlocs[:-1] realBlocs.append(tmpBlocs) return realBlocs else: return [] def findArea(self, text, reTextHeader, reTextArea, numGroupArea=0): """ Делит текст на области (с заголовками) reTextHeader - регулярное выражение для заголовка области reTextArea - регулярное выражение для всей области numGroupArea - номер групы результата поиска по регулярному выражению по всей области возвращает два списка: первый - заголовки, второй - тела областей без заголоков """ # Заголовки областей headersArea = [] # Тексты областей без заголовков textBodyArea = [] r = reTextArea.search(text) if not r: headersArea.append("") textBodyArea.append(text) return headersArea, textBodyArea txtWr = text while r: textArea = r.group(numGroupArea) txtSpl = txtWr.split(textArea) area = txtSpl[0] txtWr = txtSpl[1] if area: headersArea.append("") textBodyArea.append(area) res = reTextHeader.search(textArea) header = textArea[:res.end()] body = textArea[res.end():] headersArea.append(header) textBodyArea.append(body) if txtWr: r = reTextArea.search(txtWr) else: r = False if txtWr: headersArea.append("") textBodyArea.append(txtWr) return headersArea, textBodyArea def findBloc(self, text, captionTxtBloc, bodyTxtBloc): """ Делит текст на блоки (с заголовками) captionTxtBloc - регулярное выражение для заголовка блока bodyTxtBloc - регулярное выражение для тела блока возвращает два списка: первый - заголовки, второй - тела блоков """ # Заголовки блоков headersTxt = [] # Тексты блоков blocsTxt = [] r = captionTxtBloc.search(text) if r: headersTxt.append(r.group(0)) txtSpl = text.partition(r.group(0)) blocTxt = txtSpl[0] txtWr = txtSpl[2] rb = bodyTxtBloc.search(blocTxt) if not blocTxt: blocsTxt.append(blocTxt) if rb: blocsTxt.append(rb.group(0)) while r: r = captionTxtBloc.search(txtWr) if r: headersTxt.append(r.group(0)) txtSpl = txtWr.partition(r.group(0)) blocTxt = txtSpl[0] txtWr = txtSpl[2] rb = bodyTxtBloc.search(blocTxt) if rb: blocsTxt.append(rb.group(0)) else: blocsTxt.append(txtWr) if headersTxt and blocsTxt: if len(headersTxt) > len(blocsTxt): blocsTxt.insert(0, "") elif len(headersTxt) < len(blocsTxt): headersTxt.insert(0, "") if len(headersTxt) != len(blocsTxt): return False return headersTxt, blocsTxt else: return False class _file(_error): """ Класс для работы с файлами """ configMode = None def printWARNING(self, s): raise NotImplemented() def __init__(self): # Имя файла конфигурационного файла self.nameFileConfig = "" # Содержимое конфигурационного файла self.textConfig = "" # Имя файла шаблона self.nameFileTemplate = "" # Содержимое шаблона self.textTemplate = "" # Дескриптор файла шаблона self.F_TEMPL = None # Дескриптор файла конфигурационного файла self.F_CONF = None # тип запускаемого шаблона self.executeType = None # список скриптов на запуск self.queueExecute = [] def saveConfFile(self): """Записать конфигурацию""" if not self.textConfig: self.textConfig = self.textTemplate if self.F_CONF: try: self.F_CONF.truncate(0) self.F_CONF.seek(0) self.F_CONF.write(self.textConfig) except IOError: self.setError(_("unable to open the file:") + self.nameFileConfig) return False self.F_CONF.flush() return True elif self.executeType == "exec": processor = self.textConfig.partition("\n")[0] if processor.startswith("#!"): self.queueExecute.append((processor[2:], self.textConfig, self.nameFileTemplate)) else: self.setError(_("unable to execute '%s'") + self.textConfig) return False def openTemplFile(self, nameFileTemplate): """Открыть файл шаблона""" try: F_TEMPL = open(nameFileTemplate, "r") except IOError: self.setError(_("unable to open the file:") + nameFileTemplate) return False return F_TEMPL def closeTemplFile(self): if self.F_TEMPL: self.F_TEMPL.close() self.F_TEMPL = None def __closeOldFile(self): if self.F_CONF: self.F_CONF.close() self.F_CONF = None def __openConfFile(self, nameFileConfig): """Отктрыть конфигурационный файл""" try: if os.path.islink(nameFileConfig): # если ссылка то удаляем её os.unlink(nameFileConfig) F_CONF = open(nameFileConfig, "r+") except (IOError, OSError): try: if os.path.isdir(nameFileConfig): self.printWARNING(_("unable to open the directory as file:") + nameFileConfig) return False F_CONF = open(nameFileConfig, "w+") except (IOError, OSError): self.setError(_("unable to open the file:") + nameFileConfig) return False return F_CONF def openFiles(self, nameFileTemplate, nameFileConfig, typeFormat=None, newBuffer=None): """Открывает шаблон и конфигурационный файл""" self.textConfig = "" self.textTemplate = "" self.closeFiles() self.F_TEMPL = None self.F_CONF = None self.nameFileConfig = os.path.abspath(nameFileConfig) self.nameFileTemplate = os.path.abspath(nameFileTemplate) self.F_TEMPL = self.openTemplFile(self.nameFileTemplate) if not self.executeType and typeFormat not in ("diff", "dconf"): self.F_CONF = self.__openConfFile(self.nameFileConfig) if self.F_TEMPL and self.F_CONF: self.textTemplate = self.F_TEMPL.read() if self.configMode == T_NEWCFG: origConfigName = re.sub(r'/._cfg\d{4}_([^/]+)$', '/\\1', self.nameFileConfig) if newBuffer is None: self.textConfig = readFile(origConfigName) else: self.textConfig = newBuffer else: self.textConfig = self.F_CONF.read() def __del__(self): self.closeFiles() def closeFiles(self): """Закрытие файлов""" self.closeTemplFile() self.__closeOldFile() class utfBin(object): """Класс для преобразования в utf-8 преобразование бинарного или смеси бинарного и utf-8 кода в utf-8 и обратное преобразование методы класса encode и decode """ def _retUTF(self, char): byte = ord(char) if byte <= 127: return '_ch_', 1 elif byte <= 191: return '_nb_', 1 elif byte <= 223: return '_fb_', 2 elif byte <= 239: return '_fb_', 3 elif byte <= 247: return '_fb_', 4 else: return '_er_', 1 def _sumbUtf(self, symbols, lenTail): if not symbols: return False, 0 lenSymb = len(symbols) if lenSymb >= 4: l = 4 elif lenSymb >= 3: l = 3 elif lenSymb >= 2: l = 2 else: if symbols[0] == '_ch_': return True, 1 else: return False, 1 result = False i_ = 0 for i in range(l): i_ = i if i == 0 and symbols[i] != '_fb_': break elif i > 0 and symbols[i] != '_nb_': break if lenTail > 1 and lenTail != i_: return False, 1 if i_ > 0: result = True return result, i_ def _intToChar(self, x): he = hex(x)[2:] ret = None exec ("ret = '\\x%s'" % he) return ret def _hexToChar(self, he): ret = None exec ("ret = '\\x%s'" % he) return ret def encode(self, text): """Кодирует смешанный формат в UTF-8""" ind = 0 utf = [] lenUtf = [] indErr = [] i = 0 for ch in text: r, l = self._retUTF(ch) utf.append(r) lenUtf.append(l) i += 1 while 1: if utf[ind] == '_fb_': res, l = self._sumbUtf(utf[ind:], lenUtf[ind]) if res is False: indErr.append(ind) if l > 0: ind += l if ind >= len(utf): break else: if utf[ind] != '_ch_': indErr.append(ind) ind += 1 if ind >= len(utf): break if indErr: lenIndErr = len(indErr) block = [] blocks = [] if lenIndErr > 1: i = 1 while 1: if i == 1: block.append(indErr[i - 1]) if indErr[i] - indErr[i - 1] == 1: block.append(indErr[i]) else: if block: blocks.append(block) block = [indErr[i]] i += 1 if i >= lenIndErr: break else: block.append(indErr[0]) if block: blocks.append(block) listErr = [] for block in blocks: string = "" last_elem = None for elem in block: string += hex(ord(text[elem]))[-2:] last_elem = elem if last_elem is not None: listErr.append((block[0], "__hex__?%s?__hex__" % string, last_elem)) textOut = text deltaInd = 0 for erEl in listErr: startInd = erEl[0] + deltaInd endInd = erEl[2] + 1 + deltaInd textOut = textOut[:startInd] + erEl[1] + textOut[endInd:] deltaInd += len(erEl[1]) - (erEl[2] - erEl[0] + 1) # if i == 1: # break # i += 1 return textOut def decode(self, text): """Декодирует UTF-8 в смешанный формат""" varStart = "__hex__\?" varEnd = "\?__hex__" # -1 Это экранирование '?' которое тоже считается deltVarStart = len(varStart) - 1 deltVarEnd = len(varEnd) - 1 reVar = re.compile("%s[a-f0-9]+%s" % (varStart, varEnd), re.M) resS = reVar.search(text) textTemplateTmp = text while resS: mark = textTemplateTmp[resS.start():resS.end()] hexString = mark[deltVarStart:-deltVarEnd] i = 0 stringInsert = "" hexCode = "" for ch in hexString: if i >= 1: hexCode += ch stringInsert += self._hexToChar(hexCode) hexCode = "" i = 0 else: hexCode += ch i += 1 textTemplateTmp = textTemplateTmp.replace(mark, stringInsert) resS = reVar.search(textTemplateTmp) return textTemplateTmp class templateFormat(object): """Методы получения классов и объектов форматов шаблонов""" # Импортированные классы поддерживаемых форматов шаблонов importFormats = {} newObjProt = {} def createNewClass(self, name, bases, attrs=None): raise NotImplemented() def getClassObj(self, nameClassTemplate): """Создает класс шаблона по имени""" if nameClassTemplate in self.importFormats: classFormat = self.importFormats[nameClassTemplate] else: try: classFormat = getattr(__import__("calculate.lib.format.%s" % nameClassTemplate, globals(), locals(), [nameClassTemplate]), nameClassTemplate) except (ImportError, AttributeError): # Создаем объект из self.newObjProt с помощью # метаклассов if nameClassTemplate in self.newObjProt: # Прототип класса nameProt = self.newObjProt[nameClassTemplate] if nameProt in self.importFormats: classProt = self.importFormats[nameProt] else: try: classProt = getattr( __import__("calculate.lib.format.%s" % nameProt, globals(), locals(), [nameProt]), nameProt) except (ImportError, AttributeError): return False self.importFormats[nameProt] = classProt classFormat = self.createNewClass(nameClassTemplate, (classProt,)) else: return False self.importFormats[nameClassTemplate] = classFormat return classFormat def getFormatObj(self, formatTemplate, textTemplate): """Создание объекта формата шаблона. Объект создается на основании формата шаблона и текста шаблона""" classFormat = self.getClassObj(formatTemplate) if callable(classFormat): return classFormat(textTemplate) else: return False class _shareTemplate(object): """Общие аттрибуты для классов шаблонов""" # Метка начала переменной varStart = "#-" # Метка конца переменной varEnd = "-#" _deltVarStart = len(varStart) _deltVarEnd = len(varEnd) objVar = None def getDataUser(self, groupsInfo=False): """Получить информацию о пользователе""" userName = self.objVar.Get("ur_login") if not userName: userName = "root" import pwd try: pwdObj = pwd.getpwnam(userName) uid = pwdObj.pw_uid gid = pwdObj.pw_gid homeDir = self.objVar.Get('ur_home_path') except: raise TemplatesError(_("User %s not found") % str(userName)) if groupsInfo: import grp try: groupName = grp.getgrgid(gid).gr_name except: raise TemplatesError(_("Group ID %s not found") % str(gid)) groupsNames = map(lambda x: x.gr_name, filter(lambda x: userName in x.gr_mem, grp.getgrall())) groupsNames = [groupName] + groupsNames return uid, gid, homeDir, groupsNames return uid, gid, homeDir class templateFunction(_error, _warning, _shareTemplate, _shareTermsFunction): """Класс для функций шаблонов""" # Словарь установленных программ {"имя программы":[версии]} installProg = {} # Cписок просканированных категорий установленных программ installCategory = [] # Флаг сканирования всех установленных программ flagAllPkgScan = False # Список названий функций шаблона namesTemplateFunction = [] # Словарь {название функции шаблона: функция шаблона, ...} templateFunction = {} # Регулярное выражение для сложения sNum = re.compile("\-[^\-\+]+|[^\-\+]+") # Регулярное выражение для умножениея и деления sMD = re.compile("[^\-\+\*/]+") # директория установленных программ basePkgDir = "/var/db/pkg" # кэш для проверки наличия пакета в портежах cachePortdir = {} # стек глобальных переменных stackGlobalVars = [] # регулярное выражение для поиска версии reFindVer = re.compile("(?<=-)(?:\d+)(?:(?:\.\d+)*)" "(?:[a-z]?)(?:(?:_(?:pre|p|beta|alpha|rc)\d*)*)" "(?:-r\d+)?$") reEmptyLoad = re.compile("^\s*$|^\s*;|^\s*#") # Имя обрабатываемого шаблона nameTemplate = "" # Текст функции шаблона functText = "" # regular for discard sort number and version reData = re.compile(r"^(?:\d+-)?(.+?)(?:-(?:|always|\d+|\d(?:\d|\.|pre|_" "|-always|alpha|beta|pre|rc|[a-z][^a-z])*[a-z]?)(?:" "-r\d+)?)?$", re.S) currentAction = "merge" def printSUCCESS(self, s): raise NotImplemented() def printWARNING(self, s): raise NotImplemented() def printERROR(self, s): raise NotImplemented() @classmethod def get_pkgname_by_filename(cls, fn): fileName = os.path.split(fn)[1] if fileName == '.calculate_directory': parentDir = os.path.dirname(fn) parentDir, pkgName = os.path.split(parentDir) else: parentDir, pkgName = os.path.split(fn) category = os.path.split(parentDir)[1] # reg for discard version and sort number pkgName = cls.reData.search(pkgName).group(1) category = cls.reData.search(category).group(1) return "%s/%s" % (category, pkgName) currentBelong = "" alreadyInformed = [] def __init__(self, objVar): # Если не определен словарь функций шаблона # import services api if not self.templateFunction: # префикс функций шаблона pref = "func" # cписок [(название функции, функция), ...] dictFunc = filter(lambda x: x[0].startswith(pref) and \ hasattr(x[1], "__call__"), self.__class__.__dict__.items()) # удаляем у названия функции префикс и переводим остаток названия # в нижний регистр dictFunc = map(lambda x: (x[0][len(pref):].lower(), x[1]), dictFunc) # Формируем словарь функций шаблона self.templateFunction.update(dictFunc) # Формируем список функций шаблона for nameFunction in self.templateFunction.keys(): self.namesTemplateFunction.append(nameFunction) # Объект хранения переменных self.objVar = objVar self._reFunc = re.compile("%s%s%s" % (self.varStart, self._reFunctionText, self.varEnd), re.M) # Аттрибуты для функции шаблона ini() # Первоначальный словарь переменных для ini() self.prevDictIni = {} # Текущий словарь переменных для ini() self.currDictIni = {} # Время модификации конфигурационного файла для ini() self.timeIni = -1 self.recalculateBaseDir() # Словарь времен модификации env файлов self.timeConfigsIni = {} # Словарь хранения переменых полученных функцией env() из env файлов self.valuesVarEnv = {} # Словарь хранения опций для функции info() self.optionsInfo = {} # файл параметров сервисов envFile = self.objVar.Get("cl_env_server_path") # объект конвертирования из старого remote env файла self.convObj = False if os.access(envFile, os.R_OK): self.convObj = False elif os.access("/var/calculate/remote/calculate.env", os.R_OK): from convertenv import convertEnv self.convObj = convertEnv() def recalculateBaseDir(self): """Recalculate basedir and homedir""" # Директория другой системы self._chrootDir = self.objVar.Get("cl_chroot_path") if self._chrootDir != '/': # Изменение директории к базе пакетов self.basePkgDir = pathJoin(self._chrootDir, self.basePkgDir) self.basePkgDir = os.path.normpath(self.basePkgDir) # Базовая директория переноса шаблонов "/mnt/calculate" или "/" и.т.д self._baseDir = pathJoin(self._chrootDir, self.objVar.Get("cl_root_path")) self._baseDir = os.path.normpath(self._baseDir) self.uid, self.gid, self.homeDir, self.groups = \ self.getDataUser(groupsInfo=True) # Домашняя директория, плюс базовая директория self.homeDir = pathJoin(self._baseDir, self.homeDir) # path to configuration file for ini() function # if action is desktop configuration, then path in user directory # else config file place in /etc/calculate if self.objVar.Get('cl_action') == "desktop": self.pathConfigIni = os.path.join(self.homeDir, ".calculate") self.modeConfigIni = 0640 else: self.pathConfigIni = pathJoin(self._chrootDir, '/etc/calculate') self.modeConfigIni = 0644 self.fileConfigIni = os.path.join(self.pathConfigIni, "ini.env") # TODO: debug block # print "DEBUG", self.fileConfigIni def equalTerm(self, term, localVars): """Метод для вычисления выражения""" terms = self.sNum.findall(term) if terms: strNumers = [] for n in terms: strNum = n.strip() if "*" in strNum or "/" in strNum: strNum = self.multAndDiv(strNum, localVars) num = 0 try: num = int(strNum) except ValueError: minus = False if strNum[:1] == "-": minus = True strNum = strNum[1:] if localVars.has_key(strNum): try: num = int(localVars[strNum]) except ValueError: raise self.raiseErrTemplate( _("error: variable %s is not integer") % str(strNum)) elif self.objVar.exists(strNum): try: num = int(self.objVar.Get(strNum)) except ValueError: raise self.raiseErrTemplate( _("error: variable %s is not integer") % str(strNum)) else: raise self.raiseErrTemplate( _("error: local variable %s not defined") % str(strNum)) if minus: num = -num strNumers.append(num) return sum(strNumers) raise self.raiseErrTemplate(_("error: template term %s, incorrect data") \ % str(term)) def multAndDiv(self, term, localVars): """Метод для умножения и деления""" termTmp = term varsLocal = self.sMD.findall(term) for var in varsLocal: flagVarTxt = True try: int(var) except ValueError: flagVarTxt = False if flagVarTxt: continue varReplace = str(self.equalTerm(var, localVars)) termTmp = termTmp.replace(var, varReplace) ret = eval(termTmp) return ret def funcSum(self, funArgv, resS, localVars, textTemplateTmp, nameTemp): """Функция шаблона, вычисляет функцию sum()""" terms = funArgv.replace(" ", "").split(",") # Название локальной переменной nameLocVar = terms[0] if not localVars.has_key(nameLocVar): localVars[nameLocVar] = 0 if len(terms) == 2: if terms[1].strip(): localVars[nameLocVar] = self.equalTerm(terms[1], localVars) replace = str(localVars[nameLocVar]) else: replace = "" textTemplateTmp = textTemplateTmp[:resS.start()] + replace + \ textTemplateTmp[resS.end():] elif len(terms) == 3: if terms[1].strip(): replaceInt = self.equalTerm(terms[1], localVars) replace = str(replaceInt) else: replace = "" localVars[nameLocVar] = self.equalTerm(terms[2], localVars) textTemplateTmp = textTemplateTmp[:resS.start()] + replace + \ textTemplateTmp[resS.end():] else: raise self.raiseErrTemplate() return textTemplateTmp def funcExists(self, funArgv, resS, localVars, textTemplateTmp, nameTemp): """Функция шаблона exists(), проверяет существование файла, если существует выдает '1' если второй параметр root, то проверка осуществляется от корня. """ terms = map(lambda x: x.strip(), funArgv.split(",")) if len(terms) > 2: raise self.raiseErrTemplate() fileName = terms[0] flagNotRootFS = True if len(terms) == 2: if terms[1] == "root": flagNotRootFS = False else: raise self.raiseErrTemplate( _("The second argument of the function is not 'root'")) if fileName[0] == "~": # Получаем директорию пользователя fileName = os.path.join(self.homeDir, fileName.partition("/")[2], "")[:-1] elif fileName[0] != "/": raise self.raiseErrTemplate(_("wrong path '%s'") % fileName) else: if flagNotRootFS: fileName = pathJoin(self._baseDir, fileName) replace = "" if os.path.exists(fileName): replace = "1" textTemplateTmp = textTemplateTmp[:resS.start()] + replace + \ textTemplateTmp[resS.end():] return textTemplateTmp def funcLoad(self, funArgv, resS, localVars, textTemplateTmp, nameTemp): """Функция шаблона load(), если файл существует читает из файла локальную переменную если один параметр - выводит значение локальной переменной """ terms = funArgv.split(",") if terms: lenTerms = len(terms) if not terms[0].strip() or \ (lenTerms == 2 and not terms[1].strip()) or \ (lenTerms == 3 and not terms[2].strip()) or \ lenTerms > 3: raise self.raiseErrTemplate() else: raise self.raiseErrTemplate() flagNotRootFS = True if lenTerms == 3: if terms[2] == "root": flagNotRootFS = False else: raise self.raiseErrTemplate( _("The third argument of the function is not 'root'")) if lenTerms >= 2: if not terms[0] in ["ver", "num", "char", "key", "empty"]: raise self.raiseErrTemplate( _("the first argument of the function is neither 'ver'" " or 'num' or 'char' or 'empty'")) if lenTerms == 1: fileName = terms[0].strip() else: fileName = terms[1].strip() # Если домашняя директория if fileName[0] == "~": # Получаем директорию пользователя fileName = os.path.join(self.homeDir, fileName.partition("/")[2], "")[:-1] elif fileName[0] != "/": raise self.raiseErrTemplate(_("wrong path '%s'") % fileName) else: if flagNotRootFS: fileName = pathJoin(self._baseDir, fileName) replace = "" if os.path.exists(fileName): replace = readFile(fileName).strip() if replace and lenTerms >= 2 and terms[0] == "empty": replace = "\n".join(filter(lambda x: not self.reEmptyLoad.search(x), replace.split("\n"))) if not replace and lenTerms >= 2 and terms[0] in ["ver", "num"]: replace = "0" textTemplateTmp = textTemplateTmp[:resS.start()] + replace + \ textTemplateTmp[resS.end():] return textTemplateTmp def sharePkg(self, pkgs, force=False): """ Update packages from pkgs list """ for pkgname, category, ver, slot in pkgs: fullname = "%s/%s" % (category, pkgname) if not fullname in self.installProg or \ type(self.installProg[fullname]) != dict: self.installProg[fullname] = self.installProg[pkgname] = {} if force or not slot in self.installProg[fullname]: self.installProg[fullname][slot] = ver return self.installProg def getInstallPkgGentoo(self, category=""): pkgs = [] filterFunc = lambda x: "SLOT" == x def getFilesDir(pkgs, dirname, names): for nameFile in filter(filterFunc, names): absNameFile = os.path.join(dirname, nameFile) category, spl, pkgname = dirname.rpartition('/') dbpkg, spl, category = category.rpartition('/') slot = readFile(absNameFile).strip() pkgname, spl, rev = pkgname.rpartition("-") if rev.startswith('r'): pkgname, spl, ver = pkgname.rpartition("-") ver = "%s-%s" % (ver, rev) else: ver = rev pkgs.append((pkgname, category, ver, slot)) return True os.path.walk(os.path.join(self.basePkgDir, category), getFilesDir, pkgs) return self.sharePkg(pkgs) def pkg(self, nameProg, slot=None): if len(self.installProg) > 0: if type(self.installProg.values()[0]) != dict: self.installProg.clear() self.getInstallPkgGentoo() if nameProg in self.installProg: versions = self.installProg[nameProg] if slot: return versions.get(slot, "") if len(versions) == 1: return versions.values()[0] else: return versions[max(versions.keys(), key=getTupleVersion)] else: return "" def funcPkg(self, funArgv, resS, localVars, textTemplateTmp, nameTemp): """Функция шаблона pkg(), выдает номер версии программы""" # Название программы nameProg = funArgv.replace(" ", "") if not nameProg: nameProg = self.get_pkgname_by_filename(self.nameTemplate) # Замена функции в тексте шаблона if "/" in nameProg: category, spl, nameProg = nameProg.partition("/") nameProg, spl, uses = nameProg.partition('[') nameProg, spl, slot = nameProg.partition(":") if uses: uses = uses.rstrip("]") if not category in self.installCategory: self.getInstallPkgGentoo(category=category) self.installCategory.append(category) replace = self.pkg(nameProg, slot=slot or None) if replace and uses: pkg_use, pkg_iuse = getPkgUses("%s/%s" % (category, nameProg), replace, prefix=self.objVar.Get( 'cl_chroot_path')) for use in filter(None, uses.split(',')): if (use[0] == "-" and use[1:] in pkg_use or use[0] != "-" and use not in pkg_use): replace = "" break else: if not self.flagAllPkgScan: self.getInstallPkgGentoo() templateFunction.flagAllPkgScan = True nameProg, spl, slot = nameProg.partition(":") replace = self.pkg(nameProg, slot=slot) textTemplateTmp = textTemplateTmp[:resS.start()] + replace + \ textTemplateTmp[resS.end():] return textTemplateTmp def funcKernel(self, funArgv, resS, localVars, textTemplateTmp, nameTemp): """ Функция kernel(...), выдает значение опции конфига ядра (y,m,) """ terms = funArgv.replace(" ", "").split(",") if not terms[0].strip() or len(terms) != 1: raise self.raiseErrTemplate() kernel_opt = terms[0].upper() if kernel_opt.startswith("CONFIG_"): raise self.raiseErrTemplate( _("the option name should not starts with CONFIG_")) kernel_config = self.objVar.Get('install.os_install_kernel_config') find_str = "CONFIG_%s" % kernel_opt replace = "" for line in kernel_config: if find_str in line: if "%s=" % find_str in line: key, op, value = line.partition("=") replace = value.strip("'\"") break elif "%s is not set" % find_str in line: break textTemplateTmp = (textTemplateTmp[:resS.start()] + replace + textTemplateTmp[resS.end():]) return textTemplateTmp def funcGrep(self, funArgv, resS, localVars, textTemplateTmp, nameTemp): """ Функция grep (...), выдает значение из файла по регулярному выражению """ fname, op, regpattern = funArgv.replace(" ", "").partition(",") regpattern = regpattern.replace("(?\<", "(?<") regpattern = self._replace_hex(regpattern) if not fname or not regpattern: raise self.raiseErrTemplate() try: reg = re.compile(regpattern) except re.error: raise self.raiseErrTemplate(_("Wrong regular expression")) if fname[0] == "~": # Получаем директорию пользователя fname = os.path.join(self.homeDir, fname.partition("/")[2], "")[:-1] fname = pathJoin(self.objVar.Get('cl_chroot_path'), fname) fileContent = readFile(fname) match_data = reg.search(fileContent) if match_data: md_groups = match_data.groups() if md_groups: replace = md_groups[0] or "" else: replace = match_data.group() else: replace = "" return ( textTemplateTmp[:resS.start()] + replace + textTemplateTmp[ resS.end():]) def funcRnd(self, funArgv, resS, localVars, textTemplateTmp, nameTemp): """Функция шаблона rnd(), выдает строку случайных символов первый аргумент: 'num' - числа, 'pas' - цифры и буквы 'uuid' - цифры и строчные буквы a-f второй аргумент: количество символов """ terms = funArgv.replace(" ", "").split(",") if not terms[0].strip() or \ (len(terms) == 2 and not terms[1].strip()) or \ len(terms) != 2: raise self.raiseErrTemplate() fArgvNames = {'num': string.digits, 'pas': string.ascii_letters + string.digits, 'uuid': string.ascii_lowercase[:6] + string.digits} if not terms[0] in fArgvNames: raise self.raiseErrTemplate( _("the first argument of the function must " "be 'num', 'pas' or 'uuid'")) try: lenStr = int(terms[1]) except (ValueError, IndexError, TypeError): lenStr = 0 raise self.raiseErrTemplate( _("the second argument of the function is not a number")) choiceStr = fArgvNames[terms[0]] replace = ''.join([random.choice(choiceStr) for i in xrange(lenStr)]) textTemplateTmp = textTemplateTmp[:resS.start()] + replace + \ textTemplateTmp[resS.end():] return textTemplateTmp def funcCase(self, funArgv, resS, localVars, textTemplateTmp, nameTemp): """Функция шаблона case(), выдает переменную в определенном регистре первый аргумент: 'upper' - верхний регистр, 'lower' - нижний регистр, 'capitalize' - первая буква в верхнем регистре второй аргумент: название переменной """ terms = funArgv.replace(" ", "").split(",") if not terms[0].strip() or \ (len(terms) == 2 and not terms[1].strip()) or len(terms) != 2: raise self.raiseErrTemplate() fArgvNames = ['upper', 'lower', 'capitalize'] if not terms[0] in fArgvNames: raise self.raiseErrTemplate(_("the first argument of the function" " is neither 'upper' or 'lower' or" " 'capitalize'")) try: strValue = self.objVar.Get(terms[1]) if not strValue: strValue = "" else: strValue = str(strValue) except: raise TemplatesError( _("error in template %s") % self.nameTemplate + "\n" + _("error: variable %s not found") % str(terms[1])) replace = "" strValue = _u(strValue) if terms[0] == 'upper': replace = strValue.upper() elif terms[0] == 'lower': replace = strValue.lower() elif terms[0] == 'capitalize': replace = strValue.capitalize() if replace: replace = replace.encode("UTF-8") textTemplateTmp = textTemplateTmp[:resS.start()] + replace + \ textTemplateTmp[resS.end():] return textTemplateTmp def funcIn(self, funArgv, resS, localVars, textTemplateTmp, nameTemp): """ Function in for check value in variable """ terms = funArgv.replace(" ", "").split(",") # Название локальной переменной nameLocVar = terms[0] try: value = self.objVar.Get(nameLocVar) if type(value) in (list, tuple): terms = terms[1:] if any(x in terms for x in value): replace = "1" else: replace = "" else: if value in terms[1:]: replace = "1" else: replace = "" except Exception: raise self.raiseErrTemplate(_("error: variable %s does not exist") \ % str(nameLocVar)) textTemplateTmp = textTemplateTmp[:resS.start()] + replace + \ textTemplateTmp[resS.end():] return textTemplateTmp def funcPush(self, funArgv, resS, localVars, textTemplateTmp, nameTemp): """локальная функция записывает значение переменной в стек глобальных переменных """ terms = funArgv.replace(" ", "").split(",") # Название локальной переменной nameLocVar = terms[0] value = "" if nameLocVar in localVars.keys(): flagFoundVar = True value = localVars[nameLocVar] else: try: value = self.objVar.Get(nameLocVar) flagFoundVar = True except Exception: flagFoundVar = False if flagFoundVar: # Если переменная существует if len(terms) == 1: self.stackGlobalVars.append(str(value)) else: raise self.raiseErrTemplate(_("error: variable %s exists") \ % str(nameLocVar)) else: # Если переменная не существует if len(terms) == 1: raise self.raiseErrTemplate( _("error: variable %s does not exist") \ % str(nameLocVar)) elif len(terms) == 2: value = terms[1].strip() self.stackGlobalVars.append(str(value)) localVars[nameLocVar] = value else: raise self.raiseErrTemplate() replace = "" textTemplateTmp = textTemplateTmp[:resS.start()] + replace + \ textTemplateTmp[resS.end():] return textTemplateTmp def funcPop(self, funArgv, resS, localVars, textTemplateTmp, nameTemp): """локальная функция получает значение из стека глобальных переменных и присваивает локальной переменной """ terms = funArgv.replace(" ", "").split(",") # Название локальной переменной nameLocVar = terms[0] if len(terms) == 1: if self.stackGlobalVars: localVars[nameLocVar] = self.stackGlobalVars.pop() else: raise self.raiseErrTemplate( _("error: global variables stack empty")) else: raise self.raiseErrTemplate() replace = "" textTemplateTmp = textTemplateTmp[:resS.start()] + replace + \ textTemplateTmp[resS.end():] return textTemplateTmp def funcPrint(self, funArgv, resS, localVars, textTemplateTmp, nameTemp): """ Вывод успешного сообщения """ self.printSUCCESS(_(funArgv)) textTemplateTmp = textTemplateTmp[:resS.start()] + \ textTemplateTmp[resS.end():] return textTemplateTmp def funcWarning(self, funArgv, resS, localVars, textTemplateTmp, nameTemp): """ Вывод сообщения с предупреждением """ self.printWARNING(_(funArgv)) textTemplateTmp = textTemplateTmp[:resS.start()] + \ textTemplateTmp[resS.end():] return textTemplateTmp def funcError(self, funArgv, resS, localVars, textTemplateTmp, nameTemp): """ Вывод сообщения с ошибкой """ self.printERROR(_(funArgv)) textTemplateTmp = textTemplateTmp[:resS.start()] + \ textTemplateTmp[resS.end():] return textTemplateTmp def getElogTimestamp(self): # Получаем время модификации конфигурационного файла curTime = self.getTimeFile(self.fileConfigIni) nameLocVar = "update.timestamp" if self.timeIni != curTime: # читаем переменные из файла self.prevDictIni = self.loadVarsIni(self.fileConfigIni) self.currDictIni = {} self.currDictIni.update(self.prevDictIni) self.timeIni = self.getTimeFile(self.fileConfigIni) if nameLocVar in self.currDictIni.keys(): if self.currDictIni[nameLocVar] is None: return 0 else: val = self.currDictIni[nameLocVar].encode("UTF-8") if val.isdigit(): return int(val) return 0 elogFile = '/var/log/emerge.log' @classmethod def getLastElog(cls): # get last timestamp (of ::completed emerge) entry = EmergeLog(EmergeLogPackageTask()).get_last_time() if entry: return entry.partition(":")[0] else: return "" def funcElog(self, funArgv, resS, localVars, textTemplateTmp, nameTemp): """Function for work with emerge.log""" funArgv = funArgv.strip() rePkg = re.compile(r'\) Merging (?:Binary )?\((\S+)::', re.S) replace = "" if funArgv: lastTimestamp = self.getElogTimestamp() for line in reversed(list(readLinesFile(self.elogFile))): timestamp, op, info = line.partition(':') if timestamp.isdigit() and lastTimestamp and \ int(timestamp) < lastTimestamp: break match = rePkg.search(info) if match and match.group(1).startswith(funArgv): pkgInfo = reVerSplitToPV(match.group(1)) if "{CATEGORY}/{PN}".format(**pkgInfo) == funArgv: replace = pkgInfo['PVR'] break else: replace = self.getLastElog() textTemplateTmp = textTemplateTmp[:resS.start()] + replace + \ textTemplateTmp[resS.end():] return textTemplateTmp def funcLivemenu(self, funArgv, resS, localVars, textTemplateTmp, nameTemp): def generateSubmenu(data): base_dn = self.objVar.Get('builder.cl_builder_flash_path') for id, label, iso, vmlinuz_orig, vmlinuz, initrd_orig, initrd, \ xorg, drivers, splash in data: if splash == "splashutils": splash = "splash=silent,theme:calculate console=tty1" elif splash == "plymouth": splash = "splash" else: splash = "verbose" yield ("{id};\n{label};\n/boot/{kernel};\n" "root=live iso-scan/filename={iso};\n" "/boot/{initrd};\n" "init=/linuxrc rd.live.squashimg=livecd.squashfs " "{splash} " "nodevfs quiet noresume;\n".format( id=id, label=label, kernel=vmlinuz, initrd=initrd, splash=splash, iso=iso[len(base_dn):] )) def generateXorg(data): for id, label, iso, vmlinuz_orig, vmlinuz, initrd_orig, initrd, \ xorg, drivers, splash in data: if xorg == "on": yield id def generateVideo(data): for id, label, iso, vmlinuz_orig, vmlinuz, initrd_orig, initrd, \ xorg, drivers, splash in data: if drivers == "on": yield id data = filter(None, self.objVar.Get('builder.cl_builder_image_data')) if funArgv == 'submenu': res = "\n".join(generateSubmenu(data)) elif funArgv == 'xorg': res = " ".join(generateXorg(data)) elif funArgv == 'video': res = " ".join(generateVideo(data)) else: res = "" textTemplateTmp = textTemplateTmp[:resS.start()] + res + \ textTemplateTmp[resS.end():] return textTemplateTmp def loadVarsIni(self, iniFileName): """ Читает файл fileName создает и заполняет переменные на основе этого файла Используеться совместно c funcIni """ localVarsIni = {} # получить объект ini файла config = ConfigParser(strict=False) config.read(iniFileName, encoding="utf-8") # получаем все секции из конфигурационного файла allsect = config.sections() if not allsect: return localVarsIni # Заполняем переменные для funcIni for sect in allsect: for name, valueVar in config.items(sect, raw=True): nameVar = "%s.%s" % (sect, name) localVarsIni[nameVar] = valueVar return localVarsIni def getTimeFile(self, fileName): # Получаем время модификации файла nameEnvFile = os.path.split(fileName)[1] if nameEnvFile in self.timeConfigsIni: return self.timeConfigsIni[nameEnvFile] return 0 def funcWallpaper(self, funArgv, resS, localVars, textTemplateTmp, nameTemp): """ Получить наиболее близкое к заданному разрешение из списка обоев """ terms = funArgv.replace(" ", "").split(",") if len(terms) != 2: raise self.raiseErrTemplate(_("funtion takes only two parameters")) resol, wpath = terms if not resol: resol = "1024x768" re_resol = re.compile("^(\d+)x(\d+)$") resol = re_resol.match(resol) if not resol: raise self.raiseErrTemplate( _("the first parameter must be the resolution")) re_resol = re.compile(".*?(\d+)x(\d+).*") wpath = pathJoin(self._baseDir, wpath) res = map(lambda x: (int(x.group(1)), int(x.group(2)), x.group()), filter(None, map(re_resol.search, listDirectory(wpath)))) width = int(resol.group(1)) height = int(resol.group(2)) gep = sqrt(height ** 2 + width ** 2) k = float(width) / float(height) if res: # наиболее подходящее разрешение: # минимальная разность между пропорциями (отношение ширины к высоте) # минимальная разность между размерами (гепотенуза) near_res = min(res, key=lambda x: (abs(x[0] / float(x[1]) - k), abs(gep - sqrt( x[0] ** 2 + x[1] ** 2)))) replace = near_res[2] else: replace = "" textTemplateTmp = textTemplateTmp[:resS.start()] + replace + \ textTemplateTmp[resS.end():] return textTemplateTmp def funcIni(self, funArgv, resS, localVars, textTemplateTmp, nameTemp): """локальная функция записывает и считывает значение переменной из ini файла ~./calculate/ini.env """ # Создаем директорию if not os.path.exists(self.pathConfigIni): os.makedirs(self.pathConfigIni) os.chown(self.pathConfigIni, self.uid, self.gid) termsRaw = funArgv.split(",") flagFirst = True terms = [] for term in termsRaw: if flagFirst: terms.append(term.replace(" ", "")) flagFirst = False else: val = term.strip() # Флаг (не найдены кавычки) flagNotFoundQuote = True for el in ('"', "'"): if val.startswith(el) and val.endswith(el): terms.append(val[1:-1]) flagNotFoundQuote = False break if flagNotFoundQuote: if not val: terms.append(None) else: terms.append(val) # Название локальной переменной nameLocVar = terms[0] namesVar = nameLocVar.split(".") if len(namesVar) == 1: nameLocVar = "main.%s" % nameLocVar elif len(namesVar) > 2: raise self.raiseErrTemplate() replace = "" # Получаем время модификации конфигурационного файла curTime = self.getTimeFile(self.fileConfigIni) if len(terms) == 1: if self.timeIni != curTime: # читаем переменные из файла self.prevDictIni = self.loadVarsIni(self.fileConfigIni) self.currDictIni = {} self.currDictIni.update(self.prevDictIni) self.timeIni = self.getTimeFile(self.fileConfigIni) if nameLocVar in self.currDictIni.keys(): if self.currDictIni[nameLocVar] is None: replace = "" else: replace = self.currDictIni[nameLocVar].encode("UTF-8") elif len(terms) == 2: if self.timeIni != curTime: # читаем переменные из файла self.prevDictIni = self.loadVarsIni(self.fileConfigIni) self.currDictIni = {} self.currDictIni.update(self.prevDictIni) self.timeIni = self.getTimeFile(self.fileConfigIni) # Значение локальной переменной valueLocVar = terms[1] self.currDictIni[nameLocVar] = valueLocVar elif len(terms) == 3: if not terms[2] in ['url', 'purl', 'unicode']: raise self.raiseErrTemplate( _("the third argument of the function is neither " "'url' or 'purl' or 'unicode'")) if terms[1]: raise self.raiseErrTemplate() if self.timeIni != curTime: # читаем переменные из файла self.prevDictIni = self.loadVarsIni(self.fileConfigIni) self.currDictIni = {} self.currDictIni.update(self.prevDictIni) self.timeIni = self.getTimeFile(self.fileConfigIni) if nameLocVar in self.currDictIni.keys(): unicodeValue = self.currDictIni[nameLocVar] if unicodeValue is None: unicodeValue = "" if terms[2] in ('url', 'purl'): replace = unicodeValue.encode("UTF-8"). \ __repr__()[1:-1].replace('\\x', '%'). \ replace(' ', '%20') if terms[2] == 'purl': replace = replace.replace('/', '%2f') elif terms[2] == 'unicode': replace = unicodeValue.__repr__()[2:-1] else: raise self.raiseErrTemplate() textTemplateTmp = textTemplateTmp[:resS.start()] + replace + \ textTemplateTmp[resS.end():] return textTemplateTmp def _replace_hex(self, text): """ Заменить в строке комбинацию \\x00 на символ """ return re.sub(r'\\x([0-9a-fA-F]{2})', lambda x: chr(int(x.group(1), 16)), text) def funcReplace(self, funArgv, resS, localVars, textTemplateTmp, nameTemp): """локальная функция заменяет в значении переменной old на new replace(old, new, name_var_template) одинарные и двойные кавычки должны быть обязательно использованы в первых двух аргументах old и new "test\ntest" - преобразование строки (строка с переводом) 'test\ntest' - без преобразования (одна строка) """ def getStrArgv(terms): """Определяет в двойных или одинарных кавычках параметры Результат [(тип, аргумент),...] [("double", arg1).] """ listArgv = [] for term in terms: if term.startswith('"') and term.endswith('"'): replTerms = [(r"\'", "'"), (r'\"', '"'), (r'\n', '\n'), (r'\r', '\r'), (r'\t', '\t'), (r"\\", "\\")] textArgv = term[1:-1] for replTerm in replTerms: textArgv = textArgv.replace(*replTerm) textArgv = self._replace_hex(textArgv) listArgv.append(textArgv) elif term.startswith("'") and term.endswith("'"): listArgv.append(term[1:-1]) else: raise self.raiseErrTemplate() return listArgv terms = map(lambda x: x.strip(), funArgv.split(",")) if len(terms) != 3: raise self.raiseErrTemplate() listArgv = getStrArgv(terms[:2]) old = listArgv[0] new = listArgv[1] nameVar = terms[2] # Получаем значение переменной if nameVar in localVars: value = str(localVars[nameVar]) else: try: value = str(self.objVar.Get(nameVar)) except Exception: raise self.raiseErrTemplate( _("template variable '%s' not found") \ % str(nameVar)) replace = value.replace(old, new) textTemplateTmp = textTemplateTmp[:resS.start()] + replace + \ textTemplateTmp[resS.end():] return textTemplateTmp def funcEnv(self, funArgv, resS, localVars, textTemplateTmp, nameTemp): """Функция шаблона env(), выдает значение переменной из env файлов """ terms = funArgv.replace(" ", "").split(",") if len(terms) != 1: raise self.raiseErrTemplate() nameVar = terms[0] if nameVar in self.valuesVarEnv: replace = self.valuesVarEnv[nameVar] else: # Получаем значение из env файлов value = self.objVar.getIniVar(nameVar) if value is False: raise self.raiseErrTemplate(self.getError()) self.valuesVarEnv[nameVar] = value replace = value textTemplateTmp = textTemplateTmp[:resS.start()] + replace + \ textTemplateTmp[resS.end():] return textTemplateTmp def funcServer(self, funArgv, resS, localVars, textTemplateTmp, nameTemp): """Функция шаблона info(), выдает значение опций сервиса из /var/calculate/remote/calculate.env """ terms = funArgv.replace(" ", "").split(",") if len(terms) == 0 or len(terms) > 2: raise self.raiseErrTemplate() nameLocalVar = "" if len(terms) == 2: if not terms[1]: raise self.raiseErrTemplate() nameLocalVar = terms[1] textLine = terms[0] vals = textLine.split(".") if len(vals) != 2: raise self.raiseErrTemplate() if filter(lambda x: not x.strip(), vals): raise self.raiseErrTemplate() service, option = vals if not service or not option: raise self.raiseErrTemplate() if not self.optionsInfo: # файл /var/calculate/remote/server.env envFile = self.objVar.Get("cl_env_server_path") # получаем словарь всех информационных переменных if self.convObj: optInfo = self.convObj.convert() else: optInfo = self.objVar.getRemoteInfo(envFile) if optInfo is False: raise self.raiseErrTemplate() if optInfo: self.optionsInfo = optInfo replace = '' if service in self.optionsInfo and option in self.optionsInfo[service]: value = self.optionsInfo[service][option] if nameLocalVar: localVars[nameLocalVar] = value else: replace = value elif nameLocalVar: localVars[nameLocalVar] = "" textTemplateTmp = textTemplateTmp[:resS.start()] + replace + \ textTemplateTmp[resS.end():] return textTemplateTmp def funcGroups(self, funArgv, resS, localVars, textTemplateTmp, nameTemp): """Функция шаблона groups(), проверяет нахождение пользователя в группах, если находится выдает '1' """ terms = map(lambda x: x.strip(), funArgv.split(",")) groupNames = set(terms) userGroups = set(self.groups) replace = "" if groupNames & userGroups: replace = "1" textTemplateTmp = textTemplateTmp[:resS.start()] + replace + \ textTemplateTmp[resS.end():] return textTemplateTmp def funcBelong(self, funArgv, resS, localVars, textTemplateTmp, nameTemp): self.printWARNING(_("Function '{funcname}' used by {template} " "is deprecated and will be removed in the future" ).format(funcname="belong", template=nameTemp)) replace = "" return textTemplateTmp[:resS.start()] + replace + \ textTemplateTmp[resS.end():] def funcMerge(self, funArgv, resS, localVars, textTemplateTmp, nameTemp): """ Belong function use value in first arg and compare it for all values in cl_merge_pkg. If cl_merge_pkg empty or first arg <=cl_belogn_pkg then "1" else "" """ def uniq_warning(message): hashMessage = hashlib.md5(message).digest() if not hashMessage in self.alreadyInformed: self.printWARNING(message) self.alreadyInformed.append(hashMessage) def check_skip(pkgName): skip_data_varname = 'cl_setup_skip_%s' % self.currentAction skip_data = self.objVar.Get(skip_data_varname) pass_location = self.objVar.Get('cl_pass_location') if any(skip_data): for data in skip_data: if len(data) != 2: uniq_warning( _("Wrong entry '{data}' for {var_name}").format( data=",".join(data), var_name=skip_data_varname)) else: pkg, location = data if fnmatch(pkgName, pkg) and (location == "*" or location == pass_location): return True return False terms = funArgv.replace(" ", "").split(",") if len(terms) != 1: raise self.raiseErrTemplate() funcPkg = terms[0] if not funcPkg: funcPkg = self.get_pkgname_by_filename(self.nameTemplate) self.currentBelong = funcPkg if self.objVar.Get('cl_action') == 'patch': if funcPkg == "%s/%s" % ( self.objVar.Get('core.cl_core_pkg_category'), self.objVar.Get('core.cl_core_pkg_name')): replace = self.objVar.Get('core.cl_core_pkg_version') if check_skip(funcPkg): replace = "" else: replace = "" else: pkg = self.objVar.Get("cl_merge_pkg") replace = "" if pkg: if funcPkg in pkg: replace = "1" else: replace = "1" if replace == "1" and check_skip(funcPkg): replace = "" textTemplateTmp = textTemplateTmp[:resS.start()] + replace + \ textTemplateTmp[resS.end():] return textTemplateTmp def funcList(self, funArgv, resS, localVars, textTemplateTmp, nameTemp): """Функция шаблона list(). Если первый аргумент является именем локальной или глобальной переменной и значение переменной является списком, выдает элемент списка по второму аргументу индексу. Первый элемент имеет индекс 0 """ terms = funArgv.replace(" ", "").split(",") # У функции должно быть два аргумента if len(terms) != 2: raise self.raiseErrTemplate() # Название локальной или глобальной переменной nameLocVar = terms[0] strIndex = terms[1] try: try: intIndex = int(strIndex) except ValueError: raise TemplatesError(_("'%s' is not a number") % strIndex) if nameLocVar in localVars.keys(): value = localVars[nameLocVar] else: try: value = self.objVar.Get(nameLocVar) except Exception: raise TemplatesError(_("error: variable %s does not exist") % str(nameLocVar)) if not type(value) in (list, tuple): # Значение переменной не список или кортеж raise TemplatesError(_("value of %s is not a list or a tuple") % str(nameLocVar)) if len(value) > intIndex: replace = str(value[intIndex]) else: replace = "" return textTemplateTmp[:resS.start()] + replace + \ textTemplateTmp[resS.end():] except TemplatesError as e: raise self.raiseErrTemplate(str(e)) def funcDisk(self, funArgv, resS, localVars, textTemplateTmp, nameTemp): """Функция шаблона disk(). Первый аргумент ищется в значении переменной os_disk_install (значение os_install_disk_mount - список точек монтирования при установке) второй аргумент используется для поиска в переменной os_disk_второй_аргумент (значение os_disk_второй_аргумент - список) В os_install_disk_mount ищется первый аргумент, находим его индекс результат - элемент cписка из os_disk_второй_аргумент с этим индексом """ terms = funArgv.replace(" ", "").split(",") # У функции должно быть два аргумента if len(terms) != 2: raise self.raiseErrTemplate() # Название глобальной переменной mountPoint = terms[0] lastElementVar = terms[1] if not mountPoint or mountPoint[:1] != "/": raise self.raiseErrTemplate(_("wrong %s") % lastElementVar) nameVar = "install.os_install_disk_mount" try: try: valueVar = self.objVar.Get(nameVar) except Exception: raise TemplatesError( _("error: variable %s does not exist") % nameVar) nameElementVar = "install.os_install_disk_%s" % lastElementVar try: valueElementVar = self.objVar.Get(nameElementVar) except Exception: # Если переменная не существует nameElementVar = "install.os_disk_%s" % lastElementVar try: valueElementVar = self.objVar.Get(nameElementVar) except Exception: raise TemplatesError(_("wrong %s") % lastElementVar + "\n" + _("error: variable %s does not exist") % nameElementVar) for k, v in ((nameVar, valueVar), (nameElementVar, valueElementVar)): if not type(v) in (list, tuple): # Значение переменной не список или кортеж raise TemplatesError( _("value of %s is not a list or a tuple") % k) if len(valueVar) != len(valueElementVar): raise TemplatesError( _("%(name)s does not match %(nameElement)s in size") % {'name': nameVar, 'nameElement': nameElementVar}) index = None for num, mPoint in enumerate(valueVar): if mountPoint == mPoint: index = num break if index is None: for num, mPoint in enumerate(valueVar): if "/" == mPoint: index = num break if index is None: raise TemplatesError( _("mount point '/' or '/%s' not found " " in the value of variable os_disk_install") % mountPoint) replace = valueElementVar[index] return textTemplateTmp[:resS.start()] + replace + \ textTemplateTmp[resS.end():] except TemplatesError as e: raise self.raiseErrTemplate(str(e)) def funcModule(self, funArgv, resS, localVars, textTemplateTmp, nameTemp): """Функция шаблона module(), выдает значение аттрибута api. аргумент: путь_к_атрибуту - путь к аттрибуту возможные пути: имя_пакета.var.имя_переменной - получаем значение переменной имя_пакета.имя_метода_api - выполнение метода, получение результата all.имя_метода_api - выполнение метода для всех пакетов с api """ return textTemplateTmp def raiseErrTemplate(self, message=""): """Возвращает ошибки при обработке функций шаблона""" if message: message = "%s\n" % message else: message = "" return TemplatesError( _("error in template %s") % self.nameTemplate + "\n" + \ _("error, template term '%s'") % str(self.functText) + \ " " + message) def applyFuncTemplate(self, textTemplate, nameTemplate): """Применяет функции к тексту шаблона""" # Локальные переменные localVars = {} # Имя обрабатываемого шаблона self.nameTemplate = nameTemplate # Регулярное выражение для поиска функции в шаблоне reFunc = self._reFunc resS = reFunc.search(textTemplate) textTemplateTmp = textTemplate flagIniFunc = False writeIniFunc = False while resS: mark = textTemplateTmp[resS.start():resS.end()] self.functText = mark[self._deltVarStart:-self._deltVarEnd] funcName, spl, funcEnd = self.functText.partition("(") if funcName in self.namesTemplateFunction: # аргументы функции - '(' аргументы ')' funArgv = funcEnd.rpartition(")")[0] # вызов функции шаблона textTemplateTmp = self.templateFunction[funcName](self, funArgv, resS, localVars, textTemplateTmp, nameTemplate) resS = reFunc.search(textTemplateTmp) if funcName == "ini": if "," in funArgv: writeIniFunc = True flagIniFunc = True else: raise self.raiseErrTemplate( _("function of templates '%s' not found") \ % str(self.functText)) if flagIniFunc: # Очистка файла в случае его ошибочного чтения if not self.prevDictIni and os.path.exists(self.fileConfigIni): FD = open(self.fileConfigIni, "r+") FD.truncate(0) FD.seek(0) FD.close() # Если конф. файл модифицирован шаблоном curTime = self.getTimeFile(self.fileConfigIni) if curTime != self.timeIni: # Считаем переменные из конф. файла self.prevDictIni = self.loadVarsIni(self.fileConfigIni) self.currDictIni.update(self.prevDictIni) self.timeIni = curTime # Если словари переменных не совпадают if self.prevDictIni != self.currDictIni: # Запишем переменные в конфигурационный файл # Создание объекта парсера config = ConfigParser(strict=False) config.read(self.fileConfigIni, encoding="utf-8") comment_block = "\n".join(takewhile(lambda x: x.startswith("#"), readLinesFile( self.fileConfigIni))) for k, v in self.currDictIni.items(): if "." in k: sect, op, k = k.rpartition('.') if v is None: if config.has_section(sect): config.remove_option(sect, k) else: if not config.has_section(sect): config.add_section(sect) config[sect][k] = v for section in filter(lambda x: not config[x], config.sections()): config.remove_section(section) with open(self.fileConfigIni, 'wb') as f: if comment_block: f.write(comment_block) f.write('\n\n') config.write(f) try: oMode = getModeFile(self.fileConfigIni, mode="mode") if oMode != self.modeConfigIni: os.chmod(self.fileConfigIni, self.modeConfigIni) except OSError: pass # читаем переменные из файла self.prevDictIni = self.loadVarsIni(self.fileConfigIni) self.currDictIni.update(self.prevDictIni) self.timeConfigsIni[self.fileConfigIni] = float(time.time()) self.timeIni = self.getTimeFile(self.fileConfigIni) # Меняем владельца в случае необходимости if writeIniFunc and os.path.exists(self.fileConfigIni): uid, gid = getModeFile(self.fileConfigIni, "owner") if not self.uid in (uid, PORTAGEUID) or \ not self.gid in (gid, PORTAGEGID): try: os.chown(self.fileConfigIni, self.uid, self.gid) except OSError: self.setError(_("error") + " " + "'chown %d:%d %s'" % (self.uid, self.gid, self.fileConfigIni)) return textTemplateTmp class ChangedFiles: """ Object which contains modified files and package """ FILE_MODIFIED, FILE_REMOVED, DIR_CREATED, DIR_REMOVED, DIR_EXISTS = 0, 1, 2, 3, 4 def __init__(self): self.data = {} self.pkgs = set() def _createEntry(self, fn): if not fn in self.data: self.data[fn] = [] def addObj(self, filename, action, pkg): self._createEntry(filename) self.data[filename].append((pkg, action)) self.pkgs.add(pkg) def getPkgs(self): return self.pkgs def getPkgFiles(self, pkg): return map(lambda x: (x[0], x[1][0][1]), filter(lambda x: x[1], map(lambda x: ( x[0], filter(lambda x: x[0] == pkg, x[1])), self.data.items()))) # modes work with configuration file # T_ORIGIN - work with original config file # T_CFG - work with last ._cfg file # T_NEWCFG - new content has difference with (create new ._cfg file) T_ORIGIN, T_CFG, T_NEWCFG = 0, 1, 2 class Template(_file, _terms, _warning, xmlShare, templateFormat, _shareTemplate): """Класс для работы с шаблонами На вход 2 параметра: объект хранения переменных, имя сервиса - не обязательный параметр """ # Название файла шаблона директории templDirNameFile = ".calculate_directory" _titleList = ("Modified", "Processed template files" + ":") titleEnd = "For modify this file, create %(conf_path)s.clt template." protectPaths = [] allContents = {} if "CONFIG_PROTECT" in os.environ: protectPaths = ["/etc"] + filter(lambda x: x.strip(), os.environ["CONFIG_PROTECT"].split( " ")) protectPaths = map(lambda x: os.path.normpath(x), protectPaths) @classmethod def removeComment(cls, text): re_comment = re.compile('(?:|[{symb}]-*)\n'.format( modified=cls._titleList[0], processed=cls._titleList[1], endtitle=cls.titleEnd % {'conf_path': '.*'}, symb='"#' )) return re_comment.sub('', text) def hasError(self): return self.getError() or self.bHasError def __init__(self, objVar, servDir=False, dirsFilter=(), filesFilter=(), cltObj=True, cltFilter=True, printWarning=True, printSUCCESS=lambda x: x, printWARNING=lambda x: x, printERROR=lambda x: x, askConfirm=lambda x: x, userProfile=False, dispatchConf=None): # совместимость с вызовами из модулей предыдущих версий self.translator = RegexpLocalization("cl_templates3") if userProfile and objVar: objVar.Set('cl_protect_use_set', 'off', force=True) self.protectPaths = objVar.Get('cl_config_protect') self.dispatchConf = dispatchConf self.changedFiles = ChangedFiles() self.printSUCCESS = printSUCCESS self.printERROR = printERROR self.postmergePkgs = [] self.postmergeFile = "/var/lib/calculate/-postmerge" self.bHasError = False if printERROR: def newSetError(s): self.printERROR(s) self.bHasError = True self.setError = newSetError self.printWARNING = printWARNING self.askConfirm = askConfirm self.cltObj = None self.functObj = None # Предупреждения # self.warning = [] # Печатать ли предупреждения о корневых шаблонах без cl_name==pkg self.printWarning = printWarning # Необрабатываемые директории self.dirsFilter = dirsFilter # Необрабатываемые файлы self.filesFilter = filesFilter _file.__init__(self) # Словарь для создания объектов новых классов по образцу self.newObjProt = {'proftpd': 'apache'} # Заголовок title self.__titleHead = "--------------------------------------\ ----------------------------------------" self._titleBody = "" self._reVar = re.compile( "%s(?:[a-z0-9_]+\.)?[a-zA-Z0-9_-]+%s" % (self.varStart, self.varEnd), re.M) # Условия self._reTermBloc = re.compile( "#\?(?P(?:[a-z0-9_]+\.)?[a-zA-Z0-9\-_]+)" "(?P\((%s+|)\))?" "(?P[><=!&\|]+" "[><=!\|&\(\)%s]*)#" "\n*(?P.+?)\n*#(?P=rTerm)#(?P[ ,\t]*\n?)" % (self._reFunctionArgvText, self._reFunctionArgvInSquareBrackets), re.M | re.S) # Объект с переменными self.objVar = objVar self.recalculateBaseDir() # Последняя часть директории шаблона (имя сервиса: samba, mail) self._servDir = servDir if self._servDir: if self._servDir[0] != "/": self._servDir = "/" + self._servDir if self._servDir[-1] != "/": self._servDir += "/" self._servDir = os.path.split(self._servDir)[0] # Созданные директории self.createdDirs = [] # Примененные файлы self.filesApply = [] # номер обрабатываемого файла self.numberProcessTempl = 0 # имя текущей программы _nameProgram = self.objVar.Get("cl_name").capitalize() # версия текущей программы _versionProgram = self.objVar.Get("cl_ver") # имя и версия текущей программы self.programVersion = "%s %s" % (_nameProgram, _versionProgram) # Словарь директорий с количеством файлов шаблонов self.dictTemplates = {} # Общее количество шаблонов self.allTemplates = 0 # Объект функций шаблона self.functObj = templateFunction(self.objVar) self.functObj.printSUCCESS = self.printSUCCESS self.functObj.printWARNING = self.printWARNING self.functObj.printERROR = self.printERROR if self.printERROR: self.functObj.setError = self.printERROR self.functObj.askConfirm = self.askConfirm # Метод применения функций к шаблонам self.applyFuncTemplate = self.functObj.applyFuncTemplate # Объект для определения типа файла шаблона self.typeFileObj = typeFile() # Глобальный словарь обработанных шаблонов файлов # {путь к конф. файлу:[имена шаблонов] ...} self.dictProcessedTemplates = {} if cltObj is True: # Объект templateClt self.cltObj = templateClt(self.objVar, self.postmergePkgs, printSUCCESS=self.printSUCCESS, printERROR=self.printERROR, printWARNING=self.printWARNING, askConfirm=self.askConfirm) elif cltObj: # Объект templateClt self.cltObj = cltObj else: # Объект templateClt self.cltObj = None # Фильтровать ли шаблоны clt по конфигурационным файлам обычных шаблонов self.cltFilter = cltFilter # autoupdate файлы self.autoUpdateFiles = [] self.autoUpdateDirs = [] self.protectedFiles = self.objVar.Get('main.cl_env_path') + \ [self.functObj.fileConfigIni] # список выполненных файлов self.executedFiles = [] def recalculateBaseDir(self): """Recalculate basedir and homedir""" # Базовая директория переноса шаблонов "/mnt/calculate" или "/" и.т.д self._baseDir = pathJoin(self.objVar.Get("cl_chroot_path"), self.objVar.Get("cl_root_path")) self._baseDir = os.path.normpath(self._baseDir) self.uid, self.gid, self.homeDir = self.getDataUser() # Домашняя директория, плюс базовая директория self.homeDir = pathJoin(self._baseDir, self.homeDir) if self.cltObj: self.cltObj.recalculateBaseDir() if self.functObj: self.functObj.recalculateBaseDir() def _addFile(self, filesApl, pkg=None): """ Add files to ChangedFiles """ for fn in filesApl: if os.path.exists(fn): self.changedFiles.addObj(fn, ChangedFiles.FILE_MODIFIED, pkg or self.functObj.currentBelong) else: self.changedFiles.addObj(fn, ChangedFiles.FILE_REMOVED, pkg or self.functObj.currentBelong) def execute_command(self, cmd, lang): env = dict(os.environ) env['TERM'] = "linux" env['EINFO_QUIET'] = "yes" return process(cmd, lang=lang, envdict=dict(os.environ)) def executeTemplate(self, code, execPath): """Execute template""" p = self.execute_command(execPath, self.objVar.Get('os_locale_locale')) if "/bin/bash" in code.partition('\n')[0]: p.write("""function translate() { gettext -d cl_template "$*" } """) p.write(code) p.pipe.stdin.close() for line in p.readByLine(): if line: line = self.translator.translate(line) self.printSUCCESS(line.strip()) p.pipe.wait() if p.success(): self.executedFiles.append((code, execPath)) if p.readerr(): for line in p.readerr().split('\n'): if line: line = self.translator.translate(line) self.printWARNING(line.strip()) return True else: if p.readerr(): for line in p.readerr().split('\n'): if line: line = self.translator.translate(line) self.printERROR(line.strip()) return False def __octToInt(self, strOct): """Преобразование восьмеричного в целое (ввод строка, вывод число)""" if strOct: try: res = string.atoi(strOct, 8) except ValueError: self.setError(_("Invalid oct value: ") + str(strOct)) return False return res else: self.setError(_("Empty oct value")) return False def getTemplateType(self): """выдать тип шаблона (text, bin)""" return self.getFileType(self.nameFileTemplate) def getFileType(self, fileName): """выдать тип файла (text, bin)""" isBin = self.typeFileObj.isBinary(fileName) if isBin is True: typeTemplate = "bin" elif isBin is False: typeTemplate = "text" else: self.setError(_("ERROR") + ": getFileType()") self.setError(isBin) return False return typeTemplate def createDir(self, dirName, mode=False, uid=False, gid=False): """Создает директорию""" if os.access(dirName, os.F_OK): return True else: dMode = False prevDir, tmpSubdir = os.path.split(dirName) createDirs = [] while not os.access(prevDir, os.F_OK) and prevDir: createDirs.append(prevDir) prevDir = os.path.split(prevDir)[0] try: dUid, dGid = getModeFile(prevDir, "owner") except OSError: self.setError(_("No access to the directory: ") + prevDir) return False if not mode is False: dMode = mode if not uid is False: dUid = uid if not gid is False: dGid = gid createDirs.reverse() for nameDir in createDirs: try: if dMode: os.mkdir(nameDir, dMode) os.chmod(nameDir, dMode) else: os.mkdir(nameDir) os.chown(nameDir, dUid, dGid) except OSError: self.setError(_("Failed to create the directory: ") + nameDir) return False try: if dMode: os.mkdir(dirName, dMode) os.chmod(dirName, dMode) else: os.mkdir(dirName) os.chown(dirName, dUid, dGid) createDirs.append(dirName) except OSError: self.setError(_("Failed to create the directory: ") + dirName) return False return createDirs def applyVarsTemplate(self, textTemplate, nameTemplate): """ Заменяет переменные на их значения """ resS = self._reVar.search(textTemplate) textTemplateTmp = textTemplate while resS: mark = textTemplateTmp[resS.start():resS.end()] varName = mark[self._deltVarStart:-self._deltVarEnd] try: t = self.objVar.getInfo(varName).type if "list" in t: varValue = self.objVar.serialize(t, self.objVar.Get(varName)) else: varValue = self.objVar.Get(varName) if not varValue: varValue = "" else: varValue = str(varValue) except DataVarsError, e: raise TemplatesError(_("error in template %s") % nameTemplate + "\n" + str(e)) textTemplateTmp = textTemplateTmp.replace(mark, varValue) resS = self._reVar.search(textTemplateTmp) return textTemplateTmp def applyTermsTemplate(self, textTemplate, nameTemplate): """ Применяет условия, к условным блокам текста """ def function(text): """Функция обработки функций в заголовке""" return self.applyFuncTemplate(text, nameTemplate) resS = self._reTermBloc.search(textTemplate) textTemplateTmp = textTemplate while resS: mark = resS.group(0) body = resS.group("body") end = resS.group("end") parent = resS.group("func") if not parent: parent = "" term = resS.group("rTerm") + parent + \ resS.group("lTerm") if self._equalTerm(term, _("invalid template content: ") + \ nameTemplate, function): textTemplateTmp = textTemplateTmp.replace(mark, body + end) else: textTemplateTmp = textTemplateTmp.replace(mark, "") resS = self._reTermBloc.search(textTemplateTmp) return textTemplateTmp def getNeedTemplate(self, fileTemplate): """Применяем правила к названию файла""" dirP, fileP = os.path.split(fileTemplate) if fileP: spFile = fileP.split("?") if len(spFile) > 1: flagTrue = False for term in spFile[1:]: if self._equalTerm(term, _("invalid template name: ") + \ fileTemplate): flagTrue = True break if flagTrue: return True else: return False else: return True else: self.setError(_("invalid template name: ") + str(fileTemplate)) return False def getTitle(self, comment, commentList, configPath=""): """Выдает заголовок шаблона ( версия и.т.д)""" if configPath and self.protectPaths: flagFoundPath = False for protectPath in self.protectPaths: if self._baseDir != "/": lenBaseDir = len(self._baseDir) if len(configPath) > lenBaseDir and \ configPath[:lenBaseDir] == self._baseDir: configPath = configPath[lenBaseDir:] if configPath.startswith(protectPath + "/"): flagFoundPath = True break if flagFoundPath: origConfigPath = PkgContents.reCfg.sub("/", configPath) if self._baseDir != "/": lenBaseDir = len(self._baseDir) commentList = [x[lenBaseDir:] if x.startswith(self._baseDir) else x for x in commentList] commentList = commentList + \ [self.titleEnd % {'conf_path': origConfigPath}] if comment: commentFirst = comment commentInsert = comment commentLast = comment flagList = False # В случае открывающего и закрывающего комментария if type(comment) == types.TupleType and len(comment) == 2: commentFirst = comment[0] commentInsert = "" commentLast = comment[1] flagList = True if flagList: self._titleBody = commentFirst + "\n" else: self._titleBody = commentFirst + self.__titleHead + "\n" z = 0 flagFirst = True for com in list(self._titleList) + [""] * (len(commentList)): if com: if flagFirst: self._titleBody += commentInsert + " " + com + " " + \ self.programVersion + "\n" flagFirst = False else: self._titleBody += commentInsert + " " + com + "\n" else: self._titleBody += commentInsert + " " + \ commentList[z] + "\n" z += 1 if flagList: self._titleBody += commentLast + "\n" else: self._titleBody += commentLast + self.__titleHead + "\n" return self._titleBody else: return "" def changeMergePackage(self, package): return True def numberAllTemplates(self, number): """Количество шаблонов Вызов происходит перед наложением шаблонов в момент вызова в number находится количество обрабатываемых файлов Наследуемая функция Используется для отображения прогресса при наложениии шаблонов """ return True def numberProcessTemplates(self, number): """Номер текущего обрабатываемого шаблона Вызов происходит при наложении шаблона в момент вызова в number находится номер обрабатываемого шаблона Наследуемая функция Используется для отображения прогресса при наложениии шаблонов """ return True def templateModify(self): """ Files which created by apping templates """ return True def fixNameFileConfig(self, origfilename): """Support ._cfg0000 files for postinst""" # if self.objVar.Get('cl_ebuild_phase') != 'postinst': # return origfilename directory, filename = os.path.split(origfilename) for i in range(0, 9999): if not os.path.exists(os.path.join(directory, "._cfg%04d_%s" % (i, filename))): if i: filename = os.path.join(directory, "._cfg%04d_%s" % (i - 1, filename)) if not os.path.exists(origfilename): return origfilename # origstat = os.stat(origfilename)[stat.ST_CTIME] # newstat = os.stat(filename)[stat.ST_CTIME] self.configMode = T_CFG return filename return origfilename return origfilename def getHeaderText(self, text): textLines = text.splitlines() paramLine = "" if textLines: textLine = textLines[0] rePar = re.compile( "\s*#\s*calculate\s+\\\\?|\s*#\s*calculate\\\\?$", re.I) reP = rePar.search(textLine) if reP: reLns = re.compile(r"\A([^\\\n]*\\\n)+[^\n]*\n*", re.M) reLs = reLns.search(text) if reLs: paramLine = text[reP.end():reLs.end()] paramLine = paramLine.replace("\\", " ") else: paramLine = textLine[reP.end():] return paramLine def getTemplateDirs(self, dirsTemplates): """Check template variable cl_name in first directories and files""" skipDirs = [] skipTemplates = [] debug = False for dirsTemplate in dirsTemplates: filesAndDirs = map(lambda x: os.path.join(dirsTemplate, x), listDirectory(dirsTemplate)) for dirFile in filesAndDirs: if os.path.isdir(dirFile): flagDir = True templatePath = os.path.join(dirFile, self.templDirNameFile) if not os.path.isfile(templatePath): skipDirs.append(dirFile) continue else: flagDir = False templatePath = dirFile if os.path.isfile(templatePath): if self.getFileType(templatePath) == "bin": skipTemplates.append(dirFile) else: textTemplate = open(templatePath).read() if textTemplate: headerLine = self.getHeaderText(textTemplate) if headerLine: if not debug and \ not "cl_name==" in headerLine and \ not "ac_" in headerLine: if flagDir: skipDirs.append(dirFile) else: skipTemplates.append(dirFile) else: if flagDir: skipDirs.append(dirFile) else: skipTemplates.append(dirFile) else: skipTemplates.append(dirFile) if skipDirs or skipTemplates: # print warning self.printWARNING(_("No conditions for checking the value of " "an action variable")) skipDirTemplates = [] for skipDir in skipDirs: skipTempl = os.path.join(skipDir, self.templDirNameFile) if os.path.isfile(skipTempl): skipDirTemplates.append(skipTempl) if skipTemplates or skipDirTemplates: self.printWARNING(_("Skipped templates:")) for skipTemplate in skipTemplates + skipDirTemplates: self.printWARNING(" " * 6 + skipTemplate) if skipDirs: self.printWARNING(_("Skipped directories:")) for skipDir in skipDirs: self.printWARNING(" " * 6 + skipDir) self.printWARNING("") self.printWARNING(_("Headers of directory templates and headers " "of files on the first level should include " "an action variable")) self.printWARNING(_("Example:")) self.printWARNING("# Calculate ac_install_merge==on") return skipDirs + skipTemplates @catch_no_space_left def applyTemplates(self, progress=True, rerun=True): """Применяет шаблоны к конфигурационным файлам""" def createDictTemplates(path, prefix, dictTemplates): """Создает словарь {"директория":"кол-во шаблонов" ...} и считает общее количество шаблонов """ # Количество шаблонов self.allTemplates += 1 dirTemplate = os.path.split(path)[0] while True: if dirTemplate in dictTemplates.keys(): dictTemplates[dirTemplate] += 1 else: dictTemplates[dirTemplate] = 1 if dirTemplate == prefix: break dirTemplate = os.path.split(dirTemplate)[0] return dictTemplates self.clearErrors() self.clearWarnings() if not self.objVar.defined("cl_template_path_use"): self.setError(_("undefined variable: ") + "cl_template_path_use") return False dirsTemplates = self.objVar.Get("cl_template_path_use") # Созданные директории self.createdDirs = [] # Примененные файлы self.filesApply = [] # Номер применяемого шаблона self.numberProcessTempl = 0 # Словарь директорий с количеством файлов шаблонов self.dictTemplates = {} # Количество шаблонов self.allTemplates = 0 # Установка по умолчанию аттрибутов для функциии шаблонов ini() # Время доступа к конфигурационному файлу функции шаблона ini() self.functObj.timeIni = -1 # Первоначальный словарь переменных для ini() self.functObj.prevDictIni = {} # Текущий словарь переменных для ini() self.functObj.currDictIni = {} self.functObj.currentBelong = "" self.functObj.currentAction = "merge" # Словарь времен модификации env файлов для ini() self.functObj.timeConfigsIni = {} if self._servDir: tmpDirsTemplates = [] for dirP in dirsTemplates: dirTempl = dirP + self._servDir if os.access(dirTempl, os.F_OK): # Если директория существует tmpDirsTemplates.append(dirTempl) dirsTemplates = tmpDirsTemplates scanObj = scanDirectory() scanObj.processingFile = lambda x, y: createDictTemplates(x, y, self.dictTemplates) # Считаем количество шаблонов dirsTemplatesExists = filter(lambda x: os.path.exists(x), dirsTemplates) if not dirsTemplatesExists and not self.cltObj: return self.createdDirs, self.filesApply # check cl_name in first template dirs and files skipTemplates = self.getTemplateDirs(dirsTemplatesExists) # if not os.environ.get("EBUILD_PHASE","") and progress: # for dirTemplate in dirsTemplatesExists: # scanObj.scanningDirectory(dirTemplate) # Считаем количество шаблонов clt if self.cltObj: # Считаем количество шаблонов clt self.cltObj.countsNumberTemplates() # не считать количество файлов в объекте self.cltObj self.cltObj.checkNumberTemplate = False # начальный номер clt шаблона self.cltObj.numberProcessTempl = self.allTemplates # метод показывающий номер clt шаблона self.cltObj.numberProcessTemplates = self.numberProcessTemplates # метод показывающий номер clt шаблона self.cltObj.templateModify = self.templateModify # общее количество шаблонов self.allTemplates += self.cltObj.allTemplates self.cltObj.allTemplates = self.allTemplates self.numberAllTemplates(self.allTemplates) # Обрабатываем шаблоны locationPath = dict(self.objVar.ZipVars('main.cl_template_path', 'main.cl_template_location')) for dirTemplate in dirsTemplatesExists: self.objVar.Set('cl_pass_location', locationPath.get(dirTemplate, dirTemplate), force=True) if self.scanningTemplates(dirTemplate, skipTemplates=skipTemplates) is False: break if self.cltObj: self.objVar.Set('cl_pass_location', 'clt', True) # Созданные директории self.cltObj.createdDirs = self.createdDirs # Примененные файлы self.cltObj.filesApply = self.filesApply # Словарь директорий с количеством файлов шаблонов self.cltObj.dictTemplates = self.dictTemplates # Количество шаблонов self.cltObj.allTemplates = self.allTemplates # Установка по умолчанию аттрибутов для функциии шаблонов ini() # Время доступа к конфигурационному файлу функции шаблона ini() self.cltObj.functObj = self.functObj self.cltObj.protectedFiles = self.protectedFiles # Метод применения функций к шаблонам self.cltObj.applyFuncTemplate = self.functObj.applyFuncTemplate # Словарь примененных файлов шаблонов self.cltObj.dictProcessedTemplates = self.dictProcessedTemplates self.cltObj.changedFiles = self.changedFiles self.cltObj.autoUpdateFiles = self.autoUpdateFiles self.cltObj.autoUpdateDirs = self.autoUpdateDirs if self.cltFilter: # Шаблоны + .clt которые будут применены self.cltObj.filterApplyTemplates = {} if self.objVar.Get('cl_merge_set') == "on": for pkg in self.objVar.Get('cl_merge_pkg'): if not pkg: continue category = isPkgInstalled(pkg) if category: pkgContents = PkgContents("{CATEGORY}/{PF}".format( **category[0])) for filename in pkgContents.content.keys(): if not filename in self.cltObj.filterApplyTemplates: self.cltObj.filterApplyTemplates[ filename] = [] self.cltObj.filterApplyTemplates[ filename].append(pkg) for filename, pkgs in self.changedFiles.data.items(): filename = PkgContents.reCfg.sub("/", filename) if not filename in self.cltObj.filterApplyTemplates: self.cltObj.filterApplyTemplates[filename] = [] pkgs = filter( lambda x: x not in self.cltObj.filterApplyTemplates[filename], map(lambda x: x[0], pkgs)) self.cltObj.filterApplyTemplates[filename].extend(pkgs) self.cltObj.applyTemplatesClt() if ((self.objVar.Get('cl_merge_pkg') or self.objVar.Get('cl_action') in ( "sync", "domain", "undomain")) and self.objVar.Get('cl_merge_pkg_new')): self.objVar.Set('cl_root_path', self.objVar.Get('cl_root_path_next'), force=True) self.recalculateBaseDir() self.objVar.Set('cl_merge_pkg_pass', list( set(self.objVar.Get('cl_merge_pkg_pass')) | set(self.objVar.Get('cl_merge_pkg')) | set(self.objVar.Get('cl_merge_pkg_new'))), force=True) self.objVar.Set('cl_merge_pkg', self.objVar.Get('cl_merge_pkg_new'), force=True) self.objVar.Set('cl_merge_pkg_new', [], force=True) createdDirs = self.createdDirs filesApply = self.filesApply self.changeMergePackage(self.objVar.Get('cl_merge_pkg')) self.applyTemplates(rerun=False) createdDirs.extend(self.createdDirs) filesApply.extend(self.filesApply) self.filesApply = filesApply self.createdDirs = createdDirs if rerun: if self.cltObj: self.queueExecute.extend(self.cltObj.queueExecute) for processor, text, nameTemplate in self.queueExecute: if not self.executeTemplate(text, processor): self.setError(_("Failed to execute") + _(": ") + nameTemplate) return False self.queueExecute = [] self.filesApply = list(set(self.filesApply)) if self.objVar.Get('cl_protect_use_set') == 'on': self.updateProtectedFiles() if self.objVar.Get('cl_verbose_set') == 'on' and \ self.filesApply: self.verboseOutput( filter(lambda x: not x.endswith('/ini.env'), self.filesApply)) self.objVar.Set('cl_merge_pkg_pass', [], force=True) self.objVar.Set('cl_merge_pkg_new', [], force=True) self.objVar.Set('cl_merge_pkg', [], force=True) return self.createdDirs, self.filesApply def verboseOutput(self, filesApply): """ Verbose output applied templates """ if not filesApply: return self.printWARNING(_("Calculate Utilities have changed files") + _(":")) reGrey = re.compile(r"\._cfg\d{4}_") rootPath = self.objVar.Get('cl_root_path') for fn in sorted(list(set(filesApply))): if rootPath != '/' and self.objVar.Get('cl_action') == 'patch' and \ fn.startswith(rootPath): fn = fn[len(rootPath) + 1:] if reGrey.search(fn): self.printSUCCESS(" " * 5 + \ "%s" % fn) else: self.printSUCCESS(" " * 5 + fn) def updateProtectedFiles(self): """ Update ._cfg0000 files """ if self.objVar.Get('cl_ebuild_phase') == 'compile': return chrootPath = self.objVar.Get('cl_chroot_path') cfgs = getCfgFiles(self.objVar.Get('cl_config_protect'), prefix=chrootPath) autoUpdateDict = {} for pkg in list(set(filter(None, list(self.changedFiles.getPkgs()) + self.objVar.Get('cl_merge_pkg')))): category = isPkgInstalled(pkg, prefix=chrootPath) if category: pkgContents = PkgContents("{CATEGORY}/{PF}".format( **category[0]), prefix=chrootPath) for filename, action in self.changedFiles.getPkgFiles(pkg): if filename in self.protectedFiles: pkgContents.removeObject(filename) continue if action in (ChangedFiles.FILE_MODIFIED, ChangedFiles.DIR_CREATED, ChangedFiles.DIR_EXISTS): pkgContents.addObject(filename) elif action in (ChangedFiles.FILE_REMOVED, ChangedFiles.DIR_REMOVED): pkgContents.removeObject(filename) files = set(map(lambda x: pathJoin(chrootPath, x), pkgContents.content.keys())) if (self.objVar.Get('cl_dispatch_conf') != 'usenew' and self.objVar.Get('cl_autoupdate_set') != "on"): notUpdate = files - set(self.autoUpdateFiles) files &= set(self.autoUpdateFiles) for filename in list(notUpdate & set(cfgs.keys())): if hashlib.md5(readFile(filename)).hexdigest() == \ hashlib.md5(readFile( cfgs[filename][0][1])).hexdigest(): files.add(filename) for filename in list(files & set(cfgs.keys())): # get ctime from orig filename # if os.path.exists(filename): # ctime = os.stat(filename).st_ctime # else: # ctime = 0 # if orig filename older that .cfg cfgs[filename].sort(reverse=True) # if ctime < cfgs[filename][0][0]: try: open(filename, 'w').write( readFile(cfgs[filename][0][1])) except Exception as e: self.printERROR(str(e)) self.printWARNING( _("Failed to copy {ffrom} to {fto}").format( ffrom=cfgs[filename][0][1], fto=filename)) continue autoUpdateDict[cfgs[filename][0][1]] = filename for mtime, fn in cfgs[filename]: try: os.unlink(fn) except OSError: self.printWARNING(_("Failed to remove %s") % fn) try: pkgContents.writeContents() except IOError: self.printWARNING(_("Failed to modify %s contents") % pkg) self.filesApply = map(lambda x: autoUpdateDict.get(x, x), self.filesApply) if filter(lambda x: "._cfg" in x, self.filesApply): if self.objVar.Get('cl_ebuild_phase') != '': self.printWARNING(_("Some config files need updating. " "Perform run dispatch-conf.")) if self.dispatchConf and \ self.objVar.Get( 'cl_dispatch_conf') == 'dispatch' and \ self.objVar.Get('cl_ebuild_phase') == '': self.dispatchConf(self.filesApply) def scanningTemplates(self, scanDir, prefix=None, flagDir=False, optDir=None, skipTemplates=()): """Сканирование и обработка шаблонов в директории scanDir""" if optDir is None: optDir = {} ret = True if not prefix: prefix = os.path.realpath(scanDir) if not flagDir: # проверка корневой директории retDir = self.processingDirectory(scanDir, scanDir, optDir) if retDir is None: return None elif retDir is False: return False pathDir, objHead = retDir optDir["path"] = pathDir if not objHead is True: if objHead.typeAppend == "skip": # Установка опции пропуска директории optDir["skip"] = True if ("autoupdate" in objHead.params or self.objVar.Get('cl_autoupdate_set') == 'on'): optDir["autoupdate"] = True if flagDir or stat.S_ISDIR(os.lstat(str(scanDir))[stat.ST_MODE]): if not os.access(scanDir, os.R_OK | os.X_OK): self.printWARNING(_("Failed to read templates directory %s") % scanDir) return False for fileOrDir in sorted(listDirectory(scanDir)): absPath = os.path.join(scanDir, fileOrDir) if skipTemplates and absPath in skipTemplates: continue stInfo = os.lstat(str(absPath)) statInfo = stInfo[stat.ST_MODE] prevModule = self.objVar.defaultModule prevBelong = self.functObj.currentBelong prevAction = self.functObj.currentAction try: if stat.S_ISREG(statInfo): if not self.processingFile(absPath, prefix, optDir): ret = False continue elif stat.S_ISDIR(statInfo): # Обработка директории retDir = self.processingDirectory(absPath, prefix, optDir) if retDir is None: continue elif retDir is False: ret = False break # Опции следующей директории optNextDir = {} pathDir, objHead = retDir optNextDir["path"] = pathDir if not objHead is True: if objHead.typeAppend == "skip": # Установка опции пропуска директории optNextDir["skip"] = True if ("autoupdate" in objHead.params or self.objVar.Get( 'cl_autoupdate_set') == 'on'): optNextDir["autoupdate"] = True ret = self.scanningTemplates(absPath, prefix, True, optNextDir) if ret is False: break finally: self.objVar.defaultModule = prevModule self.functObj.currentBelong = prevBelong self.functObj.currentAction = prevAction return ret def processingFile(self, path, prefix, optFile): """Обработка в случае шаблона файла""" self.numberProcessTempl += 1 self.numberProcessTemplates(self.numberProcessTempl) # Пропуск шаблонов директорий if self.templDirNameFile == os.path.split(path)[1]: return True # Проверка на переменные в названии файла if not self.getNeedTemplate(path): if self.getError(): return False return True if self.getError(): return False nameFileConfig = path.partition(prefix)[2] # файл в системе без условий nameFileConfig = "/".join(map(lambda x: x.split("?")[0], nameFileConfig.split("/"))) # Записываем в переменную обрабатываемый файл self.objVar.Set("cl_pass_file", nameFileConfig) filesApl = self.joinTemplate(path, nameFileConfig, optFile) if self.getError(): return False if filesApl: # Настоящее имя конфигурационного файла nameFileConfig = filesApl[0] # Пишем время модификации *.env файлов if nameFileConfig.endswith(".env"): nameEnvFile = os.path.basename(nameFileConfig) self.functObj.timeConfigsIni[nameEnvFile] = float(time.time()) self.filesApply += filesApl if filesApl: self._addFile(filesApl) return True def processingDirectory(self, path, prefix, opt): """Обработка в случае директории если возвращаем None то пропуск дир.""" # Файл шаблона директории if not os.access(path, os.R_OK | os.X_OK): self.printWARNING(_("Failed to read templates directory %s") % path) return None dirInfoFile = os.path.join(path, self.templDirNameFile) newDir = pathJoin(self._baseDir, path.partition(prefix)[2]) newDir = "/".join(map(lambda x: x.split("?")[0], newDir.split("/"))) # Применяем шаблон pathDir, objHeadDir, createdDirs = \ self.getApplyHeadDir(newDir, dirInfoFile, opt) if createdDirs: self.createdDirs += createdDirs if os.path.isfile(pathDir): self.printWARNING(_("{dirpath} is a file").format(dirpath=pathDir)) self.printWARNING(_("templates in {tempath} are skipped" ).format(tempath=path)) return None if objHeadDir: return pathDir, objHeadDir else: if self.getError(): return False # Добавление количества файлов в пропущенной директории if path in self.dictTemplates.keys(): self.numberProcessTempl += self.dictTemplates[path] return None def _processMergePostmerge(self, params, templateDirFile): """Обработка параметров merge= , postmerge=""" if "merge" in params: mergePkgs = params['merge'].split(',') else: mergePkgs = [] if "postmerge" in params: postmergePkgs = params['postmerge'].split(',') else: postmergePkgs = [] if mergePkgs or postmergePkgs: if self.objVar.Get('cl_ebuild_phase') == 'postinst': for pkg in postmergePkgs: if not pkg in self.objVar.Get('cl_merge_pkg_pass'): self.objVar.Get('cl_merge_pkg_pass').append(pkg) if not pkg in self.postmergePkgs: try: with open(self.postmergeFile, "a") as f: f.write("%s\n" % pkg) self.postmergePkgs.append(pkg) except OSError: self.printWARNING( _("Failed to reconfigure package %s") % pkg) else: mergePkgs = mergePkgs + postmergePkgs for pkg in mergePkgs: if not pkg in self.objVar.Get('cl_merge_pkg_new') and \ not pkg in self.objVar.Get('cl_merge_pkg_pass') and \ not pkg in self.objVar.Get('cl_merge_pkg'): self.objVar.Get('cl_merge_pkg_new').append(pkg) def getApplyHeadDir(self, newDir, templateDirFile, optDir): """Применяет шаблон к директории (права, владелец, и.т. д)""" def function(text): """Функция обработки функций в заголовке""" return self.applyFuncTemplate(text, templateDirFile) def chownConfDir(nameDirConfig, uid, gid, nameFileTemplate): """Изменение владельца конфигурационной директории""" try: os.chown(nameDirConfig, uid, gid) except (OSError, Exception) as e: if hasattr(e, 'errno') and e.errno == 13 and \ "var/calculate/remote" in nameDirConfig: return True import pwd, grp try: userName = pwd.getpwuid(uid).pw_name except (TypeError, KeyError): userName = str(uid) try: groupName = grp.getgrgid(gid).gr_name except (TypeError, KeyError): groupName = str(gid) owner = userName + ":" + groupName self.setError(_("Failed to apply template file %s") \ % nameFileTemplate) self.setError(_("error") + " " + \ "'chown %s %s'" % (owner, nameDirConfig)) return False return True applyDir = newDir # Родительская директория if optDir.get("path"): path = optDir["path"] else: if applyDir == self._baseDir: path = os.path.dirname(self._baseDir) else: path = os.path.split(applyDir)[1] path = pathJoin(self._baseDir, path) if not os.path.exists(templateDirFile): if applyDir != self._baseDir: applyDir = os.path.join(path, os.path.split(applyDir)[1]) # Фильтрация шаблонов по названию директории realPath = os.path.join("/", applyDir.partition(self._baseDir)[2]) if realPath in self.dirsFilter: return "", False, [] # Создаем директорию если необходимо crDirs = self.createDir(applyDir, False, self.uid, self.gid) if not crDirs: return "", False, [] if "autoupdate" in optDir: self.autoUpdateDirs.append(applyDir) if crDirs is True: return applyDir, True, [] else: return applyDir, True, crDirs try: FD = open(templateDirFile) textTemplate = FD.readline().rstrip() buf = textTemplate while buf and textTemplate.endswith('\\'): buf = FD.readline() textTemplate = "%s %s" % (textTemplate[:-1], buf.rstrip()) FD.close() except IOError: self.setError(_("Failed to open the template") + _(": ") + templateDirFile) return "", False, [] headerLine = self.getHeaderText(textTemplate) if headerLine: moduleParam = filter(lambda x: x.startswith('env='), headerLine.split()) if moduleParam: self.objVar.defaultModule = moduleParam[0].partition('=')[2] try: importlib.import_module( "calculate.%s.variables" % self.objVar.defaultModule) except (ImportError, AttributeError): return "", False, [] # Заменяем переменные на их значения textTemplate = self.applyVarsTemplate(textTemplate, templateDirFile) # Заменяем функции на их значения textTemplate = self.applyFuncTemplate(textTemplate, templateDirFile) # Обработка заголовка objHead = dirHeader(templateDirFile, textTemplate, self.objVar, function) signs = {'ac_install_patch==on': 'patch', 'ac_desktop_profile==on': 'profile'} for sign in signs: if sign in textTemplate: self.functObj.currentAction = signs[sign] break # Директория с профилями не будет применена if not objHead.headerTerm: if objHead.getError(): self.setError(_("Incorrect template") + _(": ") + templateDirFile) return "", False, [] # add packeges for reconfigure self._processMergePostmerge(objHead.params, templateDirFile) # Пропускаем директорию if objHead.typeAppend == "skip": applyDir = path return applyDir, objHead, [] # Изменяем название родительской директории if "path" in objHead.params: path = objHead.params['path'] if path and path[0] == "~": # Получаем путь с заменой ~ на директорию пользователя path = os.path.join(self.homeDir, path.partition("/")[2], "")[:-1] elif not path or path and path[0] != "/": self.setError(_("Wrong value 'path' in the template") + _(": ") + templateDirFile) return "", False, [] else: path = pathJoin(self._baseDir, path) # Изменяем название директории if "name" in objHead.params: nameDir = objHead.params['name'] if "/" in nameDir or nameDir == ".." or nameDir == ".": self.setError(_("Wrong value 'name' in the template") + _(": ") + templateDirFile) return "", False, [] # Новый путь к директории applyDir = pathJoin(path, nameDir) else: applyDir = pathJoin(path, os.path.split(applyDir)[1]) # Фильтрация шаблонов по названию директории realPath = os.path.join("/", applyDir.partition(self._baseDir)[2]) if realPath in self.dirsFilter: return "", False, [] # Удаляем директорию if objHead.typeAppend == "remove": if os.path.isdir(applyDir): self.changedFiles.addObj(applyDir, ChangedFiles.DIR_REMOVED, self.functObj.currentBelong) # удаляем директорию try: removeDir(applyDir) except OSError: self.setError(_("Failed to delete the directory: ") + \ applyDir) return "", False, [] # Очищаем директорию if objHead.typeAppend == "clear": if os.path.isdir(applyDir): for rmPath in os.listdir(applyDir): removePath = pathJoin(applyDir, rmPath) if os.path.isdir(removePath): # удаляем директорию try: removeDir(removePath) except OSError: self.setError( _("Failed to delete the directory: ") + \ removePath) else: try: os.unlink(removePath) except OSError: self.setError( _("Failed to delete: ") + removePath) return "", False, [] # Созданные директории createdDirs = [] # chmod - изменяем права if "chmod" in objHead.params: mode = self.__octToInt(objHead.params['chmod']) if mode: if not os.path.exists(applyDir): crDirs = self.createDir(applyDir, mode, self.uid, self.gid) if not crDirs: return "", False, [] if not crDirs is True: createdDirs += crDirs else: try: os.chmod(applyDir, mode) except OSError: self.setError(_("Failed to change the mode for " "the directory: ") + applyDir) else: self.setError(_("Wrong value 'chmod' in the template") + _(": ") + templateDirFile) return "", False, [] # chown - изменяем владельца и группу if "chown" in objHead.params: owner = objHead.params['chown'] if owner: if ":" in owner: strUid, strGid = owner.split(":") import pwd try: uid = pwd.getpwnam(strUid).pw_uid except (KeyError, TypeError): self.setError(_("No such user on the system: ") + strUid) self.setError(_("Wrong value 'chown' in the template") + _(": ") + templateDirFile) return "", False, [] try: import grp gid = grp.getgrnam(strGid).gr_gid except (KeyError, TypeError): self.setError(_("Group not found on the system: ") + strGid) self.setError( _("Wrong value 'chown' in the template") + _(": ") + templateDirFile) return "", False, [] if not os.path.exists(applyDir): crDirs = self.createDir(applyDir, False, uid, gid) if not crDirs: return "", False, [] if not crDirs is True: createdDirs += crDirs else: if not chownConfDir(applyDir, uid, gid, templateDirFile): return "", False, [] else: self.setError(_("Wrong value 'chown' in the template") + _(": ") + templateDirFile) return "", False, [] else: self.setError(_("Wrong value 'chown' in the template") + _(": ") + templateDirFile) return "", False, [] else: # Устанавливаем владельцем директории, пользователя по умолчанию # (переменная шаблона ur_login) if os.path.exists(applyDir): self.changedFiles.addObj(applyDir, ChangedFiles.DIR_EXISTS, self.functObj.currentBelong) tUid, tGid = getModeFile(applyDir, mode="owner") if (self.uid, self.gid) != (tUid, tGid): if not chownConfDir(applyDir, self.uid, self.gid, templateDirFile): return "", False, [] else: self.changedFiles.addObj(applyDir, ChangedFiles.DIR_CREATED, self.functObj.currentBelong) crDirs = self.createDir(applyDir, False, self.uid, self.gid) if not crDirs: return "", False, [] if not crDirs is True: createdDirs += crDirs if not objHead: applyDir = "" if applyDir: if ("autoupdate" in optDir or "autoupdate" in objHead.params) and \ not self.objVar.Get('cl_merge_pkg_pass'): self.autoUpdateDirs.append(applyDir) return applyDir, objHead, createdDirs def getUidFromPasswd(self, strUid): """Get uid by username from chroot passwd file""" passwdFile = os.path.join(self._baseDir, 'etc/passwd') if os.path.exists(passwdFile): mapUid = dict( filter(lambda x: x and len(x) > 1 and x[0] and x[1], map(lambda x: x.split(':')[0:3:2], filter(lambda x: not x.startswith('#'), open(passwdFile, 'r'))))) if strUid in mapUid: return int(mapUid[strUid]) return None def getGidFromGroup(self, strGid): """Get gid by groupname from chroot group file""" groupFile = os.path.join(self._baseDir, 'etc/group') if os.path.exists(groupFile): mapGid = dict( filter(lambda x: x and len(x) > 1 and x[0] and x[1], map(lambda x: x.split(':')[0:3:2], filter(lambda x: not x.startswith('#'), open(groupFile, 'r'))))) if strGid in mapGid: return int(mapGid[strGid]) return None def checkOnNewConfigName(self, pathFile): """ Check on need update and return pathFile """ # if file in PROTECT_MASK or not in PROTECT chrootPath = self.objVar.Get('cl_chroot_path') if not filter(pathFile.startswith, map(lambda x: pathJoin(chrootPath, x), self.objVar.Get('cl_config_protect'))) or \ filter(pathFile.startswith, map(lambda x: pathJoin(chrootPath, x), self.objVar.Get('cl_config_protect_mask'))): return pathFile # if file was already modified by templates if pathFile in self.changedFiles.data.keys(): return pathFile # if using already created ._cfg file if self.configMode != T_ORIGIN: return pathFile # not current package file pkg = self.functObj.currentBelong if not pkg: if not self.allContents: fillContents(self.allContents, self.objVar.Get('cl_config_protect'), prefix=self.objVar.Get('cl_chroot_path')) origName = pathFile if chrootPath == '/' \ else pathFile[len(chrootPath):] if origName in self.allContents: pkg = self.allContents[origName] else: return pathFile pkg = isPkgInstalled(pkg, sortByVersion=True, prefix=chrootPath) if not pkg: return pathFile if checkContents("{CATEGORY}/{PF}".format(**pkg[-1]), pathFile, prefix=chrootPath, reservedFile='/var/lib/calculate/-CONTENTS-{PN}'.format( **pkg[-1]) \ if self.objVar.Get( 'cl_ebuild_phase') == 'postinst' \ else None): return pathFile real_filename = os.path.basename(pathFile) real_dirname = os.path.dirname(pathFile) self.configMode = T_NEWCFG return os.path.join(real_dirname, "._cfg0000_%s" % real_filename) def getApplyHeadTemplate(self, nameFileTemplate, nameFileConfig, templateFileType, optFile): """Применяет заголовок к шаблону (права, владелец, и.т. д)""" def function(text): """Функция обработки функций в заголовке""" return self.applyFuncTemplate(text, nameFileTemplate) def preReturn(pathProg): """Действия перед выходом из метода""" if pathProg: os.chdir(pathProg) def chownConfFile(nameFileConfig, uid, gid, nameFileTemplate, checkExists=True): """Изменение владельца конфигурационного файла""" try: if checkExists and not os.path.exists(nameFileConfig): # Создание файла FD = open(nameFileConfig, "w") FD.close() os.lchown(nameFileConfig, uid, gid) except (OSError, Exception) as e: if hasattr(e, 'errno') and e.errno == 13 and \ "var/calculate/remote" in nameFileConfig: return True import pwd import grp try: userName = pwd.getpwuid(uid).pw_name except (KeyError, TypeError): userName = str(uid) try: groupName = grp.getgrgid(gid).gr_name except (KeyError, TypeError): groupName = str(gid) owner = userName + ":" + groupName self.setError(_("Failed to apply template file %s") \ % nameFileTemplate) self.setError(_("error") + " " + \ "'chown %s %s'" % (owner, nameFileConfig)) return False return True def chmodConfFile(nameFileConfig, mode, nameFileTemplate, checkExists=True): """Изменения режима доступа конфигурационного файла""" try: if checkExists and not os.path.exists(pathOldFile): # Создание файла FD = open(nameFileConfig, "w") FD.close() os.chmod(nameFileConfig, mode) except (OSError, Exception) as e: if hasattr(e, 'errno') and e.errno == 13 and \ "var/calculate/remote" in nameFileConfig: return True self.setError(_("Failed to apply template file %s") \ % nameFileTemplate) self.setError( _("error") + " " + "'chmod %s %s'" % (str(oct(mode)), nameFileConfig)) return False return True self.closeFiles() pathProg = "" self.executeType = None # Файлы в системе к которым были применены шаблоны # В случае бинарного типа файла читаем шаблон if templateFileType == "bin": self.nameFileTemplate = os.path.abspath(nameFileTemplate) self.F_TEMPL = self.openTemplFile(self.nameFileTemplate) if not self.F_TEMPL: self.setError(_("Failed to open the template") + _(": ") + self.nameFileTemplate) return [], False self.textTemplate = self.F_TEMPL.read() self.closeTemplFile() objHeadNew = fileHeader(nameFileTemplate, self.textTemplate, False, templateFileType, objVar=self.objVar, function=function, templateObj=self) # файл шаблона не будет применен if not objHeadNew.headerTerm: if objHeadNew.getError(): self.setError(_("Incorrect template") + _(": ") + nameFileTemplate) return [], False # add packeges for reconfigure self._processMergePostmerge(objHeadNew.params, nameFileTemplate) # Родительская директория path = optFile["path"] # Изменяем название родительской директории if "path" in objHeadNew.params: path = objHeadNew.params['path'] if path and path[0] == "~": # Получаем путь с заменой ~ на директорию пользователя path = os.path.join(self.homeDir, path.partition("/")[2], "")[ :-1] elif not path or path and path[0] != "/": self.setError(_("Wrong value 'path' in the template") + _(": ") + nameFileTemplate) return [], False else: path = pathJoin(self._baseDir, path) # Путь к оригинальному файлу - pathOldFile # Изменяем путь к оригинальному файлу if objHeadNew.params.has_key("name"): nameFile = objHeadNew.params['name'] if "/" in nameFile or nameFile == ".." or nameFile == ".": self.setError(_("Wrong value 'name' in the template") + _(": ") + nameFileTemplate) return [], False # Новый путь к оригинальному файлу pathOldFile = pathJoin(path, nameFile) else: pathOldFile = pathJoin(path, os.path.split(nameFileConfig)[1]) pathOrigFile = pathOldFile if self.objVar.Get('cl_protect_use_set') == 'on': pathOldFile = self.fixNameFileConfig(pathOldFile) pathOldFile = self.checkOnNewConfigName(pathOldFile) # буффер для использование в link= newBuffer = None applyFiles = [pathOldFile] # Фильтрация шаблонов по названию файла realPath = os.path.join("/", pathOldFile.partition(self._baseDir)[2]) if realPath in self.filesFilter: return [], False typeAppendTemplate = objHeadNew.typeAppend # Параметр exec if "exec" in objHeadNew.params or "run" in objHeadNew.params: if "exec" in objHeadNew.params: paramName = "exec" else: paramName = "run" self.executeType = paramName execPath = objHeadNew.params[paramName] if not os.access(execPath, os.X_OK): self.setError( _("Wrong value '%s' in the template") % paramName + _(": ") + nameFileTemplate) self.setError(_("Failed to execute %s") % execPath) return [], False if typeAppendTemplate == "join": self.setError(_("Wrong value 'append=join' in the template") + _(": ") + nameFileTemplate) return [], False # Очищаем оригинальный файл if typeAppendTemplate == "clear": try: open(pathOldFile, "w").truncate(0) except IOError: self.setError(_("Template error") + _(": ") + nameFileTemplate) self.setError(_("Failed to clear the file") + _(": ") + pathOldFile) return applyFiles, False # Удаляем оригинальный файл if typeAppendTemplate == "remove": if objHeadNew.params.has_key("force"): pathOldFile = pathOrigFile self.configMode = T_ORIGIN try: if os.path.islink(pathOldFile): # удаляем ссылку try: os.unlink(pathOldFile) return applyFiles, False except OSError: self.setError(_("Template error") + _(": ") + nameFileTemplate) self.setError(_("Failed to delete the link") + _(": ") + pathOldFile) return [], False if os.path.isfile(pathOldFile) and self.configMode == T_ORIGIN: # удаляем файл try: os.remove(pathOldFile) return applyFiles, False except OSError: self.setError(_("Template error") + _(": ") + nameFileTemplate) self.setError(_("Failed to delete the file") + _(": ") + pathOldFile) return [], False finally: pattern = "%s/._cfg????_%s" % (os.path.dirname(pathOrigFile), os.path.basename(pathOrigFile)) for fn in glob.glob(pattern): try: os.unlink(fn) except OSError: pass if self.functObj.currentBelong: self.changedFiles.addObj(pathOrigFile, ChangedFiles.FILE_REMOVED, self.functObj.currentBelong) return [], False # Пропускаем обработку шаблона elif typeAppendTemplate == "skip": return [], False # Создаем директорию для файла если ее нет if not os.path.exists(path): if not self.createDir(path): return [], False # В случае force if objHeadNew.params.has_key("force"): if os.path.islink(pathOldFile): # удаляем ссылку newBuffer = "" try: os.unlink(pathOldFile) except OSError: self.setError(_("Template error") + _(": ") + nameFileTemplate) self.setError(_("Failed to delete the link") + _(": ") + pathOldFile) return [], False if os.path.isfile(pathOldFile): # удаляем файл newBuffer = "" try: os.remove(pathOldFile) except OSError: self.setError(_("Template error") + _(": ") + nameFileTemplate) self.setError(_("Failed to delete the file") + _(": ") + pathOldFile) return [], False flagSymlink = False # Если есть параметр mirror if objHeadNew.params.has_key("mirror"): if objHeadNew.params.has_key("link"): templateFile = objHeadNew.params['link'] if templateFile and templateFile[0] == "~": # Получаем директорию пользователя templateFile = os.path.join(self.homeDir, templateFile.partition("/")[2], "")[:-1] templateFile = pathJoin(self._baseDir, templateFile) if not os.path.exists(templateFile): if os.path.exists(pathOldFile): try: os.remove(pathOldFile) except OSError: self.setError(_("Template error") + _(": ") + nameFileTemplate) self.setError( _("Failed to delete the file") + _(": ") + pathOldFile) return [], False elif not os.path.exists(pathOldFile): return [], False # Если есть указатель на файл шаблона (link) if objHeadNew.params.has_key("link") and \ not objHeadNew.params.has_key("symbolic"): templateFile = objHeadNew.params['link'] if templateFile and templateFile[0] == "~": # Получаем директорию пользователя templateFile = os.path.join( self.homeDir, templateFile.partition("/")[2], "")[:-1] templateFile = pathJoin(self._baseDir, templateFile) foundTemplateFile = os.path.exists(templateFile) buff = None fMode, fUid, fGid = None, None, None if foundTemplateFile: try: F_CONF = self.openTemplFile(templateFile) if not F_CONF: raise IOError buff = F_CONF.read() F_CONF.close() fMode, fUid, fGid = getModeFile(templateFile) except (OSError, IOError): self.setError(_("Template error") + _(": ") + nameFileTemplate) self.setError(_("Failed to open the file") + _(": ") + templateFile) return [], False if os.path.exists(pathOldFile): newBuffer = "" try: os.remove(pathOldFile) except OSError: self.setError(_("Template error") + _(": ") + nameFileTemplate) self.setError(_("Failed to delete the file") + _(": ") + pathOldFile) return [], False if buff is not None: try: FD = open(pathOldFile, "w+") newBuffer = buff FD.write(buff) FD.close() except IOError: self.setError(_("Template error") + _(": ") + nameFileTemplate) self.setError(_("Failed to create the file") + " '%s'" \ % pathOldFile) return [], False oMode = getModeFile(pathOldFile, mode="mode") # Если права не совпадают, меняем права if fMode != oMode: if not chmodConfFile(pathOldFile, fMode, nameFileTemplate, checkExists=False): return [], False # Если символическая ссылка prevOldFile = None if objHeadNew.params.has_key("symbolic"): prevOldFile = pathOldFile pathOldFile = objHeadNew.params['link'] flagSymlink = True if not pathOldFile: raise TemplatesError( _("Missed source link in template '%s'") % str(nameFileTemplate)) if not "/" == pathOldFile[0]: pathLink = os.path.split(os.path.abspath(prevOldFile))[0] pathProg = os.getcwd() try: os.chdir(pathLink) except OSError: self.setError(_("Template error") + _(": ") + nameFileTemplate) self.setError( _("Failed to change the current directory to") + \ " " + pathLink) return [], False # chmod - изменяем права if objHeadNew.params.has_key("chmod"): mode = self.__octToInt(objHeadNew.params['chmod']) if mode: if not chmodConfFile(pathOldFile, mode, nameFileTemplate): preReturn(pathProg) return [], False else: self.setError(_("Wrong value 'chmod' in the template") + _(": ") + nameFileTemplate) preReturn(pathProg) return [], False # chown - изменяем владельца и группу if objHeadNew.params.has_key("chown"): owner = objHeadNew.params['chown'] if owner: if ":" in owner: strUid, strGid = owner.split(":") if strUid.isdigit(): uid = int(strUid) else: uid = self.getUidFromPasswd(strUid) import pwd try: if uid is None: uid = pwd.getpwnam(strUid).pw_uid except (KeyError, TypeError): self.setError(_("No such user on the system: ") + strUid) self.setError( _("Wrong value 'chown' in the template") + _(": ") + nameFileTemplate) preReturn(pathProg) return [], False if strGid.isdigit(): gid = int(strGid) else: gid = self.getGidFromGroup(strGid) try: if gid is None: import grp gid = grp.getgrnam(strGid).gr_gid except (KeyError, TypeError): self.setError(_("Group not found on the system: ") + strGid) self.setError( _("Wrong value 'chown' in the template") + _(": ") + nameFileTemplate) preReturn(pathProg) return [], False # Изменяем владельца файла if not chownConfFile(pathOldFile, uid, gid, nameFileTemplate): preReturn(pathProg) return [], False else: self.setError(_("Wrong value 'chown' in the template") + _(": ") + nameFileTemplate) preReturn(pathProg) return [], False else: self.setError(_("Wrong value 'chown' in the template") + _(": ") + nameFileTemplate) preReturn(pathProg) return [], False if not flagSymlink: self.openFiles(nameFileTemplate, pathOldFile, objHeadNew.fileType, newBuffer) if self.getError(): return [], False if not objHeadNew.params.has_key("chown"): # Устанавливаем владельцем конфигурационного файла, # пользователя по умолчанию (переменная шаблона ur_login) if os.path.exists(pathOldFile): tUid, tGid = getModeFile(pathOldFile, mode="owner") if (self.uid, self.gid) != (tUid, tGid): # Изменяем владельца файла if not chownConfFile(pathOldFile, self.uid, self.gid, nameFileTemplate, checkExists=False): preReturn(pathProg) return [], False if flagSymlink: if os.path.exists(prevOldFile) or os.path.islink(prevOldFile): try: if os.path.islink(prevOldFile): # если ссылка то удаляем её os.unlink(prevOldFile) else: # иначе удаляем файл os.remove(prevOldFile) except OSError: self.setError(_("Template error") + _(": ") + nameFileTemplate) self.setError(_("Failed to delete the file") + _(": ") + prevOldFile) preReturn(pathProg) return [], False if not "/" == pathOldFile[0]: applyFiles = [ prevOldFile] # ,os.path.join(pathLink,pathOldFile)] else: applyFiles = [prevOldFile] # ,pathOldFile] try: os.symlink(pathOldFile, prevOldFile) except OSError: self.setError(_("Template error") + _(": ") + \ nameFileTemplate) self.setError(_("Failed to create a symbolic link") + _(": ") + \ "%s -> %s" % (prevOldFile, pathOldFile)) preReturn(pathProg) return [], False if not objHeadNew.body.strip(): preReturn(pathProg) if "protected" in objHeadNew.params: self.protectedFiles += applyFiles return applyFiles, False else: applyFiles = [pathOldFile] if pathProg: os.chdir(pathProg) # Если файлы заменяются не нужно их обрабатывать дальше if typeAppendTemplate == "replace" and \ not objHeadNew.params.has_key("symbolic") and \ objHeadNew.params.has_key("link"): preReturn(pathProg) return applyFiles, False if not pathOldFile in self.dictProcessedTemplates: self.dictProcessedTemplates[pathOldFile] = [] self.dictProcessedTemplates[pathOldFile].append(nameFileTemplate) preReturn(pathProg) if ("autoupdate" in optFile or "autoupdate" in objHeadNew.params) \ and not self.objVar.Get('cl_merge_pkg_pass'): reCfg = re.compile(r"/._cfg\d{4}_", re.S) self.autoUpdateFiles += map(lambda x: reCfg.sub('/', x), applyFiles) if "protected" in objHeadNew.params: self.protectedFiles += applyFiles return applyFiles, objHeadNew def createNewClass(self, name, bases, attrs=None): """Создает объект нового класса createNewClass(self, name, bases, attrs) name - имя класса - str, bases - cписок наследуемых классов - (tuple), attrs - аттрибуты класса - {dict} """ if attrs is None: attrs = {} class newMethod(object): # Объединяем конфигурации def join(self, newObj): if newObj.__class__.__name__ == self.__class__.__name__: if hasattr(self, "docObj"): self.docObj.joinDoc(newObj.doc) # Пост обработка if hasattr(self, "postXML"): self.postXML() attrsNew = {"configName": name} if attrs: attrsNew.update(attrs) newCl = type(name, bases + (newMethod, object,), attrsNew) return newCl def fileIsUtf(self, fileName): """Проверяет файл на кодировку UTF-8""" if os.path.isfile(fileName): FD = open(os.path.abspath(fileName), 'r') newTemplate = FD.read(1) + FD.read() FD.close() try: newTemplate.decode("UTF-8") except UnicodeDecodeError: return False return True return False def joinTemplate(self, nameFileTemplate, nameFileConfig, optFile=None): """Объединения шаблона и конф. файла join(nameFileTemplate, nameFileConfig, ListOptTitle) Объединение шаблона nameFileTemplate и конф. файла nameFileConfig, ListOptTitle - список строк которые добавятся в заголовок optFile = опции для шаблона """ if optFile is None: optFile = {} # Выполняем условия для блока текста а так-же заменяем переменные self.nameFileTemplate = os.path.abspath(nameFileTemplate) self.F_TEMPL = self.openTemplFile(self.nameFileTemplate) self.textTemplate = self.F_TEMPL.read() self.configMode = T_ORIGIN self.closeTemplFile() # Флаг копирования шаблона в конфигурационный файл flagCopyTemplate = True # Тип шаблона бинарный или текстовый if self.textTemplate[:11] == "# Calculate": templateFileType = "text" else: templateFileType = self.getTemplateType() headerLine = self.getHeaderText(self.textTemplate) if headerLine: moduleParam = filter(lambda x: x.startswith('env='), headerLine.split()) if moduleParam: self.objVar.defaultModule = moduleParam[0].partition('=')[2] try: importlib.import_module( "calculate.%s.variables" % self.objVar.defaultModule) except (ImportError, AttributeError): return [] if templateFileType != "bin": # Заменяем переменные на их значения self.textTemplate = self.applyVarsTemplate(self.textTemplate, nameFileTemplate) flagCopyTemplate = False if not optFile: optFile = {"path": os.path.split(nameFileConfig)[0]} filesApply, objHeadNew = self.getApplyHeadTemplate(nameFileTemplate, nameFileConfig, templateFileType, optFile) if not objHeadNew: return filesApply if filesApply and not filter(lambda x: "calculate/ini.env" in x, filesApply): self.templateModify() if templateFileType != "bin": # Вычисляем условные блоки objHeadNew.body = self.applyTermsTemplate(objHeadNew.body, nameFileTemplate) # Вычисляем функции objHeadNew.body = self.applyFuncTemplate(objHeadNew.body, nameFileTemplate) # Настоящее имя конфигурационного файла nameFileConfig = filesApply[0] # Флаг - кодировка с бинарными примесями у файла шаблона включаем при # условии текстового файла и кодировки отличной от UTF-8 flagNotUtf8New = False # Флаг - кодировка с бинарными примесями у оригинального файла flagNotUtf8Old = False if not flagCopyTemplate: # проверяем кодировку шаблона if not self.fileIsUtf(nameFileTemplate): flagNotUtf8New = True if not (objHeadNew.params.has_key("link") and objHeadNew.params.has_key("symbolic")): # проверяем кодировку оригинального файла if not self.fileIsUtf(nameFileConfig): flagNotUtf8Old = True self.textTemplate = objHeadNew.body # Список примененных шаблонов ListOptTitle = [] if nameFileConfig in self.dictProcessedTemplates: ListOptTitle = self.dictProcessedTemplates[nameFileConfig] # Титл конфигурационного файла title = "" if ListOptTitle: title = self.getTitle(objHeadNew.comment, ListOptTitle, configPath=nameFileConfig) title = title.encode("UTF-8") objHeadOld = False if objHeadNew.comment: objHeadOld = fileHeader(nameFileConfig, self.textConfig, objHeadNew.comment) elif objHeadNew.fileType and \ objHeadNew.typeAppend in ("before", "after"): configFileType = self.getFileType(nameFileConfig) objHeadOld = fileHeader(nameFileConfig, self.textConfig, fileType=configFileType) # Строка вызова скрипта (#!/bin/bash ...) execStr = "" if objHeadNew.execStr: execStr = objHeadNew.execStr elif objHeadOld and objHeadOld.execStr: execStr = objHeadOld.execStr if objHeadNew.fileType != 'patch': wrongOpt = [x for x in ("multiline", "dotall") if objHeadNew.params.has_key(x)] if wrongOpt: self.setError( _("Option %s should be used for format=patch only") % wrongOpt[0]) return None if objHeadNew.fileType != 'dconf': wrongOpt = [x for x in ("dconf",) if objHeadNew.params.has_key(x)] if wrongOpt: self.setError( _("Option %s should be used for format=dconf only") % wrongOpt[0]) return None if objHeadNew.fileType: formatTemplate = objHeadNew.fileType typeAppendTemplate = objHeadNew.typeAppend if formatTemplate in ("patch", "diff", "dconf"): if typeAppendTemplate != "patch": self.setError( _("Wrong option append=%(type)s in template %(file)s") % {"type": typeAppendTemplate, "file": nameFileTemplate}) return None # создаем объект формата шаблона objTempl = self.getFormatObj(formatTemplate, self.textTemplate) if formatTemplate == 'patch': if objHeadNew.params.has_key("multiline"): objTempl.setMultiline() if objHeadNew.params.has_key("dotall"): objTempl.setDotall() if formatTemplate == 'dconf': if "dconf" in objHeadNew.params: objTempl.setPath(objHeadNew.params['dconf']) objTempl.setUser(self.objVar.Get('ur_login')) if not objTempl: self.setError( _("Incorrect header parameter format=%s " "in the template") \ % formatTemplate + " " + nameFileTemplate) return None if objHeadOld and objHeadOld.body: self.textConfig = objHeadOld.body # обработка конфигурационного файла self.textTemplate = objTempl.processingFile(self.textConfig, pathJoin( self.objVar.Get( 'cl_chroot_path'), self.objVar.Get( 'cl_root_path'))) error = objTempl.getError() if error: if formatTemplate == "dconf": self.printERROR(error.strip()) raise TemplatesError(_("Failed to use dconf ") + \ nameFileTemplate) raise TemplatesError(_("Failed to use patch ") + \ nameFileTemplate) elif (formatTemplate == 'diff' and self.objVar.Get('cl_verbose_set') == "on"): self.printSUCCESS(_("Appling patch") + " " + \ os.path.basename(nameFileTemplate)) if execStr: self.textConfig = execStr + title + self.textTemplate else: self.textConfig = title + self.textTemplate if formatTemplate in ("diff",): return objTempl.patchFiles elif formatTemplate in ("dconf",): return [] else: self.saveConfFile() if 'run' in objHeadNew.params: if not self.executeTemplate(self.textConfig, objHeadNew.params['run']): self.setError(_("Wrong template") + _(": ") + \ nameFileTemplate) self.setError(_("Failed to execute") + _(": ") + \ self.nameFileConfig) return None return None return filesApply if 'exec' not in objHeadNew.params else None # Создаем объект в случае параметра format в заголовке if (typeAppendTemplate == "replace" or typeAppendTemplate == "before" or typeAppendTemplate == "after") and \ not (formatTemplate == "bin" or formatTemplate == "raw"): # Преобразовываем бинарные файлы objTxtCoder = None if flagNotUtf8New: objTxtCoder = utfBin() self.textTemplate = objTxtCoder.encode(self.textTemplate) # создаем объект формата шаблона objTemplNew = self.getFormatObj(formatTemplate, self.textTemplate) if not objTemplNew: self.setError(_("Incorrect header parameter format=%s " "in the template") % formatTemplate + " " + nameFileTemplate) return None if "xml_" in formatTemplate: if objTemplNew.getError(): self.setError(_("Wrong template") + _(": ") + nameFileTemplate) return None # Имя файла внутри xml xfce конфигурационных файлов nameRootNode = \ nameFileConfig.rpartition("/")[2].split(".")[0] objTemplNew.setNameBodyNode(nameRootNode) # Объект Документ docObj = objTemplNew.docObj # Удаление комментариев из документа docObj.removeComment(docObj.getNodeBody()) # Добавление необходимых переводов строк docObj.insertBRtoBody(docObj.getNodeBody()) # Добавление необходимых разделителей между областями docObj.insertBeforeSepAreas(docObj.getNodeBody()) # Пост обработка if 'postXML' in dir(objTemplNew): objTemplNew.postXML() # Получение текстового файла из XML документа self.textTemplate = objTemplNew.getConfig().encode("UTF-8") # Если не UTF-8 производим преобразование if objTxtCoder: self.textTemplate = objTxtCoder.decode(self.textTemplate) # Титл для объединения if ListOptTitle: title = self.getTitle(objTemplNew._comment, ListOptTitle, configPath=nameFileConfig) title = title.encode("UTF-8") # Замена if typeAppendTemplate == "replace": if "xml_" in formatTemplate: data = self.textTemplate.split("\n") data.insert(1, title) self.textConfig = "\n".join(data) else: if objHeadNew.execStr: self.textConfig = objHeadNew.execStr + title + \ self.textTemplate else: self.textConfig = title + self.textTemplate self.saveConfFile() if 'run' in objHeadNew.params: if not self.executeTemplate(self.textConfig, objHeadNew.params['run']): self.setError(_("Wrong template") + _(": ") + \ nameFileTemplate) self.setError(_("Failed to execute") + _(": ") + \ self.nameFileConfig) return None return None return filesApply if not 'exec' in objHeadNew.params else None # Вверху elif typeAppendTemplate == "before": if "xml_" in formatTemplate: self.setError( _("Wrong option append=before in template %s") % nameFileTemplate) return None if objHeadOld and objHeadOld.body: self.textConfig = objHeadOld.body if self.textTemplate and self.textTemplate[-1] == "\n": tmpTemplate = self.textTemplate + self.textConfig else: tmpTemplate = self.textTemplate + "\n" + self.textConfig if execStr: self.textConfig = execStr + title + tmpTemplate else: self.textConfig = title + tmpTemplate self.saveConfFile() if 'run' in objHeadNew.params: if not self.executeTemplate(self.textConfig, objHeadNew.params['run']): self.setError(_("Wrong template") + _(": ") + nameFileTemplate) self.setError(_("Failed to execute") + _(": ") + self.nameFileConfig) return None return None return filesApply if 'exec' not in objHeadNew.params else None # Внизу elif typeAppendTemplate == "after": if "xml_" in formatTemplate: self.setError( _("Wrong option append=after in template %s") % nameFileTemplate) return None if objHeadOld and objHeadOld.body: self.textConfig = objHeadOld.body if self.textTemplate[-1] == "\n": tmpTemplate = self.textConfig + self.textTemplate else: tmpTemplate = self.textConfig + "\n" + self.textTemplate if execStr: self.textConfig = execStr + title + tmpTemplate else: self.textConfig = title + tmpTemplate self.saveConfFile() if 'run' in objHeadNew.params: if not self.executeTemplate(self.textConfig, objHeadNew.params['run']): self.setError(_("Wrong template") + _(": ") + nameFileTemplate) self.setError(_("Failed to execute") + _(": ") + self.nameFileConfig) return None return None return filesApply if 'exec' not in objHeadNew.params else None # Объединение elif typeAppendTemplate == "join": objTxtCoder = None if flagNotUtf8New: objTxtCoder = utfBin() self.textTemplate = objTxtCoder.encode(self.textTemplate) if formatTemplate == "raw": self.setError( _("Incorrect header parameter append=%s " "in the template") % typeAppendTemplate + " " + nameFileTemplate) return None # создаем объект формата шаблона objTemplNew = self.getFormatObj(formatTemplate, self.textTemplate) if not objTemplNew: self.setError( _("Incorrect header parameter format=%s in " "the template") % formatTemplate + " " + nameFileTemplate) return None if "xml_" in formatTemplate: if objTemplNew.getError(): self.setError(_("Wrong template") + _(": ") + \ nameFileTemplate) return None nameRootNode = nameFileConfig.rpartition("/")[2].split(".")[ 0] objTemplNew.setNameBodyNode(nameRootNode) # Титл для объединения if ListOptTitle: title = self.getTitle(objTemplNew._comment, ListOptTitle, configPath=nameFileConfig) title = title.encode("UTF-8") # В случае пустого конфигурационного файла reNoClean = re.compile("[^\s]", re.M) if not self.textConfig or \ not reNoClean.search(self.textConfig): self.textConfig = "" objHeadOld = fileHeader(nameFileConfig, self.textConfig, objTemplNew._comment) if objHeadOld.body: self.textConfig = objHeadOld.body else: self.textConfig = "" if flagNotUtf8Old: objTxtCoder = utfBin() self.textConfig = objTxtCoder.encode(self.textConfig) # создаем объект формата шаблона для конфигурационного файла objTemplOld = self.getFormatObj(formatTemplate, self.textConfig) if not objTemplOld: self.setError(_("Error in template %s") % nameFileConfig) return None if "xml_" in formatTemplate: if objTemplOld.getError(): self.setError(_("Wrong template") + _(": ") + nameFileConfig) return None nameRootNode = nameFileConfig.rpartition("/")[2].split(".")[ 0] objTemplOld.setNameBodyNode(nameRootNode) objTemplOld.join(objTemplNew) if "xml_" in formatTemplate: if objTemplOld.getError(): self.setError(_("Wrong template") + _(": ") + \ nameFileTemplate) return None data = objTemplOld.getConfig().encode("UTF-8").split("\n") data.insert(1, title) self.textConfig = "\n".join(data) else: if execStr: self.textConfig = execStr + title + \ objTemplOld.getConfig().encode( "UTF-8") else: self.textConfig = title + \ objTemplOld.getConfig().encode( "UTF-8") # Декодируем если кодировка не UTF-8 if objTxtCoder: self.textTemplate = objTxtCoder.decode(self.textTemplate) self.textConfig = objTxtCoder.decode(self.textConfig) self.saveConfFile() if 'run' in objHeadNew.params: if not self.executeTemplate(self.textConfig, objHeadNew.params['run']): self.setError(_("Wrong template") + _(": ") + \ nameFileTemplate) self.setError(_("Failed to execute") + _(": ") + \ self.nameFileConfig) return None return None return filesApply if not 'exec' in objHeadNew.params else None else: self.setError(_("Wrong template option (type append)") + _(": ") + typeAppendTemplate) return None else: self.setError(_("Template type not found: ") + nameFileTemplate) return None class scanDirectoryClt(object): """Класс для cканирования директорий с файлами .clt""" # Расширение файла шаблона extFileTemplate = ".clt" lenExtFileTemplate = len(extFileTemplate) filterApplyTemplates = {} reHeader = re.compile(r"\s*#\s*calculate\s*", re.I) def __init__(self, objVar=None): if objVar: self.objVar = objVar def processingFile(self, path, prefix, optFile=None): """Обработка в случае файла""" return True def hasBelong(self, filename): """ Change belong function """ f = open(filename, 'r') s = f.readline() if self.reHeader.search(s): while s: if "belong(" in s or "merge(" in s: return True if not s.strip().endswith('\\'): break s = f.readline() return False def scanningTemplates(self, scanDir, prefix=None, flagDir=False, objVar=None): """Сканирование и обработка шаблонов в директории scanDir""" if not objVar: objVar = self.objVar if not prefix: prefix = os.path.realpath(scanDir) if flagDir or stat.S_ISDIR(os.lstat(str(scanDir))[stat.ST_MODE]): for fileOrDir in sorted(listDirectory(scanDir)): absPath = os.path.join(scanDir, fileOrDir) stInfo = os.lstat(str(absPath)) statInfo = stInfo[stat.ST_MODE] if fileOrDir.endswith(self.extFileTemplate) and \ stat.S_ISREG(statInfo): if (not self.filterApplyTemplates and objVar.Get('cl_merge_set') == 'off' or self.filterApplyTemplates and absPath[:-self.lenExtFileTemplate] in self.filterApplyTemplates.keys() or self.hasBelong(absPath)): prevDefault = objVar.defaultModule if not self.processingFile(absPath, prefix): return False objVar.defaultModule = prevDefault elif stat.S_ISDIR(statInfo): if not self.scanningTemplates(absPath, prefix, True): return False return True class templateClt(scanDirectoryClt, Template): """Класс для обработки шаблонов c расширением .clt""" def __init__(self, objVar, postmergePkgs, **kwargs): self.checkNumberTemplate = True Template.__init__(self, objVar, cltObj=False, **kwargs) self.postmergePkgs = postmergePkgs applyPackages = ["calculate-core"] self.flagApplyTemplates = False if self.objVar.Get("cl_name") in applyPackages: self.flagApplyTemplates = True # Базовая директория переноса шаблонов "/mnt/calculate" или "/" и.т.д self._chrootDir = os.path.normpath(self.objVar.Get("cl_chroot_path")) def applyTemplate(self, path): """Применение отдельного .clt шаблона""" if not self.flagApplyTemplates: return True return self.processingFile(path, "") def processingFile(self, path, prefix, optFile=None): """Обработка в случае шаблона файла clt""" self.numberProcessTempl += 1 self.numberProcessTemplates(self.numberProcessTempl) # Пропуск шаблонов директорийscanningTemplates if self.templDirNameFile == os.path.split(path)[1]: return True self.functObj.currentBelong = "" # Проверка на переменные в названии файла if not self.getNeedTemplate(path): if self.getError(): return False return True if self.getError(): return False if prefix and prefix[-1] == "/": prefix = prefix[:-1] if prefix and path.startswith(prefix): nameFileConfig = path.partition(prefix)[2] else: nameFileConfig = path nameFileConfig = nameFileConfig[:-self.lenExtFileTemplate] origFileName = nameFileConfig nameFileConfig = pathJoin(self._baseDir, nameFileConfig) # файл в системе без условий nameFileConfig = "/".join(map(lambda x: x.split("?")[0], nameFileConfig.split("/"))) # Записываем в переменную обрабатываемый файл self.objVar.Set("cl_pass_file", nameFileConfig) filesApl = self.joinTemplate(path, nameFileConfig) if self.getError(): return False if filesApl: if self.functObj.currentBelong: self._addFile(filesApl) else: if origFileName in self.filterApplyTemplates: for pkg in self.filterApplyTemplates[origFileName]: self._addFile(filesApl, pkg=pkg) else: if not self.allContents: fillContents(self.allContents, self.objVar.Get('cl_config_protect'), prefix=self.objVar.Get('cl_chroot_path')) for fn in filesApl: fn_orig = PkgContents.reCfg.sub('/', fn) if self.objVar.Get('cl_chroot_path') != '/': fn_orig = \ fn_orig[len(self.objVar.Get('cl_chroot_path')):] if fn_orig in self.allContents: self._addFile([fn], pkg=self.allContents[fn_orig]) # Настоящее имя конфигурационного файла nameFileConfig = filesApl[0] # Пишем время модификации *.env файлов if nameFileConfig.endswith(".env"): nameEnvFile = os.path.basename(nameFileConfig) self.functObj.timeConfigsIni[nameEnvFile] = float(time.time()) self.filesApply += filesApl return nameFileConfig else: return True def countsNumberTemplates(self, dirsTemplates=()): """Считаем количество шаблонов""" def createDictTemplates(path, prefix, dictTemplates): """Создает словарь {"директория":"кол-во шаблонов" ...} и считает общее количество шаблонов """ # Количество шаблонов self.allTemplates += 1 dirTemplate = os.path.split(path)[0] while True: if dirTemplate in dictTemplates.keys(): dictTemplates[dirTemplate] += 1 else: dictTemplates[dirTemplate] = 1 if dirTemplate == prefix: break dirTemplate = os.path.split(dirTemplate)[0] return dictTemplates if not dirsTemplates: dirsTemplates = self.objVar.Get("cl_template_clt_path") dirsTemplates.sort() scanObj = scanDirectoryClt(objVar=self.objVar) scanObj.processingFile = lambda x, y: createDictTemplates(x, y, self.dictTemplates) # Считаем количество шаблонов for dirTemplate in dirsTemplates: scanObj.scanningTemplates(dirTemplate, "/") def applyTemplatesClt(self, cltPath=None): """Применяет шаблоны к конфигурационным файлам""" if not self.flagApplyTemplates: return [], [] if cltPath is None and \ not self.objVar.defined("cl_template_clt_path"): self.setError(_("undefined variable: ") + "cl_template_clt_path") return False if cltPath is None: dirsTemplates = self.objVar.Get("cl_template_clt_path") else: dirsTemplates = cltPath dirsTemplates.sort() if self.checkNumberTemplate: # Созданные директории self.createdDirs = [] # Примененные файлы self.filesApply = [] # Номер применяемого шаблона self.numberProcessTempl = 0 # Словарь директорий с количеством файлов шаблонов self.dictTemplates = {} # Количество шаблонов self.allTemplates = 0 # Установка по умолчанию аттрибутов для функциии шаблонов ini() # Время доступа к конфигурационному файлу функции шаблона ini() self.functObj.timeIni = -1 # Первоначальный словарь переменных для ini() self.functObj.prevDictIni = {} # Текущий словарь переменных для ini() self.functObj.currDictIni = {} # Словарь времен модификации env файлов для ini() self.functObj.timeConfigsIni = {} # Считаем количество шаблонов self.countsNumberTemplates(dirsTemplates=dirsTemplates) self.numberAllTemplates(self.allTemplates) # default module self.defaultModule = "main" # Обрабатываем шаблоны for dirTemplate in dirsTemplates: if self.scanningTemplates(dirTemplate, self._chrootDir) is False: break return self.createdDirs, self.filesApply class ProgressTemplate(Template): """ Progress template for wsdl interface """ def __init__(self, setValueCallback, *args, **kwargs): Template.__init__(self, *args, **kwargs) self.setValueCallback = setValueCallback self.value = None self.firstValue = True def numberAllTemplates(self, number): self.maximum = number return True def numberProcessTemplates(self, number): maximum = self.maximum or 1 value = number * 100 / maximum if value != self.value: self.setValueCallback(min(100, max(0, value))) self.value = value return True def templateModify(self): if self.firstValue and hasattr(self, "onFirstValue"): self.onFirstValue() self.firstValue = False class SystemIni(object): _inifile = '/etc/calculate/ini.env' @property def inifile(self): if self.dv: return pathJoin(self.dv.Get('cl_chroot_path'), self._inifile) else: return self._inifile def __init__(self, dv=None): self.config = ConfigParser(strict=False) self.dv = dv self.config.read(self.inifile, encoding="utf-8") def getVar(self, section, varname): return self.config.get(section, varname, raw=True, fallback="") def delVar(self, section, varname): try: self.config.remove_option(section, varname) for section in filter(lambda x: not self.config[x], self.config.sections()): self.config.remove_section(section) self.__write() except NoSectionError: pass def __write(self): comment_block = "\n".join(takewhile(lambda x: x.startswith("#"), readLinesFile(self.inifile))) with open(self.inifile, 'wb') as f: if comment_block: f.write(comment_block) f.write('\n\n') self.config.write(f) def setVar(self, section, var_dict): if not self.config.has_section(section): self.config.add_section(section) for k, v in var_dict.items(): self.config.set(section, k, v) self.__write()