%PDF- %PDF-
| Direktori : /proc/thread-self/root/usr/lib/calibre/calibre/ebooks/oeb/polish/check/ |
| Current File : //proc/thread-self/root/usr/lib/calibre/calibre/ebooks/oeb/polish/check/css.py |
#!/usr/bin/env python3
# License: GPL v3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
import atexit
import json
import numbers
import sys
from collections import namedtuple
from itertools import repeat
from qt.core import QApplication, QEventLoop, pyqtSignal, sip
from qt.webengine import (
QWebEnginePage, QWebEngineProfile, QWebEngineScript
)
from calibre import detect_ncpus as cpu_count, prints
from calibre.ebooks.oeb.polish.check.base import ERROR, WARN, BaseError
from calibre.gui2 import must_use_qt
from calibre.gui2.webengine import secure_webengine
class CSSParseError(BaseError):
level = ERROR
is_parsing_error = True
class CSSError(BaseError):
level = ERROR
class CSSWarning(BaseError):
level = WARN
def as_int_or_none(x):
if x is not None and not isinstance(x, numbers.Integral):
try:
x = int(x)
except Exception:
x = None
return x
def message_to_error(message, name, line_offset=0):
rule = message.get('rule', {})
rule_id = rule.get('id') or ''
cls = CSSWarning
if message.get('type') == 'error':
cls = CSSParseError if rule.get('name') == 'Parsing Errors' else CSSError
title = message.get('message') or _('Unknown error')
line = as_int_or_none(message.get('line'))
col = as_int_or_none(message.get('col'))
if col is not None:
col -= 1
if line is not None:
line += line_offset
ans = cls(title, name, line, col)
ans.HELP = rule.get('desc') or ''
ans.css_rule_id = rule_id
if ans.HELP and 'url' in rule:
ans.HELP += ' ' + _('See <a href="{}">detailed description</a>.').format(rule['url'])
return ans
def csslint_js():
ans = getattr(csslint_js, 'ans', None)
if ans is None:
ans = csslint_js.ans = P('csslint.js', data=True, allow_user_override=False).decode('utf-8') + '''
window.check_css = function(src) {
var rules = CSSLint.getRules();
var ruleset = {};
var ignored_rules = {
'order-alphabetical': 1,
'font-sizes': 1,
'zero-units': 1,
'bulletproof-font-face': 1,
'import': 1,
'box-model': 1,
'adjoining-classes': 1,
'box-sizing': 1,
'compatible-vendor-prefixes': 1,
'text-indent': 1,
'unique-headings': 1,
'fallback-colors': 1,
'font-faces': 1,
'regex-selectors': 1,
'universal-selector': 1,
'unqualified-attributes': 1,
'overqualified-elements': 1,
'shorthand': 1,
'duplicate-background-images': 1,
'floats': 1,
'ids': 1,
'gradients': 1
};
var error_rules = {
'known-properties': 1,
'duplicate-properties': 1,
'vendor-prefix': 1
};
for (var i = 0; i < rules.length; i++) {
var rule = rules[i];
if (!ignored_rules[rule.id] && rule.browsers === "All") ruleset[rule.id] = error_rules[rule.id] ? 2 : 1;
}
var result = CSSLint.verify(src, ruleset);
return result;
}
document.title = 'ready';
'''
return ans
def create_profile():
ans = getattr(create_profile, 'ans', None)
if ans is None:
ans = create_profile.ans = QWebEngineProfile(QApplication.instance())
s = QWebEngineScript()
s.setName('csslint.js')
s.setSourceCode(csslint_js())
s.setWorldId(QWebEngineScript.ScriptWorldId.ApplicationWorld)
ans.scripts().insert(s)
return ans
class Worker(QWebEnginePage):
work_done = pyqtSignal(object, object)
def __init__(self):
must_use_qt()
QWebEnginePage.__init__(self, create_profile(), QApplication.instance())
self.titleChanged.connect(self.title_changed)
secure_webengine(self.settings())
self.console_messages = []
self.ready = False
self.working = False
self.pending = None
self.setHtml('')
def title_changed(self, new_title):
if new_title == 'ready':
self.ready = True
if self.pending is not None:
self.check_css(self.pending)
self.pending = None
def javaScriptConsoleMessage(self, level, msg, lineno, source_id):
msg = f'{source_id}:{lineno}:{msg}'
self.console_messages.append(msg)
try:
print(msg)
except Exception:
pass
def check_css(self, src):
self.working = True
self.console_messages = []
self.runJavaScript(
f'window.check_css({json.dumps(src)})', QWebEngineScript.ScriptWorldId.ApplicationWorld, self.check_done)
def check_css_when_ready(self, src):
if self.ready:
self.check_css(src)
else:
self.working = True
self.pending = src
def check_done(self, result):
self.working = False
self.work_done.emit(self, result)
class Pool:
def __init__(self):
self.workers = []
self.max_workers = cpu_count()
def add_worker(self):
w = Worker()
w.work_done.connect(self.work_done)
self.workers.append(w)
def check_css(self, css_sources):
self.pending = list(enumerate(css_sources))
self.results = list(repeat(None, len(css_sources)))
self.working = True
self.assign_work()
app = QApplication.instance()
while self.working:
app.processEvents(QEventLoop.ProcessEventsFlag.WaitForMoreEvents | QEventLoop.ProcessEventsFlag.ExcludeUserInputEvents)
return self.results
def assign_work(self):
while self.pending:
if len(self.workers) < self.max_workers:
self.add_worker()
for w in self.workers:
if not w.working:
idx, src = self.pending.pop()
w.result_idx = idx
w.check_css_when_ready(src)
break
else:
break
def work_done(self, worker, result):
if not isinstance(result, dict):
result = worker.console_messages
self.results[worker.result_idx] = result
self.assign_work()
if not self.pending and not [w for w in self.workers if w.working]:
self.working = False
def shutdown(self):
def safe_delete(x):
if not sip.isdeleted(x):
sip.delete(x)
for i in self.workers:
safe_delete(i)
self.workers = []
pool = Pool()
shutdown = pool.shutdown
atexit.register(shutdown)
Job = namedtuple('Job', 'name css line_offset')
def create_job(name, css, line_offset=0, is_declaration=False):
if is_declaration:
css = 'div{\n' + css + '\n}'
line_offset -= 1
return Job(name, css, line_offset)
def check_css(jobs):
errors = []
if not jobs:
return errors
results = pool.check_css([j.css for j in jobs])
for job, result in zip(jobs, results):
if isinstance(result, dict):
for msg in result['messages']:
err = message_to_error(msg, job.name, job.line_offset)
if err is not None:
errors.append(err)
elif isinstance(result, list) and result:
errors.append(CSSParseError(_('Failed to process CSS in {name} with errors: {errors}').format(
name=job.name, errors='\n'.join(result)), job.name))
else:
errors.append(CSSParseError(_('Failed to process CSS in {name}').format(name=job.name), job.name))
return errors
def main():
with open(sys.argv[-1], 'rb') as f:
css = f.read().decode('utf-8')
errors = check_css([create_job(sys.argv[-1], css)])
for error in errors:
prints(error)
if __name__ == '__main__':
try:
main()
finally:
shutdown()