* fix: update build code path for new specfile. many small fixes wohooo :)
* apply patch: handle None as defaul params * fix: use run_batch. no need to duplicate code! * fix: xmlfile, error msg, and param * move specfile into correct place * update metadata
This commit is contained in:
@@ -1,15 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE Project SYSTEM "Project-3.8.dtd">
|
||||
<!-- Project file for project pisi -->
|
||||
<!-- Saved: 2005-11-02, 20:32:59 -->
|
||||
<!-- Copyright (C) 2005 PiSi Development Team, -->
|
||||
<!-- Saved: 2005-11-03, 02:14:40 -->
|
||||
<!-- Copyright (C) 2005 PiSi Development Team, pisi@uludag.org.tr -->
|
||||
<Project version="3.8">
|
||||
<ProgLanguage mixed="0">Python</ProgLanguage>
|
||||
<UIType>Qt</UIType>
|
||||
<Description>The package management software of Pardus distribution.
|
||||
</Description>
|
||||
<Version></Version>
|
||||
<Author>PiSi Development Team</Author>
|
||||
<Email></Email>
|
||||
<Email>pisi@uludag.org.tr</Email>
|
||||
<Sources>
|
||||
<Source>
|
||||
<Dir>tools</Dir>
|
||||
@@ -372,6 +373,14 @@
|
||||
<Dir>tests</Dir>
|
||||
<Name>xmlexttests.py</Name>
|
||||
</Source>
|
||||
<Source>
|
||||
<Dir>tests</Dir>
|
||||
<Name>specfileoldtests.py</Name>
|
||||
</Source>
|
||||
<Source>
|
||||
<Dir>pisi</Dir>
|
||||
<Name>specfileold.py</Name>
|
||||
</Source>
|
||||
</Sources>
|
||||
<Forms>
|
||||
</Forms>
|
||||
@@ -409,10 +418,10 @@
|
||||
<VcsOtherData>{'standardLayout': 1}</VcsOtherData>
|
||||
</Vcs>
|
||||
<FiletypeAssociations>
|
||||
<FiletypeAssociation pattern="*.py" type="SOURCES" />
|
||||
<FiletypeAssociation pattern="*.ui.h" type="FORMS" />
|
||||
<FiletypeAssociation pattern="*.ptl" type="SOURCES" />
|
||||
<FiletypeAssociation pattern="*.idl" type="INTERFACES" />
|
||||
<FiletypeAssociation pattern="*.ui" type="FORMS" />
|
||||
<FiletypeAssociation pattern="*.ptl" type="SOURCES" />
|
||||
<FiletypeAssociation pattern="*.py" type="SOURCES" />
|
||||
</FiletypeAssociations>
|
||||
</Project>
|
||||
|
||||
+19
-19
@@ -50,8 +50,8 @@ def get_file_type(path, pinfoList):
|
||||
ftype = ""
|
||||
path = "/"+path # we need a real path.
|
||||
for pinfo in pinfoList:
|
||||
if util.subpath(pinfo.pathname, path):
|
||||
length = len(pinfo.pathname)
|
||||
if util.subpath(pinfo.path, path):
|
||||
length = len(pinfo.path)
|
||||
if depth < length:
|
||||
depth = length
|
||||
ftype = pinfo.fileType
|
||||
@@ -62,20 +62,20 @@ def check_path_collision(package, pkgList):
|
||||
the paths of packages in pkgList. The return value will be the
|
||||
list containing the paths that collide."""
|
||||
collisions = []
|
||||
for pinfo in package.paths:
|
||||
for pinfo in package.files:
|
||||
for pkg in pkgList:
|
||||
if pkg is package:
|
||||
continue
|
||||
for path in pkg.paths:
|
||||
# if pinfo.pathname is a subpath of path.pathname like
|
||||
# the example below. path.patname is marked as a
|
||||
for path in pkg.files:
|
||||
# if pinfo.path is a subpath of path.path like
|
||||
# the example below. path.path is marked as a
|
||||
# collide. Exp:
|
||||
# pinfo.pathname: /usr/share
|
||||
# path.pathname: /usr/share/doc
|
||||
if util.subpath(pinfo.pathname, path.pathname):
|
||||
collisions.append(path.pathname)
|
||||
# pinfo.path: /usr/share
|
||||
# path.path: /usr/share/doc
|
||||
if util.subpath(pinfo.path, path.path):
|
||||
collisions.append(path.path)
|
||||
ctx.ui.error(_('Path %s belongs in multiple packages') %
|
||||
path.pathname)
|
||||
path.path)
|
||||
return collisions
|
||||
|
||||
# a dynamic build context
|
||||
@@ -191,10 +191,10 @@ class Builder:
|
||||
ctx.ui.info(_("CCache detected..."))
|
||||
|
||||
def fetch_source_archive(self):
|
||||
ctx.ui.info(_("Fetching source from: %s") % self.spec.source.archiveUri)
|
||||
ctx.ui.info(_("Fetching source from: %s") % self.spec.source.archive.uri)
|
||||
self.sourceArchive.fetch()
|
||||
ctx.ui.info(_("Source archive is stored: %s/%s")
|
||||
%(ctx.config.archives_dir(), self.spec.source.archiveName))
|
||||
%(ctx.config.archives_dir(), self.spec.source.archive.name))
|
||||
|
||||
def unpack_source_archive(self):
|
||||
ctx.ui.info(_("Unpacking archive..."))
|
||||
@@ -274,7 +274,7 @@ class Builder:
|
||||
|
||||
# find out the build dependencies that are not satisfied...
|
||||
dep_unsatis = []
|
||||
for dep in self.spec.source.buildDeps:
|
||||
for dep in self.spec.source.buildDependencies:
|
||||
if not dependency.installed_satisfies_dep(dep):
|
||||
dep_unsatis.append(dep)
|
||||
|
||||
@@ -340,8 +340,8 @@ class Builder:
|
||||
|
||||
size, d = 0, self.bctx.pkg_install_dir()
|
||||
|
||||
for path in package.paths:
|
||||
size += util.dir_size(util.join_path(d, path.pathname))
|
||||
for path in package.files:
|
||||
size += util.dir_size(util.join_path(d, path.path))
|
||||
|
||||
metadata.package.installedSize = str(size)
|
||||
|
||||
@@ -377,15 +377,15 @@ class Builder:
|
||||
# add the files under material path
|
||||
for fpath, fhash in util.get_file_hashes(path, collisions, install_dir):
|
||||
frpath = util.removepathprefix(install_dir, fpath) # relative path
|
||||
ftype = get_file_type(frpath, package.paths)
|
||||
ftype = get_file_type(frpath, package.files)
|
||||
try: # broken links and empty dirs can cause problem
|
||||
fsize = str(os.path.getsize(fpath))
|
||||
except OSError:
|
||||
fsize = None
|
||||
d[frpath] = FileInfo(frpath, ftype, fsize, fhash)
|
||||
|
||||
for pinfo in package.paths:
|
||||
wildcard_path = util.join_path(install_dir, pinfo.pathname)
|
||||
for pinfo in package.files:
|
||||
wildcard_path = util.join_path(install_dir, pinfo.path)
|
||||
for path in glob.glob(wildcard_path):
|
||||
add_path(path)
|
||||
|
||||
|
||||
+9
-9
@@ -39,9 +39,10 @@ class Source:
|
||||
class Package:
|
||||
__metaclass__ = xmlfile.autoxml
|
||||
|
||||
# FIXME: copied attributes
|
||||
t_Name = [ xmlfile.String, xmlfile.mandatory ]
|
||||
t_Summary = [ xmlfile.String, xmlfile.mandatory ]
|
||||
t_Description = [ xmlfile.String, xmlfile.mandatory ]
|
||||
t_Summary = [ xmlfile.LocalText, xmlfile.optional ]
|
||||
t_Description = [ xmlfile.LocalText, xmlfile.optional ]
|
||||
t_IsA = [ [xmlfile.String], xmlfile.optional]
|
||||
t_PartOf = [xmlfile.String, xmlfile.optional]
|
||||
t_License = [ [xmlfile.String], xmlfile.optional]
|
||||
@@ -53,6 +54,7 @@ class Package:
|
||||
#t_RequiresComar = [ [xmlfile.String], xmlfile.mandatory, "Requires/COMAR"]
|
||||
t_AdditionalFiles = [ [specfile.AdditionalFile], xmlfile.optional]
|
||||
t_History = [ [specfile.Update], xmlfile.optional]
|
||||
|
||||
t_Build = [ xmlfile.Integer, xmlfile.optional]
|
||||
t_Distribution = [ xmlfile.String, xmlfile.mandatory]
|
||||
t_DistributionRelease = [ xmlfile.String, xmlfile.mandatory]
|
||||
@@ -81,25 +83,23 @@ class MetaData(xmlfile.XmlFile):
|
||||
#t_History = [ [Update], xmlfile.mandatory]
|
||||
|
||||
def from_spec(self, src, pkg):
|
||||
self.source = SourceInfo()
|
||||
self.source.name = src.name
|
||||
self.source.homepage = src.homepage
|
||||
self.source.packager = src.packager
|
||||
self.package = PackageInfo()
|
||||
self.package.name = pkg.name
|
||||
self.package.summary = pkg.summary
|
||||
self.package.description = pkg.description
|
||||
self.package.icon = pkg.icon
|
||||
self.package.isa = pkg.isa
|
||||
self.package.partof = pkg.partof
|
||||
self.package.isA = pkg.isA
|
||||
self.package.partOf = pkg.partOf
|
||||
self.package.license = pkg.license
|
||||
self.package.runtimeDeps = pkg.runtimeDeps
|
||||
self.package.paths = pkg.paths
|
||||
self.package.runtimeDependencies = pkg.runtimeDependencies
|
||||
self.package.files = pkg.files
|
||||
# FIXME: no need to copy full history with comments
|
||||
self.package.history = src.history
|
||||
self.package.conflicts = pkg.conflicts
|
||||
self.package.providesComar = pkg.providesComar
|
||||
self.package.requiresComar = pkg.requiresComar
|
||||
#self.package.requiresComar = pkg.requiresComar
|
||||
self.package.additionalFiles = pkg.additionalFiles
|
||||
|
||||
# FIXME: right way to do it?
|
||||
|
||||
+8
-10
@@ -36,11 +36,9 @@ class SourceArchive:
|
||||
"""source archive. this is a class responsible for fetching
|
||||
and unpacking a source archive"""
|
||||
def __init__(self, bctx):
|
||||
self.url = URI(bctx.spec.source.archiveUri)
|
||||
self.url = URI(bctx.spec.source.archive.uri)
|
||||
self.archiveFile = join(ctx.config.archives_dir(), self.url.filename())
|
||||
self.archiveName = bctx.spec.source.archiveName
|
||||
self.archiveType = bctx.spec.source.archiveType
|
||||
self.archiveSHA1 = bctx.spec.source.archiveSHA1
|
||||
self.archive = bctx.spec.source.archive
|
||||
self.bctx = bctx
|
||||
|
||||
def fetch(self, interactive=True):
|
||||
@@ -55,18 +53,18 @@ class SourceArchive:
|
||||
return False
|
||||
|
||||
# check hash
|
||||
if util.check_file_hash(self.archiveFile, self.archiveSHA1):
|
||||
if util.check_file_hash(self.archiveFile, self.archive.sha1sum):
|
||||
if interactive:
|
||||
ctx.ui.info(_('%s [cached]') % self.archiveName)
|
||||
ctx.ui.info(_('%s [cached]') % self.archive.name)
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def unpack(self, cleanDir=True):
|
||||
def unpack(self, clean_dir=True):
|
||||
|
||||
# check archive file's integrity
|
||||
if not util.check_file_hash(self.archiveFile, self.archiveSHA1):
|
||||
if not util.check_file_hash(self.archiveFile, self.archive.sha1sum):
|
||||
raise Error, _("unpack: check_file_hash failed")
|
||||
|
||||
archive = Archive(self.archiveFile, self.archiveType)
|
||||
archive.unpack(self.bctx.pkg_work_dir(), cleanDir)
|
||||
archive = Archive(self.archiveFile, self.archive.type)
|
||||
archive.unpack(self.bctx.pkg_work_dir(), clean_dir)
|
||||
|
||||
@@ -0,0 +1,246 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2005, TUBITAK/UEKAE
|
||||
#
|
||||
# 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 2 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# Please read the COPYING file.
|
||||
#
|
||||
# Authors: Eray Ozkural <eray@uludag.org.tr>
|
||||
# Baris Metin <baris@uludag.org.tr>
|
||||
|
||||
"""
|
||||
Specfile module is our handler for PSPEC files. PSPEC (PISI SPEC)
|
||||
files are specification files for PISI source packages. This module
|
||||
provides read and write routines for PSPEC files.
|
||||
"""
|
||||
|
||||
import gettext
|
||||
__trans = gettext.translation('pisi', fallback=True)
|
||||
_ = __trans.ugettext
|
||||
|
||||
# standard python modules
|
||||
from os.path import basename
|
||||
|
||||
# pisi modules
|
||||
from pisi.xmlext import *
|
||||
import pisi.xmlfile as xmlfile
|
||||
from pisi.xmlfile import XmlFile
|
||||
import pisi.context as ctx
|
||||
from pisi.dependency import DepInfo
|
||||
from pisi.util import Checks
|
||||
|
||||
class Error(pisi.Error):
|
||||
pass
|
||||
|
||||
__metaclass__ = xmlfile.autoxml
|
||||
|
||||
class Packager:
|
||||
|
||||
t_Name = [xmlfile.Text, xmlfile.mandatory]
|
||||
t_Email = [xmlfile.String, xmlfile.mandatory]
|
||||
def __str__(self):
|
||||
s = "%s <%s>" % (self.name, self.email)
|
||||
return s
|
||||
|
||||
|
||||
class AdditionalFile:
|
||||
|
||||
s_Filename = [xmlfile.String, xmlfile.mandatory]
|
||||
a_Target = [xmlfile.String, xmlfile.mandatory]
|
||||
a_Permission = [xmlfile.String, xmlfile.optional]
|
||||
|
||||
def __str__(self):
|
||||
s = "%s -> %s " % (self.filename, self.target)
|
||||
if self.permission:
|
||||
s += '(%s)' % self.permission
|
||||
return s
|
||||
|
||||
|
||||
class Patch:
|
||||
|
||||
s_Filename = [xmlfile.String, xmlfile.mandatory]
|
||||
a_compressionType = [xmlfile.String, xmlfile.optional]
|
||||
a_level = [xmlfile.String, xmlfile.optional]
|
||||
a_target = [xmlfile.String, xmlfile.optional]
|
||||
|
||||
def __str__(self):
|
||||
s = self.filename
|
||||
if self.compressionType:
|
||||
s += ' (' + self.compressionType + ')'
|
||||
if self.level:
|
||||
s += ' level:' + self.level
|
||||
if self.target:
|
||||
s += ' target:' + self.target
|
||||
return s
|
||||
|
||||
|
||||
class Update:
|
||||
|
||||
a_release = [xmlfile.String, xmlfile.mandatory]
|
||||
t_Date = [xmlfile.String, xmlfile.mandatory]
|
||||
t_Version = [xmlfile.String, xmlfile.mandatory]
|
||||
t_Type = [xmlfile.String, xmlfile.optional]
|
||||
|
||||
def __str__(self):
|
||||
s = self.date
|
||||
s += ", ver=" + self.version
|
||||
s += ", rel=" + self.release
|
||||
if self.type:
|
||||
s += ", type=" + self.type
|
||||
return s
|
||||
|
||||
|
||||
class Path:
|
||||
|
||||
s_Path = [xmlfile.String, xmlfile.mandatory]
|
||||
a_fileType = [xmlfile.String, xmlfile.optional]
|
||||
|
||||
def __str__(self):
|
||||
s = self.path
|
||||
s += ", type=" + self.fileType
|
||||
return s
|
||||
|
||||
class Dependency:
|
||||
|
||||
s_Package = [xmlfile.String, xmlfile.mandatory]
|
||||
a_versionFrom = [xmlfile.String, xmlfile.optional]
|
||||
a_versionTo = [xmlfile.String, xmlfile.optional]
|
||||
a_releaseFrom = [xmlfile.String, xmlfile.optional]
|
||||
a_releaseTo = [xmlfile.String, xmlfile.optional]
|
||||
|
||||
def __str__(self):
|
||||
s = self.package
|
||||
if self.versionFrom:
|
||||
s += 'ver >= ' + self.versionFrom
|
||||
if self.versionTo:
|
||||
s += 'ver <= ' + self.versionTo
|
||||
if self.releaseFrom:
|
||||
s += 'rel >= ' + self.releaseFrom
|
||||
if self.releaseTo:
|
||||
s += 'rel <= ' + self.releaseTo
|
||||
return s
|
||||
|
||||
class ComarProvide:
|
||||
|
||||
s_om = [xmlfile.String, xmlfile.mandatory]
|
||||
a_script = [xmlfile.String, xmlfile.mandatory]
|
||||
|
||||
def __str__(self):
|
||||
# FIXME: descriptive enough?
|
||||
s = self.script
|
||||
s += ' (' + self.om + ')'
|
||||
return s
|
||||
|
||||
|
||||
class Archive:
|
||||
|
||||
s_uri = [ xmlfile.String, xmlfile.mandatory ]
|
||||
a_type =[ xmlfile.String, xmlfile.mandatory ]
|
||||
a_sha1sum =[ xmlfile.String, xmlfile.mandatory ]
|
||||
|
||||
def decode_post(self):
|
||||
self.name = basename(self.uri)
|
||||
|
||||
|
||||
class Source:
|
||||
|
||||
t_Name = [xmlfile.String, xmlfile.mandatory]
|
||||
t_Homepage = [xmlfile.String, xmlfile.mandatory]
|
||||
t_Packager = [Packager, xmlfile.mandatory]
|
||||
t_Summary = [xmlfile.String, xmlfile.mandatory]
|
||||
t_Description = [xmlfile.String, xmlfile.mandatory]
|
||||
t_IsA = [ [xmlfile.String], xmlfile.mandatory]
|
||||
t_PartOf = [xmlfile.String, xmlfile.mandatory]
|
||||
t_Icon = [ xmlfile.String, xmlfile.optional]
|
||||
t_License = [ [xmlfile.String], xmlfile.mandatory]
|
||||
t_Archive = [Archive, xmlfile.mandatory ]
|
||||
t_Patches = [ [Patch], xmlfile.optional]
|
||||
t_BuildDependencies = [ [Dependency], xmlfile.optional]
|
||||
t_Version = [ xmlfile.String, xmlfile.optional]
|
||||
t_Release = [ xmlfile.String, xmlfile.optional]
|
||||
|
||||
|
||||
class Package:
|
||||
|
||||
t_Name = [ xmlfile.String, xmlfile.mandatory ]
|
||||
t_Summary = [ xmlfile.String, xmlfile.mandatory ]
|
||||
t_Description = [ xmlfile.String, xmlfile.mandatory ]
|
||||
t_IsA = [ [xmlfile.String], xmlfile.optional]
|
||||
t_PartOf = [xmlfile.String, xmlfile.optional]
|
||||
t_License = [ [xmlfile.String], xmlfile.optional]
|
||||
t_Icon = [ xmlfile.String, xmlfile.optional]
|
||||
t_RuntimeDependencies = [ [Dependency], xmlfile.optional]
|
||||
t_Files = [ [Path], xmlfile.optional]
|
||||
t_Conflicts = [ [xmlfile.String], xmlfile.optional, "Conflicts/Package"]
|
||||
t_ProvidesComar = [ [ComarProvide], xmlfile.optional, "Provides/COMAR"]
|
||||
#t_RequiresComar = [ [xmlfile.String], xmlfile.mandatory, "Requires/COMAR"]
|
||||
t_AdditionalFiles = [ [AdditionalFile], xmlfile.optional]
|
||||
t_History = [ [Update], xmlfile.optional]
|
||||
|
||||
|
||||
class SpecFile(XmlFile):
|
||||
__metaclass__ = xmlfile.autoxml #needed when we specify a superclass
|
||||
|
||||
tag = "PISI"
|
||||
|
||||
t_Source = [ Source, xmlfile.mandatory]
|
||||
t_Packages = [ [Package], xmlfile.mandatory, "Package"]
|
||||
t_History = [ [Update], xmlfile.mandatory]
|
||||
|
||||
#we're not doing this with the init hook right now
|
||||
#def init(self, tag = "PISI"):
|
||||
#ignore tag
|
||||
#XmlFile.__init__(self, tag)
|
||||
|
||||
def read_hook(self, errs):
|
||||
"""Read PSPEC file"""
|
||||
self.merge_tags()
|
||||
self.override_tags()
|
||||
|
||||
def merge_tags(self):
|
||||
"""Merge tags from Source in Packages. Some tags in Packages merged
|
||||
with the tags from Source. There is a more detailed
|
||||
description in documents."""
|
||||
|
||||
# FIXME: copy only needed information
|
||||
# no need to keep full history with comments in metadata.xml
|
||||
self.source.history = self.history
|
||||
|
||||
# To avoid tag duplication in PSPEC we need to get
|
||||
# the last version and release information
|
||||
# from the most recent History/Update.
|
||||
if not self.source.version:
|
||||
self.source.version = self.history[0].version
|
||||
if not self.source.release:
|
||||
self.source.release = self.history[0].release
|
||||
|
||||
tmp = []
|
||||
for pkg in self.packages:
|
||||
pkg.isA.extend(self.source.isA)
|
||||
pkg.history = self.history
|
||||
tmp.append(pkg)
|
||||
self.packages = tmp
|
||||
|
||||
def override_tags(self):
|
||||
"""Override tags from Source in Packages. Some tags in Packages
|
||||
overrides the tags from Source. There is a more detailed
|
||||
description in documents."""
|
||||
|
||||
tmp = []
|
||||
for pkg in self.packages:
|
||||
if not pkg.summary:
|
||||
pkg.summary = self.source.summary
|
||||
if not pkg.description:
|
||||
pkg.description = self.source.description
|
||||
if not pkg.partOf:
|
||||
pkg.partOf = self.source.partOf
|
||||
if not pkg.license:
|
||||
pkg.license = self.source.license
|
||||
if not pkg.icon:
|
||||
pkg.icon = self.source.icon
|
||||
tmp.append(pkg)
|
||||
self.packages = tmp
|
||||
+10
-8
@@ -395,20 +395,22 @@ def uncompress(patchFile, compressType="gz", targetDir=None):
|
||||
return filePath
|
||||
|
||||
|
||||
def do_patch(sourceDir, patchFile, level, target = ''):
|
||||
def do_patch(sourceDir, patchFile, level = 0, target = ''):
|
||||
"""simple function to apply patches.."""
|
||||
cwd = os.getcwd()
|
||||
os.chdir(sourceDir)
|
||||
|
||||
if level == None:
|
||||
level = 0
|
||||
if target == None:
|
||||
target = ''
|
||||
|
||||
check_file(patchFile)
|
||||
level = int(level)
|
||||
cmd = "patch -p%d %s< %s" % (level, target, patchFile)
|
||||
p = os.popen(cmd)
|
||||
o = p.readlines()
|
||||
retval = p.close()
|
||||
if retval:
|
||||
(successful, lines) = run_batch("patch -p%d %s< %s" %
|
||||
(level, target, patchFile))
|
||||
if not successful:
|
||||
raise Error(_("ERROR: patch (%s) failed: %s") % (patchFile,
|
||||
strlist (o)))
|
||||
strlist (lines)))
|
||||
|
||||
os.chdir(cwd)
|
||||
|
||||
|
||||
+3
-3
@@ -84,14 +84,14 @@ class LocalText(dict):
|
||||
nodes = getAllNodes(node, self.tag)
|
||||
if not nodes:
|
||||
if self.req == mandatory:
|
||||
errs.append(where + _("LocalText '%s' should have at least one '%s' tag\n") %
|
||||
self.tag)
|
||||
errs.append(where + _(" At least one '%s' tag should have local text") %
|
||||
self.tag )
|
||||
else:
|
||||
for node in nodes:
|
||||
lang = getNodeAttribute(node, "xml:lang")
|
||||
c = getNodeText(node)
|
||||
if not c:
|
||||
errs.append(where + _("'%s' language of tag '%s' should have some text data\n") %
|
||||
errs.append(where + _("'%s' language of tag '%s' is empty") %
|
||||
(lang, self.tag))
|
||||
# FIXME: check for dups and 'en'
|
||||
if not lang:
|
||||
|
||||
Reference in New Issue
Block a user