<commit_before># -*- coding: utf-8 -*-
"""Indexing hdf5 files"""
# NOTICE: THIS FILE IS ALSO PART OF THE CLIENT. IT SHOULD NOT CONTAINS REFERENCES TO THE SERVER OR TWISTED PKG.
ext='.h5'

import hashlib
import os
import cPickle as pickle
from traceback import print_exc
import sqlite3
import functools
import threading
from misura.canon.csutil import lockme,unlockme

import tables
from tables.nodes import filenode

from .. import csutil, option

from filemanager import FileManager
import digisign

testColumn=('file', 'serial', 'uid', 'id', 'date', 'instrument', 'flavour', 'name', 'elapsed', 'nSamples' , 'comment','verify')
testColumnDefault=['file','serial','uid', 'id', 'date', 'instrument', 'flavour', 'name', 1, 1 , 'comment',0]
testColDef=('text','text','text','text','text','text','text','text', 'real','integer','text','bool')
testTableDef='''(file text unique, serial text, uid text primary key, id text, date text, instrument text, flavour text,
		name text, elapsed real, nSamples integer, comment text,verify bool)'''
syncTableDef='''(file text, serial text, uid text primary key, id text, date text, instrument text, flavour text,
		name text, elapsed real, nSamples integer, comment text,verify bool)'''
errorTableDef='''(file text, serial text, uid text, id text, date text, instrument text, flavour text,
		name text, elapsed real, nSamples integer, comment text,verify bool,error text)'''
sampleTableDef='''(file text, ii integer, idx integer, material text, name text, comment text,
		dim integer, height integer, volume integer,
		sintering real, softening real, sphere real, halfSphere real, melting real )'''
testColConverter={}
colConverter={'text':unicode,'real':float,'bool':bool,'integer':int}
for i,n in enumerate(testColumn):
	testColConverter[n]=colConverter[testColDef[i]]

def dbcom(func):
	"""Decorator to open db before operations and close at the end."""
	@functools.wraps(func)
	def safedb_wrapper(self, *args, **kwargs):
		try:
			r=self._lock.acquire(False)
			if not r:
				raise BaseException('Impossible to lock database')
			self.open_db()
			return func(self, *args, **kwargs)
		finally:
			try:
				self.close_db()
			finally:
				self._lock.release()
	return safedb_wrapper

class Indexer(object):
	public=['rebuild','searchUID','update','header','listMaterials',
		'query','remove','get_len','list_tests','get_dbpath']
	cur=False
	conn=False
	addr='LOCAL'
	def __init__(self,dbPath=False,paths=[],log=False):
		self._lock=threading.Lock()
		self.dbPath=dbPath
		self.paths=paths
		if log is False:
			log=csutil.FakeLogger()
		self.log=log
		self.test=FileManager(self)
		self.dbPath=dbPath
		if dbPath and not os.path.exists(dbPath):
			self.rebuild()

	@dbcom
	def execute(self,query,*a,**kw):
		self.cur.execute(query,*a,**kw)
		self.conn.commit()

	@dbcom
	def execute_fetchall(self,query,*a,**kw):
		g=self.cur.execute(query,*a,**kw)
		return g.fetchall()

	@dbcom
	def execute_fetchone(self,query,*a,**kw):
		g=self.cur.execute(query,*a,**kw)
		return g.fetchone()

	def open_db(self,db=False):
		if not db:
			db=self.dbPath
		self.dbPath=db
		if not self.dbPath:
			print 'Indexer: no dbpath set!'
			return False
		self.conn=sqlite3.connect(self.dbPath, detect_types=sqlite3.PARSE_DECLTYPES)
		self.cur=self.conn.cursor()
		self.cur.execute("CREATE TABLE if not exists test "+testTableDef)
		self.cur.execute("CREATE TABLE if not exists sample "+sampleTableDef)
		# Sync tables
		self.cur.execute("create table if not exists sync_exclude "+syncTableDef)
		self.cur.execute("create table if not exists sync_queue "+syncTableDef)
		self.cur.execute("create table if not exists sync_approve "+syncTableDef)
		self.cur.execute("create table if not exists sync_error "+errorTableDef)
		self.conn.commit()
		return True

	def close_db(self):
		if self.cur:
			self.cur.close()
			self.conn.close()
		self.cur=False
		self.conn=False
		return True

	def close(self):
		return self.close_db()

	def tab_len(self,table_name):
		"""Returns length of a table"""
		r= self.execute_fetchone('SELECT COUNT(*) from {}'.format(table_name))
		if not r:
			return 0
		return r[0]

	def _searchUID(self,uid,full=False):
		"""Unlocked searchUID"""
		uid=str(uid)
		g=self.cur.execute('SELECT file FROM test WHERE uid=?', [uid])
		r=g.fetchall()
		if len(r)==0:
			return False
		file_path=r[0][0]
		if not os.path.exists(file_path):
			self._clear_file_path(file_path)
			return False
		# Return full line
		if full:
			return r[0]
		return file_path

	@dbcom
	def searchUID(self,uid,full=False):
		"""Search `uid` in tests table and return its path or full record if `full`"""
		return self._searchUID(uid, full)

	@unlockme
	def rebuild(self):
		"""Completely recreate the SQLite Database indexing all test files."""
		if not self.dbPath: return False
		if os.path.exists(self.dbPath):
			os.remove(self.dbPath)
		if not self._lock.acquire(False):
			self.log.error('Cannot lock database for rebuild')
			return False
		self.open_db(self.dbPath)

		conn=self.conn
		cur=self.cur
		cur.execute("DROP TABLE IF EXISTS test")
		cur.execute("CREATE TABLE test "+testTableDef)
		cur.execute("DROP TABLE IF EXISTS sample")
		cur.execute("CREATE TABLE sample "+sampleTableDef)
		cur.execute("DROP TABLE IF EXISTS sync_exclude")
		cur.execute("DROP TABLE IF EXISTS sync_queue")
		cur.execute("DROP TABLE IF EXISTS sync_approve")
		cur.execute("DROP TABLE IF EXISTS sync_error")
		conn.commit()
		self.close_db()
		self._lock.release()
		tn=0
		for path in self.paths:
			for root, dirs, files in os.walk(path):
				for fn in files:
					if not fn.endswith(ext): continue
					fp=os.path.join(root,fn)
					print 'Appending',fp
					tn+=self.appendFile(fp,fn)

		return 'Done. Found %i tests.' % tn

	def appendFile(self,fp,fn=False):
		#TODO: remove unused fn argument!!!
		if not os.path.exists(fp):
			print 'File not found',fp
			return False
		if not fn:
			fn=os.path.basename(fp)
		r=0
		t=False
		try:
			t=tables.openFile(fp,mode='r+')
			if not getattr(t.root, 'conf', False):
				self.log.debug('Tree configuration not found',fp)
				t.close()
				return False
			r=self._appendFile(t,fp,fn)
		except:
			print_exc()
		if t:
			t.close()
		return r

	@dbcom
	def _appendFile(self, t, fp,fn):
		"""Inserts a new file in the database"""
		#FIXME: inter-thread #412
		cur=self.cur
		conf=getattr(t.root,'conf',False)
		# Load configuration
		node=filenode.openNode(t.root.conf, 'r')
		node.seek(0)
		tree=node.read()
		node.close()
		tree=pickle.loads(tree)

		###
		# Test row
		###
		test={}
		test['file']=fp
		instrument=conf.attrs.instrument
		test['instrument']=instrument
		if not tree.has_key(instrument):
			print tree.keys()
			self.log.debug('Instrument tree missing')
			return False

		for p in 'name,comment,nSamples,date,elapsed,id'.split(','):
			test[p]=tree[instrument]['measure']['self'][p]['current']
		if test['date']<=1:
			test['date']=os.stat(fp).st_ctime
		test['serial']=conf.attrs.serial
		if not getattr(conf.attrs,'uid',False):
			self.log.debug('UID attribute not found')
			sname=tree[instrument]['measure']['id']
			test['uid']=hashlib.md5('%s_%s_%i' % (test['serial'], test['date'], sname)).hexdigest()
		else:
			test['uid']=conf.attrs.uid
		test['flavour']='Standard'
		v=[]
		for k in 'file,serial,uid,id,date,instrument,flavour,name,elapsed,nSamples,comment'.split(','):
			v.append(testColConverter[k](test[k]))
# 		ok=digisign.verify(t)
		#Performance problem: should be only verified on request.
		ok=False
		print 'File verify:',ok
		v.append(ok)

		# Remove any old entry
		self._clear_file_path(fn)

		cmd='?,'*len(v)
		cmd='INSERT INTO test VALUES ('+cmd[:-1]+')'
		print 'Executing',cmd,v
		self.cur.execute(cmd,v)
		r=cur.fetchall()
		###
		# Sample Rows
		###
		for i in range(8):
			s='sample%i' % i
			if s not in tree[instrument].keys(): break
			smp=tree[instrument][s]
			v=[fp]
			for k in 'ii,index,material,name,comment,dim,height,volume,sintering,softening,sphere,halfSphere,melting'.split(','):
				val=0
				if smp.has_key(k):
					val=smp[k]['current']
				v.append(val)
			cmd='?,'*len(v)
			cmd='INSERT INTO sample VALUES ('+cmd[:-1]+')'
			cur.execute(cmd,v)
			r=cur.fetchall()
			print 'Result:',r
		self.conn.commit()

		###
		# Options
		###
		return True
		s=option.SqlStore()
		s.cursor=cur
		s.write_tree(tree,preset=test['uid'])
		self.conn.commit()
		return True

	def update(self):
		"""Updates the database by inserting new files and removing deleted files"""
		#TODO
		pass

	@dbcom
	def remove(self,uid):
		fn=self._searchUID(uid)
		if not fn:
			self.log.error('Impossible delete:',uid,'not found.')
			return False
		return self.remove_file(fn)

	def _clear_file_path(self,file_path):
		"""Remove file in `file_path` from db"""
		e=self.cur.execute('delete from test where file=?',(file_path,))
		print 'Deleted from test',e.rowcount
		e=self.cur.execute('delete from sample where file=?',(file_path,))
		print 'Deleted from sample',e.rowcount
		self.conn.commit()
		return True

	@dbcom
	def remove_file(self,file_path):
		r= self._clear_by_file_path(file_path)
		if os.path.exists(file_path):
			os.remove(file_path)
		return r

	@dbcom
	def header(self):
		self.cur.execute('PRAGMA table_info(test)')
		r=self.cur.fetchall()
		print 'Indexer.header',r
		return [str(col[1]) for col in r]

	@dbcom
	def listMaterials(self):
		"""Lists all materials present in the database"""
		self.cur.execute('SELECT material FROM sample')
		r=self.cur.fetchall()
		r=list(a[0] for a in r)
		r=list(set(r))
		print 'LIST MATERIALS',r
		return r

	@dbcom
	def query(self, conditions={}):
		#FIXME: inter-thread #412
		if len(conditions)==0:
			self.cur.execute('SELECT * from test')
		else:
			cnd=[]
			vals=[]
			for k,v in conditions.iteritems():
				cnd.append(k+' like ?')
				vals.append('%'+v+'%')
			cnd=' AND '.join(cnd)
			cmd='SELECT * from test WHERE '+cnd
			self.log.debug('Executing',cmd,vals)
			self.cur.execute(cmd,vals)
		r=self.cur.fetchall()
		return r

	@dbcom
	def get_len(self):
		self.cur.execute('SELECT Count(*) FROM test')
		r=self.cur.fetchone()[0]
		return r

	@dbcom
	def list_tests(self,start=0,stop=25):
		self.cur.execute('SELECT * FROM test ORDER BY rowid DESC LIMIT ? OFFSET ?',(stop-start,start))
		r=[]
		for record in self.cur.fetchall():
			file_path=record[0]
			if not os.path.exists(file_path):
				self.log.debug('Removing obsolete database entry: ',file_path)
				self._clear_file_path(file_path)
			else:
				r.append(record)
		return r

	def get_dbpath(self):
		return self.dbPath

<commit_msg>per-thread sqlite3 database connection cache for @property self.conn self.cur<commit_after># -*- coding: utf-8 -*-
"""Indexing hdf5 files"""
# NOTICE: THIS FILE IS ALSO PART OF THE CLIENT. IT SHOULD NOT CONTAINS REFERENCES TO THE SERVER OR TWISTED PKG.
ext='.h5'

import hashlib
import os
import cPickle as pickle
from traceback import print_exc
import sqlite3
import functools
import threading
from misura.canon.csutil import lockme,unlockme

import tables
from tables.nodes import filenode

from .. import csutil, option

from filemanager import FileManager
import digisign

testColumn=('file', 'serial', 'uid', 'id', 'date', 'instrument', 'flavour', 'name', 'elapsed', 'nSamples' , 'comment','verify')
testColumnDefault=['file','serial','uid', 'id', 'date', 'instrument', 'flavour', 'name', 1, 1 , 'comment',0]
testColDef=('text','text','text','text','text','text','text','text', 'real','integer','text','bool')
testTableDef='''(file text unique, serial text, uid text primary key, id text, date text, instrument text, flavour text,
		name text, elapsed real, nSamples integer, comment text,verify bool)'''
syncTableDef='''(file text, serial text, uid text primary key, id text, date text, instrument text, flavour text,
		name text, elapsed real, nSamples integer, comment text,verify bool)'''
errorTableDef='''(file text, serial text, uid text, id text, date text, instrument text, flavour text,
		name text, elapsed real, nSamples integer, comment text,verify bool,error text)'''
sampleTableDef='''(file text, ii integer, idx integer, material text, name text, comment text,
		dim integer, height integer, volume integer,
		sintering real, softening real, sphere real, halfSphere real, melting real )'''
testColConverter={}
colConverter={'text':unicode,'real':float,'bool':bool,'integer':int}
for i,n in enumerate(testColumn):
	testColConverter[n]=colConverter[testColDef[i]]

def dbcom(func):
	"""Decorator to open db before operations and close at the end."""
	@functools.wraps(func)
	def safedb_wrapper(self, *args, **kwargs):
		try:
			r=self._lock.acquire(False)
			if not r:
				raise BaseException('Impossible to lock database')
			self.open_db()
			return func(self, *args, **kwargs)
		finally:
			try:
				self.close_db()
			finally:
				self._lock.acquire(False)
				self._lock.release()
	return safedb_wrapper

def tid():
	return threading.current_thread().ident

class Indexer(object):
	public=['rebuild','searchUID','update','header','listMaterials',
		'query','remove','get_len','list_tests','get_dbpath']
# 	cur=False
# 	conn=False
	addr='LOCAL'
	def __init__(self,dbPath=False,paths=[],log=False):
		self._lock=threading.Lock()
		self.threads={}
		self.dbPath=dbPath
		self.paths=paths
		if log is False:
			log=csutil.FakeLogger()
		self.log=log
		self.test=FileManager(self)
		self.dbPath=dbPath
		if dbPath and not os.path.exists(dbPath):
			self.rebuild()

	@dbcom
	def execute(self,query,*a,**kw):
		self.cur.execute(query,*a,**kw)
		self.conn.commit()

	@dbcom
	def execute_fetchall(self,query,*a,**kw):
		g=self.cur.execute(query,*a,**kw)
		return g.fetchall()

	@dbcom
	def execute_fetchone(self,query,*a,**kw):
		g=self.cur.execute(query,*a,**kw)
		return g.fetchone()

	@property
	def conn(self):
		return self.threads.get(tid(),[False])[0]

	@property
	def cur(self):
		return self.threads.get(tid(),[False])[-1]

	def open_db(self,db=False):
		if not db:
			db=self.dbPath
		self.dbPath=db
		if not self.dbPath:
			print 'Indexer: no dbpath set!'
			return False

		conn,cur=self.threads.get(tid(),(False,False))
		if conn:
			try:
				cur=conn.cursor()
			except sqlite3.ProgrammingError:
				conn=False
		if not conn:
			conn=sqlite3.connect(self.dbPath, detect_types=sqlite3.PARSE_DECLTYPES)
			cur=conn.cursor()
			self.threads[tid()]=(conn,cur)

		cur.execute("CREATE TABLE if not exists test "+testTableDef)
		cur.execute("CREATE TABLE if not exists sample "+sampleTableDef)
		# Sync tables
		cur.execute("create table if not exists sync_exclude "+syncTableDef)
		cur.execute("create table if not exists sync_queue "+syncTableDef)
		cur.execute("create table if not exists sync_approve "+syncTableDef)
		cur.execute("create table if not exists sync_error "+errorTableDef)
		conn.commit()
		return True

	def close_db(self):
		conn,cur=self.threads.get(tid(),(0,0))
		if cur:
			cur.close()
		if conn:
			conn.close()
		return True

	def close(self):
		return self.close_db()

	def tab_len(self,table_name):
		"""Returns length of a table"""
		r= self.execute_fetchone('SELECT COUNT(*) from {}'.format(table_name))
		if not r:
			return 0
		return r[0]

	def _searchUID(self,uid,full=False):
		"""Unlocked searchUID"""
		uid=str(uid)
		g=self.cur.execute('SELECT file FROM test WHERE uid=?', [uid])
		r=g.fetchall()
		if len(r)==0:
			return False
		file_path=r[0][0]
		if not os.path.exists(file_path):
			self._clear_file_path(file_path)
			return False
		# Return full line
		if full:
			return r[0]
		return file_path

	@dbcom
	def searchUID(self,uid,full=False):
		"""Search `uid` in tests table and return its path or full record if `full`"""
		return self._searchUID(uid, full)

	@unlockme
	def rebuild(self):
		"""Completely recreate the SQLite Database indexing all test files."""
		if not self.dbPath: return False
		if os.path.exists(self.dbPath):
			os.remove(self.dbPath)
		if not self._lock.acquire(False):
			self.log.error('Cannot lock database for rebuild')
			return False
		self.open_db(self.dbPath)

		conn=self.conn
		cur=self.cur
		cur.execute("DROP TABLE IF EXISTS test")
		cur.execute("CREATE TABLE test "+testTableDef)
		cur.execute("DROP TABLE IF EXISTS sample")
		cur.execute("CREATE TABLE sample "+sampleTableDef)
		cur.execute("DROP TABLE IF EXISTS sync_exclude")
		cur.execute("DROP TABLE IF EXISTS sync_queue")
		cur.execute("DROP TABLE IF EXISTS sync_approve")
		cur.execute("DROP TABLE IF EXISTS sync_error")
		conn.commit()
		self.close_db()
		self._lock.release()
		tn=0
		for path in self.paths:
			for root, dirs, files in os.walk(path):
				for fn in files:
					if not fn.endswith(ext): continue
					fp=os.path.join(root,fn)
					print 'Appending',fp
					tn+=self.appendFile(fp,fn)

		return 'Done. Found %i tests.' % tn

	def appendFile(self,fp,fn=False):
		#TODO: remove unused fn argument!!!
		if not os.path.exists(fp):
			print 'File not found',fp
			return False
		if not fn:
			fn=os.path.basename(fp)
		r=0
		t=False
		try:
			t=tables.openFile(fp,mode='r+')
			if not getattr(t.root, 'conf', False):
				self.log.debug('Tree configuration not found',fp)
				t.close()
				return False
			r=self._appendFile(t,fp,fn)
		except:
			print_exc()
		if t:
			t.close()
		return r

	@dbcom
	def _appendFile(self, t, fp,fn):
		"""Inserts a new file in the database"""
		#FIXME: inter-thread #412
		cur=self.cur
		conf=getattr(t.root,'conf',False)
		# Load configuration
		node=filenode.openNode(t.root.conf, 'r')
		node.seek(0)
		tree=node.read()
		node.close()
		tree=pickle.loads(tree)

		###
		# Test row
		###
		test={}
		test['file']=fp
		instrument=conf.attrs.instrument
		test['instrument']=instrument
		if not tree.has_key(instrument):
			print tree.keys()
			self.log.debug('Instrument tree missing')
			return False

		for p in 'name,comment,nSamples,date,elapsed,id'.split(','):
			test[p]=tree[instrument]['measure']['self'][p]['current']
		if test['date']<=1:
			test['date']=os.stat(fp).st_ctime
		test['serial']=conf.attrs.serial
		if not getattr(conf.attrs,'uid',False):
			self.log.debug('UID attribute not found')
			sname=tree[instrument]['measure']['id']
			test['uid']=hashlib.md5('%s_%s_%i' % (test['serial'], test['date'], sname)).hexdigest()
		else:
			test['uid']=conf.attrs.uid
		test['flavour']='Standard'
		v=[]
		for k in 'file,serial,uid,id,date,instrument,flavour,name,elapsed,nSamples,comment'.split(','):
			v.append(testColConverter[k](test[k]))
# 		ok=digisign.verify(t)
		#Performance problem: should be only verified on request.
		ok=False
		print 'File verify:',ok
		v.append(ok)

		# Remove any old entry
		self._clear_file_path(fn)

		cmd='?,'*len(v)
		cmd='INSERT INTO test VALUES ('+cmd[:-1]+')'
		print 'Executing',cmd,v
		self.cur.execute(cmd,v)
		r=cur.fetchall()
		###
		# Sample Rows
		###
		for i in range(8):
			s='sample%i' % i
			if s not in tree[instrument].keys(): break
			smp=tree[instrument][s]
			v=[fp]
			for k in 'ii,index,material,name,comment,dim,height,volume,sintering,softening,sphere,halfSphere,melting'.split(','):
				val=0
				if smp.has_key(k):
					val=smp[k]['current']
				v.append(val)
			cmd='?,'*len(v)
			cmd='INSERT INTO sample VALUES ('+cmd[:-1]+')'
			cur.execute(cmd,v)
			r=cur.fetchall()
			print 'Result:',r
		self.conn.commit()

		###
		# Options
		###
		return True
		s=option.SqlStore()
		s.cursor=cur
		s.write_tree(tree,preset=test['uid'])
		self.conn.commit()
		return True

	def update(self):
		"""Updates the database by inserting new files and removing deleted files"""
		#TODO
		pass

	@dbcom
	def remove(self,uid):
		fn=self._searchUID(uid)
		if not fn:
			self.log.error('Impossible delete:',uid,'not found.')
			return False
		return self.remove_file(fn)

	def _clear_file_path(self,file_path):
		"""Remove file in `file_path` from db"""
		e=self.cur.execute('delete from test where file=?',(file_path,))
		print 'Deleted from test',e.rowcount
		e=self.cur.execute('delete from sample where file=?',(file_path,))
		print 'Deleted from sample',e.rowcount
		self.conn.commit()
		return True

	@dbcom
	def remove_file(self,file_path):
		r= self._clear_by_file_path(file_path)
		if os.path.exists(file_path):
			os.remove(file_path)
		return r

	@dbcom
	def header(self):
		self.cur.execute('PRAGMA table_info(test)')
		r=self.cur.fetchall()
		print 'Indexer.header',r
		return [str(col[1]) for col in r]

	@dbcom
	def listMaterials(self):
		"""Lists all materials present in the database"""
		self.cur.execute('SELECT material FROM sample')
		r=self.cur.fetchall()
		r=list(a[0] for a in r)
		r=list(set(r))
		print 'LIST MATERIALS',r
		return r

	@dbcom
	def query(self, conditions={}):
		#FIXME: inter-thread #412
		if len(conditions)==0:
			self.cur.execute('SELECT * from test')
		else:
			cnd=[]
			vals=[]
			for k,v in conditions.iteritems():
				cnd.append(k+' like ?')
				vals.append('%'+v+'%')
			cnd=' AND '.join(cnd)
			cmd='SELECT * from test WHERE '+cnd
			self.log.debug('Executing',cmd,vals)
			self.cur.execute(cmd,vals)
		r=self.cur.fetchall()
		return r

	@dbcom
	def get_len(self):
		self.cur.execute('SELECT Count(*) FROM test')
		r=self.cur.fetchone()[0]
		return r

	@dbcom
	def list_tests(self,start=0,stop=25):
		self.cur.execute('SELECT * FROM test ORDER BY rowid DESC LIMIT ? OFFSET ?',(stop-start,start))
		r=[]
		for record in self.cur.fetchall():
			file_path=record[0]
			if not os.path.exists(file_path):
				self.log.debug('Removing obsolete database entry: ',file_path)
				self._clear_file_path(file_path)
			else:
				r.append(record)
		return r

	def get_dbpath(self):
		return self.dbPath

<|endoftext|>


<commit_before>from __future__ import unicode_literals
import os

from mkdocs import utils, legacy
from mkdocs.config.base import Config, ValidationError

class BaseConfigOption(object):

    def __init__(self):
        self.warnings = []
        self.default = None

    def is_required(self):
        return False

    def validate(self, value):
        return self.run_validation(value)

    def reset_warnings(self):
        self.warnings = []

    def pre_validation(self, config, key_name):
        """
        After all options have passed validation, perform a post validation
        process to do any additional changes dependant on other config values.

        The post validation process method should be implemented by subclasses.
        """

    def run_validation(self, value):
        """
        Perform validation for a value.

        The run_validation method should be implemented by subclasses.
        """
        return value

    def post_validation(self, config, key_name):
        """
        After all options have passed validation, perform a post validation
        process to do any additional changes dependant on other config values.

        The post validation process method should be implemented by subclasses.
        """

class SubConfig(BaseConfigOption, Config):
    def __init__(self, *config_options):
        BaseConfigOption.__init__(self)
        Config.__init__(self, config_options)
        self.default = {}

    def validate(self, value):
        self.load_dict(value)
        return self.run_validation(value)

    def run_validation(self, value):
        Config.validate(self)
        return self

class OptionallyRequired(BaseConfigOption):
    """
    The BaseConfigOption adds support for default values and required values

    It then delegates the validation and (optional) post validation processing
    to subclasses.
    """

    def __init__(self, default=None, required=False):
        super(OptionallyRequired, self).__init__()
        self.default = default
        self.required = required

    def is_required(self):
        return self.required

    def validate(self, value):
        """
        Perform some initial validation.

        If the option is empty (None) and isn't required, leave it as such. If
        it is empty but has a default, use that. Finally, call the
        run_validation method on the subclass unless.
        """

        if value is None:
            if self.default is not None:
                value = self.default
            elif not self.required:
                return
            elif self.required:
                raise ValidationError("Required configuration not provided.")

        return self.run_validation(value)

class Type(OptionallyRequired):
    """
    Type Config Option

    Validate the type of a config option against a given Python type.
    """

    def __init__(self, type_, length=None, **kwargs):
        super(Type, self).__init__(**kwargs)
        self._type = type_
        self.length = length

    def run_validation(self, value):

        if not isinstance(value, self._type):
            msg = ("Expected type: {0} but recieved: {1}"
                   .format(self._type, type(value)))
        elif self.length is not None and len(value) != self.length:
            msg = ("Expected type: {0} with lenght {2} but recieved: {1} with "
                   "length {3}").format(self._type, value, self.length,
                                        len(value))
        else:
            return value

        raise ValidationError(msg)

class Deprecated(BaseConfigOption):

    def __init__(self, moved_to=None):
        super(Deprecated, self).__init__()
        self.default = None
        self.moved_to = moved_to

    def pre_validation(self, config, key_name):

        if config.get(key_name) is None or self.moved_to is None:
            return

        warning = ('The configuration option {0} has been deprecated and will '
                   'be removed in a future release of MkDocs.')
        self.warnings.append(warning)

        if '.' not in self.moved_to:
            target = config
            target_key = self.moved_to
        else:
            move_to, target_key = self.moved_to.rsplit('.', 1)

            target = config
            for key in move_to.split('.'):
                target = target.setdefault(key, {})

                if not isinstance(target, dict):
                    # We can't move it for the user
                    return

        target[target_key] = config.pop(key_name)

class URL(OptionallyRequired):
    """
    URL Config Option

    Validate a URL by requiring a scheme is present.
    """

    def run_validation(self, value):

        try:
            parsed_url = utils.urlparse(value)
        except (AttributeError, TypeError):
            raise ValidationError("Unable to parse the URL.")

        if parsed_url.scheme:
            return value

        raise ValidationError(
            "The URL isn't valid, it should include the http:// (scheme)")

class RepoURL(URL):
    """
    Repo URL Config Option

    A small extension to the URL config that sets the repo_name, based on the
    url if it hasn't already been provided.
    """

    def post_validation(self, config, key_name):

        if config['repo_url'] is not None and config.get('repo_name') is None:
            repo_host = utils.urlparse(
                config['repo_url']).netloc.lower()
            if repo_host == 'github.com':
                config['repo_name'] = 'GitHub'
            elif repo_host == 'bitbucket.org':
                config['repo_name'] = 'Bitbucket'
            else:
                config['repo_name'] = repo_host.split('.')[0].title()

class Dir(Type):
    """
    Dir Config Option

    Validate a path to a directory, optionally verifying that it exists.
    """

    def __init__(self, exists=False, **kwargs):
        super(Dir, self).__init__(type_=utils.string_types, **kwargs)
        self.exists = exists

    def run_validation(self, value):

        value = super(Dir, self).run_validation(value)

        if self.exists and not os.path.isdir(value):
            raise ValidationError("The path {0} doesn't exist".format(value))

        return os.path.abspath(value)

class SiteDir(Dir):
    """
    SiteDir Config Option

    Validates the site_dir and docs_dir directories do not contain each other.
    """

    def post_validation(self, config, key_name):

        # Validate that the docs_dir and site_dir don't contain the
        # other as this will lead to copying back and forth on each
        # and eventually make a deep nested mess.
        if config['docs_dir'].startswith(config['site_dir']):
            raise ValidationError(
                ("The 'docs_dir' should not be within the 'site_dir' as this "
                 "can mean the source files are overwritten by the output or "
                 "it will be deleted if --clean is passed to mkdocs build."
                 "(site_dir: '{0}', docs_dir: '{1}')"
                 ).format(config['site_dir'], config['docs_dir']))
        elif config['site_dir'].startswith(config['docs_dir']):
            self.warnings.append(
                ("The 'site_dir' should not be within the 'docs_dir' as this "
                 "leads to the build directory being copied into itself and "
                 "duplicate nested files in the 'site_dir'."
                 "(site_dir: '{0}', docs_dir: '{1}')"
                 ).format(config['site_dir'], config['docs_dir']))

class ThemeDir(Dir):
    """
    ThemeDir Config Option

    Post validation, verify the theme_dir and do some path munging.

    TODO: This could probably be improved and/or moved from here. It's a tad
    gross really.
    """

    def post_validation(self, config, key_name):

        theme_in_config = any(['theme' in c for c in config.user_configs])

        package_dir = os.path.abspath(
            os.path.join(os.path.dirname(__file__), '..'))
        theme_dir = [utils.get_themes()[config['theme']], ]
        config['mkdocs_templates'] = os.path.join(package_dir, 'templates')

        if config['theme_dir'] is not None:
            # If the user has given us a custom theme but not a
            # builtin theme name then we don't want to merge them.
            if not theme_in_config:
                theme_dir = []
            theme_dir.insert(0, config['theme_dir'])

        config['theme_dir'] = theme_dir

        # Add the search assets to the theme_dir, this means that
        # they will then we copied into the output directory but can
        # be overwritten by themes if needed.
        search_assets = os.path.join(package_dir, 'assets', 'search')
        config['theme_dir'].append(search_assets)

class Theme(OptionallyRequired):
    """
    Theme Config Option

    Validate that the theme is one of the builtin Mkdocs theme names.
    """

    def run_validation(self, value):
        themes = utils.get_theme_names()

        if value in themes:
            if value in ['mkdocs', 'readthedocs']:
                return value

            self.warnings.append(
                ("The theme '{0}' will be removed in an upcoming MkDocs "
                 "release. See http://www.mkdocs.org/about/release-notes/ "
                 "for more details").format(value)
            )
            return value

        raise ValidationError("Unrecognised theme.")

class Extras(OptionallyRequired):
    """
    Extras Config Option

    Validate the extra configs are a list and populate them with a set of files
    if not provided.
    """

    def __init__(self, file_match=None, **kwargs):
        super(Extras, self).__init__(**kwargs)
        self.file_match = file_match

    def run_validation(self, value):

        if isinstance(value, list):
            return value
        else:
            raise ValidationError(
                "Expected a list, got {0}".format(type(value)))

    def walk_docs_dir(self, docs_dir):

        if self.file_match is None:
            raise StopIteration

        for (dirpath, dirs, filenames) in os.walk(docs_dir):
            dirs.sort()
            for filename in sorted(filenames):
                fullpath = os.path.join(dirpath, filename)

                # Some editors (namely Emacs) will create temporary symlinks
                # for internal magic. We can just ignore these files.
                if os.path.islink(fullpath):
                    if not os.path.exists(os.readlink(fullpath)):
                        continue

                relpath = os.path.normpath(os.path.relpath(fullpath, docs_dir))
                if self.file_match(relpath):
                    yield relpath

    def post_validation(self, config, key_name):

        if config[key_name] is not None:
            return

        extras = []

        for filename in self.walk_docs_dir(config['docs_dir']):
            extras.append(filename)

        config[key_name] = extras

class Pages(Extras):
    """
    Pages Config Option

    Validate the pages config, performing comparability if the config appears
    to be the old structure. Automatically add all markdown files if none are
    provided.
    """

    def __init__(self, **kwargs):
        super(Pages, self).__init__(utils.is_markdown_file, **kwargs)

    def run_validation(self, value):

        if not isinstance(value, list):
            raise ValidationError(
                "Expected a list, got {0}".format(type(value)))

        if len(value) == 0:
            return

        # TODO: Remove in 1.0
        config_types = set(type(l) for l in value)

        if config_types.issubset(set([utils.text_type, dict, str])):
            return value

        if config_types.issubset(set([utils.text_type, list, str])):
            return legacy.pages_compat_shim(value)

        raise ValidationError("Invalid pages config. {0} {1}".format(
            config_types,
            set([utils.text_type, dict, ])
        ))

    def post_validation(self, config, key_name):

        if config[key_name] is not None:
            return

        pages = []

        for filename in self.walk_docs_dir(config['docs_dir']):

            if os.path.splitext(filename)[0] == 'index':
                pages.insert(0, filename)
            else:
                pages.append(filename)

        config[key_name] = utils.nest_paths(pages)

class NumPages(OptionallyRequired):
    """
    NumPages Config Option

    Set the value to True if the number of pages is greater than the given
    number (defaults to 1).
    """

    def __init__(self, at_lest=1, **kwargs):
        super(NumPages, self).__init__(**kwargs)
        self.at_lest = at_lest

    def post_validation(self, config, key_name):

        if config[key_name] is not None:
            return

        try:
            config[key_name] = len(config['pages']) > self.at_lest
        except TypeError:
            config[key_name] = False

class Private(OptionallyRequired):
    """
    Private Config Option

    A config option only for internal use. Raises an error if set by the user.
    """

    def run_validation(self, value):
        raise ValidationError('For internal use only.')

class MarkdownExtensions(OptionallyRequired):
    """
    Markdown Extensions Config Option

    A list of extensions. If a list item contains extension configs,
    those are set on the private  setting passed to `configkey`. The
    `builtins` keyword accepts a list of extensions which cannot be
    overriden by the user. However, builtins can be duplicated to define
    config options for them if desired.
    """
    def __init__(self, builtins=None, configkey='mdx_configs', **kwargs):
        super(MarkdownExtensions, self).__init__(**kwargs)
        self.builtins = builtins or []
        self.configkey = configkey
        self.configdata = {}

    def run_validation(self, value):
        if not isinstance(value, (list, tuple)):
            raise ValidationError('Invalid Markdown Extensions configuration')
        extensions = []
        for item in value:
            if isinstance(item, dict):
                if len(item) > 1:
                    raise ValidationError('Invalid Markdown Extensions configuration')
                ext, cfg = item.popitem()
                extensions.append(ext)
                if cfg is None:
                    continue
                if not isinstance(cfg, dict):
                    raise ValidationError('Invalid config options for Markdown '
                                          "Extension '{0}'.".format(ext))
                self.configdata[ext] = cfg
            elif isinstance(item, utils.string_types):
                extensions.append(item)
            else:
                raise ValidationError('Invalid Markdown Extensions configuration')
        return utils.reduce_list(self.builtins + extensions)

    def post_validation(self, config, key_name):
        config[self.configkey] = self.configdata
<commit_msg>Only display the warning for a specific set of themes<commit_after>from __future__ import unicode_literals
import os

from mkdocs import utils, legacy
from mkdocs.config.base import Config, ValidationError

class BaseConfigOption(object):

    def __init__(self):
        self.warnings = []
        self.default = None

    def is_required(self):
        return False

    def validate(self, value):
        return self.run_validation(value)

    def reset_warnings(self):
        self.warnings = []

    def pre_validation(self, config, key_name):
        """
        After all options have passed validation, perform a post validation
        process to do any additional changes dependant on other config values.

        The post validation process method should be implemented by subclasses.
        """

    def run_validation(self, value):
        """
        Perform validation for a value.

        The run_validation method should be implemented by subclasses.
        """
        return value

    def post_validation(self, config, key_name):
        """
        After all options have passed validation, perform a post validation
        process to do any additional changes dependant on other config values.

        The post validation process method should be implemented by subclasses.
        """

class SubConfig(BaseConfigOption, Config):
    def __init__(self, *config_options):
        BaseConfigOption.__init__(self)
        Config.__init__(self, config_options)
        self.default = {}

    def validate(self, value):
        self.load_dict(value)
        return self.run_validation(value)

    def run_validation(self, value):
        Config.validate(self)
        return self

class OptionallyRequired(BaseConfigOption):
    """
    The BaseConfigOption adds support for default values and required values

    It then delegates the validation and (optional) post validation processing
    to subclasses.
    """

    def __init__(self, default=None, required=False):
        super(OptionallyRequired, self).__init__()
        self.default = default
        self.required = required

    def is_required(self):
        return self.required

    def validate(self, value):
        """
        Perform some initial validation.

        If the option is empty (None) and isn't required, leave it as such. If
        it is empty but has a default, use that. Finally, call the
        run_validation method on the subclass unless.
        """

        if value is None:
            if self.default is not None:
                value = self.default
            elif not self.required:
                return
            elif self.required:
                raise ValidationError("Required configuration not provided.")

        return self.run_validation(value)

class Type(OptionallyRequired):
    """
    Type Config Option

    Validate the type of a config option against a given Python type.
    """

    def __init__(self, type_, length=None, **kwargs):
        super(Type, self).__init__(**kwargs)
        self._type = type_
        self.length = length

    def run_validation(self, value):

        if not isinstance(value, self._type):
            msg = ("Expected type: {0} but recieved: {1}"
                   .format(self._type, type(value)))
        elif self.length is not None and len(value) != self.length:
            msg = ("Expected type: {0} with lenght {2} but recieved: {1} with "
                   "length {3}").format(self._type, value, self.length,
                                        len(value))
        else:
            return value

        raise ValidationError(msg)

class Deprecated(BaseConfigOption):

    def __init__(self, moved_to=None):
        super(Deprecated, self).__init__()
        self.default = None
        self.moved_to = moved_to

    def pre_validation(self, config, key_name):

        if config.get(key_name) is None or self.moved_to is None:
            return

        warning = ('The configuration option {0} has been deprecated and will '
                   'be removed in a future release of MkDocs.')
        self.warnings.append(warning)

        if '.' not in self.moved_to:
            target = config
            target_key = self.moved_to
        else:
            move_to, target_key = self.moved_to.rsplit('.', 1)

            target = config
            for key in move_to.split('.'):
                target = target.setdefault(key, {})

                if not isinstance(target, dict):
                    # We can't move it for the user
                    return

        target[target_key] = config.pop(key_name)

class URL(OptionallyRequired):
    """
    URL Config Option

    Validate a URL by requiring a scheme is present.
    """

    def run_validation(self, value):

        try:
            parsed_url = utils.urlparse(value)
        except (AttributeError, TypeError):
            raise ValidationError("Unable to parse the URL.")

        if parsed_url.scheme:
            return value

        raise ValidationError(
            "The URL isn't valid, it should include the http:// (scheme)")

class RepoURL(URL):
    """
    Repo URL Config Option

    A small extension to the URL config that sets the repo_name, based on the
    url if it hasn't already been provided.
    """

    def post_validation(self, config, key_name):

        if config['repo_url'] is not None and config.get('repo_name') is None:
            repo_host = utils.urlparse(
                config['repo_url']).netloc.lower()
            if repo_host == 'github.com':
                config['repo_name'] = 'GitHub'
            elif repo_host == 'bitbucket.org':
                config['repo_name'] = 'Bitbucket'
            else:
                config['repo_name'] = repo_host.split('.')[0].title()

class Dir(Type):
    """
    Dir Config Option

    Validate a path to a directory, optionally verifying that it exists.
    """

    def __init__(self, exists=False, **kwargs):
        super(Dir, self).__init__(type_=utils.string_types, **kwargs)
        self.exists = exists

    def run_validation(self, value):

        value = super(Dir, self).run_validation(value)

        if self.exists and not os.path.isdir(value):
            raise ValidationError("The path {0} doesn't exist".format(value))

        return os.path.abspath(value)

class SiteDir(Dir):
    """
    SiteDir Config Option

    Validates the site_dir and docs_dir directories do not contain each other.
    """

    def post_validation(self, config, key_name):

        # Validate that the docs_dir and site_dir don't contain the
        # other as this will lead to copying back and forth on each
        # and eventually make a deep nested mess.
        if config['docs_dir'].startswith(config['site_dir']):
            raise ValidationError(
                ("The 'docs_dir' should not be within the 'site_dir' as this "
                 "can mean the source files are overwritten by the output or "
                 "it will be deleted if --clean is passed to mkdocs build."
                 "(site_dir: '{0}', docs_dir: '{1}')"
                 ).format(config['site_dir'], config['docs_dir']))
        elif config['site_dir'].startswith(config['docs_dir']):
            self.warnings.append(
                ("The 'site_dir' should not be within the 'docs_dir' as this "
                 "leads to the build directory being copied into itself and "
                 "duplicate nested files in the 'site_dir'."
                 "(site_dir: '{0}', docs_dir: '{1}')"
                 ).format(config['site_dir'], config['docs_dir']))

class ThemeDir(Dir):
    """
    ThemeDir Config Option

    Post validation, verify the theme_dir and do some path munging.

    TODO: This could probably be improved and/or moved from here. It's a tad
    gross really.
    """

    def post_validation(self, config, key_name):

        theme_in_config = any(['theme' in c for c in config.user_configs])

        package_dir = os.path.abspath(
            os.path.join(os.path.dirname(__file__), '..'))
        theme_dir = [utils.get_themes()[config['theme']], ]
        config['mkdocs_templates'] = os.path.join(package_dir, 'templates')

        if config['theme_dir'] is not None:
            # If the user has given us a custom theme but not a
            # builtin theme name then we don't want to merge them.
            if not theme_in_config:
                theme_dir = []
            theme_dir.insert(0, config['theme_dir'])

        config['theme_dir'] = theme_dir

        # Add the search assets to the theme_dir, this means that
        # they will then we copied into the output directory but can
        # be overwritten by themes if needed.
        search_assets = os.path.join(package_dir, 'assets', 'search')
        config['theme_dir'].append(search_assets)

class Theme(OptionallyRequired):
    """
    Theme Config Option

    Validate that the theme is one of the builtin Mkdocs theme names.
    """

    def run_validation(self, value):
        themes = utils.get_theme_names()

        if value in themes:

            # These themes have been moved to the mkdocs-bootstrap and
            # mkdocs-bootswatch packages. At some point we wont depend on
            # these by default.
            moved_themes = [
                'bootstrap', 'amelia', 'cerulean', 'cosmo', 'cyborg',
                'flatly', 'journal', 'readable', 'simplex', 'slate',
                'spacelab', 'united', 'yeti'
            ]

            if value not in moved_themes:
                return value

            self.warnings.append(
                ("The theme '{0}' will be removed in an upcoming MkDocs "
                 "release. See http://www.mkdocs.org/about/release-notes/ "
                 "for more details").format(value)
            )
            return value

        raise ValidationError("Unrecognised theme.")

class Extras(OptionallyRequired):
    """
    Extras Config Option

    Validate the extra configs are a list and populate them with a set of files
    if not provided.
    """

    def __init__(self, file_match=None, **kwargs):
        super(Extras, self).__init__(**kwargs)
        self.file_match = file_match

    def run_validation(self, value):

        if isinstance(value, list):
            return value
        else:
            raise ValidationError(
                "Expected a list, got {0}".format(type(value)))

    def walk_docs_dir(self, docs_dir):

        if self.file_match is None:
            raise StopIteration

        for (dirpath, dirs, filenames) in os.walk(docs_dir):
            dirs.sort()
            for filename in sorted(filenames):
                fullpath = os.path.join(dirpath, filename)

                # Some editors (namely Emacs) will create temporary symlinks
                # for internal magic. We can just ignore these files.
                if os.path.islink(fullpath):
                    if not os.path.exists(os.readlink(fullpath)):
                        continue

                relpath = os.path.normpath(os.path.relpath(fullpath, docs_dir))
                if self.file_match(relpath):
                    yield relpath

    def post_validation(self, config, key_name):

        if config[key_name] is not None:
            return

        extras = []

        for filename in self.walk_docs_dir(config['docs_dir']):
            extras.append(filename)

        config[key_name] = extras

class Pages(Extras):
    """
    Pages Config Option

    Validate the pages config, performing comparability if the config appears
    to be the old structure. Automatically add all markdown files if none are
    provided.
    """

    def __init__(self, **kwargs):
        super(Pages, self).__init__(utils.is_markdown_file, **kwargs)

    def run_validation(self, value):

        if not isinstance(value, list):
            raise ValidationError(
                "Expected a list, got {0}".format(type(value)))

        if len(value) == 0:
            return

        # TODO: Remove in 1.0
        config_types = set(type(l) for l in value)

        if config_types.issubset(set([utils.text_type, dict, str])):
            return value

        if config_types.issubset(set([utils.text_type, list, str])):
            return legacy.pages_compat_shim(value)

        raise ValidationError("Invalid pages config. {0} {1}".format(
            config_types,
            set([utils.text_type, dict, ])
        ))

    def post_validation(self, config, key_name):

        if config[key_name] is not None:
            return

        pages = []

        for filename in self.walk_docs_dir(config['docs_dir']):

            if os.path.splitext(filename)[0] == 'index':
                pages.insert(0, filename)
            else:
                pages.append(filename)

        config[key_name] = utils.nest_paths(pages)

class NumPages(OptionallyRequired):
    """
    NumPages Config Option

    Set the value to True if the number of pages is greater than the given
    number (defaults to 1).
    """

    def __init__(self, at_lest=1, **kwargs):
        super(NumPages, self).__init__(**kwargs)
        self.at_lest = at_lest

    def post_validation(self, config, key_name):

        if config[key_name] is not None:
            return

        try:
            config[key_name] = len(config['pages']) > self.at_lest
        except TypeError:
            config[key_name] = False

class Private(OptionallyRequired):
    """
    Private Config Option

    A config option only for internal use. Raises an error if set by the user.
    """

    def run_validation(self, value):
        raise ValidationError('For internal use only.')

class MarkdownExtensions(OptionallyRequired):
    """
    Markdown Extensions Config Option

    A list of extensions. If a list item contains extension configs,
    those are set on the private  setting passed to `configkey`. The
    `builtins` keyword accepts a list of extensions which cannot be
    overriden by the user. However, builtins can be duplicated to define
    config options for them if desired.
    """
    def __init__(self, builtins=None, configkey='mdx_configs', **kwargs):
        super(MarkdownExtensions, self).__init__(**kwargs)
        self.builtins = builtins or []
        self.configkey = configkey
        self.configdata = {}

    def run_validation(self, value):
        if not isinstance(value, (list, tuple)):
            raise ValidationError('Invalid Markdown Extensions configuration')
        extensions = []
        for item in value:
            if isinstance(item, dict):
                if len(item) > 1:
                    raise ValidationError('Invalid Markdown Extensions configuration')
                ext, cfg = item.popitem()
                extensions.append(ext)
                if cfg is None:
                    continue
                if not isinstance(cfg, dict):
                    raise ValidationError('Invalid config options for Markdown '
                                          "Extension '{0}'.".format(ext))
                self.configdata[ext] = cfg
            elif isinstance(item, utils.string_types):
                extensions.append(item)
            else:
                raise ValidationError('Invalid Markdown Extensions configuration')
        return utils.reduce_list(self.builtins + extensions)

    def post_validation(self, config, key_name):
        config[self.configkey] = self.configdata
<|endoftext|>


<commit_before>import sys
import os
import shutil
import getpass
import subprocess
from django.core import management
from django.template import Context, Template
from modoboa.core.management import Command

dbconn_tpl = """
    '{{ conn_name }}': {
        'ENGINE': 'django.db.backends.{{ dbtype }}',  # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'.
        'NAME': '{{ dbname }}',                       # Or path to database file if using sqlite3.
        'USER': '{{ username }}',                     # Not used with sqlite3.
        'PASSWORD': '{{ password }}',                 # Not used with sqlite3.
        'HOST': '{{ dbhost }}',                       # Set to empty string for localhost. Not used with sqlite3.
        'PORT': '',                      # Set to empty string for default. Not used with sqlite3.{% if dbtype == 'mysql' %}
        'OPTIONS' : {
            "init_command" : 'SET foreign_key_checks = 0;',
        },{% endif %}
    },
"""

class DeployCommand(Command):
    help = "Create a fresh django project (calling startproject) and apply Modoboa specific settings."

    def __init__(self, *args, **kwargs):
        super(DeployCommand, self).__init__(*args, **kwargs)
        self._parser.add_argument('name', type=str,
                                  help='The name of your Modoboa instance')
        self._parser.add_argument('--with-amavis', action='store_true', default=False,
                                  help='Include amavis configuration')
        self._parser.add_argument('--run-syncdb', action='store_true', default=False,
                                  help='Run django syncdb command')
        self._parser.add_argument('--run-collectstatic', action='store_true', default=False,
                                  help='Run django collectstatic command')

    def _exec_django_command(self, name, cwd, *args):
        """Run a django command for the freshly created project

        :param name: the command name
        :param cwd: the directory where the command must be executed
        """
        cmd = 'python manage.py %s %s' % (name, " ".join(args))
        if not self._verbose:
            p = subprocess.Popen(cmd,
                                 stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
                                 shell=True, cwd=cwd)
            output = p.communicate()
        else:
            p = subprocess.Popen(cmd, shell=True, cwd=cwd)
            p.wait()
            output = None
        if p.returncode:
            if output:
                print >>sys.stderr, "\n".join(filter(lambda l: l != None, output))
            print >>sys.stderr, "%s failed, check your configuration" % cmd

    def ask_db_info(self, name='default'):
        """Prompt the user for database information

        Gather all information required to create a new database
        connection (into settings.py).

        :param name: the connection name
        """
        print "Configuring database connection: %s" % name
        info = {'conn_name' : name}
        info['dbtype'] = raw_input('Database type (mysql or postgres): ')
        if info['dbtype'] not in ['mysql', 'postgres']:
            info['dbtype'] = 'mysql'
        if info['dbtype'] == 'postgres':
            info['dbtype'] = 'postgresql_psycopg2'
        info['dbhost'] = raw_input('Database host (default: localhost): ')
        if info['dbhost'] == '':
            info['dbhost'] = 'localhost'
        info['dbname'] = raw_input('Database name: ')
        info['username'] = raw_input('Username: ')
        info['password'] = getpass.getpass('Password: ')
        return info

    def handle(self, parsed_args):
        management.call_command('startproject', parsed_args.name, verbosity=False)
        if os.path.exists("%(name)s/%(name)s" % {'name' : parsed_args.name}):
            # Django 1.4+
            path = "%(name)s/%(name)s" % {'name' : parsed_args.name}
            sys.path.append(parsed_args.name)
            django14 = True
        else:
            path = parsed_args.name
            sys.path.append(".")
            django14 = False

        t = Template(dbconn_tpl)
        default_conn = t.render(Context(self.ask_db_info()))
        amavis_conn = t.render(Context(self.ask_db_info('amavis'))) if parsed_args.with_amavis \
            else None

        mod = __import__(parsed_args.name, globals(), locals(), ['settings'])
        tpl = self._render_template("%s/settings.py" % self._templates_dir, {
            'default_conn' : default_conn, 'amavis_conn' : amavis_conn,
            'secret_key' : mod.settings.SECRET_KEY,
            'name' : parsed_args.name, 'django14' : django14
            })
        fp = open("%s/settings.py" % path, "w")
        fp.write(tpl)
        fp.close()
        shutil.copyfile("%s/urls.py" % self._templates_dir, "%s/urls.py" % path)
        os.mkdir("%s/media" % path)

        if parsed_args.run_syncdb:
            self._exec_django_command("syncdb", parsed_args.name, '--migrate', '--noinput')
            self._exec_django_command("loaddata", parsed_args.name, 'initial_users.json')

        if parsed_args.run_collectstatic:
            self._exec_django_command("collectstatic", parsed_args.name, '--noinput')
<commit_msg>wip: command line tool<commit_after>import sys
import os
import shutil
import getpass
import subprocess
from django.core import management
from django.template import Context, Template
from modoboa.core.management import Command

dbconn_tpl = """
    '{{ conn_name }}': {
        'ENGINE': 'django.db.backends.{{ dbtype }}',  # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'.
        'NAME': '{{ dbname }}',                       # Or path to database file if using sqlite3.
        'USER': '{{ username }}',                     # Not used with sqlite3.
        'PASSWORD': '{{ password }}',                 # Not used with sqlite3.
        'HOST': '{{ dbhost }}',                       # Set to empty string for localhost. Not used with sqlite3.
        'PORT': '',                      # Set to empty string for default. Not used with sqlite3.{% if dbtype == 'mysql' %}
        'OPTIONS' : {
            "init_command" : 'SET foreign_key_checks = 0;',
        },{% endif %}
    },
"""

class DeployCommand(Command):
    help = "Create a fresh django project (calling startproject) and apply Modoboa specific settings."

    def __init__(self, *args, **kwargs):
        super(DeployCommand, self).__init__(*args, **kwargs)
        self._parser.add_argument('name', type=str,
                                  help='The name of your Modoboa instance')
        self._parser.add_argument('--with-amavis', action='store_true', default=False,
                                  help='Include amavis configuration')
        self._parser.add_argument('--syncdb', action='store_true', default=False,
                                  help='Run django syncdb command')
        self._parser.add_argument('--collectstatic', action='store_true', default=False,
                                  help='Run django collectstatic command')

    def _exec_django_command(self, name, cwd, *args):
        """Run a django command for the freshly created project

        :param name: the command name
        :param cwd: the directory where the command must be executed
        """
        cmd = 'python manage.py %s %s' % (name, " ".join(args))
        if not self._verbose:
            p = subprocess.Popen(cmd,
                                 stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
                                 shell=True, cwd=cwd)
            output = p.communicate()
        else:
            p = subprocess.Popen(cmd, shell=True, cwd=cwd)
            p.wait()
            output = None
        if p.returncode:
            if output:
                print >>sys.stderr, "\n".join(filter(lambda l: l != None, output))
            print >>sys.stderr, "%s failed, check your configuration" % cmd

    def ask_db_info(self, name='default'):
        """Prompt the user for database information

        Gather all information required to create a new database
        connection (into settings.py).

        :param name: the connection name
        """
        print "Configuring database connection: %s" % name
        info = {'conn_name' : name}
        info['dbtype'] = raw_input('Database type (mysql or postgres): ')
        if info['dbtype'] not in ['mysql', 'postgres']:
            info['dbtype'] = 'mysql'
        if info['dbtype'] == 'postgres':
            info['dbtype'] = 'postgresql_psycopg2'
        info['dbhost'] = raw_input('Database host (default: localhost): ')
        if info['dbhost'] == '':
            info['dbhost'] = 'localhost'
        info['dbname'] = raw_input('Database name: ')
        info['username'] = raw_input('Username: ')
        info['password'] = getpass.getpass('Password: ')
        return info

    def handle(self, parsed_args):
        management.call_command('startproject', parsed_args.name, verbosity=False)
        if os.path.exists("%(name)s/%(name)s" % {'name' : parsed_args.name}):
            # Django 1.4+
            path = "%(name)s/%(name)s" % {'name' : parsed_args.name}
            sys.path.append(parsed_args.name)
            django14 = True
        else:
            path = parsed_args.name
            sys.path.append(".")
            django14 = False

        t = Template(dbconn_tpl)
        default_conn = t.render(Context(self.ask_db_info()))
        amavis_conn = t.render(Context(self.ask_db_info('amavis'))) if parsed_args.with_amavis \
            else None

        mod = __import__(parsed_args.name, globals(), locals(), ['settings'])
        tpl = self._render_template("%s/settings.py" % self._templates_dir, {
            'default_conn' : default_conn, 'amavis_conn' : amavis_conn,
            'secret_key' : mod.settings.SECRET_KEY,
            'name' : parsed_args.name, 'django14' : django14
            })
        fp = open("%s/settings.py" % path, "w")
        fp.write(tpl)
        fp.close()
        shutil.copyfile("%s/urls.py" % self._templates_dir, "%s/urls.py" % path)
        os.mkdir("%s/media" % path)

        if parsed_args.syncdb:
            self._exec_django_command("syncdb", parsed_args.name, '--migrate', '--noinput')
            self._exec_django_command("loaddata", parsed_args.name, 'initial_users.json')

        if parsed_args.collectstatic:
            self._exec_django_command("collectstatic", parsed_args.name, '--noinput')
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-

"""
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 3 of the License,
    or (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
    See the GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, see <http://www.gnu.org/licenses/>.

    @author: Walter Purcaro
"""

from os.path import basename
from re import search

from module.plugins.Hook import Hook

class SkipRev(Hook):
    __name__ = "SkipRev"
    __version__ = "0.07"
    __description__ = "Skip download when filename has rev extension"
    __config__ = [
        ("activated", "bool", "Activated", "False"),
        ("number", "int", "Do not skip until rev part", "1")
    ]
    __author_name__ = ("Walter Purcaro")
    __author_mail__ = ("vuolter@gmail.com")

    def downloadPreparing(self, pyfile):
        # self.logDebug("self.downloadPreparing")
        name = basename(pyfile.name)
        if not name.endswith(".rev"):
            return
        number = self.getConfig("number")
        part = search(r'\.part(\d+)\.rev$', name)
        if not part or int(part.group(1)) <= number:
            return
        self.logInfo("Skipping " + name)
        pyfile.setStatus("skipped")
<commit_msg>Removed SkipRev. See #167<commit_after><|endoftext|>


<commit_before># -*- coding: utf-8 -*-
############################################################################
# This program is free software: you can redistribute it and/or modify     #
# it under the terms of the GNU Affero General Public License as           #
# published by the Free Software Foundation, either version 3 of the       #
# License, or (at your option) any later version.                          #
#                                                                          #
# This program is distributed in the hope that it will be useful,          #
# but WITHOUT ANY WARRANTY; without even the implied warranty of           #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            #
# GNU Affero General Public License for more details.                      #
#                                                                          #
# You should have received a copy of the GNU Affero General Public License #
# along with this program.  If not, see <http://www.gnu.org/licenses/>.    #
############################################################################

# Test links (random.bin):
# http://www.load.to/JWydcofUY6/random.bin
# http://www.load.to/oeSmrfkXE/random100.bin

import re

from module.plugins.internal.CaptchaService import SolveMedia
from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo

class LoadTo(SimpleHoster):
    __name__ = "LoadTo"
    __type__ = "hoster"
    __pattern__ = r'http://(?:www\.)?load\.to/\w+'
    __version__ = "0.14"
    __description__ = """Load.to hoster plugin"""
    __author_name__ = ("halfman", "stickell")
    __author_mail__ = ("Pulpan3@gmail.com", "l.stickell@yahoo.it")

    FILE_INFO_PATTERN = r'<title>(?P<N>.+) \/\/ Load.to</title>'
    FILE_SIZE_PATTERN = r']+>(?P<Z>.+)
\s*Size: (?P<S>.*) (?P[kKmMgG]?i?[bB])'
    OFFLINE_PATTERN = r'Can\'t find file\. Please check URL'

    LINK_PATTERN = r'<form method="post" action="(.+?)"'
    WAIT_PATTERN = r'type="submit" value="Download \((\d+)\)"'
    SOLVEMEDIA_PATTERN = r'http://api\.solvemedia\.com/papi/challenge\.noscript\?k=([^"]+)'

    def setup(self):
        self.multiDL = True
        self.chunkLimit = 1

    def process(self, pyfile):
        self.html = self.load(pyfile.url, decode=True)
        self.getFileInfo()

        # Check if File is online
        if re.search(self.OFFLINE_PATTERN, self.html):
            self.offline()

        # Search for Download URL
        m = re.search(self.LINK_PATTERN, self.html)
        if not m:
            self.parseError("Unable to detect download URL")

        download_url = m.group(1)

        # Set Timer - may be obsolete
        m = re.search(self.WAIT_PATTERN, self.html)
        if m:
            self.wait(m.group(1))

        # Load.to is using solvemedia captchas since ~july 2014:
        found = re.search(self.SOLVEMEDIA_PATTERN, self.html)
        if not found:
            self.download(download_url)
        else:
            captcha_key = found.group(1)
            solvemedia = SolveMedia(self)
            captcha_challenge, captcha_response = solvemedia.challenge(captcha_key)
            self.download(download_url, post={"adcopy_challenge": captcha_challenge, "adcopy_response": captcha_response})
            check = self.checkDownload({"404": re.compile("\A
404 Not Found
")})
            if check == "404":
                self.logWarning("The captcha you entered was incorrect. Please try again.")
                self.invalidCaptcha()
                self.retry()

getInfo = create_getInfo(LoadTo)
<commit_msg>[LoadTo] renamed pattern<commit_after># -*- coding: utf-8 -*-
############################################################################
# This program is free software: you can redistribute it and/or modify     #
# it under the terms of the GNU Affero General Public License as           #
# published by the Free Software Foundation, either version 3 of the       #
# License, or (at your option) any later version.                          #
#                                                                          #
# This program is distributed in the hope that it will be useful,          #
# but WITHOUT ANY WARRANTY; without even the implied warranty of           #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            #
# GNU Affero General Public License for more details.                      #
#                                                                          #
# You should have received a copy of the GNU Affero General Public License #
# along with this program.  If not, see <http://www.gnu.org/licenses/>.    #
############################################################################

# Test links (random.bin):
# http://www.load.to/JWydcofUY6/random.bin
# http://www.load.to/oeSmrfkXE/random100.bin

import re

from module.plugins.internal.CaptchaService import SolveMedia
from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo

class LoadTo(SimpleHoster):
    __name__ = "LoadTo"
    __type__ = "hoster"
    __pattern__ = r'http://(?:www\.)?load\.to/\w+'
    __version__ = "0.15"
    __description__ = """Load.to hoster plugin"""
    __author_name__ = ("halfman", "stickell")
    __author_mail__ = ("Pulpan3@gmail.com", "l.stickell@yahoo.it")

    FILE_NAME_PATTERN = r'<title>(?P<N>.+) \/\/ Load.to</title>'
    FILE_SIZE_PATTERN = r']+>(?P<Z>.+)
\s*Size: (?P<S>.*) (?P[kKmMgG]?i?[bB])'
    OFFLINE_PATTERN = r'Can\'t find file\. Please check URL'

    LINK_PATTERN = r'<form method="post" action="(.+?)"'
    WAIT_PATTERN = r'type="submit" value="Download \((\d+)\)"'
    SOLVEMEDIA_PATTERN = r'http://api\.solvemedia\.com/papi/challenge\.noscript\?k=([^"]+)'

    def setup(self):
        self.multiDL = True
        self.chunkLimit = 1

    def process(self, pyfile):
        self.html = self.load(pyfile.url, decode=True)
        self.getFileInfo()

        # Check if File is online
        if re.search(self.OFFLINE_PATTERN, self.html):
            self.offline()

        # Search for Download URL
        m = re.search(self.LINK_PATTERN, self.html)
        if not m:
            self.parseError("Unable to detect download URL")

        download_url = m.group(1)

        # Set Timer - may be obsolete
        m = re.search(self.WAIT_PATTERN, self.html)
        if m:
            self.wait(m.group(1))

        # Load.to is using solvemedia captchas since ~july 2014:
        found = re.search(self.SOLVEMEDIA_PATTERN, self.html)
        if not found:
            self.download(download_url)
        else:
            captcha_key = found.group(1)
            solvemedia = SolveMedia(self)
            captcha_challenge, captcha_response = solvemedia.challenge(captcha_key)
            self.download(download_url, post={"adcopy_challenge": captcha_challenge, "adcopy_response": captcha_response})
            check = self.checkDownload({"404": re.compile("\A
404 Not Found
")})
            if check == "404":
                self.logWarning("The captcha you entered was incorrect. Please try again.")
                self.invalidCaptcha()
                self.retry()

getInfo = create_getInfo(LoadTo)
<|endoftext|>


<commit_before># coding=utf-8
#
# ROSREPO
# Manage ROS workspaces with multiple Gitlab repositories
#
# Author: Timo Röhling
#
# Copyright 2016 Fraunhofer FKIE
#
# 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.
#
#
__author__ = "Timo Röhling <timo.roehling@fkie.fraunhofer.de>"
__copyright__ = "Copyright (c) 2016 Fraunhofer FKIE"
__license__ = "Apache-2.0"
__version__ = "3.0.43"
<commit_msg>3.0.44<commit_after># coding=utf-8
#
# ROSREPO
# Manage ROS workspaces with multiple Gitlab repositories
#
# Author: Timo Röhling
#
# Copyright 2016 Fraunhofer FKIE
#
# 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.
#
#
__author__ = "Timo Röhling <timo.roehling@fkie.fraunhofer.de>"
__copyright__ = "Copyright (c) 2016 Fraunhofer FKIE"
__license__ = "Apache-2.0"
__version__ = "3.0.44"
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
from configs.module import Module
import importlib
from . import access
import fnmatch
importlib.reload(access)
import utils

def init():
    m = Module('rights')
    m.set_help('Manage user rights.')
    m.add_command_hook('getrights',
        {
            'function': getrights,
            'help': 'Get user rights.',
            'args': [
                {
                    'name': 'base',
                    'keyvalue': '',
                    'optional': True,
                    'help': 'Only show base rights.',
                    },
                {
                    'name': 'user',
                    'optional': True,
                    'help': 'The user to view, defaults to caller. ([<nick>]:'
                    '[<hostname>]:[<NickServ Account>])'
                    },
                {
                    'name': 'rights',
                    'optional': True,
                    'help': 'Rights to test.'
                    }
                ],
            })
    m.add_command_hook('addrights',
        {
            'function': addrights,
            'help': 'Add user rights.',
            'args': [
                {
                    'name': 'user',
                    'optional': False,
                    'help': 'The user to set. ([<nick>]:'
                    '[<hostname>]:[<NickServ Account>])'
                    },
                        {
                    'name': 'rights',
                    'optional': False,
                    'end': True,
                    'help': 'The rights to set.'
                    },
                ],
            })
    m.add_command_hook('delrights',
        {
            'function': delrights,
            'help': 'Delete user rights.',
            'args': [
                {
                    'name': 'user',
                    'optional': False,
                    'help': 'The user to set. ([<nick>]:'
                    '[<hostname>]:[<NickServ Account>])'
                    },
                        {
                    'name': 'rights',
                    'optional': False,
                    'end': True,
                    'help': 'The rights to set.'
                    },
                ],
            })
    m.add_command_hook('getusers',
        {
            'function': getusers,
            'help': 'Get a list of users matching a "regex".',
            'args': [
                {
                    'name': 'search',
                    'optional': False,
                    'help': 'The "regex" to search for, uses fnmatchcase().'
                    },
                ],
            })
    m.add_command_hook('listrights',
        {
            'function': listrights,
            'help': 'Get a list of all rights.',
            'args': [],
            })
    m.add_rights([
        'disable',
        '%,disable',
        'admin',
        'owner',
        'normal',
        '%,normal',
        '%,op',
        '%,voice',
        ])
    m.add_implicit_rights(
        {
            '%,op': '%,normal',
            '%,voice': '%,normal',
            'admin': ['%,normal', 'normal'],
            'owner': 'admin',
        })
    m.add_command_alias('setrights', 'addrights')
    m.add_command_alias('removerights', 'delrights')
    m.add_base_hook('whois.done', whois_done)
    return m

def whois_done(server, nick, whois):
    access = server.import_module('rights.access', False)
    rights = access.getrights(server, "%s=%s@%s=%s" % (
        nick, whois['ident'], whois['host'],
        whois['authed'] if 'authed' in whois else ''))
    for c in whois['channels']:
        if (c + ',op') in rights and c not in whois['op']:
            server.write_cmd('MODE', '%s +o %s' % (c, nick))
        elif (c + ',voice') in rights and c not in whois['voice']:
            server.write_cmd('MODE', '%s +v %s' % (c, nick))

def listrights(fp, args):
    r = []
    for m in fp.server.modules:
        r += m.rights
    return utils.ltos(sorted(r, key=lambda x: x.strip('%,')))

def getusers(fp, args):
    search = args.getlinstr('search')
    try:
        access.raiseifnotformeduser(search)
    except access.AccessLevelError:
        return "Malformed user!"
    results = []
    for user in fp.server.adb:
        if fnmatch.fnmatchcase(user, search):
            results.append(user)
    return('Results: %s' % (utils.ltos(results)))

def getrights(fp, args):
    user = args.getlinstr('user', fp.accesslevelname)
    if user == '.':
        user = fp.accesslevelname
    try:
        access.raiseifnotformeduser(user)
    except access.AccessLevelError:
        return "Malformed user!"
    extra = []
    for c in fp.server.channels:
        try:
            extra += fp.channelrights('='.join(user.split('=')[:-2]), c)
        except IndexError:
            pass
    if 'base' not in args.lin and not args.getlinstr('rights', ''):
        r = access.fullrights(fp, access.getrights(fp.server, user)
        + extra)
    else:
        r = access.getrights(fp.server, user)
    if args.getlinstr('rights', ''):
        hasrights = True
        for right in args.getlinstr('rights', '').split(' '):
            if right not in r:
                hasrights = False
        return "Yes" if hasrights else "No"
    return user + ': ' + utils.ltos(sorted(
        utils.unique(r,
        ), key=lambda x: x.strip('#-')), '; ')

def rightexists(fp, right):
    r = []
    for m in fp.server.modules:
        r += m.rights
    if len(right.split(',')) == 2:
        if '%,' + right.split(',')[1].strip('-') in r:
            return True
        r = right.split(',')[1].strip('-')
    if right.find(':') == 0:
        return True
    elif right in r:
        return True
    else:
        return False

def addrights(fp, args):
    user = args.getlinstr('user')
    try:
        access.raiseifnotformeduser(user)
    except access.AccessLevelError:
        return "Malformed user!"
    finished = []
    for right in args.getlinstr('rights').split(' '):
        sright = right.strip('-')
        if not rightexists(fp, sright):
            return 'That right does not exist.'
        if sright == 'owner':
            return 'You may not set owner.'
        elif sright == 'admin' and not fp.hasright('owner'):
            return 'You may not set admin.'
        channel = "#"
        if access.ischannel(user):
            channel = user
        elif len(right.split(',')) == 2:
            channel = right.split(',')[0]
        if not (fp.hasright('owner')):
            if channel != '#' and not fp.hasright(channel + ',op'):
                return (
                    '%s: You must be either an owner or operator.' % (
                        right))
            elif channel == '#' and not fp.hasright('admin'):
                return (
                    '%s: You must be either an owner or admin.' % (
                        right))
        access.setright(fp.server, user, right)
        finished.append(right)
    return '%s added to %s' % (utils.ltos(finished, '; '), user)

def delrights(fp, args):
    user = args.getlinstr('user')
    try:
        access.raiseifnotformeduser(user)
    except access.AccessLevelError:
        return "Malformed user!"
    finished = []
    for right in args.getlinstr('rights').split(' '):
        sright = right.strip('-')
        if not rightexists(fp, sright):
            return 'That right does not exist.'
        if sright == 'owner':
            return 'You may not set owner.'
        elif sright == 'admin' and not fp.hasright('owner'):
            return 'You may not set admin.'
        channel = "#"
        if access.ischannel(user):
            channel = user
        elif len(right.split(',')) == 2:
            channel = right.split(',')[0]
        if not (fp.hasright('owner')):
            if channel != '#' and not fp.hasright(channel + ',op'):
                return (
                    '%s: You must be either an owner or operator.' % (
                        right))
            elif channel == '#' and not fp.hasright('admin'):
                return (
                    '%s: You must be either an owner or admin.' % (
                        right))
        access.delright(fp.server, user, right)
        finished.append(right)
    return '%s removed from %s' % (utils.ltos(finished, '; '), user)

<commit_msg>fix bad logic<commit_after># -*- coding: utf-8 -*-
from configs.module import Module
import importlib
from . import access
import fnmatch
importlib.reload(access)
import utils

def init():
    m = Module('rights')
    m.set_help('Manage user rights.')
    m.add_command_hook('getrights',
        {
            'function': getrights,
            'help': 'Get user rights.',
            'args': [
                {
                    'name': 'base',
                    'keyvalue': '',
                    'optional': True,
                    'help': 'Only show base rights.',
                    },
                {
                    'name': 'user',
                    'optional': True,
                    'help': 'The user to view, defaults to caller. ([<nick>]:'
                    '[<hostname>]:[<NickServ Account>])'
                    },
                {
                    'name': 'rights',
                    'optional': True,
                    'help': 'Rights to test.'
                    }
                ],
            })
    m.add_command_hook('addrights',
        {
            'function': addrights,
            'help': 'Add user rights.',
            'args': [
                {
                    'name': 'user',
                    'optional': False,
                    'help': 'The user to set. ([<nick>]:'
                    '[<hostname>]:[<NickServ Account>])'
                    },
                        {
                    'name': 'rights',
                    'optional': False,
                    'end': True,
                    'help': 'The rights to set.'
                    },
                ],
            })
    m.add_command_hook('delrights',
        {
            'function': delrights,
            'help': 'Delete user rights.',
            'args': [
                {
                    'name': 'user',
                    'optional': False,
                    'help': 'The user to set. ([<nick>]:'
                    '[<hostname>]:[<NickServ Account>])'
                    },
                        {
                    'name': 'rights',
                    'optional': False,
                    'end': True,
                    'help': 'The rights to set.'
                    },
                ],
            })
    m.add_command_hook('getusers',
        {
            'function': getusers,
            'help': 'Get a list of users matching a "regex".',
            'args': [
                {
                    'name': 'search',
                    'optional': False,
                    'help': 'The "regex" to search for, uses fnmatchcase().'
                    },
                ],
            })
    m.add_command_hook('listrights',
        {
            'function': listrights,
            'help': 'Get a list of all rights.',
            'args': [],
            })
    m.add_rights([
        'disable',
        '%,disable',
        'admin',
        'owner',
        'normal',
        '%,normal',
        '%,op',
        '%,voice',
        ])
    m.add_implicit_rights(
        {
            '%,op': '%,normal',
            '%,voice': '%,normal',
            'admin': ['%,normal', 'normal'],
            'owner': 'admin',
        })
    m.add_command_alias('setrights', 'addrights')
    m.add_command_alias('removerights', 'delrights')
    m.add_base_hook('whois.done', whois_done)
    return m

def whois_done(server, nick, whois):
    access = server.import_module('rights.access', False)
    rights = access.getrights(server, "%s=%s@%s=%s" % (
        nick, whois['ident'], whois['host'],
        whois['authed'] if 'authed' in whois else ''))
    for c in whois['channels']:
        if (c + ',op') in rights and c not in whois['op']:
            server.write_cmd('MODE', '%s +o %s' % (c, nick))
        elif (c + ',voice') in rights and c not in whois['voice']:
            server.write_cmd('MODE', '%s +v %s' % (c, nick))

def listrights(fp, args):
    r = []
    for m in fp.server.modules:
        r += m.rights
    return utils.ltos(sorted(r, key=lambda x: x.strip('%,')))

def getusers(fp, args):
    search = args.getlinstr('search')
    try:
        access.raiseifnotformeduser(search)
    except access.AccessLevelError:
        return "Malformed user!"
    results = []
    for user in fp.server.adb:
        if fnmatch.fnmatchcase(user, search):
            results.append(user)
    return('Results: %s' % (utils.ltos(results)))

def getrights(fp, args):
    user = args.getlinstr('user', fp.accesslevelname)
    if user == '.':
        user = fp.accesslevelname
    try:
        access.raiseifnotformeduser(user)
    except access.AccessLevelError:
        return "Malformed user!"
    extra = []
    for c in fp.server.channels:
        try:
            extra += fp.channelrights('='.join(user.split('=')[:-2]), c)
        except IndexError:
            pass
    if 'base' not in args.lin or args.getlinstr('rights', ''):
        r = access.fullrights(fp, access.getrights(fp.server, user)
        + extra)
    else:
        r = access.getrights(fp.server, user)
    if args.getlinstr('rights', ''):
        hasrights = True
        for right in args.getlinstr('rights', '').split(' '):
            if right not in r:
                hasrights = False
        return "Yes" if hasrights else "No"
    return user + ': ' + utils.ltos(sorted(
        utils.unique(r,
        ), key=lambda x: x.strip('#-')), '; ')

def rightexists(fp, right):
    r = []
    for m in fp.server.modules:
        r += m.rights
    if len(right.split(',')) == 2:
        if '%,' + right.split(',')[1].strip('-') in r:
            return True
        r = right.split(',')[1].strip('-')
    if right.find(':') == 0:
        return True
    elif right in r:
        return True
    else:
        return False

def addrights(fp, args):
    user = args.getlinstr('user')
    try:
        access.raiseifnotformeduser(user)
    except access.AccessLevelError:
        return "Malformed user!"
    finished = []
    for right in args.getlinstr('rights').split(' '):
        sright = right.strip('-')
        if not rightexists(fp, sright):
            return 'That right does not exist.'
        if sright == 'owner':
            return 'You may not set owner.'
        elif sright == 'admin' and not fp.hasright('owner'):
            return 'You may not set admin.'
        channel = "#"
        if access.ischannel(user):
            channel = user
        elif len(right.split(',')) == 2:
            channel = right.split(',')[0]
        if not (fp.hasright('owner')):
            if channel != '#' and not fp.hasright(channel + ',op'):
                return (
                    '%s: You must be either an owner or operator.' % (
                        right))
            elif channel == '#' and not fp.hasright('admin'):
                return (
                    '%s: You must be either an owner or admin.' % (
                        right))
        access.setright(fp.server, user, right)
        finished.append(right)
    return '%s added to %s' % (utils.ltos(finished, '; '), user)

def delrights(fp, args):
    user = args.getlinstr('user')
    try:
        access.raiseifnotformeduser(user)
    except access.AccessLevelError:
        return "Malformed user!"
    finished = []
    for right in args.getlinstr('rights').split(' '):
        sright = right.strip('-')
        if not rightexists(fp, sright):
            return 'That right does not exist.'
        if sright == 'owner':
            return 'You may not set owner.'
        elif sright == 'admin' and not fp.hasright('owner'):
            return 'You may not set admin.'
        channel = "#"
        if access.ischannel(user):
            channel = user
        elif len(right.split(',')) == 2:
            channel = right.split(',')[0]
        if not (fp.hasright('owner')):
            if channel != '#' and not fp.hasright(channel + ',op'):
                return (
                    '%s: You must be either an owner or operator.' % (
                        right))
            elif channel == '#' and not fp.hasright('admin'):
                return (
                    '%s: You must be either an owner or admin.' % (
                        right))
        access.delright(fp.server, user, right)
        finished.append(right)
    return '%s removed from %s' % (utils.ltos(finished, '; '), user)

<|endoftext|>