diff options
Diffstat (limited to 'import-layers/yocto-poky/bitbake')
303 files changed, 31372 insertions, 16688 deletions
diff --git a/import-layers/yocto-poky/bitbake/LICENSE b/import-layers/yocto-poky/bitbake/LICENSE index a57f9a419..5d4a4c2a8 100644 --- a/import-layers/yocto-poky/bitbake/LICENSE +++ b/import-layers/yocto-poky/bitbake/LICENSE @@ -5,8 +5,13 @@ The following external components are distributed with this software: * The Toaster Simple UI application is based upon the Django project template, the files of which are covered by the BSD license and are copyright (c) Django Software Foundation and individual contributors. -* Twitter Bootstrap (including Glyphicons), redistributed under the Apache License 2.0. - +* Twitter Bootstrap (including Glyphicons), redistributed under the MIT license * jQuery is redistributed under the MIT license. +* Twitter typeahead.js redistributed under the MIT license. Note that the JS source has one small modification, so the full unminified file is currently included to make it obvious where this is. + +* jsrender is redistributed under the MIT license. + * QUnit is redistributed under the MIT license. + +* Font Awesome fonts redistributed under the SIL Open Font License 1.1 diff --git a/import-layers/yocto-poky/bitbake/bin/bitbake b/import-layers/yocto-poky/bitbake/bin/bitbake index b03683e12..2a4fc7203 100755 --- a/import-layers/yocto-poky/bitbake/bin/bitbake +++ b/import-layers/yocto-poky/bitbake/bin/bitbake @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # ex:ts=4:sw=4:sts=4:et # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- # @@ -35,7 +35,10 @@ except RuntimeError as exc: from bb import cookerdata from bb.main import bitbake_main, BitBakeConfigParameters, BBMainException -__version__ = "1.30.0" +if sys.getfilesystemencoding() != "utf-8": + sys.exit("Please use a locale setting which supports utf-8.\nPython can't change the filesystem locale after loading so we need a utf-8 when python starts or things won't work.") + +__version__ = "1.32.0" if __name__ == "__main__": if __version__ != bb.__version__: diff --git a/import-layers/yocto-poky/bitbake/bin/bitbake-diffsigs b/import-layers/yocto-poky/bitbake/bin/bitbake-diffsigs index 196f0b73e..527d2c7a9 100755 --- a/import-layers/yocto-poky/bitbake/bin/bitbake-diffsigs +++ b/import-layers/yocto-poky/bitbake/bin/bitbake-diffsigs @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # bitbake-diffsigs # BitBake task signature data comparison utility @@ -24,6 +24,7 @@ import warnings import fnmatch import optparse import logging +import pickle sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(sys.argv[0])), 'lib')) @@ -95,7 +96,7 @@ def find_compare_task(bbhandler, pn, taskname): # Recurse into signature comparison output = bb.siggen.compare_sigfiles(latestfiles[0], latestfiles[1], recursecb) if output: - print '\n'.join(output) + print('\n'.join(output)) sys.exit(0) @@ -114,14 +115,13 @@ parser.add_option("-t", "--task", options, args = parser.parse_args(sys.argv) if options.taskargs: - tinfoil = bb.tinfoil.Tinfoil() - tinfoil.prepare(config_only = True) - find_compare_task(tinfoil, options.taskargs[0], options.taskargs[1]) + with bb.tinfoil.Tinfoil() as tinfoil: + tinfoil.prepare(config_only=True) + find_compare_task(tinfoil, options.taskargs[0], options.taskargs[1]) else: if len(args) == 1: parser.print_help() else: - import cPickle try: if len(args) == 2: output = bb.siggen.dump_sigfile(sys.argv[1]) @@ -130,9 +130,9 @@ else: except IOError as e: logger.error(str(e)) sys.exit(1) - except cPickle.UnpicklingError, EOFError: + except (pickle.UnpicklingError, EOFError): logger.error('Invalid signature data - ensure you are specifying sigdata/siginfo files') sys.exit(1) if output: - print '\n'.join(output) + print('\n'.join(output)) diff --git a/import-layers/yocto-poky/bitbake/bin/bitbake-dumpsig b/import-layers/yocto-poky/bitbake/bin/bitbake-dumpsig index 656d93a5a..58ba1cad0 100755 --- a/import-layers/yocto-poky/bitbake/bin/bitbake-dumpsig +++ b/import-layers/yocto-poky/bitbake/bin/bitbake-dumpsig @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # bitbake-dumpsig # BitBake task signature dump utility @@ -23,6 +23,7 @@ import sys import warnings import optparse import logging +import pickle sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(sys.argv[0])), 'lib')) @@ -51,15 +52,14 @@ options, args = parser.parse_args(sys.argv) if len(args) == 1: parser.print_help() else: - import cPickle try: output = bb.siggen.dump_sigfile(args[1]) except IOError as e: logger.error(str(e)) sys.exit(1) - except cPickle.UnpicklingError, EOFError: + except (pickle.UnpicklingError, EOFError): logger.error('Invalid signature data - ensure you are specifying a sigdata/siginfo file') sys.exit(1) if output: - print '\n'.join(output) + print('\n'.join(output)) diff --git a/import-layers/yocto-poky/bitbake/bin/bitbake-layers b/import-layers/yocto-poky/bitbake/bin/bitbake-layers index d47a6690e..946def220 100755 --- a/import-layers/yocto-poky/bitbake/bin/bitbake-layers +++ b/import-layers/yocto-poky/bitbake/bin/bitbake-layers @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # This script has subcommands which operate against your bitbake layers, either # displaying useful information, or acting against them. @@ -23,1048 +23,105 @@ import logging import os import sys -import fnmatch -from collections import defaultdict import argparse -import re -import httplib, urlparse, json -import subprocess bindir = os.path.dirname(__file__) topdir = os.path.dirname(bindir) sys.path[0:0] = [os.path.join(topdir, 'lib')] -import bb.cache -import bb.cooker -import bb.providers -import bb.utils import bb.tinfoil +def tinfoil_init(parserecipes): + import bb.tinfoil + tinfoil = bb.tinfoil.Tinfoil(tracking=True) + tinfoil.prepare(not parserecipes) + tinfoil.logger.setLevel(logger.getEffectiveLevel()) + return tinfoil + + def logger_create(name, output=sys.stderr): logger = logging.getLogger(name) - console = logging.StreamHandler(output) - format = bb.msg.BBLogFormatter("%(levelname)s: %(message)s") - if output.isatty(): - format.enable_color() - console.setFormatter(format) - logger.addHandler(console) + loggerhandler = logging.StreamHandler(output) + loggerhandler.setFormatter(logging.Formatter("%(levelname)s: %(message)s")) + logger.addHandler(loggerhandler) logger.setLevel(logging.INFO) return logger -logger = logger_create('bitbake-layers', sys.stdout) - -class UserError(Exception): - pass - -class Commands(): - def __init__(self): - self.bbhandler = None - self.bblayers = [] - - def init_bbhandler(self, config_only = False): - if not self.bbhandler: - self.bbhandler = bb.tinfoil.Tinfoil(tracking=True) - self.bblayers = (self.bbhandler.config_data.getVar('BBLAYERS', True) or "").split() - self.bbhandler.prepare(config_only) - layerconfs = self.bbhandler.config_data.varhistory.get_variable_items_files('BBFILE_COLLECTIONS', self.bbhandler.config_data) - self.bbfile_collections = {layer: os.path.dirname(os.path.dirname(path)) for layer, path in layerconfs.iteritems()} - - - def do_show_layers(self, args): - """show current configured layers""" - self.init_bbhandler(config_only = True) - logger.plain("%s %s %s" % ("layer".ljust(20), "path".ljust(40), "priority")) - logger.plain('=' * 74) - for layer, _, regex, pri in self.bbhandler.cooker.recipecache.bbfile_config_priorities: - layerdir = self.bbfile_collections.get(layer, None) - layername = self.get_layer_name(layerdir) - logger.plain("%s %s %d" % (layername.ljust(20), layerdir.ljust(40), pri)) - - - def do_add_layer(self, args): - """Add a layer to bblayers.conf - -Adds the specified layer to bblayers.conf -""" - layerdir = os.path.abspath(args.layerdir) - if not os.path.exists(layerdir): - sys.stderr.write("Specified layer directory doesn't exist\n") - return 1 - - layer_conf = os.path.join(layerdir, 'conf', 'layer.conf') - if not os.path.exists(layer_conf): - sys.stderr.write("Specified layer directory doesn't contain a conf/layer.conf file\n") - return 1 - - bblayers_conf = os.path.join('conf', 'bblayers.conf') - if not os.path.exists(bblayers_conf): - sys.stderr.write("Unable to find bblayers.conf\n") - return 1 - - (notadded, _) = bb.utils.edit_bblayers_conf(bblayers_conf, layerdir, None) - if notadded: - for item in notadded: - sys.stderr.write("Specified layer %s is already in BBLAYERS\n" % item) - - - def do_remove_layer(self, args): - """Remove a layer from bblayers.conf - -Removes the specified layer from bblayers.conf -""" - bblayers_conf = os.path.join('conf', 'bblayers.conf') - if not os.path.exists(bblayers_conf): - sys.stderr.write("Unable to find bblayers.conf\n") - return 1 - - if args.layerdir.startswith('*'): - layerdir = args.layerdir - elif not '/' in args.layerdir: - layerdir = '*/%s' % args.layerdir - else: - layerdir = os.path.abspath(args.layerdir) - (_, notremoved) = bb.utils.edit_bblayers_conf(bblayers_conf, None, layerdir) - if notremoved: - for item in notremoved: - sys.stderr.write("No layers matching %s found in BBLAYERS\n" % item) - return 1 - - - def get_json_data(self, apiurl): - proxy_settings = os.environ.get("http_proxy", None) - conn = None - _parsedurl = urlparse.urlparse(apiurl) - path = _parsedurl.path - query = _parsedurl.query - def parse_url(url): - parsedurl = urlparse.urlparse(url) - if parsedurl.netloc[0] == '[': - host, port = parsedurl.netloc[1:].split(']', 1) - if ':' in port: - port = port.rsplit(':', 1)[1] - else: - port = None - else: - if parsedurl.netloc.count(':') == 1: - (host, port) = parsedurl.netloc.split(":") - else: - host = parsedurl.netloc - port = None - return (host, 80 if port is None else int(port)) - - if proxy_settings is None: - host, port = parse_url(apiurl) - conn = httplib.HTTPConnection(host, port) - conn.request("GET", path + "?" + query) - else: - host, port = parse_url(proxy_settings) - conn = httplib.HTTPConnection(host, port) - conn.request("GET", apiurl) - - r = conn.getresponse() - if r.status != 200: - raise Exception("Failed to read " + path + ": %d %s" % (r.status, r.reason)) - return json.loads(r.read()) - - - def get_layer_deps(self, layername, layeritems, layerbranches, layerdependencies, branchnum, selfname=False): - def layeritems_info_id(items_name, layeritems): - litems_id = None - for li in layeritems: - if li['name'] == items_name: - litems_id = li['id'] - break - return litems_id - - def layerbranches_info(items_id, layerbranches): - lbranch = {} - for lb in layerbranches: - if lb['layer'] == items_id and lb['branch'] == branchnum: - lbranch['id'] = lb['id'] - lbranch['vcs_subdir'] = lb['vcs_subdir'] - break - return lbranch - - def layerdependencies_info(lb_id, layerdependencies): - ld_deps = [] - for ld in layerdependencies: - if ld['layerbranch'] == lb_id and not ld['dependency'] in ld_deps: - ld_deps.append(ld['dependency']) - if not ld_deps: - logger.error("The dependency of layerDependencies is not found.") - return ld_deps - - def layeritems_info_name_subdir(items_id, layeritems): - litems = {} - for li in layeritems: - if li['id'] == items_id: - litems['vcs_url'] = li['vcs_url'] - litems['name'] = li['name'] - break - return litems - - if selfname: - selfid = layeritems_info_id(layername, layeritems) - lbinfo = layerbranches_info(selfid, layerbranches) - if lbinfo: - selfsubdir = lbinfo['vcs_subdir'] - else: - logger.error("%s is not found in the specified branch" % layername) - return - selfurl = layeritems_info_name_subdir(selfid, layeritems)['vcs_url'] - if selfurl: - return selfurl, selfsubdir - else: - logger.error("Cannot get layer %s git repo and subdir" % layername) - return - ldict = {} - itemsid = layeritems_info_id(layername, layeritems) - if not itemsid: - return layername, None - lbid = layerbranches_info(itemsid, layerbranches) - if lbid: - lbid = layerbranches_info(itemsid, layerbranches)['id'] - else: - logger.error("%s is not found in the specified branch" % layername) - return None, None - for dependency in layerdependencies_info(lbid, layerdependencies): - lname = layeritems_info_name_subdir(dependency, layeritems)['name'] - lurl = layeritems_info_name_subdir(dependency, layeritems)['vcs_url'] - lsubdir = layerbranches_info(dependency, layerbranches)['vcs_subdir'] - ldict[lname] = lurl, lsubdir - return None, ldict - - - def get_fetch_layer(self, fetchdir, url, subdir, fetch_layer): - layername = self.get_layer_name(url) - if os.path.splitext(layername)[1] == '.git': - layername = os.path.splitext(layername)[0] - repodir = os.path.join(fetchdir, layername) - layerdir = os.path.join(repodir, subdir) - if not os.path.exists(repodir): - if fetch_layer: - result = subprocess.call('git clone %s %s' % (url, repodir), shell = True) - if result: - logger.error("Failed to download %s" % url) - return None, None - else: - return layername, layerdir - else: - logger.plain("Repository %s needs to be fetched" % url) - return layername, layerdir - elif os.path.exists(layerdir): - return layername, layerdir - else: - logger.error("%s is not in %s" % (url, subdir)) - return None, None - - - def do_layerindex_fetch(self, args): - """Fetches a layer from a layer index along with its dependent layers, and adds them to conf/bblayers.conf. -""" - self.init_bbhandler(config_only = True) - apiurl = self.bbhandler.config_data.getVar('BBLAYERS_LAYERINDEX_URL', True) - if not apiurl: - logger.error("Cannot get BBLAYERS_LAYERINDEX_URL") - return 1 - else: - if apiurl[-1] != '/': - apiurl += '/' - apiurl += "api/" - apilinks = self.get_json_data(apiurl) - branches = self.get_json_data(apilinks['branches']) - - branchnum = 0 - for branch in branches: - if branch['name'] == args.branch: - branchnum = branch['id'] - break - if branchnum == 0: - validbranches = ', '.join([branch['name'] for branch in branches]) - logger.error('Invalid layer branch name "%s". Valid branches: %s' % (args.branch, validbranches)) - return 1 - - ignore_layers = [] - for collection in self.bbhandler.config_data.getVar('BBFILE_COLLECTIONS', True).split(): - lname = self.bbhandler.config_data.getVar('BBLAYERS_LAYERINDEX_NAME_%s' % collection, True) - if lname: - ignore_layers.append(lname) - - if args.ignore: - ignore_layers.extend(args.ignore.split(',')) - - layeritems = self.get_json_data(apilinks['layerItems']) - layerbranches = self.get_json_data(apilinks['layerBranches']) - layerdependencies = self.get_json_data(apilinks['layerDependencies']) - invaluenames = [] - repourls = {} - printlayers = [] - def query_dependencies(layers, layeritems, layerbranches, layerdependencies, branchnum): - depslayer = [] - for layername in layers: - invaluename, layerdict = self.get_layer_deps(layername, layeritems, layerbranches, layerdependencies, branchnum) - if layerdict: - repourls[layername] = self.get_layer_deps(layername, layeritems, layerbranches, layerdependencies, branchnum, selfname=True) - for layer in layerdict: - if not layer in ignore_layers: - depslayer.append(layer) - printlayers.append((layername, layer, layerdict[layer][0], layerdict[layer][1])) - if not layer in ignore_layers and not layer in repourls: - repourls[layer] = (layerdict[layer][0], layerdict[layer][1]) - if invaluename and not invaluename in invaluenames: - invaluenames.append(invaluename) - return depslayer - - depslayers = query_dependencies(args.layername, layeritems, layerbranches, layerdependencies, branchnum) - while depslayers: - depslayer = query_dependencies(depslayers, layeritems, layerbranches, layerdependencies, branchnum) - depslayers = depslayer - if invaluenames: - for invaluename in invaluenames: - logger.error('Layer "%s" not found in layer index' % invaluename) - return 1 - logger.plain("%s %s %s %s" % ("Layer".ljust(19), "Required by".ljust(19), "Git repository".ljust(54), "Subdirectory")) - logger.plain('=' * 115) - for layername in args.layername: - layerurl = repourls[layername] - logger.plain("%s %s %s %s" % (layername.ljust(20), '-'.ljust(20), layerurl[0].ljust(55), layerurl[1])) - printedlayers = [] - for layer, dependency, gitrepo, subdirectory in printlayers: - if dependency in printedlayers: - continue - logger.plain("%s %s %s %s" % (dependency.ljust(20), layer.ljust(20), gitrepo.ljust(55), subdirectory)) - printedlayers.append(dependency) - - if repourls: - fetchdir = self.bbhandler.config_data.getVar('BBLAYERS_FETCH_DIR', True) - if not fetchdir: - logger.error("Cannot get BBLAYERS_FETCH_DIR") - return 1 - if not os.path.exists(fetchdir): - os.makedirs(fetchdir) - addlayers = [] - for repourl, subdir in repourls.values(): - name, layerdir = self.get_fetch_layer(fetchdir, repourl, subdir, not args.show_only) - if not name: - # Error already shown - return 1 - addlayers.append((subdir, name, layerdir)) - if not args.show_only: - for subdir, name, layerdir in set(addlayers): - if os.path.exists(layerdir): - if subdir: - logger.plain("Adding layer \"%s\" to conf/bblayers.conf" % subdir) - else: - logger.plain("Adding layer \"%s\" to conf/bblayers.conf" % name) - localargs = argparse.Namespace() - localargs.layerdir = layerdir - self.do_add_layer(localargs) - else: - break - - - def do_layerindex_show_depends(self, args): - """Find layer dependencies from layer index. -""" - args.show_only = True - args.ignore = [] - self.do_layerindex_fetch(args) - - - def version_str(self, pe, pv, pr = None): - verstr = "%s" % pv - if pr: - verstr = "%s-%s" % (verstr, pr) - if pe: - verstr = "%s:%s" % (pe, verstr) - return verstr - - - def do_show_overlayed(self, args): - """list overlayed recipes (where the same recipe exists in another layer) - -Lists the names of overlayed recipes and the available versions in each -layer, with the preferred version first. Note that skipped recipes that -are overlayed will also be listed, with a " (skipped)" suffix. -""" - self.init_bbhandler() - - items_listed = self.list_recipes('Overlayed recipes', None, True, args.same_version, args.filenames, True, None) - - # Check for overlayed .bbclass files - classes = defaultdict(list) - for layerdir in self.bblayers: - classdir = os.path.join(layerdir, 'classes') - if os.path.exists(classdir): - for classfile in os.listdir(classdir): - if os.path.splitext(classfile)[1] == '.bbclass': - classes[classfile].append(classdir) - - # Locating classes and other files is a bit more complicated than recipes - - # layer priority is not a factor; instead BitBake uses the first matching - # file in BBPATH, which is manipulated directly by each layer's - # conf/layer.conf in turn, thus the order of layers in bblayers.conf is a - # factor - however, each layer.conf is free to either prepend or append to - # BBPATH (or indeed do crazy stuff with it). Thus the order in BBPATH might - # not be exactly the order present in bblayers.conf either. - bbpath = str(self.bbhandler.config_data.getVar('BBPATH', True)) - overlayed_class_found = False - for (classfile, classdirs) in classes.items(): - if len(classdirs) > 1: - if not overlayed_class_found: - logger.plain('=== Overlayed classes ===') - overlayed_class_found = True - - mainfile = bb.utils.which(bbpath, os.path.join('classes', classfile)) - if args.filenames: - logger.plain('%s' % mainfile) - else: - # We effectively have to guess the layer here - logger.plain('%s:' % classfile) - mainlayername = '?' - for layerdir in self.bblayers: - classdir = os.path.join(layerdir, 'classes') - if mainfile.startswith(classdir): - mainlayername = self.get_layer_name(layerdir) - logger.plain(' %s' % mainlayername) - for classdir in classdirs: - fullpath = os.path.join(classdir, classfile) - if fullpath != mainfile: - if args.filenames: - print(' %s' % fullpath) - else: - print(' %s' % self.get_layer_name(os.path.dirname(classdir))) - - if overlayed_class_found: - items_listed = True; - - if not items_listed: - logger.plain('No overlayed files found.') - - - def do_show_recipes(self, args): - """list available recipes, showing the layer they are provided by - -Lists the names of recipes and the available versions in each -layer, with the preferred version first. Optionally you may specify -pnspec to match a specified recipe name (supports wildcards). Note that -skipped recipes will also be listed, with a " (skipped)" suffix. -""" - self.init_bbhandler() - - inheritlist = args.inherits.split(',') if args.inherits else [] - if inheritlist or args.pnspec or args.multiple: - title = 'Matching recipes:' - else: - title = 'Available recipes:' - self.list_recipes(title, args.pnspec, False, False, args.filenames, args.multiple, inheritlist) - - - def list_recipes(self, title, pnspec, show_overlayed_only, show_same_ver_only, show_filenames, show_multi_provider_only, inherits): - if inherits: - bbpath = str(self.bbhandler.config_data.getVar('BBPATH', True)) - for classname in inherits: - classfile = 'classes/%s.bbclass' % classname - if not bb.utils.which(bbpath, classfile, history=False): - raise UserError('No class named %s found in BBPATH' % classfile) - - pkg_pn = self.bbhandler.cooker.recipecache.pkg_pn - (latest_versions, preferred_versions) = bb.providers.findProviders(self.bbhandler.config_data, self.bbhandler.cooker.recipecache, pkg_pn) - allproviders = bb.providers.allProviders(self.bbhandler.cooker.recipecache) - - # Ensure we list skipped recipes - # We are largely guessing about PN, PV and the preferred version here, - # but we have no choice since skipped recipes are not fully parsed - skiplist = self.bbhandler.cooker.skiplist.keys() - skiplist.sort( key=lambda fileitem: self.bbhandler.cooker.collection.calc_bbfile_priority(fileitem) ) - skiplist.reverse() - for fn in skiplist: - recipe_parts = os.path.splitext(os.path.basename(fn))[0].split('_') - p = recipe_parts[0] - if len(recipe_parts) > 1: - ver = (None, recipe_parts[1], None) - else: - ver = (None, 'unknown', None) - allproviders[p].append((ver, fn)) - if not p in pkg_pn: - pkg_pn[p] = 'dummy' - preferred_versions[p] = (ver, fn) - - def print_item(f, pn, ver, layer, ispref): - if f in skiplist: - skipped = ' (skipped)' - else: - skipped = '' - if show_filenames: - if ispref: - logger.plain("%s%s", f, skipped) - else: - logger.plain(" %s%s", f, skipped) - else: - if ispref: - logger.plain("%s:", pn) - logger.plain(" %s %s%s", layer.ljust(20), ver, skipped) - - global_inherit = (self.bbhandler.config_data.getVar('INHERIT', True) or "").split() - cls_re = re.compile('classes/') - - preffiles = [] - items_listed = False - for p in sorted(pkg_pn): - if pnspec: - if not fnmatch.fnmatch(p, pnspec): - continue - - if len(allproviders[p]) > 1 or not show_multi_provider_only: - pref = preferred_versions[p] - realfn = bb.cache.Cache.virtualfn2realfn(pref[1]) - preffile = realfn[0] - - # We only display once per recipe, we should prefer non extended versions of the - # recipe if present (so e.g. in OpenEmbedded, openssl rather than nativesdk-openssl - # which would otherwise sort first). - if realfn[1] and realfn[0] in self.bbhandler.cooker.recipecache.pkg_fn: - continue - - if inherits: - matchcount = 0 - recipe_inherits = self.bbhandler.cooker_data.inherits.get(preffile, []) - for cls in recipe_inherits: - if cls_re.match(cls): - continue - classname = os.path.splitext(os.path.basename(cls))[0] - if classname in global_inherit: - continue - elif classname in inherits: - matchcount += 1 - if matchcount != len(inherits): - # No match - skip this recipe - continue - - if preffile not in preffiles: - preflayer = self.get_file_layer(preffile) - multilayer = False - same_ver = True - provs = [] - for prov in allproviders[p]: - provfile = bb.cache.Cache.virtualfn2realfn(prov[1])[0] - provlayer = self.get_file_layer(provfile) - provs.append((provfile, provlayer, prov[0])) - if provlayer != preflayer: - multilayer = True - if prov[0] != pref[0]: - same_ver = False - - if (multilayer or not show_overlayed_only) and (same_ver or not show_same_ver_only): - if not items_listed: - logger.plain('=== %s ===' % title) - items_listed = True - print_item(preffile, p, self.version_str(pref[0][0], pref[0][1]), preflayer, True) - for (provfile, provlayer, provver) in provs: - if provfile != preffile: - print_item(provfile, p, self.version_str(provver[0], provver[1]), provlayer, False) - # Ensure we don't show two entries for BBCLASSEXTENDed recipes - preffiles.append(preffile) - - return items_listed - +def logger_setup_color(logger, color='auto'): + from bb.msg import BBLogFormatter + console = logging.StreamHandler(sys.stdout) + formatter = BBLogFormatter("%(levelname)s: %(message)s") + console.setFormatter(formatter) + logger.handlers = [console] + if color == 'always' or (color == 'auto' and console.stream.isatty()): + formatter.enable_color() - def do_flatten(self, args): - """flatten layer configuration into a separate output directory. - -Takes the specified layers (or all layers in the current layer -configuration if none are specified) and builds a "flattened" directory -containing the contents of all layers, with any overlayed recipes removed -and bbappends appended to the corresponding recipes. Note that some manual -cleanup may still be necessary afterwards, in particular: - -* where non-recipe files (such as patches) are overwritten (the flatten - command will show a warning for these) -* where anything beyond the normal layer setup has been added to - layer.conf (only the lowest priority number layer's layer.conf is used) -* overridden/appended items from bbappends will need to be tidied up -* when the flattened layers do not have the same directory structure (the - flatten command should show a warning when this will cause a problem) - -Warning: if you flatten several layers where another layer is intended to -be used "inbetween" them (in layer priority order) such that recipes / -bbappends in the layers interact, and then attempt to use the new output -layer together with that other layer, you may no longer get the same -build results (as the layer priority order has effectively changed). -""" - if len(args.layer) == 1: - logger.error('If you specify layers to flatten you must specify at least two') - return 1 - - outputdir = args.outputdir - if os.path.exists(outputdir) and os.listdir(outputdir): - logger.error('Directory %s exists and is non-empty, please clear it out first' % outputdir) - return 1 - - self.init_bbhandler() - layers = self.bblayers - if len(args.layer) > 2: - layernames = args.layer - found_layernames = [] - found_layerdirs = [] - for layerdir in layers: - layername = self.get_layer_name(layerdir) - if layername in layernames: - found_layerdirs.append(layerdir) - found_layernames.append(layername) - - for layername in layernames: - if not layername in found_layernames: - logger.error('Unable to find layer %s in current configuration, please run "%s show-layers" to list configured layers' % (layername, os.path.basename(sys.argv[0]))) - return - layers = found_layerdirs - else: - layernames = [] - - # Ensure a specified path matches our list of layers - def layer_path_match(path): - for layerdir in layers: - if path.startswith(os.path.join(layerdir, '')): - return layerdir - return None - - applied_appends = [] - for layer in layers: - overlayed = [] - for f in self.bbhandler.cooker.collection.overlayed.iterkeys(): - for of in self.bbhandler.cooker.collection.overlayed[f]: - if of.startswith(layer): - overlayed.append(of) - - logger.plain('Copying files from %s...' % layer ) - for root, dirs, files in os.walk(layer): - for f1 in files: - f1full = os.sep.join([root, f1]) - if f1full in overlayed: - logger.plain(' Skipping overlayed file %s' % f1full ) - else: - ext = os.path.splitext(f1)[1] - if ext != '.bbappend': - fdest = f1full[len(layer):] - fdest = os.path.normpath(os.sep.join([outputdir,fdest])) - bb.utils.mkdirhier(os.path.dirname(fdest)) - if os.path.exists(fdest): - if f1 == 'layer.conf' and root.endswith('/conf'): - logger.plain(' Skipping layer config file %s' % f1full ) - continue - else: - logger.warn('Overwriting file %s', fdest) - bb.utils.copyfile(f1full, fdest) - if ext == '.bb': - for append in self.bbhandler.cooker.collection.get_file_appends(f1full): - if layer_path_match(append): - logger.plain(' Applying append %s to %s' % (append, fdest)) - self.apply_append(append, fdest) - applied_appends.append(append) - - # Take care of when some layers are excluded and yet we have included bbappends for those recipes - for b in self.bbhandler.cooker.collection.bbappends: - (recipename, appendname) = b - if appendname not in applied_appends: - first_append = None - layer = layer_path_match(appendname) - if layer: - if first_append: - self.apply_append(appendname, first_append) - else: - fdest = appendname[len(layer):] - fdest = os.path.normpath(os.sep.join([outputdir,fdest])) - bb.utils.mkdirhier(os.path.dirname(fdest)) - bb.utils.copyfile(appendname, fdest) - first_append = fdest - - # Get the regex for the first layer in our list (which is where the conf/layer.conf file will - # have come from) - first_regex = None - layerdir = layers[0] - for layername, pattern, regex, _ in self.bbhandler.cooker.recipecache.bbfile_config_priorities: - if regex.match(os.path.join(layerdir, 'test')): - first_regex = regex - break - - if first_regex: - # Find the BBFILES entries that match (which will have come from this conf/layer.conf file) - bbfiles = str(self.bbhandler.config_data.getVar('BBFILES', True)).split() - bbfiles_layer = [] - for item in bbfiles: - if first_regex.match(item): - newpath = os.path.join(outputdir, item[len(layerdir)+1:]) - bbfiles_layer.append(newpath) - - if bbfiles_layer: - # Check that all important layer files match BBFILES - for root, dirs, files in os.walk(outputdir): - for f1 in files: - ext = os.path.splitext(f1)[1] - if ext in ['.bb', '.bbappend']: - f1full = os.sep.join([root, f1]) - entry_found = False - for item in bbfiles_layer: - if fnmatch.fnmatch(f1full, item): - entry_found = True - break - if not entry_found: - logger.warning("File %s does not match the flattened layer's BBFILES setting, you may need to edit conf/layer.conf or move the file elsewhere" % f1full) - - def get_file_layer(self, filename): - layerdir = self.get_file_layerdir(filename) - if layerdir: - return self.get_layer_name(layerdir) - else: - return '?' - - def get_file_layerdir(self, filename): - layer = bb.utils.get_file_layer(filename, self.bbhandler.config_data) - return self.bbfile_collections.get(layer, None) - - def remove_layer_prefix(self, f): - """Remove the layer_dir prefix, e.g., f = /path/to/layer_dir/foo/blah, the - return value will be: layer_dir/foo/blah""" - f_layerdir = self.get_file_layerdir(f) - if not f_layerdir: - return f - prefix = os.path.join(os.path.dirname(f_layerdir), '') - return f[len(prefix):] if f.startswith(prefix) else f - - def get_layer_name(self, layerdir): - return os.path.basename(layerdir.rstrip(os.sep)) - - def apply_append(self, appendname, recipename): - with open(appendname, 'r') as appendfile: - with open(recipename, 'a') as recipefile: - recipefile.write('\n') - recipefile.write('##### bbappended from %s #####\n' % self.get_file_layer(appendname)) - recipefile.writelines(appendfile.readlines()) - - def do_show_appends(self, args): - """list bbappend files and recipe files they apply to - -Lists recipes with the bbappends that apply to them as subitems. -""" - self.init_bbhandler() - - logger.plain('=== Appended recipes ===') - - pnlist = list(self.bbhandler.cooker_data.pkg_pn.keys()) - pnlist.sort() - appends = False - for pn in pnlist: - if self.show_appends_for_pn(pn): - appends = True - - if self.show_appends_for_skipped(): - appends = True - - if not appends: - logger.plain('No append files found') - - def show_appends_for_pn(self, pn): - filenames = self.bbhandler.cooker_data.pkg_pn[pn] - - best = bb.providers.findBestProvider(pn, - self.bbhandler.config_data, - self.bbhandler.cooker_data, - self.bbhandler.cooker_data.pkg_pn) - best_filename = os.path.basename(best[3]) - - return self.show_appends_output(filenames, best_filename) - - def show_appends_for_skipped(self): - filenames = [os.path.basename(f) - for f in self.bbhandler.cooker.skiplist.iterkeys()] - return self.show_appends_output(filenames, None, " (skipped)") - - def show_appends_output(self, filenames, best_filename, name_suffix = ''): - appended, missing = self.get_appends_for_files(filenames) - if appended: - for basename, appends in appended: - logger.plain('%s%s:', basename, name_suffix) - for append in appends: - logger.plain(' %s', append) - - if best_filename: - if best_filename in missing: - logger.warn('%s: missing append for preferred version', - best_filename) - return True - else: - return False - - def get_appends_for_files(self, filenames): - appended, notappended = [], [] - for filename in filenames: - _, cls = bb.cache.Cache.virtualfn2realfn(filename) - if cls: - continue - - basename = os.path.basename(filename) - appends = self.bbhandler.cooker.collection.get_file_appends(basename) - if appends: - appended.append((basename, list(appends))) - else: - notappended.append(basename) - return appended, notappended - - def do_show_cross_depends(self, args): - """Show dependencies between recipes that cross layer boundaries. - -Figure out the dependencies between recipes that cross layer boundaries. - -NOTE: .bbappend files can impact the dependencies. -""" - ignore_layers = (args.ignore or '').split(',') - - self.init_bbhandler() - - pkg_fn = self.bbhandler.cooker_data.pkg_fn - bbpath = str(self.bbhandler.config_data.getVar('BBPATH', True)) - self.require_re = re.compile(r"require\s+(.+)") - self.include_re = re.compile(r"include\s+(.+)") - self.inherit_re = re.compile(r"inherit\s+(.+)") - - global_inherit = (self.bbhandler.config_data.getVar('INHERIT', True) or "").split() - - # The bb's DEPENDS and RDEPENDS - for f in pkg_fn: - f = bb.cache.Cache.virtualfn2realfn(f)[0] - # Get the layername that the file is in - layername = self.get_file_layer(f) - - # The DEPENDS - deps = self.bbhandler.cooker_data.deps[f] - for pn in deps: - if pn in self.bbhandler.cooker_data.pkg_pn: - best = bb.providers.findBestProvider(pn, - self.bbhandler.config_data, - self.bbhandler.cooker_data, - self.bbhandler.cooker_data.pkg_pn) - self.check_cross_depends("DEPENDS", layername, f, best[3], args.filenames, ignore_layers) - - # The RDPENDS - all_rdeps = self.bbhandler.cooker_data.rundeps[f].values() - # Remove the duplicated or null one. - sorted_rdeps = {} - # The all_rdeps is the list in list, so we need two for loops - for k1 in all_rdeps: - for k2 in k1: - sorted_rdeps[k2] = 1 - all_rdeps = sorted_rdeps.keys() - for rdep in all_rdeps: - all_p = bb.providers.getRuntimeProviders(self.bbhandler.cooker_data, rdep) - if all_p: - if f in all_p: - # The recipe provides this one itself, ignore - continue - best = bb.providers.filterProvidersRunTime(all_p, rdep, - self.bbhandler.config_data, - self.bbhandler.cooker_data)[0][0] - self.check_cross_depends("RDEPENDS", layername, f, best, args.filenames, ignore_layers) - - # The RRECOMMENDS - all_rrecs = self.bbhandler.cooker_data.runrecs[f].values() - # Remove the duplicated or null one. - sorted_rrecs = {} - # The all_rrecs is the list in list, so we need two for loops - for k1 in all_rrecs: - for k2 in k1: - sorted_rrecs[k2] = 1 - all_rrecs = sorted_rrecs.keys() - for rrec in all_rrecs: - all_p = bb.providers.getRuntimeProviders(self.bbhandler.cooker_data, rrec) - if all_p: - if f in all_p: - # The recipe provides this one itself, ignore - continue - best = bb.providers.filterProvidersRunTime(all_p, rrec, - self.bbhandler.config_data, - self.bbhandler.cooker_data)[0][0] - self.check_cross_depends("RRECOMMENDS", layername, f, best, args.filenames, ignore_layers) - - # The inherit class - cls_re = re.compile('classes/') - if f in self.bbhandler.cooker_data.inherits: - inherits = self.bbhandler.cooker_data.inherits[f] - for cls in inherits: - # The inherits' format is [classes/cls, /path/to/classes/cls] - # ignore the classes/cls. - if not cls_re.match(cls): - classname = os.path.splitext(os.path.basename(cls))[0] - if classname in global_inherit: - continue - inherit_layername = self.get_file_layer(cls) - if inherit_layername != layername and not inherit_layername in ignore_layers: - if not args.filenames: - f_short = self.remove_layer_prefix(f) - cls = self.remove_layer_prefix(cls) - else: - f_short = f - logger.plain("%s inherits %s" % (f_short, cls)) - - # The 'require/include xxx' in the bb file - pv_re = re.compile(r"\${PV}") - with open(f, 'r') as fnfile: - line = fnfile.readline() - while line: - m, keyword = self.match_require_include(line) - # Found the 'require/include xxxx' - if m: - needed_file = m.group(1) - # Replace the ${PV} with the real PV - if pv_re.search(needed_file) and f in self.bbhandler.cooker_data.pkg_pepvpr: - pv = self.bbhandler.cooker_data.pkg_pepvpr[f][1] - needed_file = re.sub(r"\${PV}", pv, needed_file) - self.print_cross_files(bbpath, keyword, layername, f, needed_file, args.filenames, ignore_layers) - line = fnfile.readline() - - # The "require/include xxx" in conf/machine/*.conf, .inc and .bbclass - conf_re = re.compile(".*/conf/machine/[^\/]*\.conf$") - inc_re = re.compile(".*\.inc$") - # The "inherit xxx" in .bbclass - bbclass_re = re.compile(".*\.bbclass$") - for layerdir in self.bblayers: - layername = self.get_layer_name(layerdir) - for dirpath, dirnames, filenames in os.walk(layerdir): - for name in filenames: - f = os.path.join(dirpath, name) - s = conf_re.match(f) or inc_re.match(f) or bbclass_re.match(f) - if s: - with open(f, 'r') as ffile: - line = ffile.readline() - while line: - m, keyword = self.match_require_include(line) - # Only bbclass has the "inherit xxx" here. - bbclass="" - if not m and f.endswith(".bbclass"): - m, keyword = self.match_inherit(line) - bbclass=".bbclass" - # Find a 'require/include xxxx' - if m: - self.print_cross_files(bbpath, keyword, layername, f, m.group(1) + bbclass, args.filenames, ignore_layers) - line = ffile.readline() - - def print_cross_files(self, bbpath, keyword, layername, f, needed_filename, show_filenames, ignore_layers): - """Print the depends that crosses a layer boundary""" - needed_file = bb.utils.which(bbpath, needed_filename) - if needed_file: - # Which layer is this file from - needed_layername = self.get_file_layer(needed_file) - if needed_layername != layername and not needed_layername in ignore_layers: - if not show_filenames: - f = self.remove_layer_prefix(f) - needed_file = self.remove_layer_prefix(needed_file) - logger.plain("%s %s %s" %(f, keyword, needed_file)) - - def match_inherit(self, line): - """Match the inherit xxx line""" - return (self.inherit_re.match(line), "inherits") - - def match_require_include(self, line): - """Match the require/include xxx line""" - m = self.require_re.match(line) - keyword = "requires" - if not m: - m = self.include_re.match(line) - keyword = "includes" - return (m, keyword) - - def check_cross_depends(self, keyword, layername, f, needed_file, show_filenames, ignore_layers): - """Print the DEPENDS/RDEPENDS file that crosses a layer boundary""" - best_realfn = bb.cache.Cache.virtualfn2realfn(needed_file)[0] - needed_layername = self.get_file_layer(best_realfn) - if needed_layername != layername and not needed_layername in ignore_layers: - if not show_filenames: - f = self.remove_layer_prefix(f) - best_realfn = self.remove_layer_prefix(best_realfn) - - logger.plain("%s %s %s" % (f, keyword, best_realfn)) +logger = logger_create('bitbake-layers', sys.stdout) def main(): - - cmds = Commands() - - def add_command(cmdname, function, *args, **kwargs): - # Convert docstring for function to help (one-liner shown in main --help) and description (shown in subcommand --help) - docsplit = function.__doc__.splitlines() - help = docsplit[0] - if len(docsplit) > 1: - desc = '\n'.join(docsplit[1:]) - else: - desc = help - subparser = subparsers.add_parser(cmdname, *args, help=help, description=desc, formatter_class=argparse.RawTextHelpFormatter, **kwargs) - subparser.set_defaults(func=function) - return subparser - - parser = argparse.ArgumentParser(description="BitBake layers utility", - epilog="Use %(prog)s <subcommand> --help to get help on a specific command") + parser = argparse.ArgumentParser( + description="BitBake layers utility", + epilog="Use %(prog)s <subcommand> --help to get help on a specific command", + add_help=False) parser.add_argument('-d', '--debug', help='Enable debug output', action='store_true') parser.add_argument('-q', '--quiet', help='Print only errors', action='store_true') - subparsers = parser.add_subparsers(title='subcommands', metavar='<subcommand>') - - parser_show_layers = add_command('show-layers', cmds.do_show_layers) + parser.add_argument('--color', choices=['auto', 'always', 'never'], default='auto', help='Colorize output (where %(metavar)s is %(choices)s)', metavar='COLOR') - parser_add_layer = add_command('add-layer', cmds.do_add_layer) - parser_add_layer.add_argument('layerdir', help='Layer directory to add') + global_args, unparsed_args = parser.parse_known_args() - parser_remove_layer = add_command('remove-layer', cmds.do_remove_layer) - parser_remove_layer.add_argument('layerdir', help='Layer directory to remove (wildcards allowed, enclose in quotes to avoid shell expansion)') - parser_remove_layer.set_defaults(func=cmds.do_remove_layer) - - parser_show_overlayed = add_command('show-overlayed', cmds.do_show_overlayed) - parser_show_overlayed.add_argument('-f', '--filenames', help='instead of the default formatting, list filenames of higher priority recipes with the ones they overlay indented underneath', action='store_true') - parser_show_overlayed.add_argument('-s', '--same-version', help='only list overlayed recipes where the version is the same', action='store_true') - - parser_show_recipes = add_command('show-recipes', cmds.do_show_recipes) - parser_show_recipes.add_argument('-f', '--filenames', help='instead of the default formatting, list filenames of higher priority recipes with the ones they overlay indented underneath', action='store_true') - parser_show_recipes.add_argument('-m', '--multiple', help='only list where multiple recipes (in the same layer or different layers) exist for the same recipe name', action='store_true') - parser_show_recipes.add_argument('-i', '--inherits', help='only list recipes that inherit the named class', metavar='CLASS', default='') - parser_show_recipes.add_argument('pnspec', nargs='?', help='optional recipe name specification (wildcards allowed, enclose in quotes to avoid shell expansion)') - - parser_show_appends = add_command('show-appends', cmds.do_show_appends) + # Help is added here rather than via add_help=True, as we don't want it to + # be handled by parse_known_args() + parser.add_argument('-h', '--help', action='help', default=argparse.SUPPRESS, + help='show this help message and exit') + subparsers = parser.add_subparsers(title='subcommands', metavar='<subcommand>') + subparsers.required = True - parser_flatten = add_command('flatten', cmds.do_flatten) - parser_flatten.add_argument('layer', nargs='*', help='Optional layer(s) to flatten (otherwise all are flattened)') - parser_flatten.add_argument('outputdir', help='Output directory') + if global_args.debug: + logger.setLevel(logging.DEBUG) + elif global_args.quiet: + logger.setLevel(logging.ERROR) - parser_show_cross_depends = add_command('show-cross-depends', cmds.do_show_cross_depends) - parser_show_cross_depends.add_argument('-f', '--filenames', help='show full file path', action='store_true') - parser_show_cross_depends.add_argument('-i', '--ignore', help='ignore dependencies on items in the specified layer(s) (split multiple layer names with commas, no spaces)', metavar='LAYERNAME') + logger_setup_color(logger, global_args.color) - parser_layerindex_fetch = add_command('layerindex-fetch', cmds.do_layerindex_fetch) - parser_layerindex_fetch.add_argument('-n', '--show-only', help='show dependencies and do nothing else', action='store_true') - parser_layerindex_fetch.add_argument('-b', '--branch', help='branch name to fetch (default %(default)s)', default='master') - parser_layerindex_fetch.add_argument('-i', '--ignore', help='assume the specified layers do not need to be fetched/added (separate multiple layers with commas, no spaces)', metavar='LAYER') - parser_layerindex_fetch.add_argument('layername', nargs='+', help='layer to fetch') + plugins = [] + tinfoil = tinfoil_init(False) + try: + for path in ([topdir] + + tinfoil.config_data.getVar('BBPATH', True).split(':')): + pluginpath = os.path.join(path, 'lib', 'bblayers') + bb.utils.load_plugins(logger, plugins, pluginpath) - parser_layerindex_show_depends = add_command('layerindex-show-depends', cmds.do_layerindex_show_depends) - parser_layerindex_show_depends.add_argument('-b', '--branch', help='branch name to fetch (default %(default)s)', default='master') - parser_layerindex_show_depends.add_argument('layername', nargs='+', help='layer to query') + registered = False + for plugin in plugins: + if hasattr(plugin, 'register_commands'): + registered = True + plugin.register_commands(subparsers) + if hasattr(plugin, 'tinfoil_init'): + plugin.tinfoil_init(tinfoil) - args = parser.parse_args() + if not registered: + logger.error("No commands registered - missing plugins?") + sys.exit(1) - if args.debug: - logger.setLevel(logging.DEBUG) - elif args.quiet: - logger.setLevel(logging.ERROR) + args = parser.parse_args(unparsed_args, namespace=global_args) - try: - ret = args.func(args) - except UserError as err: - logger.error(str(err)) - ret = 1 + if getattr(args, 'parserecipes', False): + tinfoil.config_data.disableTracking() + tinfoil.parseRecipes() + tinfoil.config_data.enableTracking() - return ret + return args.func(args) + finally: + tinfoil.shutdown() if __name__ == "__main__": try: ret = main() + except bb.BBHandledException: + ret = 1 except Exception: ret = 1 import traceback diff --git a/import-layers/yocto-poky/bitbake/bin/bitbake-prserv b/import-layers/yocto-poky/bitbake/bin/bitbake-prserv index 03821446b..f38d2dd88 100755 --- a/import-layers/yocto-poky/bitbake/bin/bitbake-prserv +++ b/import-layers/yocto-poky/bitbake/bin/bitbake-prserv @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 import os import sys,logging import optparse diff --git a/import-layers/yocto-poky/bitbake/bin/bitbake-selftest b/import-layers/yocto-poky/bitbake/bin/bitbake-selftest index 462eb1b2b..380e00361 100755 --- a/import-layers/yocto-poky/bitbake/bin/bitbake-selftest +++ b/import-layers/yocto-poky/bitbake/bin/bitbake-selftest @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # # Copyright (C) 2012 Richard Purdie # @@ -25,31 +25,48 @@ try: except RuntimeError as exc: sys.exit(str(exc)) -def usage(): - print('usage: [BB_SKIP_NETTESTS=yes] %s [-v] [testname1 [testname2]...]' % os.path.basename(sys.argv[0])) - -verbosity = 1 - -tests = sys.argv[1:] -if '-v' in sys.argv: - tests.remove('-v') - verbosity = 2 - -if tests: - if '--help' in sys.argv[1:]: - usage() - sys.exit(0) -else: - tests = ["bb.tests.codeparser", - "bb.tests.cow", - "bb.tests.data", - "bb.tests.fetch", - "bb.tests.parse", - "bb.tests.utils"] +tests = ["bb.tests.codeparser", + "bb.tests.cow", + "bb.tests.data", + "bb.tests.fetch", + "bb.tests.parse", + "bb.tests.utils"] for t in tests: t = '.'.join(t.split('.')[:3]) __import__(t) -unittest.main(argv=["bitbake-selftest"] + tests, verbosity=verbosity) +# Set-up logging +class StdoutStreamHandler(logging.StreamHandler): + """Special handler so that unittest is able to capture stdout""" + def __init__(self): + # Override __init__() because we don't want to set self.stream here + logging.Handler.__init__(self) + + @property + def stream(self): + # We want to dynamically write wherever sys.stdout is pointing to + return sys.stdout + + +handler = StdoutStreamHandler() +bb.logger.addHandler(handler) +bb.logger.setLevel(logging.DEBUG) + + +ENV_HELP = """\ +Environment variables: + BB_SKIP_NETTESTS set to 'yes' in order to skip tests using network + connection + BB_TMPDIR_NOCLEAN set to 'yes' to preserve test tmp directories +""" + +class main(unittest.main): + def _print_help(self, *args, **kwargs): + super(main, self)._print_help(*args, **kwargs) + print(ENV_HELP) + + +if __name__ == '__main__': + main(defaultTest=tests, buffer=True) diff --git a/import-layers/yocto-poky/bitbake/bin/bitbake-worker b/import-layers/yocto-poky/bitbake/bin/bitbake-worker index 767a1c033..500f2ad16 100755 --- a/import-layers/yocto-poky/bitbake/bin/bitbake-worker +++ b/import-layers/yocto-poky/bitbake/bin/bitbake-worker @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 import os import sys @@ -10,8 +10,12 @@ import bb import select import errno import signal +import pickle from multiprocessing import Lock +if sys.getfilesystemencoding() != "utf-8": + sys.exit("Please use a locale setting which supports utf-8.\nPython can't change the filesystem locale after loading so we need a utf-8 when python starts or things won't work.") + # Users shouldn't be running this code directly if len(sys.argv) != 2 or not sys.argv[1].startswith("decafbad"): print("bitbake-worker is meant for internal execution by bitbake itself, please don't use it standalone.") @@ -30,19 +34,16 @@ if sys.argv[1].startswith("decafbadbad"): # updates to log files for use with tail try: if sys.stdout.name == '<stdout>': - sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0) + import fcntl + fl = fcntl.fcntl(sys.stdout.fileno(), fcntl.F_GETFL) + fl |= os.O_SYNC + fcntl.fcntl(sys.stdout.fileno(), fcntl.F_SETFL, fl) + #sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0) except: pass logger = logging.getLogger("BitBake") -try: - import cPickle as pickle -except ImportError: - import pickle - bb.msg.note(1, bb.msg.domain.Cache, "Importing cPickle failed. Falling back to a very slow implementation.") - - worker_pipe = sys.stdout.fileno() bb.utils.nonblockingfd(worker_pipe) # Need to guard against multiprocessing being used in child processes @@ -62,10 +63,10 @@ if 0: consolelog.setFormatter(conlogformat) logger.addHandler(consolelog) -worker_queue = "" +worker_queue = b"" def worker_fire(event, d): - data = "<event>" + pickle.dumps(event) + "</event>" + data = b"<event>" + pickle.dumps(event) + b"</event>" worker_fire_prepickled(data) def worker_fire_prepickled(event): @@ -91,7 +92,7 @@ def worker_child_fire(event, d): global worker_pipe global worker_pipe_lock - data = "<event>" + pickle.dumps(event) + "</event>" + data = b"<event>" + pickle.dumps(event) + b"</event>" try: worker_pipe_lock.acquire() worker_pipe.write(data) @@ -114,7 +115,7 @@ def sigterm_handler(signum, frame): os.killpg(0, signal.SIGTERM) sys.exit() -def fork_off_task(cfg, data, workerdata, fn, task, taskname, appends, taskdepdata, quieterrors=False): +def fork_off_task(cfg, data, databuilder, workerdata, fn, task, taskname, appends, taskdepdata, quieterrors=False): # We need to setup the environment BEFORE the fork, since # a fork() or exec*() activates PSEUDO... @@ -159,7 +160,8 @@ def fork_off_task(cfg, data, workerdata, fn, task, taskname, appends, taskdepdat pipeout = os.fdopen(pipeout, 'wb', 0) pid = os.fork() except OSError as e: - bb.msg.fatal("RunQueue", "fork failed: %d (%s)" % (e.errno, e.strerror)) + logger.critical("fork failed: %d (%s)" % (e.errno, e.strerror)) + sys.exit(1) if pid == 0: def child(): @@ -191,15 +193,19 @@ def fork_off_task(cfg, data, workerdata, fn, task, taskname, appends, taskdepdat if umask: os.umask(umask) - data.setVar("BB_WORKERCONTEXT", "1") - data.setVar("BB_TASKDEPDATA", taskdepdata) - data.setVar("BUILDNAME", workerdata["buildname"]) - data.setVar("DATE", workerdata["date"]) - data.setVar("TIME", workerdata["time"]) - bb.parse.siggen.set_taskdata(workerdata["sigdata"]) - ret = 0 try: - the_data = bb.cache.Cache.loadDataFull(fn, appends, data) + bb_cache = bb.cache.NoCache(databuilder) + (realfn, virtual, mc) = bb.cache.virtualfn2realfn(fn) + the_data = databuilder.mcdata[mc] + the_data.setVar("BB_WORKERCONTEXT", "1") + the_data.setVar("BB_TASKDEPDATA", taskdepdata) + the_data.setVar("BUILDNAME", workerdata["buildname"]) + the_data.setVar("DATE", workerdata["date"]) + the_data.setVar("TIME", workerdata["time"]) + bb.parse.siggen.set_taskdata(workerdata["sigdata"]) + ret = 0 + + the_data = bb_cache.loadDataFull(fn, appends) the_data.setVar('BB_TASKHASH', workerdata["runq_hash"][task]) bb.utils.set_process_name("%s:%s" % (the_data.getVar("PN", True), taskname.replace("do_", ""))) @@ -207,14 +213,24 @@ def fork_off_task(cfg, data, workerdata, fn, task, taskname, appends, taskdepdat # exported_vars() returns a generator which *cannot* be passed to os.environ.update() # successfully. We also need to unset anything from the environment which shouldn't be there exports = bb.data.exported_vars(the_data) + bb.utils.empty_environment() for e, v in exports: os.environ[e] = v + for e in fakeenv: os.environ[e] = fakeenv[e] the_data.setVar(e, fakeenv[e]) the_data.setVarFlag(e, 'export', "1") + task_exports = the_data.getVarFlag(taskname, 'exports', True) + if task_exports: + for e in task_exports.split(): + the_data.setVarFlag(e, 'export', '1') + v = the_data.getVar(e, True) + if v is not None: + os.environ[e] = v + if quieterrors: the_data.setVarFlag(taskname, "quieterrors", "1") @@ -240,7 +256,7 @@ def fork_off_task(cfg, data, workerdata, fn, task, taskname, appends, taskdepdat bb.utils.process_profilelog(profname) os._exit(ret) else: - for key, value in envbackup.iteritems(): + for key, value in iter(envbackup.items()): if value is None: del os.environ[key] else: @@ -257,22 +273,22 @@ class runQueueWorkerPipe(): if pipeout: pipeout.close() bb.utils.nonblockingfd(self.input) - self.queue = "" + self.queue = b"" def read(self): start = len(self.queue) try: - self.queue = self.queue + self.input.read(102400) + self.queue = self.queue + (self.input.read(102400) or b"") except (OSError, IOError) as e: if e.errno != errno.EAGAIN: raise end = len(self.queue) - index = self.queue.find("</event>") + index = self.queue.find(b"</event>") while index != -1: worker_fire_prepickled(self.queue[:index+8]) self.queue = self.queue[index+8:] - index = self.queue.find("</event>") + index = self.queue.find(b"</event>") return (end > start) def close(self): @@ -288,7 +304,7 @@ class BitbakeWorker(object): def __init__(self, din): self.input = din bb.utils.nonblockingfd(self.input) - self.queue = "" + self.queue = b"" self.cookercfg = None self.databuilder = None self.data = None @@ -325,12 +341,12 @@ class BitbakeWorker(object): except (OSError, IOError): pass if len(self.queue): - self.handle_item("cookerconfig", self.handle_cookercfg) - self.handle_item("workerdata", self.handle_workerdata) - self.handle_item("runtask", self.handle_runtask) - self.handle_item("finishnow", self.handle_finishnow) - self.handle_item("ping", self.handle_ping) - self.handle_item("quit", self.handle_quit) + self.handle_item(b"cookerconfig", self.handle_cookercfg) + self.handle_item(b"workerdata", self.handle_workerdata) + self.handle_item(b"runtask", self.handle_runtask) + self.handle_item(b"finishnow", self.handle_finishnow) + self.handle_item(b"ping", self.handle_ping) + self.handle_item(b"quit", self.handle_quit) for pipe in self.build_pipes: self.build_pipes[pipe].read() @@ -340,12 +356,12 @@ class BitbakeWorker(object): def handle_item(self, item, func): - if self.queue.startswith("<" + item + ">"): - index = self.queue.find("</" + item + ">") + if self.queue.startswith(b"<" + item + b">"): + index = self.queue.find(b"</" + item + b">") while index != -1: func(self.queue[(len(item) + 2):index]) self.queue = self.queue[(index + len(item) + 3):] - index = self.queue.find("</" + item + ">") + index = self.queue.find(b"</" + item + b">") def handle_cookercfg(self, data): self.cookercfg = pickle.loads(data) @@ -359,12 +375,13 @@ class BitbakeWorker(object): bb.msg.loggerDefaultVerbose = self.workerdata["logdefaultverbose"] bb.msg.loggerVerboseLogs = self.workerdata["logdefaultverboselogs"] bb.msg.loggerDefaultDomains = self.workerdata["logdefaultdomain"] - self.data.setVar("PRSERV_HOST", self.workerdata["prhost"]) + for mc in self.databuilder.mcdata: + self.databuilder.mcdata[mc].setVar("PRSERV_HOST", self.workerdata["prhost"]) def handle_ping(self, _): workerlog_write("Handling ping\n") - logger.warn("Pong from bitbake-worker!") + logger.warning("Pong from bitbake-worker!") def handle_quit(self, data): workerlog_write("Handling quit\n") @@ -377,7 +394,7 @@ class BitbakeWorker(object): fn, task, taskname, quieterrors, appends, taskdepdata = pickle.loads(data) workerlog_write("Handling runtask %s %s %s\n" % (task, fn, taskname)) - pid, pipein, pipeout = fork_off_task(self.cookercfg, self.data, self.workerdata, fn, task, taskname, appends, taskdepdata, quieterrors) + pid, pipein, pipeout = fork_off_task(self.cookercfg, self.data, self.databuilder, self.workerdata, fn, task, taskname, appends, taskdepdata, quieterrors) self.build_pids[pid] = task self.build_pipes[pid] = runQueueWorkerPipe(pipein, pipeout) @@ -409,12 +426,12 @@ class BitbakeWorker(object): self.build_pipes[pid].close() del self.build_pipes[pid] - worker_fire_prepickled("<exitcode>" + pickle.dumps((task, status)) + "</exitcode>") + worker_fire_prepickled(b"<exitcode>" + pickle.dumps((task, status)) + b"</exitcode>") def handle_finishnow(self, _): if self.build_pids: logger.info("Sending SIGTERM to remaining %s tasks", len(self.build_pids)) - for k, v in self.build_pids.iteritems(): + for k, v in iter(self.build_pids.items()): try: os.kill(-k, signal.SIGTERM) os.waitpid(-1, 0) @@ -424,7 +441,7 @@ class BitbakeWorker(object): self.build_pipes[pipe].read() try: - worker = BitbakeWorker(sys.stdin) + worker = BitbakeWorker(os.fdopen(sys.stdin.fileno(), 'rb')) if not profiling: worker.serve() else: diff --git a/import-layers/yocto-poky/bitbake/bin/bitdoc b/import-layers/yocto-poky/bitbake/bin/bitdoc index defb3dd37..274467882 100755 --- a/import-layers/yocto-poky/bitbake/bin/bitdoc +++ b/import-layers/yocto-poky/bitbake/bin/bitdoc @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # ex:ts=4:sw=4:sts=4:et # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- # diff --git a/import-layers/yocto-poky/bitbake/bin/image-writer b/import-layers/yocto-poky/bitbake/bin/image-writer deleted file mode 100755 index e30ab45e3..000000000 --- a/import-layers/yocto-poky/bitbake/bin/image-writer +++ /dev/null @@ -1,122 +0,0 @@ -#!/usr/bin/env python - -# Copyright (c) 2012 Wind River Systems, Inc. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 as -# published by the Free Software Foundation. -# -# 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, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -import os -import sys -sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname( \ - os.path.abspath(__file__))), 'lib')) -try: - import bb -except RuntimeError as exc: - sys.exit(str(exc)) - -import gtk -import optparse -import pygtk - -from bb.ui.crumbs.hobwidget import HobAltButton, HobButton -from bb.ui.crumbs.hig.crumbsmessagedialog import CrumbsMessageDialog -from bb.ui.crumbs.hig.deployimagedialog import DeployImageDialog -from bb.ui.crumbs.hig.imageselectiondialog import ImageSelectionDialog - -# I put all the fs bitbake supported here. Need more test. -DEPLOYABLE_IMAGE_TYPES = ["jffs2", "cramfs", "ext2", "ext3", "ext4", "btrfs", "squashfs", "ubi", "vmdk"] -Title = "USB Image Writer" - -class DeployWindow(gtk.Window): - def __init__(self, image_path=''): - super(DeployWindow, self).__init__() - - if len(image_path) > 0: - valid = True - if not os.path.exists(image_path): - valid = False - lbl = "<b>Invalid image file path: %s.</b>\nPress <b>Select Image</b> to select an image." % image_path - else: - image_path = os.path.abspath(image_path) - extend_name = os.path.splitext(image_path)[1][1:] - if extend_name not in DEPLOYABLE_IMAGE_TYPES: - valid = False - lbl = "<b>Undeployable imge type: %s</b>\nPress <b>Select Image</b> to select an image." % extend_name - - if not valid: - image_path = '' - crumbs_dialog = CrumbsMessageDialog(self, lbl, gtk.STOCK_DIALOG_INFO) - button = crumbs_dialog.add_button("Close", gtk.RESPONSE_OK) - HobButton.style_button(button) - crumbs_dialog.run() - crumbs_dialog.destroy() - - self.deploy_dialog = DeployImageDialog(Title, image_path, self, - gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT - | gtk.DIALOG_NO_SEPARATOR, None, standalone=True) - close_button = self.deploy_dialog.add_button("Close", gtk.RESPONSE_NO) - HobAltButton.style_button(close_button) - close_button.connect('clicked', gtk.main_quit) - - write_button = self.deploy_dialog.add_button("Write USB image", gtk.RESPONSE_YES) - HobAltButton.style_button(write_button) - - self.deploy_dialog.connect('select_image_clicked', self.select_image_clicked_cb) - self.deploy_dialog.connect('destroy', gtk.main_quit) - response = self.deploy_dialog.show() - - def select_image_clicked_cb(self, dialog): - cwd = os.getcwd() - dialog = ImageSelectionDialog(cwd, DEPLOYABLE_IMAGE_TYPES, Title, self, gtk.FILE_CHOOSER_ACTION_SAVE ) - button = dialog.add_button("Cancel", gtk.RESPONSE_NO) - HobAltButton.style_button(button) - button = dialog.add_button("Open", gtk.RESPONSE_YES) - HobAltButton.style_button(button) - response = dialog.run() - - if response == gtk.RESPONSE_YES: - if not dialog.image_names: - lbl = "<b>No selections made</b>\nClicked the radio button to select a image." - crumbs_dialog = CrumbsMessageDialog(self, lbl, gtk.STOCK_DIALOG_INFO) - button = crumbs_dialog.add_button("Close", gtk.RESPONSE_OK) - HobButton.style_button(button) - crumbs_dialog.run() - crumbs_dialog.destroy() - dialog.destroy() - return - - # get the full path of image - image_path = os.path.join(dialog.image_folder, dialog.image_names[0]) - self.deploy_dialog.set_image_text_buffer(image_path) - self.deploy_dialog.set_image_path(image_path) - - dialog.destroy() - -def main(): - parser = optparse.OptionParser( - usage = """%prog [-h] [image_file] - -%prog writes bootable images to USB devices. You can -provide the image file on the command line or select it using the GUI.""") - - options, args = parser.parse_args(sys.argv) - image_file = args[1] if len(args) > 1 else '' - dw = DeployWindow(image_file) - -if __name__ == '__main__': - try: - main() - gtk.main() - except Exception: - import traceback - traceback.print_exc() diff --git a/import-layers/yocto-poky/bitbake/bin/toaster b/import-layers/yocto-poky/bitbake/bin/toaster index 70c66d2c2..f92d38eca 100755 --- a/import-layers/yocto-poky/bitbake/bin/toaster +++ b/import-layers/yocto-poky/bitbake/bin/toaster @@ -17,10 +17,12 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see http://www.gnu.org/licenses/. -# Usage: source toaster [start|stop] -# [webport=<port>] [noui] [noweb] - -# Helper function to kill a background toaster development server +HELP=" +Usage: source toaster start|stop [webport=<address:port>] [noweb] + Optional arguments: + [noweb] Setup the environment for building with toaster but don't start the development server + [webport] Set the development server (default: localhost:8000) +" webserverKillAll() { @@ -31,9 +33,6 @@ webserverKillAll() while kill -0 $pid 2>/dev/null; do kill -SIGTERM -$pid 2>/dev/null sleep 1 - # Kill processes if they are still running - may happen - # in interactive shells - ps fux | grep "python.*manage.py runserver" | awk '{print $2}' | xargs kill done rm ${pidfile} fi @@ -57,7 +56,8 @@ webserverStartAll() echo "Failed migrations, aborting system start" 1>&2 return $retval fi - + # Make sure that checksettings can pick up any value for TEMPLATECONF + export TEMPLATECONF $MANAGE checksettings --traceback || retval=1 if [ $retval -eq 1 ]; then @@ -67,7 +67,7 @@ webserverStartAll() echo "Starting webserver..." - $MANAGE runserver "0.0.0.0:$WEB_PORT" \ + $MANAGE runserver "$ADDR_PORT" \ </dev/null >>${BUILDDIR}/toaster_web.log 2>&1 \ & echo $! >${BUILDDIR}/.toastermain.pid @@ -77,7 +77,8 @@ webserverStartAll() retval=1 rm "${BUILDDIR}/.toastermain.pid" else - echo "Webserver address: http://0.0.0.0:$WEB_PORT/" + echo "Toaster development webserver started at http://$ADDR_PORT" + echo -e "\nYou can now run 'bitbake <target>' on the command line and monitor your build in Toaster.\nYou can also use a Toaster project to configure and run a build.\n" fi return $retval @@ -91,14 +92,8 @@ stop_system() # prevent reentry if [ $INSTOPSYSTEM -eq 1 ]; then return; fi INSTOPSYSTEM=1 - if [ -f ${BUILDDIR}/.toasterui.pid ]; then - kill `cat ${BUILDDIR}/.toasterui.pid` 2>/dev/null - rm ${BUILDDIR}/.toasterui.pid - fi webserverKillAll # unset exported variables - unset DATABASE_URL - unset TOASTER_CONF unset TOASTER_DIR unset BITBAKE_UI unset BBBASEDIR @@ -109,11 +104,11 @@ stop_system() verify_prereq() { # Verify Django version - reqfile=$(python -c "import os; print os.path.realpath('$BBBASEDIR/toaster-requirements.txt')") + reqfile=$(python3 -c "import os; print(os.path.realpath('$BBBASEDIR/toaster-requirements.txt'))") exp='s/Django\([><=]\+\)\([^,]\+\),\([><=]\+\)\(.\+\)/' exp=$exp'import sys,django;version=django.get_version().split(".");' exp=$exp'sys.exit(not (version \1 "\2".split(".") and version \3 "\4".split(".")))/p' - if ! sed -n "$exp" $reqfile | python - ; then + if ! sed -n "$exp" $reqfile | python3 - ; then req=`grep ^Django $reqfile` echo "This program needs $req" echo "Please install with pip install -r $reqfile" @@ -133,8 +128,8 @@ else fi export BBBASEDIR=`dirname $TOASTER`/.. -MANAGE=$BBBASEDIR/lib/toaster/manage.py -OEROOT=`dirname $TOASTER`/../.. +MANAGE="python3 $BBBASEDIR/lib/toaster/manage.py" +OE_ROOT=`dirname $TOASTER`/../.. # this is the configuraton file we are using for toaster # we are using the same logic that oe-setup-builddir uses @@ -144,29 +139,17 @@ OEROOT=`dirname $TOASTER`/../.. # in the local layers that currently make using an arbitrary # toasterconf.json difficult. -. $OEROOT/.templateconf +. $OE_ROOT/.templateconf if [ -n "$TEMPLATECONF" ]; then if [ ! -d "$TEMPLATECONF" ]; then # Allow TEMPLATECONF=meta-xyz/conf as a shortcut - if [ -d "$OEROOT/$TEMPLATECONF" ]; then - TEMPLATECONF="$OEROOT/$TEMPLATECONF" - fi - if [ ! -d "$TEMPLATECONF" ]; then - echo >&2 "Error: '$TEMPLATECONF' must be a directory containing toasterconf.json" - return 1 + if [ -d "$OE_ROOT/$TEMPLATECONF" ]; then + TEMPLATECONF="$OE_ROOT/$TEMPLATECONF" fi fi fi -if [ "$TOASTER_CONF" = "" ]; then - TOASTER_CONF="$TEMPLATECONF/toasterconf.json" - export TOASTER_CONF=$(python -c "import os; print os.path.realpath('$TOASTER_CONF')") -fi - -if [ ! -f $TOASTER_CONF ]; then - echo "$TOASTER_CONF configuration file not found. Set TOASTER_CONF to specify file or fix .templateconf" - return 1 -fi +unset OE_ROOT # this defines the dir toaster will use for # 1) clones of layers (in _toaster_clones ) @@ -178,7 +161,7 @@ fi export TOASTER_DIR=`pwd` WEBSERVER=1 -WEB_PORT="8000" +ADDR_PORT="localhost:8000" unset CMD for param in $*; do case $param in @@ -192,7 +175,24 @@ for param in $*; do CMD=$param ;; webport=*) - WEB_PORT="${param#*=}" + ADDR_PORT="${param#*=}" + # Split the addr:port string + ADDR=`echo $ADDR_PORT | cut -f 1 -d ':'` + PORT=`echo $ADDR_PORT | cut -f 2 -d ':'` + # If only a port has been speified then set address to localhost. + if [ $ADDR = $PORT ] ; then + ADDR_PORT="localhost:$PORT" + fi + ;; + --help) + echo "$HELP" + return 0 + ;; + *) + echo "$HELP" + return 1 + ;; + esac done @@ -226,11 +226,9 @@ if [ "$CMD" = "start" ] ; then return 1 fi elif [ "$CMD" = "" ]; then - if [ -z "$BBSERVER" ]; then - CMD="start" - else - CMD="stop" - fi + echo "No command specified" + echo "$HELP" + return 1 fi echo "The system will $CMD." @@ -241,15 +239,9 @@ case $CMD in start ) # check if addr:port is not in use if [ "$CMD" == 'start' ]; then - $MANAGE checksocket "0.0.0.0:$WEB_PORT" || return 1 - fi - - # kill Toaster web server if it's alive - if [ -e $BUILDDIR/.toastermain.pid ] && kill -0 `cat $BUILDDIR/.toastermain.pid`; then - echo "Warning: bitbake appears to be dead, but the Toaster web server is running." 1>&2 - echo " Something fishy is going on." 1>&2 - echo "Cleaning up the web server to start from a clean slate." - webserverKillAll + if [ $WEBSERVER -gt 0 ]; then + $MANAGE checksocket "$ADDR_PORT" || return 1 + fi fi # Create configuration file @@ -262,7 +254,6 @@ case $CMD in return 4 fi export BITBAKE_UI='toasterui' - export DATABASE_URL=`$MANAGE get-dburl` $MANAGE runbuilds & echo $! >${BUILDDIR}/.runbuilds.pid # set fail safe stop system on terminal exit trap stop_system SIGHUP diff --git a/import-layers/yocto-poky/bitbake/bin/toaster-eventreplay b/import-layers/yocto-poky/bitbake/bin/toaster-eventreplay index 615a7aed1..80967a093 100755 --- a/import-layers/yocto-poky/bitbake/bin/toaster-eventreplay +++ b/import-layers/yocto-poky/bitbake/bin/toaster-eventreplay @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # ex:ts=4:sw=4:sts=4:et # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- # @@ -21,154 +21,106 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +""" +This command takes a filename as a single parameter. The filename is read +as a build eventlog, and the ToasterUI is used to process events in the file +and log data in the database +""" -# This command takes a filename as a single parameter. The filename is read -# as a build eventlog, and the ToasterUI is used to process events in the file -# and log data in the database - -from __future__ import print_function import os -import sys, logging +import sys +import json +import pickle +import codecs -# mangle syspath to allow easy import of modules -sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), - 'lib')) +from collections import namedtuple +# mangle syspath to allow easy import of modules +from os.path import join, dirname, abspath +sys.path.insert(0, join(dirname(dirname(abspath(__file__))), 'lib')) import bb.cooker from bb.ui import toasterui -import sys -import logging -import json, pickle - - -class FileReadEventsServerConnection(): - """ Emulates a connection to a bitbake server that feeds - events coming actually read from a saved log file. - """ - - class MockConnection(): - """ fill-in for the proxy to the server. we just return generic data +class EventPlayer: + """Emulate a connection to a bitbake server.""" + + def __init__(self, eventfile, variables): + self.eventfile = eventfile + self.variables = variables + self.eventmask = [] + + def waitEvent(self, _timeout): + """Read event from the file.""" + line = self.eventfile.readline().strip() + if not line: + return + try: + event_str = json.loads(line)['vars'].encode('utf-8') + event = pickle.loads(codecs.decode(event_str, 'base64')) + event_name = "%s.%s" % (event.__module__, event.__class__.__name__) + if event_name not in self.eventmask: + return + return event + except ValueError as err: + print("Failed loading ", line) + raise err + + def runCommand(self, command_line): + """Emulate running a command on the server.""" + name = command_line[0] + + if name == "getVariable": + var_name = command_line[1] + variable = self.variables.get(var_name) + if variable: + return variable['v'], None + return None, "Missing variable %s" % var_name + + elif name == "getAllKeysWithFlags": + dump = {} + flaglist = command_line[1] + for key, val in self.variables.items(): + try: + if not key.startswith("__"): + dump[key] = { + 'v': val['v'], + 'history' : val['history'], + } + for flag in flaglist: + dump[key][flag] = val[flag] + except Exception as err: + print(err) + return (dump, None) + + elif name == 'setEventMask': + self.eventmask = command_line[-1] + return True, None + + else: + raise Exception("Command %s not implemented" % command_line[0]) + + def getEventHandle(self): """ - def __init__(self, sc): - self._sc = sc - - def runCommand(self, commandArray): - """ emulates running a command on the server; only read-only commands are accepted """ - command_name = commandArray[0] - - if command_name == "getVariable": - if commandArray[1] in self._sc._variables: - return (self._sc._variables[commandArray[1]]['v'], None) - return (None, "Missing variable") - - elif command_name == "getAllKeysWithFlags": - dump = {} - flaglist = commandArray[1] - for k in self._sc._variables.keys(): - try: - if not k.startswith("__"): - v = self._sc._variables[k]['v'] - dump[k] = { - 'v' : v , - 'history' : self._sc._variables[k]['history'], - } - for d in flaglist: - dump[k][d] = self._sc._variables[k][d] - except Exception as e: - print(e) - return (dump, None) - else: - raise Exception("Command %s not implemented" % commandArray[0]) - - def terminateServer(self): - """ do not do anything """ - pass - - - - class EventReader(): - def __init__(self, sc): - self._sc = sc - self.firstraise = 0 - - def _create_event(self, line): - def _import_class(name): - assert len(name) > 0 - assert "." in name, name - - components = name.strip().split(".") - modulename = ".".join(components[:-1]) - moduleklass = components[-1] - - module = __import__(modulename, fromlist=[str(moduleklass)]) - return getattr(module, moduleklass) - - # we build a toaster event out of current event log line - try: - event_data = json.loads(line.strip()) - event_class = _import_class(event_data['class']) - event_object = pickle.loads(json.loads(event_data['vars'])) - except ValueError as e: - print("Failed loading ", line) - raise e - - if not isinstance(event_object, event_class): - raise Exception("Error loading objects %s class %s ", event_object, event_class) - - return event_object - - def waitEvent(self, timeout): - - nextline = self._sc._eventfile.readline() - if len(nextline) == 0: - # the build data ended, while toasterui still waits for events. - # this happens when the server was abruptly stopped, so we simulate this - self.firstraise += 1 - if self.firstraise == 1: - raise KeyboardInterrupt() - else: - return None - else: - self._sc.lineno += 1 - return self._create_event(nextline) - - - def _readVariables(self, variableline): - self._variables = json.loads(variableline.strip())['allvariables'] - - - def __init__(self, file_name): - self.connection = FileReadEventsServerConnection.MockConnection(self) - self._eventfile = open(file_name, "r") - - # we expect to have the variable dump at the start of the file - self.lineno = 1 - self._readVariables(self._eventfile.readline()) - - self.events = FileReadEventsServerConnection.EventReader(self) - - - + This method is called by toasterui. + The return value is passed to self.runCommand but not used there. + """ + pass +def main(argv): + with open(argv[-1]) as eventfile: + # load variables from the first line + variables = json.loads(eventfile.readline().strip())['allvariables'] -class MockConfigParameters(): - """ stand-in for cookerdata.ConfigParameters; as we don't really config a cooker, this - serves just to supply needed interfaces for the toaster ui to work """ - def __init__(self): - self.observe_only = True # we can only read files + params = namedtuple('ConfigParams', ['observe_only'])(True) + player = EventPlayer(eventfile, variables) + return toasterui.main(player, player, params) # run toaster ui on our mock bitbake class if __name__ == "__main__": - if len(sys.argv) < 2: - print("Usage: %s event.log " % sys.argv[0]) + if len(sys.argv) != 2: + print("Usage: %s <event file>" % os.path.basename(sys.argv[0])) sys.exit(1) - file_name = sys.argv[-1] - mock_connection = FileReadEventsServerConnection(file_name) - configParams = MockConfigParameters() - - # run the main program and set exit code to the returned value - sys.exit(toasterui.main(mock_connection.connection, mock_connection.events, configParams)) + sys.exit(main(sys.argv)) diff --git a/import-layers/yocto-poky/bitbake/contrib/dump_cache.py b/import-layers/yocto-poky/bitbake/contrib/dump_cache.py index e1f23090b..f4d4c1b12 100755 --- a/import-layers/yocto-poky/bitbake/contrib/dump_cache.py +++ b/import-layers/yocto-poky/bitbake/contrib/dump_cache.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # ex:ts=4:sw=4:sts=4:et # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- # @@ -29,14 +29,14 @@ import warnings sys.path.insert(0, os.path.join(os.path.abspath(os.path.dirname(sys.argv[0])), '../lib')) from bb.cache import CoreRecipeInfo -import cPickle as pickle +import pickle as pickle def main(argv=None): """ Get the mapping for the target recipe. """ if len(argv) != 1: - print >>sys.stderr, "Error, need one argument!" + print("Error, need one argument!", file=sys.stderr) return 2 cachefile = argv[0] @@ -56,7 +56,7 @@ def main(argv=None): continue # 1.0 is the default version for a no PV recipe. - if val.__dict__.has_key("pv"): + if "pv" in val.__dict__: pv = val.pv else: pv = "1.0" diff --git a/import-layers/yocto-poky/bitbake/doc/bitbake-user-manual/bitbake-user-manual-execution.xml b/import-layers/yocto-poky/bitbake/doc/bitbake-user-manual/bitbake-user-manual-execution.xml index b1b72e0aa..e4cc422ea 100644 --- a/import-layers/yocto-poky/bitbake/doc/bitbake-user-manual/bitbake-user-manual-execution.xml +++ b/import-layers/yocto-poky/bitbake/doc/bitbake-user-manual/bitbake-user-manual-execution.xml @@ -596,7 +596,7 @@ "<link linkend='checksums'>Checksums (Signatures)</link>" section for information). It is also possible to append extra metadata to the stamp using - the "stamp-extra-info" task flag. + the <filename>[stamp-extra-info]</filename> task flag. For example, OpenEmbedded uses this flag to make some tasks machine-specific. </para> @@ -653,7 +653,8 @@ </itemizedlist> It is possible to have functions run before and after a task's main function. - This is done using the "prefuncs" and "postfuncs" flags of the task + This is done using the <filename>[prefuncs]</filename> + and <filename>[postfuncs]</filename> flags of the task that lists the functions to run. </para> </section> @@ -827,7 +828,7 @@ itself. The simplest parameter to pass is "none", which causes a set of signature information to be written out into - <filename>STAMP_DIR</filename> + <filename>STAMPS_DIR</filename> corresponding to the targets specified. The other currently available parameter is "printdiff", which causes BitBake to try to establish the closest @@ -915,7 +916,7 @@ <para> Finally, after all the setscene tasks have executed, BitBake calls the function listed in - <link linkend='var-BB_SETSCENE_VERIFY_FUNCTION'><filename>BB_SETSCENE_VERIFY_FUNCTION</filename></link> + <link linkend='var-BB_SETSCENE_VERIFY_FUNCTION2'><filename>BB_SETSCENE_VERIFY_FUNCTION2</filename></link> with the list of tasks BitBake thinks has been "covered". The metadata can then ensure that this list is correct and can inform BitBake that it wants specific tasks to be run regardless diff --git a/import-layers/yocto-poky/bitbake/doc/bitbake-user-manual/bitbake-user-manual-fetching.xml b/import-layers/yocto-poky/bitbake/doc/bitbake-user-manual/bitbake-user-manual-fetching.xml index f168cfa68..2a3340b39 100644 --- a/import-layers/yocto-poky/bitbake/doc/bitbake-user-manual/bitbake-user-manual-fetching.xml +++ b/import-layers/yocto-poky/bitbake/doc/bitbake-user-manual/bitbake-user-manual-fetching.xml @@ -670,8 +670,8 @@ The <filename>module</filename> and <filename>vob</filename> options are combined to create the <filename>load</filename> rule in the view config spec. - As an example, consider the <filename>vob</filename> and - <filename>module</filename> values from the + As an example, consider the <filename>vob</filename> and + <filename>module</filename> values from the <filename>SRC_URI</filename> statement at the start of this section. Combining those values results in the following: <literallayout class='monospaced'> @@ -716,6 +716,68 @@ </para> </section> + <section id='perforce-fetcher'> + <title>Perforce Fetcher (<filename>p4://</filename>)</title> + + <para> + This fetcher submodule fetches code from the + <ulink url='https://www.perforce.com/'>Perforce</ulink> + source control system. + The executable used is specified by + <filename>FETCHCMD_p4</filename>, which defaults + to "p4". + The fetcher's temporary working directory is set by + <link linkend='var-P4DIR'><filename>P4DIR</filename></link>, + which defaults to "DL_DIR/p4". + </para> + + <para> + To use this fetcher, make sure your recipe has proper + <link linkend='var-SRC_URI'><filename>SRC_URI</filename></link>, + <link linkend='var-SRCREV'><filename>SRCREV</filename></link>, and + <link linkend='var-PV'><filename>PV</filename></link> values. + The p4 executable is able to use the config file defined by your + system's <filename>P4CONFIG</filename> environment variable in + order to define the Perforce server URL and port, username, and + password if you do not wish to keep those values in a recipe + itself. + If you choose not to use <filename>P4CONFIG</filename>, + or to explicitly set variables that <filename>P4CONFIG</filename> + can contain, you can specify the <filename>P4PORT</filename> value, + which is the server's URL and port number, and you can + specify a username and password directly in your recipe within + <filename>SRC_URI</filename>. + </para> + + <para> + Here is an example that relies on <filename>P4CONFIG</filename> + to specify the server URL and port, username, and password, and + fetches the Head Revision: + <literallayout class='monospaced'> + SRC_URI = "p4://example-depot/main/source/..." + SRCREV = "${AUTOREV}" + PV = "p4-${SRCPV}" + S = "${WORKDIR}/p4" + </literallayout> + </para> + + <para> + Here is an example that specifies the server URL and port, + username, and password, and fetches a Revision based on a Label: + <literallayout class='monospaced'> + P4PORT = "tcp:p4server.example.net:1666" + SRC_URI = "p4://user:passwd@example-depot/main/source/..." + SRCREV = "release-1.0" + PV = "p4-${SRCPV}" + S = "${WORKDIR}/p4" + </literallayout> + <note> + You should always set <filename>S</filename> + to <filename>"${WORKDIR}/p4"</filename> in your recipe. + </note> + </para> + </section> + <section id='other-fetchers'> <title>Other Fetchers</title> @@ -726,9 +788,6 @@ Bazaar (<filename>bzr://</filename>) </para></listitem> <listitem><para> - Perforce (<filename>p4://</filename>) - </para></listitem> - <listitem><para> Trees using Git Annex (<filename>gitannex://</filename>) </para></listitem> <listitem><para> diff --git a/import-layers/yocto-poky/bitbake/doc/bitbake-user-manual/bitbake-user-manual-hello.xml b/import-layers/yocto-poky/bitbake/doc/bitbake-user-manual/bitbake-user-manual-hello.xml index f6d82b4f3..8b7edbff5 100644 --- a/import-layers/yocto-poky/bitbake/doc/bitbake-user-manual/bitbake-user-manual-hello.xml +++ b/import-layers/yocto-poky/bitbake/doc/bitbake-user-manual/bitbake-user-manual-hello.xml @@ -399,7 +399,7 @@ ERROR: Unable to parse base: ParseError in configuration INHERITs: Could not inh <link linkend='var-BBFILES'>BBFILES</link> += "${LAYERDIR}/*.bb" <link linkend='var-BBFILE_COLLECTIONS'>BBFILE_COLLECTIONS</link> += "mylayer" - <link linkend='var-BBFILE_PATTERN'>BBFILE_PATTERN_mylayer</link> := "^${LAYERDIR}/" + <link linkend='var-BBFILE_PATTERN'>BBFILE_PATTERN_mylayer</link> := "^${LAYERDIR_RE}/" </literallayout> For information on these variables, click the links to go to the definitions in the glossary.</para> diff --git a/import-layers/yocto-poky/bitbake/doc/bitbake-user-manual/bitbake-user-manual-intro.xml b/import-layers/yocto-poky/bitbake/doc/bitbake-user-manual/bitbake-user-manual-intro.xml index 7a37edd50..4d58dc456 100644 --- a/import-layers/yocto-poky/bitbake/doc/bitbake-user-manual/bitbake-user-manual-intro.xml +++ b/import-layers/yocto-poky/bitbake/doc/bitbake-user-manual/bitbake-user-manual-intro.xml @@ -634,6 +634,25 @@ </para> </section> + <section id='executing-a-list-of-task-and-recipe-combinations'> + <title>Executing a List of Task and Recipe Combinations</title> + + <para> + The BitBake command line supports specifying different + tasks for individual targets when you specify multiple + targets. + For example, suppose you had two targets (or recipes) + <filename>myfirstrecipe</filename> and + <filename>mysecondrecipe</filename> and you needed + BitBake to run <filename>taskA</filename> for the first + recipe and <filename>taskB</filename> for the second + recipe: + <literallayout class='monospaced'> + $ bitbake myfirstrecipe:do_taskA mysecondrecipe:do_taskB + </literallayout> + </para> + </section> + <section id='generating-dependency-graphs'> <title>Generating Dependency Graphs</title> diff --git a/import-layers/yocto-poky/bitbake/doc/bitbake-user-manual/bitbake-user-manual-metadata.xml b/import-layers/yocto-poky/bitbake/doc/bitbake-user-manual/bitbake-user-manual-metadata.xml index 6329cd6e4..71bb25bf7 100644 --- a/import-layers/yocto-poky/bitbake/doc/bitbake-user-manual/bitbake-user-manual-metadata.xml +++ b/import-layers/yocto-poky/bitbake/doc/bitbake-user-manual/bitbake-user-manual-metadata.xml @@ -44,28 +44,73 @@ VARIABLE = " " </literallayout> </para> + + <para> + You can use single quotes instead of double quotes + when setting a variable's value. + Doing so allows you to use values that contain the double + quote character: + <literallayout class='monospaced'> + VARIABLE = 'I have a " in my value' + </literallayout> + <note> + Unlike in Bourne shells, single quotes work identically + to double quotes in all other ways. + They do not suppress variable expansions. + </note> + </para> </section> <section id='variable-expansion'> <title>Variable Expansion</title> <para> - BitBake supports variables referencing one another's - contents using a syntax that is similar to shell scripting. - Following is an example that results in <filename>A</filename> - containing "aval" and <filename>B</filename> evaluating to - "preavalpost" based on that current value of - <filename>A</filename>. + Variables can reference the contents of other variables + using a syntax that is similar to variable expansion in + Bourne shells. + The following assignments + result in A containing "aval" and B evaluating to "preavalpost". <literallayout class='monospaced'> A = "aval" B = "pre${A}post" </literallayout> - You should realize that whenever <filename>B</filename> is - referenced, its evaluation will depend on the state of - <filename>A</filename> at that time. - Thus, later evaluations of <filename>B</filename> in the - previous example could result in different values - depending on the value of <filename>A</filename>. + <note> + Unlike in Bourne shells, the curly braces are mandatory: + Only <filename>${FOO}</filename> and not + <filename>$FOO</filename> is recognized as an expansion of + <filename>FOO</filename>. + </note> + The "=" operator does not immediately expand variable + references in the right-hand side. + Instead, expansion is deferred until the variable assigned to + is actually used. + The result depends on the current values of the referenced + variables. + The following example should clarify this behavior: + <literallayout class='monospaced'> + A = "${B} baz" + B = "${C} bar" + C = "foo" + *At this point, ${A} equals "foo bar baz"* + C = "qux" + *At this point, ${A} equals "qux bar baz"* + B = "norf" + *At this point, ${A} equals "norf baz"* + </literallayout> + Contrast this behavior with the + <link linkend='immediate-variable-expansion'>immediate variable expansion</link> + operator (i.e. ":="). + </para> + + <para> + If the variable expansion syntax is used on a variable that + does not exist, the string is kept as is. + For example, given the following assignment, + <filename>BAR</filename> expands to the literal string + "${FOO}" as long as <filename>FOO</filename> does not exist. + <literallayout class='monospaced'> + BAR = "${FOO}" + </literallayout> </para> </section> @@ -232,6 +277,15 @@ override syntax. </note> </para> + + <para> + It is also possible to append and prepend to shell + functions and BitBake-style Python functions. + See the + "<link linkend='shell-functions'>Shell Functions</link>" and + "<link linkend='bitbake-style-python-functions'>BitBake-Style Python Functions</link> + sections for examples. + </para> </section> <section id='removing-override-style-syntax'> @@ -259,6 +313,60 @@ "789 123456" and <filename>FOO2</filename> becomes "ghi abcdef". </para> + + <para> + Like "_append" and "_prepend", "_remove" + is deferred until after parsing completes. + </para> + </section> + + <section id='override-style-operation-advantages'> + <title>Override Style Operation Advantages</title> + + <para> + An advantage of the override style operations + "_append", "_prepend", and "_remove" as compared to the + "+=" and "=+" operators is that the override style + operators provide guaranteed operations. + For example, consider a class <filename>foo.bbclass</filename> + that needs to add the value "val" to the variable + <filename>FOO</filename>, and a recipe that uses + <filename>foo.bbclass</filename> as follows: + <literallayout class='monospaced'> + inherit foo + + FOO = "initial" + </literallayout> + If <filename>foo.bbclass</filename> uses the "+=" operator, + as follows, then the final value of <filename>FOO</filename> + will be "initial", which is not what is desired: + <literallayout class='monospaced'> + FOO += "val" + </literallayout> + If, on the other hand, <filename>foo.bbclass</filename> + uses the "_append" operator, then the final value of + <filename>FOO</filename> will be "initial val", as intended: + <literallayout class='monospaced'> + FOO_append = " val" + </literallayout> + <note> + It is never necessary to use "+=" together with "_append". + The following sequence of assignments appends "barbaz" to + <filename>FOO</filename>: + <literallayout class='monospaced'> + FOO_append = "bar" + FOO_append = "baz" + </literallayout> + The only effect of changing the second assignment in the + previous example to use "+=" would be to add a space before + "baz" in the appended value (due to how the "+=" operator + works). + </note> + Another advantage of the override style operations is that + you can combine them with other overrides as described in the + "<link linkend='conditional-syntax-overrides'>Conditional Syntax (Overrides)</link>" + section. + </para> </section> <section id='variable-flag-syntax'> @@ -277,8 +385,7 @@ You can define, append, and prepend values to variable flags. All the standard syntax operations previously mentioned work for variable flags except for override style syntax - (i.e. <filename>_prepend</filename>, <filename>_append</filename>, - and <filename>_remove</filename>). + (i.e. "_prepend", "_append", and "_remove"). </para> <para> @@ -289,9 +396,9 @@ FOO[a] += "456" </literallayout> The variable <filename>FOO</filename> has two flags: - <filename>a</filename> and <filename>b</filename>. + <filename>[a]</filename> and <filename>[b]</filename>. The flags are immediately set to "abc" and "123", respectively. - The <filename>a</filename> flag becomes "abc 456". + The <filename>[a]</filename> flag becomes "abc 456". </para> <para> @@ -330,7 +437,43 @@ PN = "${@bb.parse.BBHandler.vars_from_file(d.getVar('FILE', False),d)[0] or 'defaultpkgname'}" PV = "${@bb.parse.BBHandler.vars_from_file(d.getVar('FILE', False),d)[1] or '1.0'}" </literallayout> + <note> + Inline Python expressions work just like variable expansions + insofar as the "=" and ":=" operators are concerned. + Given the following assignment, <filename>foo()</filename> + is called each time <filename>FOO</filename> is expanded: + <literallayout class='monospaced'> + FOO = "${@foo()}" + </literallayout> + Contrast this with the following immediate assignment, where + <filename>foo()</filename> is only called once, while the + assignment is parsed: + <literallayout class='monospaced'> + FOO := "${@foo()}" + </literallayout> + </note> + For a different way to set variables with Python code during + parsing, see the + "<link linkend='anonymous-python-functions'>Anonymous Python Functions</link>" + section. + </para> + </section> + + <section id='unsetting-variables'> + <title>Unseting variables</title> + + <para> + It is possible to completely remove a variable or a variable flag + from BitBake's internal data dictionary by using the "unset" keyword. + Here is an example: + <literallayout class='monospaced'> + unset DATE + unset do_fetch[noexec] + </literallayout> + These two statements remove the <filename>DATE</filename> and the + <filename>do_fetch[noexec]</filename> flag. </para> + </section> <section id='providing-pathnames'> @@ -357,6 +500,53 @@ </section> </section> + <section id='exporting-variables-to-the-environment'> + <title>Exporting Variables to the Environment</title> + + <para> + You can export variables to the environment of running + tasks by using the <filename>export</filename> keyword. + For example, in the following example, the + <filename>do_foo</filename> task prints "value from + the environment" when run: + <literallayout class='monospaced'> + export ENV_VARIABLE + ENV_VARIABLE = "value from the environment" + + do_foo() { + bbplain "$ENV_VARIABLE" + } + </literallayout> + <note> + BitBake does not expand <filename>$ENV_VARIABLE</filename> + in this case because it lacks the obligatory + <filename>{}</filename>. + Rather, <filename>$ENV_VARIABLE</filename> is expanded + by the shell. + </note> + It does not matter whether + <filename>export ENV_VARIABLE</filename> appears before or + after assignments to <filename>ENV_VARIABLE</filename>. + </para> + + <para> + It is also possible to combine <filename>export</filename> + with setting a value for the variable. + Here is an example: + <literallayout class='monospaced'> + export ENV_VARIABLE = "<replaceable>variable-value</replaceable>" + </literallayout> + In the output of <filename>bitbake -e</filename>, variables + that are exported to the environment are preceded by "export". + </para> + + <para> + Among the variables commonly exported to the environment + are <filename>CC</filename> and <filename>CFLAGS</filename>, + which are picked up by many build systems. + </para> + </section> + <section id='conditional-syntax-overrides'> <title>Conditional Syntax (Overrides)</title> @@ -455,6 +645,34 @@ KERNEL_FEATURES_append_qemux86-64=" cfg/sound.scc cfg/paravirt_kvm.scc" </literallayout> </para></listitem> + <listitem><para><emphasis>Setting a Variable for a Single Task:</emphasis> + BitBake supports setting a variable just for the + duration of a single task. + Here is an example: + <literallayout class='monospaced'> + FOO_task-configure = "val 1" + FOO_task-compile = "val 2" + </literallayout> + In the previous example, <filename>FOO</filename> + has the value "val 1" while the + <filename>do_configure</filename> task is executed, + and the value "val 2" while the + <filename>do_compile</filename> task is executed. + </para> + + <para>Internally, this is implemented by prepending + the task (e.g. "task-compile:") to the value of + <link linkend='var-OVERRIDES'><filename>OVERRIDES</filename></link> + for the local datastore of the <filename>do_compile</filename> + task.</para> + + <para>You can also use this syntax with other combinations + (e.g. "<filename>_prepend</filename>") as shown in the + following example: + <literallayout class='monospaced'> + EXTRA_OEMAKE_prepend_task-compile = "${PARALLEL_MAKE} " + </literallayout> + </para></listitem> </itemizedlist> </para> </section> @@ -545,15 +763,15 @@ OVERRIDES = "foo" A = "Y" A_foo_append = "Z" - A_foo_append += "X" + A_foo_append = "X" </literallayout> For this case, before any overrides are resolved, <filename>A</filename> is set to "Y" using an immediate assignment. After this immediate assignment, <filename>A_foo</filename> is set to "Z", and then further appended with - "X" leaving the variable set to "Z X". + "X" leaving the variable set to "ZX". Finally, applying the override for "foo" results in the conditional - variable <filename>A</filename> becoming "Z X" (i.e. + variable <filename>A</filename> becoming "ZX" (i.e. <filename>A</filename> is replaced with <filename>A_foo</filename>). </para> @@ -572,7 +790,7 @@ Initially, <filename>A</filename> is set to "1 45" because of the three statements that use immediate operators. After these assignments are made, BitBake applies the - <filename>_append</filename> operations. + "_append" operations. Those operations result in <filename>A</filename> becoming "1 4523". </para> </section> @@ -842,7 +1060,7 @@ directly as functions, tasks, or both. They can also be called by other shell functions. </para></listitem> - <listitem><para><emphasis>BitBake Style Python Functions:</emphasis> + <listitem><para><emphasis>BitBake-Style Python Functions:</emphasis> Functions written in Python and executed by BitBake or other Python functions using <filename>bb.build.exec_func()</filename>. </para></listitem> @@ -881,10 +1099,60 @@ such as <filename>dash</filename>. You should not use Bash-specific script (bashisms). </para> + + <para> + Overrides and override-style operators like + <filename>_append</filename> and + <filename>_prepend</filename> can also be applied to + shell functions. + Most commonly, this application would be used in a + <filename>.bbappend</filename> file to modify functions in + the main recipe. + It can also be used to modify functions inherited from + classes. + </para> + + <para> + As an example, consider the following: + <literallayout class='monospaced'> + do_foo() { + bbplain first + fn + } + + fn_prepend() { + bbplain second + } + + fn() { + bbplain third + } + + do_foo_append() { + bbplain fourth + } + </literallayout> + Running <filename>do_foo</filename> + prints the following: + <literallayout class='monospaced'> + recipename do_foo: first + recipename do_foo: second + recipename do_foo: third + recipename do_foo: fourth + </literallayout> + <note> + Overrides and override-style operators can + be applied to any shell function, not just + <link linkend='tasks'>tasks</link>. + </note> + You can use the <filename>bitbake -e</filename> <replaceable>recipename</replaceable> + command to view the final assembled function + after all overrides have been applied. + </para> </section> <section id='bitbake-style-python-functions'> - <title>BitBake Style Python Functions</title> + <title>BitBake-Style Python Functions</title> <para> These functions are written in Python and executed by @@ -905,19 +1173,51 @@ Also in these types of functions, the datastore ("d") is a global variable and is always automatically available. - </para> - - <note> - Variable expressions (e.g. <filename>${X}</filename>) are no - longer expanded within Python functions. - This behavior is intentional in order to allow you to freely - set variable values to expandable expressions without having - them expanded prematurely. - If you do wish to expand a variable within a Python function, - use <filename>d.getVar("X", True)</filename>. - Or, for more complicated expressions, use - <filename>d.expand()</filename>. - </note> + <note> + Variable expressions (e.g. <filename>${X}</filename>) + are no longer expanded within Python functions. + This behavior is intentional in order to allow you + to freely set variable values to expandable expressions + without having them expanded prematurely. + If you do wish to expand a variable within a Python + function, use <filename>d.getVar("X", True)</filename>. + Or, for more complicated expressions, use + <filename>d.expand()</filename>. + </note> + </para> + + <para> + Similar to shell functions, you can also apply overrides + and override-style operators to BitBake-style Python + functions. + </para> + + <para> + As an example, consider the following: + <literallayout class='monospaced'> + python do_foo_prepend() { + bb.plain("first") + } + + python do_foo() { + bb.plain("second") + } + + python do_foo_append() { + bb.plain("third") + } + </literallayout> + Running <filename>do_foo</filename> prints + the following: + <literallayout class='monospaced'> + recipename do_foo: first + recipename do_foo: second + recipename do_foo: third + </literallayout> + You can use the <filename>bitbake -e</filename> <replaceable>recipename</replaceable> + command to view the final assembled function + after all overrides have been applied. + </para> </section> <section id='python-functions'> @@ -961,36 +1261,178 @@ </para> </section> + <section id='bitbake-style-python-functions-versus-python-functions'> + <title>Bitbake-Style Python Functions Versus Python Functions</title> + + <para> + Following are some important differences between + BitBake-style Python functions and regular Python + functions defined with "def": + <itemizedlist> + <listitem><para> + Only BitBake-style Python functions can be + <link linkend='tasks'>tasks</link>. + </para></listitem> + <listitem><para> + Overrides and override-style operators can only + be applied to BitBake-style Python functions. + </para></listitem> + <listitem><para> + Only regular Python functions can take arguments + and return values. + </para></listitem> + <listitem><para> + <link linkend='variable-flags'>Variable flags</link> + such as <filename>[dirs]</filename>, + <filename>[cleandirs]</filename>, and + <filename>[lockfiles]</filename> can be used + on BitBake-style Python functions, but not on + regular Python functions. + </para></listitem> + <listitem><para> + BitBake-style Python functions generate a separate + <filename>${</filename><link linkend='var-T'><filename>T</filename></link><filename>}/run.</filename><replaceable>function-name</replaceable><filename>.</filename><replaceable>pid</replaceable> + script that is executed to run the function, and also + generate a log file in + <filename>${T}/log.</filename><replaceable>function-name</replaceable><filename>.</filename><replaceable>pid</replaceable> + if they are executed as tasks.</para> + + <para> + Regular Python functions execute "inline" and do not + generate any files in <filename>${T}</filename>. + </para></listitem> + <listitem><para> + Regular Python functions are called with the usual + Python syntax. + BitBake-style Python functions are usually tasks and + are called directly by BitBake, but can also be called + manually from Python code by using the + <filename>bb.build.exec_func()</filename> function. + Here is an example: + <literallayout class='monospaced'> + bb.build.exec_func("my_bitbake_style_function", d) + </literallayout> + <note> + <filename>bb.build.exec_func()</filename> can also + be used to run shell functions from Python code. + If you want to run a shell function before a Python + function within the same task, then you can use a + parent helper Python function that starts by running + the shell function with + <filename>bb.build.exec_func()</filename> and then + runs the Python code. + </note></para> + + <para>To detect errors from functions executed with + <filename>bb.build.exec_func()</filename>, you + can catch the <filename>bb.build.FuncFailed</filename> + exception. + <note> + Functions in metadata (recipes and classes) should + not themselves raise + <filename>bb.build.FuncFailed</filename>. + Rather, <filename>bb.build.FuncFailed</filename> + should be viewed as a general indicator that the + called function failed by raising an exception. + For example, an exception raised by + <filename>bb.fatal()</filename> will be caught inside + <filename>bb.build.exec_func()</filename>, and a + <filename>bb.build.FuncFailed</filename> will be raised + in response. + </note> + </para></listitem> + </itemizedlist> + </para> + + <para> + Due to their simplicity, you should prefer regular Python functions + over BitBake-style Python functions unless you need a feature specific + to BitBake-style Python functions. + Regular Python functions in metadata are a more recent invention than + BitBake-style Python functions, and older code tends to use + <filename>bb.build.exec_func()</filename> more often. + </para> + </section> + <section id='anonymous-python-functions'> <title>Anonymous Python Functions</title> <para> - Sometimes it is useful to run some code during - parsing to set variables or to perform other operations - programmatically. - To do this, you can define an anonymous Python function. - Here is an example that conditionally sets a - variable based on the value of another variable: + Sometimes it is useful to set variables or perform + other operations programmatically during parsing. + To do this, you can define special Python functions, + called anonymous Python functions, that run at the + end of parsing. + For example, the following conditionally sets a variable + based on the value of another variable: <literallayout class='monospaced'> - python __anonymous () { + python () { if d.getVar('SOMEVAR', True) == 'value': d.setVar('ANOTHERVAR', 'value2') } </literallayout> - The "__anonymous" function name is optional, so the - following example is functionally equivalent to the above: + An equivalent way to mark a function as an anonymous + function is to give it the name "__anonymous", rather + than no name. + </para> + + <para> + Anonymous Python functions always run at the end + of parsing, regardless of where they are defined. + If a recipe contains many anonymous functions, they + run in the same order as they are defined within the + recipe. + As an example, consider the following snippet: <literallayout class='monospaced'> python () { - if d.getVar('SOMEVAR', True) == 'value': - d.setVar('ANOTHERVAR', 'value2') + d.setVar('FOO', 'foo 2') + } + + FOO = "foo 1" + + python () { + d.appendVar('BAR', ' bar 2') + } + + BAR = "bar 1" + </literallayout> + The previous example is conceptually equivalent to the + following snippet: + <literallayout class='monospaced'> + FOO = "foo 1" + BAR = "bar 1" + FOO = "foo 2" + BAR += "bar 2" + </literallayout> + <filename>FOO</filename> ends up with the value "foo 2", + and <filename>BAR</filename> with the value "bar 1 bar 2". + Just as in the second snippet, the values set for the + variables within the anonymous functions become available + to tasks, which always run after parsing. + </para> + + <para> + Overrides and override-style operators such as + "<filename>_append</filename>" are applied before + anonymous functions run. + In the following example, <filename>FOO</filename> ends + up with the value "foo from anonymous": + <literallayout class='monospaced'> + FOO = "foo" + FOO_append = " from outside" + + python () { + d.setVar("FOO", "foo from anonymous") } </literallayout> - Because unlike other Python functions anonymous - Python functions are executed during parsing, the - "d" variable within an anonymous Python function represents - the datastore for the entire recipe. - Consequently, you can set variable values here and - those values can be picked up by other functions. + For methods you can use with anonymous Python functions, + see the + "<link linkend='functions-you-can-call-from-within-python'>Functions You Can Call From Within Python</link>" + section. + For a different method to run Python code during parsing, + see the + "<link linkend='inline-python-variable-expansion'>Inline Python Variable Expansion</link>" + section. </para> </section> @@ -1089,37 +1531,29 @@ <title>Tasks</title> <para> - Tasks are BitBake execution units that originate as - functions and make up the steps that BitBake needs to run - for given recipe. - Tasks are only supported in recipe (<filename>.bb</filename> - or <filename>.inc</filename>) and class - (<filename>.bbclass</filename>) files. - By convention, task names begin with the string "do_". - </para> - - <para> - Here is an example of a task that prints out the date: - <literallayout class='monospaced'> - python do_printdate () { - import time - print time.strftime('%Y%m%d', time.gmtime()) - } - addtask printdate after do_fetch before do_build - </literallayout> + Tasks are BitBake execution units that make up the + steps that BitBake can run for a given recipe. + Tasks are only supported in recipes and classes + (i.e. in <filename>.bb</filename> files and files + included or inherited from <filename>.bb</filename> + files). + By convention, tasks have names that start with "do_". </para> <section id='promoting-a-function-to-a-task'> <title>Promoting a Function to a Task</title> <para> - Any function can be promoted to a task by applying the + Tasks are either + <link linkend='shell-functions'>shell functions</link> or + <link linkend='bitbake-style-python-functions'>BitBake-style Python functions</link> + that have been promoted to tasks by using the <filename>addtask</filename> command. - The <filename>addtask</filename> command also describes - inter-task dependencies. - Here is the function from the previous section but with the - <filename>addtask</filename> command promoting it to a task - and defining some dependencies: + The <filename>addtask</filename> command can also + optionally describe dependencies between the + task and other tasks. + Here is an example that shows how to define a task + and declare some dependencies: <literallayout class='monospaced'> python do_printdate () { import time @@ -1127,15 +1561,81 @@ } addtask printdate after do_fetch before do_build </literallayout> - In the example, the function is defined and then promoted - as a task. - The <filename>do_printdate</filename> task becomes a dependency of - the <filename>do_build</filename> task, which is the default - task. - And, the <filename>do_printdate</filename> task is dependent upon - the <filename>do_fetch</filename> task. - Execution of the <filename>do_build</filename> task results - in the <filename>do_printdate</filename> task running first. + The first argument to <filename>addtask</filename> + is the name of the function to promote to + a task. + If the name does not start with "do_", "do_" is + implicitly added, which enforces the convention that + all task names start with "do_". + </para> + + <para> + In the previous example, the + <filename>do_printdate</filename> task becomes a + dependency of the <filename>do_build</filename> + task, which is the default task (i.e. the task run by + the <filename>bitbake</filename> command unless + another task is specified explicitly). + Additionally, the <filename>do_printdate</filename> + task becomes dependent upon the + <filename>do_fetch</filename> task. + Running the <filename>do_build</filename> task + results in the <filename>do_printdate</filename> + task running first. + <note> + If you try out the previous example, you might see that + the <filename>do_printdate</filename> task is only run + the first time you build the recipe with + the <filename>bitbake</filename> command. + This is because BitBake considers the task "up-to-date" + after that initial run. + If you want to force the task to always be rerun for + experimentation purposes, you can make BitBake always + consider the task "out-of-date" by using the + <filename>[</filename><link linkend='variable-flags'><filename>nostamp</filename></link><filename>]</filename> + variable flag, as follows: + <literallayout class='monospaced'> + do_printdate[nostamp] = "1" + </literallayout> + You can also explicitly run the task and provide the + <filename>-f</filename> option as follows: + <literallayout class='monospaced'> + $ bitbake <replaceable>recipe</replaceable> -c printdate -f + </literallayout> + When manually selecting a task to run with the + <filename>bitbake</filename> <replaceable>recipe</replaceable> <filename>-c</filename> <replaceable>task</replaceable> + command, you can omit the "do_" prefix as part of the + task name. + </note> + </para> + + <para> + You might wonder about the practical effects of using + <filename>addtask</filename> without specifying any + dependencies as is done in the following example: + <literallayout class='monospaced'> + addtask printdate + </literallayout> + In this example, assuming dependencies have not been + added through some other means, the only way to run + the task is by explicitly selecting it with + <filename>bitbake</filename> <replaceable>recipe</replaceable> <filename>-c printdate</filename>. + You can use the + <filename>do_listtasks</filename> task to list all tasks + defined in a recipe as shown in the following example: + <literallayout class='monospaced'> + $ bitbake <replaceable>recipe</replaceable> -c listtasks + </literallayout> + For more information on task dependencies, see the + "<link linkend='dependencies'>Dependencies</link>" + section. + </para> + + <para> + See the + "<link linkend='variable-flags'>Variable Flags</link>" + section for information on variable flags you can use with + tasks. </para> </section> @@ -1172,7 +1672,7 @@ <para> If you want dependencies such as these to remain intact, use - the <filename>noexec</filename> varflag to disable the task + the <filename>[noexec]</filename> varflag to disable the task instead of using the <filename>deltask</filename> command to delete it: <literallayout class='monospaced'> @@ -1295,10 +1795,13 @@ Tasks support a number of these flags which control various functionality of the task: <itemizedlist> - <listitem><para><emphasis>cleandirs:</emphasis> - Empty directories that should created before the task runs. + <listitem><para><emphasis><filename>[cleandirs]</filename>:</emphasis> + Empty directories that should be created before the + task runs. + Directories that already exist are removed and recreated + to empty them. </para></listitem> - <listitem><para><emphasis>depends:</emphasis> + <listitem><para><emphasis><filename>[depends]</filename>:</emphasis> Controls inter-task dependencies. See the <link linkend='var-DEPENDS'><filename>DEPENDS</filename></link> @@ -1306,7 +1809,7 @@ "<link linkend='inter-task-dependencies'>Inter-Task Dependencies</link>" section for more information. </para></listitem> - <listitem><para><emphasis>deptask:</emphasis> + <listitem><para><emphasis><filename>[deptask]</filename>:</emphasis> Controls task build-time dependencies. See the <link linkend='var-DEPENDS'><filename>DEPENDS</filename></link> @@ -1314,12 +1817,13 @@ "<link linkend='build-dependencies'>Build Dependencies</link>" section for more information. </para></listitem> - <listitem><para><emphasis>dirs:</emphasis> + <listitem><para><emphasis><filename>[dirs]</filename>:</emphasis> Directories that should be created before the task runs. - The last directory listed will be used as the work directory - for the task. + Directories that already exist are left as is. + The last directory listed is used as the + current working directory for the task. </para></listitem> - <listitem><para><emphasis>lockfiles:</emphasis> + <listitem><para><emphasis><filename>[lockfiles]</filename>:</emphasis> Specifies one or more lockfiles to lock while the task executes. Only one task may hold a lockfile, and any task that @@ -1328,23 +1832,32 @@ You can use this variable flag to accomplish mutual exclusion. </para></listitem> - <listitem><para><emphasis>noexec:</emphasis> - Marks the tasks as being empty and no execution required. - The <filename>noexec</filename> flag can be used to set up + <listitem><para><emphasis><filename>[noexec]</filename>:</emphasis> + When set to "1", marks the task as being empty, with + no execution required. + You can use the <filename>[noexec]</filename> flag to set up tasks as dependency placeholders, or to disable tasks defined elsewhere that are not needed in a particular recipe. </para></listitem> - <listitem><para><emphasis>nostamp:</emphasis> - Tells BitBake to not generate a stamp file for a task, - which implies the task should always be executed. + <listitem><para><emphasis><filename>[nostamp]</filename>:</emphasis> + When set to "1", tells BitBake to not generate a stamp + file for a task, which implies the task should always + be executed. + <note><title>Caution</title> + Any task that depends (possibly indirectly) on a + <filename>[nostamp]</filename> task will always be + executed as well. + This can cause unnecessary rebuilding if you are + not careful. + </note> </para></listitem> - <listitem><para><emphasis>postfuncs:</emphasis> + <listitem><para><emphasis><filename>[postfuncs]</filename>:</emphasis> List of functions to call after the completion of the task. </para></listitem> - <listitem><para><emphasis>prefuncs:</emphasis> + <listitem><para><emphasis><filename>[prefuncs]</filename>:</emphasis> List of functions to call before the task executes. </para></listitem> - <listitem><para><emphasis>rdepends:</emphasis> + <listitem><para><emphasis><filename>[rdepends]</filename>:</emphasis> Controls inter-task runtime dependencies. See the <link linkend='var-RDEPENDS'><filename>RDEPENDS</filename></link> @@ -1354,7 +1867,7 @@ "<link linkend='inter-task-dependencies'>Inter-Task Dependencies</link>" section for more information. </para></listitem> - <listitem><para><emphasis>rdeptask:</emphasis> + <listitem><para><emphasis><filename>[rdeptask]</filename>:</emphasis> Controls task runtime dependencies. See the <link linkend='var-RDEPENDS'><filename>RDEPENDS</filename></link> @@ -1364,12 +1877,12 @@ "<link linkend='runtime-dependencies'>Runtime Dependencies</link>" section for more information. </para></listitem> - <listitem><para><emphasis>recideptask:</emphasis> + <listitem><para><emphasis><filename>[recideptask]</filename>:</emphasis> When set in conjunction with <filename>recrdeptask</filename>, specifies a task that should be inspected for additional dependencies. </para></listitem> - <listitem><para><emphasis>recrdeptask:</emphasis> + <listitem><para><emphasis><filename>[recrdeptask]</filename>:</emphasis> Controls task recursive runtime dependencies. See the <link linkend='var-RDEPENDS'><filename>RDEPENDS</filename></link> @@ -1379,12 +1892,12 @@ "<link linkend='recursive-dependencies'>Recursive Dependencies</link>" section for more information. </para></listitem> - <listitem><para><emphasis>stamp-extra-info:</emphasis> + <listitem><para><emphasis><filename>[stamp-extra-info]</filename>:</emphasis> Extra stamp information to append to the task's stamp. As an example, OpenEmbedded uses this flag to allow machine-specific tasks. </para></listitem> - <listitem><para><emphasis>umask:</emphasis> + <listitem><para><emphasis><filename>[umask]</filename>:</emphasis> The umask to run the task under. </para></listitem> </itemizedlist> @@ -1397,7 +1910,7 @@ "<link linkend='checksums'>Checksums (Signatures)</link>" section. <itemizedlist> - <listitem><para><emphasis>vardeps:</emphasis> + <listitem><para><emphasis><filename>[vardeps]</filename>:</emphasis> Specifies a space-separated list of additional variables to add to a variable's dependencies for the purposes of calculating its signature. @@ -1406,17 +1919,17 @@ does not allow BitBake to automatically determine that the variable is referred to. </para></listitem> - <listitem><para><emphasis>vardepsexclude:</emphasis> + <listitem><para><emphasis><filename>[vardepsexclude]</filename>:</emphasis> Specifies a space-separated list of variables that should be excluded from a variable's dependencies for the purposes of calculating its signature. </para></listitem> - <listitem><para><emphasis>vardepvalue:</emphasis> + <listitem><para><emphasis><filename>[vardepvalue]</filename>:</emphasis> If set, instructs BitBake to ignore the actual value of the variable and instead use the specified value when calculating the variable's signature. </para></listitem> - <listitem><para><emphasis>vardepvalueexclude:</emphasis> + <listitem><para><emphasis><filename>[vardepvalueexclude]</filename>:</emphasis> Specifies a pipe-separated list of strings to exclude from the variable's value when calculating the variable's signature. @@ -1624,15 +2137,32 @@ <title>Dependencies</title> <para> - To allow for efficient operation given multiple processes - executing in parallel, BitBake handles dependencies at - the task level. - BitBake supports a robust method to handle these dependencies. - </para> + To allow for efficient parallel processing, BitBake handles + dependencies at the task level. + Dependencies can exist both between tasks within a single recipe + and between tasks in different recipes. + Following are examples of each: + <itemizedlist> + <listitem><para>For tasks within a single recipe, a + recipe's <filename>do_configure</filename> + task might need to complete before its + <filename>do_compile</filename> task can run. + </para></listitem> + <listitem><para>For tasks in different recipes, one + recipe's <filename>do_configure</filename> + task might require another recipe's + <filename>do_populate_sysroot</filename> + task to finish first such that the libraries and headers + provided by the other recipe are available. + </para></listitem> + </itemizedlist> + </para> - <para> - This section describes several types of dependency mechanisms. - </para> + <para> + This section describes several ways to declare dependencies. + Remember, even though dependencies are declared in different ways, they + are all simply dependencies between tasks. + </para> <section id='dependencies-internal-to-the-bb-file'> <title>Dependencies Internal to the <filename>.bb</filename> File</title> @@ -1648,11 +2178,49 @@ <literallayout class='monospaced'> addtask printdate after do_fetch before do_build </literallayout> - In this example, the <filename>printdate</filename> task is - depends on the completion of the <filename>do_fetch</filename> + In this example, the <filename>do_printdate</filename> + task depends on the completion of the + <filename>do_fetch</filename> task, and the + <filename>do_build</filename> task depends on the + completion of the <filename>do_printdate</filename> task. - And, the <filename>do_build</filename> depends on the completion - of the <filename>printdate</filename> task. + <note><para> + For a task to run, it must be a direct or indirect + dependency of some other task that is scheduled to + run.</para> + + <para>For illustration, here are some examples: + <itemizedlist> + <listitem><para> + The directive + <filename>addtask mytask before do_configure</filename> + causes <filename>do_mytask</filename> to run before + <filename>do_configure</filename> runs. + Be aware that <filename>do_mytask</filename> still only + runs if its <link linkend='checksums'>input checksum</link> + has changed since the last time it was run. + Changes to the input checksum of + <filename>do_mytask</filename> also indirectly cause + <filename>do_configure</filename> to run. + </para></listitem> + <listitem><para> + The directive + <filename>addtask mytask after do_configure</filename> + by itself never causes <filename>do_mytask</filename> + to run. + <filename>do_mytask</filename> can still be run manually + as follows: + <literallayout class='monospaced'> + $ bitbake <replaceable>recipe</replaceable> -c mytask + </literallayout> + Declaring <filename>do_mytask</filename> as a dependency + of some other task that is scheduled to run also causes + it to run. + Regardless, the task runs after + <filename>do_configure</filename>. + </para></listitem> + </itemizedlist></para> + </note> </para> </section> @@ -1663,7 +2231,8 @@ BitBake uses the <link linkend='var-DEPENDS'><filename>DEPENDS</filename></link> variable to manage build time dependencies. - The "deptask" varflag for tasks signifies the task of each + The <filename>[deptask]</filename> varflag for tasks + signifies the task of each item listed in <filename>DEPENDS</filename> that must complete before that task can be executed. Here is an example: @@ -1692,7 +2261,8 @@ packages. Each of those packages can have <filename>RDEPENDS</filename> and <filename>RRECOMMENDS</filename> runtime dependencies. - The "rdeptask" flag for tasks is used to signify the task of each + The <filename>[rdeptask]</filename> flag for tasks is used to + signify the task of each item runtime dependency which must have completed before that task can be executed. <literallayout class='monospaced'> @@ -1708,7 +2278,7 @@ <title>Recursive Dependencies</title> <para> - BitBake uses the "recrdeptask" flag to manage + BitBake uses the <filename>[recrdeptask]</filename> flag to manage recursive task dependencies. BitBake looks through the build-time and runtime dependencies of the current recipe, looks through @@ -1722,6 +2292,21 @@ </para> <para> + The <filename>[recrdeptask]</filename> flag is most commonly + used in high-level + recipes that need to wait for some task to finish "globally". + For example, <filename>image.bbclass</filename> has the following: + <literallayout class='monospaced'> + do_rootfs[recrdeptask] += "do_packagedata" + </literallayout> + This statement says that the <filename>do_packagedata</filename> + task of the current recipe and all recipes reachable + (by way of dependencies) from the + image recipe must run before the <filename>do_rootfs</filename> + task can run. + </para> + + <para> You might want to not only have BitBake look for dependencies of those tasks, but also have BitBake look for build-time and runtime dependencies of the dependent @@ -1738,7 +2323,8 @@ <title>Inter-Task Dependencies</title> <para> - BitBake uses the "depends" flag in a more generic form + BitBake uses the <filename>[depends]</filename> + flag in a more generic form to manage inter-task dependencies. This more generic form allows for inter-dependency checks for specific tasks rather than checks for @@ -1754,109 +2340,158 @@ </para> <para> - The "rdepends" flag works in a similar way but takes targets + The <filename>[rdepends]</filename> flag works in a similar + way but takes targets in the runtime namespace instead of the build-time dependency namespace. </para> </section> </section> - <section id='accessing-datastore-variables-using-python'> - <title>Accessing Datastore Variables Using Python</title> + <section id='functions-you-can-call-from-within-python'> + <title>Functions You Can Call From Within Python</title> <para> - It is often necessary to access variables in the - BitBake datastore using Python functions. - The Bitbake datastore has an API that allows you this - access. - Here is a list of available operations: + BitBake provides many functions you can call from + within Python functions. + This section lists the most commonly used functions, + and mentions where to find others. </para> - <para> - <informaltable frame='none'> - <tgroup cols='2' align='left' colsep='1' rowsep='1'> - <colspec colname='c1' colwidth='1*'/> - <colspec colname='c2' colwidth='1*'/> - <thead> - <row> - <entry align="left"><emphasis>Operation</emphasis></entry> - <entry align="left"><emphasis>Description</emphasis></entry> - </row> - </thead> - <tbody> - <row> - <entry align="left"><filename>d.getVar("X", expand=False)</filename></entry> - <entry align="left">Returns the value of variable "X". - Using "expand=True" expands the value.</entry> - </row> - <row> - <entry align="left"><filename>d.setVar("X", "value")</filename></entry> - <entry align="left">Sets the variable "X" to "value".</entry> - </row> - <row> - <entry align="left"><filename>d.appendVar("X", "value")</filename></entry> - <entry align="left">Adds "value" to the end of the variable "X".</entry> - </row> - <row> - <entry align="left"><filename>d.prependVar("X", "value")</filename></entry> - <entry align="left">Adds "value" to the start of the variable "X".</entry> - </row> - <row> - <entry align="left"><filename>d.delVar("X")</filename></entry> - <entry align="left">Deletes the variable "X" from the datastore.</entry> - </row> - <row> - <entry align="left"><filename>d.renameVar("X", "Y")</filename></entry> - <entry align="left">Renames the variable "X" to "Y".</entry> - </row> - <row> - <entry align="left"><filename>d.getVarFlag("X", flag, expand=False)</filename></entry> - <entry align="left">Gets then named flag from the variable "X". - Using "expand=True" expands the named flag.</entry> - </row> - <row> - <entry align="left"><filename>d.setVarFlag("X", flag, "value")</filename></entry> - <entry align="left">Sets the named flag for variable "X" to "value".</entry> - </row> - <row> - <entry align="left"><filename>d.appendVarFlag("X", flag, "value")</filename></entry> - <entry align="left">Appends "value" to the named flag on the - variable "X".</entry> - </row> - <row> - <entry align="left"><filename>d.prependVarFlag("X", flag, "value")</filename></entry> - <entry align="left">Prepends "value" to the named flag on - the variable "X".</entry> - </row> - <row> - <entry align="left"><filename>d.delVarFlag("X", flag)</filename></entry> - <entry align="left">Deletes the named flag on the variable - "X" from the datastore.</entry> - </row> - <row> - <entry align="left"><filename>d.setVarFlags("X", flagsdict)</filename></entry> - <entry align="left">Sets the flags specified in - the <filename>flagsdict()</filename> parameter. - <filename>setVarFlags</filename> does not clear previous flags. - Think of this operation as <filename>addVarFlags</filename>.</entry> - </row> - <row> - <entry align="left"><filename>d.getVarFlags("X")</filename></entry> - <entry align="left">Returns a <filename>flagsdict</filename> of the flags for - the variable "X".</entry> - </row> - <row> - <entry align="left"><filename>d.delVarFlags("X")</filename></entry> - <entry align="left">Deletes all the flags for the variable "X".</entry> - </row> - <row> - <entry align="left"><filename>d.expand(expression)</filename></entry> - <entry align="left">Expands variable references in the specified string expression.</entry> - </row> - </tbody> - </tgroup> - </informaltable> - </para> + <section id='functions-for-accessing-datastore-variables'> + <title>Functions for Accessing Datastore Variables</title> + + <para> + It is often necessary to access variables in the + BitBake datastore using Python functions. + The Bitbake datastore has an API that allows you this + access. + Here is a list of available operations: + </para> + + <para> + <informaltable frame='none'> + <tgroup cols='2' align='left' colsep='1' rowsep='1'> + <colspec colname='c1' colwidth='1*'/> + <colspec colname='c2' colwidth='1*'/> + <thead> + <row> + <entry align="left"><emphasis>Operation</emphasis></entry> + <entry align="left"><emphasis>Description</emphasis></entry> + </row> + </thead> + <tbody> + <row> + <entry align="left"><filename>d.getVar("X", expand)</filename></entry> + <entry align="left">Returns the value of variable "X". + Using "expand=True" expands the value. + Returns "None" if the variable "X" does not exist.</entry> + </row> + <row> + <entry align="left"><filename>d.setVar("X", "value")</filename></entry> + <entry align="left">Sets the variable "X" to "value".</entry> + </row> + <row> + <entry align="left"><filename>d.appendVar("X", "value")</filename></entry> + <entry align="left">Adds "value" to the end of the variable "X". + Acts like <filename>d.setVar("X", "value")</filename> + if the variable "X" does not exist.</entry> + </row> + <row> + <entry align="left"><filename>d.prependVar("X", "value")</filename></entry> + <entry align="left">Adds "value" to the start of the variable "X". + Acts like <filename>d.setVar("X", "value")</filename> + if the variable "X" does not exist.</entry> + </row> + <row> + <entry align="left"><filename>d.delVar("X")</filename></entry> + <entry align="left">Deletes the variable "X" from the datastore. + Does nothing if the variable "X" does not exist.</entry> + </row> + <row> + <entry align="left"><filename>d.renameVar("X", "Y")</filename></entry> + <entry align="left">Renames the variable "X" to "Y". + Does nothing if the variable "X" does not exist.</entry> + </row> + <row> + <entry align="left"><filename>d.getVarFlag("X", flag, expand)</filename></entry> + <entry align="left">Returns the value of variable "X". + Using "expand=True" expands the value. + Returns "None" if either the variable "X" or the named flag + does not exist.</entry> + </row> + <row> + <entry align="left"><filename>d.setVarFlag("X", flag, "value")</filename></entry> + <entry align="left">Sets the named flag for variable "X" to "value".</entry> + </row> + <row> + <entry align="left"><filename>d.appendVarFlag("X", flag, "value")</filename></entry> + <entry align="left">Appends "value" to the named flag on the + variable "X". + Acts like <filename>d.setVarFlag("X", flag, "value")</filename> + if the named flag does not exist.</entry> + </row> + <row> + <entry align="left"><filename>d.prependVarFlag("X", flag, "value")</filename></entry> + <entry align="left">Prepends "value" to the named flag on + the variable "X". + Acts like <filename>d.setVarFlag("X", flag, "value")</filename> + if the named flag does not exist.</entry> + </row> + <row> + <entry align="left"><filename>d.delVarFlag("X", flag)</filename></entry> + <entry align="left">Deletes the named flag on the variable + "X" from the datastore.</entry> + </row> + <row> + <entry align="left"><filename>d.setVarFlags("X", flagsdict)</filename></entry> + <entry align="left">Sets the flags specified in + the <filename>flagsdict()</filename> parameter. + <filename>setVarFlags</filename> does not clear previous flags. + Think of this operation as <filename>addVarFlags</filename>.</entry> + </row> + <row> + <entry align="left"><filename>d.getVarFlags("X")</filename></entry> + <entry align="left">Returns a <filename>flagsdict</filename> + of the flags for the variable "X". + Returns "None" if the variable "X" does not exist.</entry> + </row> + <row> + <entry align="left"><filename>d.delVarFlags("X")</filename></entry> + <entry align="left">Deletes all the flags for the variable "X". + Does nothing if the variable "X" does not exist.</entry> + </row> + <row> + <entry align="left"><filename>d.expand(expression)</filename></entry> + <entry align="left">Expands variable references in the specified + string expression. + References to variables that do not exist are left as is. + For example, <filename>d.expand("foo ${X}")</filename> + expands to the literal string "foo ${X}" if the + variable "X" does not exist.</entry> + </row> + </tbody> + </tgroup> + </informaltable> + </para> + </section> + + <section id='other-functions'> + <title>Other Functions</title> + + <para> + You can find many other functions that can be called + from Python by looking at the source code of the + <filename>bb</filename> module, which is in + <filename>bitbake/lib/bb</filename>. + For example, + <filename>bitbake/lib/bb/utils.py</filename> includes + the commonly used functions + <filename>bb.utils.contains()</filename> and + <filename>bb.utils.mkdirhier()</filename>, which come + with docstrings. + </para> + </section> </section> <section id='task-checksums-and-setscene'> @@ -1897,7 +2532,7 @@ the "setscene" part of the task's execution in order to validate the list of task hashes. </para></listitem> - <listitem><para><filename>BB_SETSCENE_VERIFY_FUNCTION</filename> + <listitem><para><filename>BB_SETSCENE_VERIFY_FUNCTION2</filename> Specifies a function to call that verifies the list of planned task execution before the main task execution happens. diff --git a/import-layers/yocto-poky/bitbake/doc/bitbake-user-manual/bitbake-user-manual-ref-variables.xml b/import-layers/yocto-poky/bitbake/doc/bitbake-user-manual/bitbake-user-manual-ref-variables.xml index ae7e4cee8..66d8f844e 100644 --- a/import-layers/yocto-poky/bitbake/doc/bitbake-user-manual/bitbake-user-manual-ref-variables.xml +++ b/import-layers/yocto-poky/bitbake/doc/bitbake-user-manual/bitbake-user-manual-ref-variables.xml @@ -52,7 +52,7 @@ <link linkend='var-MIRRORS'>M</link> <!-- <link linkend='var-glossary-n'>N</link> --> <link linkend='var-OVERRIDES'>O</link> - <link linkend='var-PACKAGES'>P</link> + <link linkend='var-P4DIR'>P</link> <!-- <link linkend='var-QMAKE_PROFILES'>Q</link> --> <link linkend='var-RDEPENDS'>R</link> <link linkend='var-SECTION'>S</link> @@ -716,7 +716,7 @@ </glossdef> </glossentry> - <glossentry id='var-BB_SETSCENE_VERIFY_FUNCTION'><glossterm>BB_SETSCENE_VERIFY_FUNCTION</glossterm> + <glossentry id='var-BB_SETSCENE_VERIFY_FUNCTION2'><glossterm>BB_SETSCENE_VERIFY_FUNCTION2</glossterm> <glossdef> <para> Specifies a function to call that verifies the list of @@ -986,6 +986,29 @@ BBCLASSEXTEND =+ "native nativesdk" BBCLASSEXTEND =+ "multilib:<replaceable>multilib_name</replaceable>" </literallayout> + <note> + <para> + Internally, the <filename>BBCLASSEXTEND</filename> + mechanism generates recipe variants by rewriting + variable values and applying overrides such as + <filename>_class-native</filename>. + For example, to generate a native version of a recipe, + a + <link linkend='var-DEPENDS'><filename>DEPENDS</filename></link> + on "foo" is rewritten to a <filename>DEPENDS</filename> + on "foo-native". + </para> + + <para> + Even when using <filename>BBCLASSEXTEND</filename>, the + recipe is only parsed once. + Parsing once adds some limitations. + For example, it is not possible to + include a different file depending on the variant, + since <filename>include</filename> statements are + processed when the recipe is parsed. + </para> + </note> </para> </glossdef> </glossentry> @@ -994,7 +1017,7 @@ <glossdef> <para> Sets the BitBake debug output level to a specific value - as incremented by the <filename>-d</filename> command line + as incremented by the <filename>-D</filename> command line option. <note> You must set this variable in the external environment @@ -1636,6 +1659,17 @@ </glossdef> </glossentry> + <glossentry id='var-LAYERDIR_RE'><glossterm>LAYERDIR_RE</glossterm> + <glossdef> + <para>When used inside the <filename>layer.conf</filename> configuration + file, this variable provides the path of the current layer, + escaped for use in a regular expression + (<link linkend='var-BBFILE_PATTERN'><filename>BBFILE_PATTERN</filename></link>). + This variable is not available outside of <filename>layer.conf</filename> + and references are expanded immediately when parsing of the file completes.</para> + </glossdef> + </glossentry> + <glossentry id='var-LAYERVERSION'><glossterm>LAYERVERSION</glossterm> <glossdef> <para>Optionally specifies the version of a layer as a single number. @@ -1737,6 +1771,15 @@ <glossdiv id='var-glossary-p'><title>P</title> + <glossentry id='var-P4DIR'><glossterm>P4DIR</glossterm> + <glossdef> + <para> + The directory in which a local copy of a Perforce depot + is stored when it is fetched. + </para> + </glossdef> + </glossentry> + <glossentry id='var-PACKAGES'><glossterm>PACKAGES</glossterm> <glossdef> <para>The list of packages the recipe creates. @@ -1933,6 +1976,27 @@ The <filename>PROVIDES</filename> statement results in the "libav" recipe also being known as "libpostproc". </para> + + <para> + In addition to providing recipes under alternate names, + the <filename>PROVIDES</filename> mechanism is also used + to implement virtual targets. + A virtual target is a name that corresponds to some + particular functionality (e.g. a Linux kernel). + Recipes that provide the functionality in question list the + virtual target in <filename>PROVIDES</filename>. + Recipes that depend on the functionality in question can + include the virtual target in + <link linkend='var-DEPENDS'><filename>DEPENDS</filename></link> + to leave the choice of provider open. + </para> + + <para> + Conventionally, virtual targets have names on the form + "virtual/function" (e.g. "virtual/kernel"). + The slash is simply part of the name and has no + syntactical significance. + </para> </glossdef> </glossentry> diff --git a/import-layers/yocto-poky/bitbake/lib/bb/COW.py b/import-layers/yocto-poky/bitbake/lib/bb/COW.py index 6917ec378..77a05cfe3 100644 --- a/import-layers/yocto-poky/bitbake/lib/bb/COW.py +++ b/import-layers/yocto-poky/bitbake/lib/bb/COW.py @@ -23,19 +23,17 @@ # Assign a file to __warn__ to get warnings about slow operations. # -from __future__ import print_function + import copy import types ImmutableTypes = ( - types.NoneType, bool, complex, float, int, - long, tuple, frozenset, - basestring + str ) MUTABLE = "__mutable__" @@ -61,7 +59,7 @@ class COWDictMeta(COWMeta): __call__ = cow def __setitem__(cls, key, value): - if not isinstance(value, ImmutableTypes): + if value is not None and not isinstance(value, ImmutableTypes): if not isinstance(value, COWMeta): cls.__hasmutable__ = True key += MUTABLE @@ -116,7 +114,7 @@ class COWDictMeta(COWMeta): cls.__setitem__(key, cls.__marker__) def __revertitem__(cls, key): - if not cls.__dict__.has_key(key): + if key not in cls.__dict__: key += MUTABLE delattr(cls, key) @@ -183,7 +181,7 @@ class COWSetMeta(COWDictMeta): COWDictMeta.__delitem__(cls, repr(hash(value))) def __in__(cls, value): - return COWDictMeta.has_key(repr(hash(value))) + return repr(hash(value)) in COWDictMeta def iterkeys(cls): raise TypeError("sets don't have keys") @@ -192,12 +190,10 @@ class COWSetMeta(COWDictMeta): raise TypeError("sets don't have 'items'") # These are the actual classes you use! -class COWDictBase(object): - __metaclass__ = COWDictMeta +class COWDictBase(object, metaclass = COWDictMeta): __count__ = 0 -class COWSetBase(object): - __metaclass__ = COWSetMeta +class COWSetBase(object, metaclass = COWSetMeta): __count__ = 0 if __name__ == "__main__": @@ -217,11 +213,11 @@ if __name__ == "__main__": print() print("a", a) - for x in a.iteritems(): + for x in a.items(): print(x) print("--") print("b", b) - for x in b.iteritems(): + for x in b.items(): print(x) print() @@ -229,11 +225,11 @@ if __name__ == "__main__": b['a'] = 'c' print("a", a) - for x in a.iteritems(): + for x in a.items(): print(x) print("--") print("b", b) - for x in b.iteritems(): + for x in b.items(): print(x) print() @@ -248,22 +244,22 @@ if __name__ == "__main__": a['set'].add("o2") print("a", a) - for x in a['set'].itervalues(): + for x in a['set'].values(): print(x) print("--") print("b", b) - for x in b['set'].itervalues(): + for x in b['set'].values(): print(x) print() b['set'].add('o3') print("a", a) - for x in a['set'].itervalues(): + for x in a['set'].values(): print(x) print("--") print("b", b) - for x in b['set'].itervalues(): + for x in b['set'].values(): print(x) print() @@ -273,7 +269,7 @@ if __name__ == "__main__": a['set2'].add("o2") print("a", a) - for x in a.iteritems(): + for x in a.items(): print(x) print("--") print("b", b) @@ -287,13 +283,13 @@ if __name__ == "__main__": except KeyError: print("Yay! deleted key raises error") - if b.has_key('b'): + if 'b' in b: print("Boo!") else: print("Yay - has_key with delete works!") print("a", a) - for x in a.iteritems(): + for x in a.items(): print(x) print("--") print("b", b) @@ -304,7 +300,7 @@ if __name__ == "__main__": b.__revertitem__('b') print("a", a) - for x in a.iteritems(): + for x in a.items(): print(x) print("--") print("b", b) @@ -314,7 +310,7 @@ if __name__ == "__main__": b.__revertitem__('dict') print("a", a) - for x in a.iteritems(): + for x in a.items(): print(x) print("--") print("b", b) diff --git a/import-layers/yocto-poky/bitbake/lib/bb/__init__.py b/import-layers/yocto-poky/bitbake/lib/bb/__init__.py index 502ad839e..f019d4831 100644 --- a/import-layers/yocto-poky/bitbake/lib/bb/__init__.py +++ b/import-layers/yocto-poky/bitbake/lib/bb/__init__.py @@ -21,11 +21,11 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -__version__ = "1.30.0" +__version__ = "1.32.0" import sys -if sys.version_info < (2, 7, 3): - raise RuntimeError("Sorry, python 2.7.3 or later is required for this version of bitbake") +if sys.version_info < (3, 4, 0): + raise RuntimeError("Sorry, python 3.4.0 or later is required for this version of bitbake") class BBHandledException(Exception): @@ -84,8 +84,8 @@ def plain(*args): mainlogger.plain(''.join(args)) def debug(lvl, *args): - if isinstance(lvl, basestring): - mainlogger.warn("Passed invalid debug level '%s' to bb.debug", lvl) + if isinstance(lvl, str): + mainlogger.warning("Passed invalid debug level '%s' to bb.debug", lvl) args = (lvl,) + args lvl = 1 mainlogger.debug(lvl, ''.join(args)) @@ -94,7 +94,7 @@ def note(*args): mainlogger.info(''.join(args)) def warn(*args): - mainlogger.warn(''.join(args)) + mainlogger.warning(''.join(args)) def error(*args, **kwargs): mainlogger.error(''.join(args), extra=kwargs) diff --git a/import-layers/yocto-poky/bitbake/lib/bb/build.py b/import-layers/yocto-poky/bitbake/lib/bb/build.py index db5072cb4..c4c8aeb64 100644 --- a/import-layers/yocto-poky/bitbake/lib/bb/build.py +++ b/import-layers/yocto-poky/bitbake/lib/bb/build.py @@ -35,8 +35,8 @@ import stat import bb import bb.msg import bb.process -from contextlib import nested -from bb import event, utils +import bb.progress +from bb import data, event, utils bblogger = logging.getLogger('BitBake') logger = logging.getLogger('BitBake.Build') @@ -61,8 +61,13 @@ def reset_cache(): # in all namespaces, hence we add them to __builtins__. # If we do not do this and use the exec globals, they will # not be available to subfunctions. -__builtins__['bb'] = bb -__builtins__['os'] = os +if hasattr(__builtins__, '__setitem__'): + builtins = __builtins__ +else: + builtins = __builtins__.__dict__ + +builtins['bb'] = bb +builtins['os'] = os class FuncFailed(Exception): def __init__(self, name = None, logfile = None): @@ -133,6 +138,25 @@ class TaskInvalid(TaskBase): super(TaskInvalid, self).__init__(task, None, metadata) self._message = "No such task '%s'" % task +class TaskProgress(event.Event): + """ + Task made some progress that could be reported to the user, usually in + the form of a progress bar or similar. + NOTE: this class does not inherit from TaskBase since it doesn't need + to - it's fired within the task context itself, so we don't have any of + the context information that you do in the case of the other events. + The event PID can be used to determine which task it came from. + The progress value is normally 0-100, but can also be negative + indicating that progress has been made but we aren't able to determine + how much. + The rate is optional, this is simply an extra string to display to the + user if specified. + """ + def __init__(self, progress, rate=None): + self.progress = progress + self.rate = rate + event.Event.__init__(self) + class LogTee(object): def __init__(self, logger, outfile): @@ -164,11 +188,10 @@ class LogTee(object): def exec_func(func, d, dirs = None, pythonexception=False): """Execute a BB 'function'""" - body = d.getVar(func, False) - if not body: - if body is None: - logger.warn("Function %s doesn't exist", func) - return + try: + oldcwd = os.getcwd() + except: + oldcwd = None flags = d.getVarFlags(func) cleandirs = flags.get('cleandirs') @@ -187,8 +210,13 @@ def exec_func(func, d, dirs = None, pythonexception=False): bb.utils.mkdirhier(adir) adir = dirs[-1] else: - adir = d.getVar('B', True) - bb.utils.mkdirhier(adir) + adir = None + + body = d.getVar(func, False) + if not body: + if body is None: + logger.warning("Function %s doesn't exist", func) + return ispython = flags.get('python') @@ -233,6 +261,18 @@ def exec_func(func, d, dirs = None, pythonexception=False): else: exec_func_shell(func, d, runfile, cwd=adir) + try: + curcwd = os.getcwd() + except: + curcwd = None + + if oldcwd and curcwd != oldcwd: + try: + bb.warn("Task %s changed cwd to %s" % (func, curcwd)) + os.chdir(oldcwd) + except: + pass + _functionfmt = """ {function}(d) """ @@ -248,7 +288,8 @@ def exec_func_python(func, d, runfile, cwd=None, pythonexception=False): if cwd: try: olddir = os.getcwd() - except OSError: + except OSError as e: + bb.warn("%s: Cannot get cwd: %s" % (func, e)) olddir = None os.chdir(cwd) @@ -274,8 +315,8 @@ def exec_func_python(func, d, runfile, cwd=None, pythonexception=False): if cwd and olddir: try: os.chdir(olddir) - except OSError: - pass + except OSError as e: + bb.warn("%s: Cannot restore cwd %s: %s" % (func, olddir, e)) def shell_trap_code(): return '''#!/bin/sh\n @@ -323,7 +364,7 @@ trap '' 0 exit $ret ''') - os.chmod(runfile, 0775) + os.chmod(runfile, 0o775) cmd = runfile if d.getVarFlag(func, 'fakeroot', False): @@ -336,41 +377,64 @@ exit $ret else: logfile = sys.stdout + progress = d.getVarFlag(func, 'progress', True) + if progress: + if progress == 'percent': + # Use default regex + logfile = bb.progress.BasicProgressHandler(d, outfile=logfile) + elif progress.startswith('percent:'): + # Use specified regex + logfile = bb.progress.BasicProgressHandler(d, regex=progress.split(':', 1)[1], outfile=logfile) + elif progress.startswith('outof:'): + # Use specified regex + logfile = bb.progress.OutOfProgressHandler(d, regex=progress.split(':', 1)[1], outfile=logfile) + else: + bb.warn('%s: invalid task progress varflag value "%s", ignoring' % (func, progress)) + + fifobuffer = bytearray() def readfifo(data): - lines = data.split('\0') - for line in lines: - splitval = line.split(' ', 1) - cmd = splitval[0] - if len(splitval) > 1: - value = splitval[1] + nonlocal fifobuffer + fifobuffer.extend(data) + while fifobuffer: + message, token, nextmsg = fifobuffer.partition(b"\00") + if token: + splitval = message.split(b' ', 1) + cmd = splitval[0].decode("utf-8") + if len(splitval) > 1: + value = splitval[1].decode("utf-8") + else: + value = '' + if cmd == 'bbplain': + bb.plain(value) + elif cmd == 'bbnote': + bb.note(value) + elif cmd == 'bbwarn': + bb.warn(value) + elif cmd == 'bberror': + bb.error(value) + elif cmd == 'bbfatal': + # The caller will call exit themselves, so bb.error() is + # what we want here rather than bb.fatal() + bb.error(value) + elif cmd == 'bbfatal_log': + bb.error(value, forcelog=True) + elif cmd == 'bbdebug': + splitval = value.split(' ', 1) + level = int(splitval[0]) + value = splitval[1] + bb.debug(level, value) + else: + bb.warn("Unrecognised command '%s' on FIFO" % cmd) + fifobuffer = nextmsg else: - value = '' - if cmd == 'bbplain': - bb.plain(value) - elif cmd == 'bbnote': - bb.note(value) - elif cmd == 'bbwarn': - bb.warn(value) - elif cmd == 'bberror': - bb.error(value) - elif cmd == 'bbfatal': - # The caller will call exit themselves, so bb.error() is - # what we want here rather than bb.fatal() - bb.error(value) - elif cmd == 'bbfatal_log': - bb.error(value, forcelog=True) - elif cmd == 'bbdebug': - splitval = value.split(' ', 1) - level = int(splitval[0]) - value = splitval[1] - bb.debug(level, value) + break tempdir = d.getVar('T', True) fifopath = os.path.join(tempdir, 'fifo.%s' % os.getpid()) if os.path.exists(fifopath): os.unlink(fifopath) os.mkfifo(fifopath) - with open(fifopath, 'r+') as fifo: + with open(fifopath, 'r+b', buffering=0) as fifo: try: bb.debug(2, "Executing shell function %s" % func) @@ -501,21 +565,32 @@ def _exec_task(fn, task, d, quieterr): flags = localdata.getVarFlags(task) - event.fire(TaskStarted(task, logfn, flags, localdata), localdata) try: - for func in (prefuncs or '').split(): - exec_func(func, localdata) - exec_func(task, localdata) - for func in (postfuncs or '').split(): - exec_func(func, localdata) - except FuncFailed as exc: - if quieterr: - event.fire(TaskFailedSilent(task, logfn, localdata), localdata) - else: - errprinted = errchk.triggered + try: + event.fire(TaskStarted(task, logfn, flags, localdata), localdata) + except (bb.BBHandledException, SystemExit): + return 1 + except FuncFailed as exc: logger.error(str(exc)) - event.fire(TaskFailed(task, logfn, localdata, errprinted), localdata) - return 1 + return 1 + + try: + for func in (prefuncs or '').split(): + exec_func(func, localdata) + exec_func(task, localdata) + for func in (postfuncs or '').split(): + exec_func(func, localdata) + except FuncFailed as exc: + if quieterr: + event.fire(TaskFailedSilent(task, logfn, localdata), localdata) + else: + errprinted = errchk.triggered + logger.error(str(exc)) + event.fire(TaskFailed(task, logfn, localdata, errprinted), localdata) + return 1 + except bb.BBHandledException: + event.fire(TaskFailed(task, logfn, localdata, True), localdata) + return 1 finally: sys.stdout.flush() sys.stderr.flush() @@ -575,7 +650,7 @@ def exec_task(fn, task, d, profile = False): event.fire(failedevent, d) return 1 -def stamp_internal(taskname, d, file_name, baseonly=False): +def stamp_internal(taskname, d, file_name, baseonly=False, noextra=False): """ Internal stamp helper function Makes sure the stamp directory exists @@ -598,6 +673,8 @@ def stamp_internal(taskname, d, file_name, baseonly=False): if baseonly: return stamp + if noextra: + extrainfo = "" if not stamp: return @@ -693,12 +770,12 @@ def write_taint(task, d, file_name = None): with open(taintfn, 'w') as taintf: taintf.write(str(uuid.uuid4())) -def stampfile(taskname, d, file_name = None): +def stampfile(taskname, d, file_name = None, noextra=False): """ Return the stamp for a given task (d can be a data dict or dataCache) """ - return stamp_internal(taskname, d, file_name) + return stamp_internal(taskname, d, file_name, noextra=noextra) def add_tasks(tasklist, d): task_deps = d.getVar('_task_deps', False) @@ -774,6 +851,7 @@ def deltask(task, d): bbtasks = d.getVar('__BBTASKS', False) or [] if task in bbtasks: bbtasks.remove(task) + d.delVarFlag(task, 'task') d.setVar('__BBTASKS', bbtasks) d.delVarFlag(task, 'deps') diff --git a/import-layers/yocto-poky/bitbake/lib/bb/cache.py b/import-layers/yocto-poky/bitbake/lib/bb/cache.py index af5b9fbc6..dd9cfdfac 100644 --- a/import-layers/yocto-poky/bitbake/lib/bb/cache.py +++ b/import-layers/yocto-poky/bitbake/lib/bb/cache.py @@ -28,22 +28,16 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - import os +import sys import logging +import pickle from collections import defaultdict import bb.utils logger = logging.getLogger("BitBake.Cache") -try: - import cPickle as pickle -except ImportError: - import pickle - logger.info("Importing cPickle failed. " - "Falling back to a very slow implementation.") - -__cache_version__ = "149" +__cache_version__ = "150" def getCacheFile(path, filename, data_hash): return os.path.join(path, filename + "." + data_hash) @@ -80,7 +74,7 @@ class RecipeInfoCommon(object): out_dict = dict((var, metadata.getVarFlag(var, flag, True)) for var in varlist) if squash: - return dict((k,v) for (k,v) in out_dict.iteritems() if v) + return dict((k,v) for (k,v) in out_dict.items() if v) else: return out_dict @@ -240,7 +234,7 @@ class CoreRecipeInfo(RecipeInfoCommon): cachedata.universe_target.append(self.pn) cachedata.hashfn[fn] = self.hashfilename - for task, taskhash in self.basetaskhashes.iteritems(): + for task, taskhash in self.basetaskhashes.items(): identifier = '%s.%s' % (fn, task) cachedata.basetaskhash[identifier] = taskhash @@ -250,14 +244,136 @@ class CoreRecipeInfo(RecipeInfoCommon): cachedata.fakerootdirs[fn] = self.fakerootdirs cachedata.extradepsfunc[fn] = self.extradepsfunc +def virtualfn2realfn(virtualfn): + """ + Convert a virtual file name to a real one + the associated subclass keyword + """ + mc = "" + if virtualfn.startswith('multiconfig:'): + elems = virtualfn.split(':') + mc = elems[1] + virtualfn = ":".join(elems[2:]) + + fn = virtualfn + cls = "" + if virtualfn.startswith('virtual:'): + elems = virtualfn.split(':') + cls = ":".join(elems[1:-1]) + fn = elems[-1] + + return (fn, cls, mc) + +def realfn2virtual(realfn, cls, mc): + """ + Convert a real filename + the associated subclass keyword to a virtual filename + """ + if cls: + realfn = "virtual:" + cls + ":" + realfn + if mc: + realfn = "multiconfig:" + mc + ":" + realfn + return realfn + +def variant2virtual(realfn, variant): + """ + Convert a real filename + the associated subclass keyword to a virtual filename + """ + if variant == "": + return realfn + if variant.startswith("multiconfig:"): + elems = variant.split(":") + if elems[2]: + return "multiconfig:" + elems[1] + ":virtual:" + ":".join(elems[2:]) + ":" + realfn + return "multiconfig:" + elems[1] + ":" + realfn + return "virtual:" + variant + ":" + realfn + +def parse_recipe(bb_data, bbfile, appends, mc=''): + """ + Parse a recipe + """ + + chdir_back = False + + bb_data.setVar("__BBMULTICONFIG", mc) + + # expand tmpdir to include this topdir + bb_data.setVar('TMPDIR', bb_data.getVar('TMPDIR', True) or "") + bbfile_loc = os.path.abspath(os.path.dirname(bbfile)) + oldpath = os.path.abspath(os.getcwd()) + bb.parse.cached_mtime_noerror(bbfile_loc) + + # The ConfHandler first looks if there is a TOPDIR and if not + # then it would call getcwd(). + # Previously, we chdir()ed to bbfile_loc, called the handler + # and finally chdir()ed back, a couple of thousand times. We now + # just fill in TOPDIR to point to bbfile_loc if there is no TOPDIR yet. + if not bb_data.getVar('TOPDIR', False): + chdir_back = True + bb_data.setVar('TOPDIR', bbfile_loc) + try: + if appends: + bb_data.setVar('__BBAPPEND', " ".join(appends)) + bb_data = bb.parse.handle(bbfile, bb_data) + if chdir_back: + os.chdir(oldpath) + return bb_data + except: + if chdir_back: + os.chdir(oldpath) + raise + + + +class NoCache(object): + + def __init__(self, databuilder): + self.databuilder = databuilder + self.data = databuilder.data + + def loadDataFull(self, virtualfn, appends): + """ + Return a complete set of data for fn. + To do this, we need to parse the file. + """ + logger.debug(1, "Parsing %s (full)" % virtualfn) + (fn, virtual, mc) = virtualfn2realfn(virtualfn) + bb_data = self.load_bbfile(virtualfn, appends, virtonly=True) + return bb_data[virtual] + + def load_bbfile(self, bbfile, appends, virtonly = False): + """ + Load and parse one .bb build file + Return the data and whether parsing resulted in the file being skipped + """ + + if virtonly: + (bbfile, virtual, mc) = virtualfn2realfn(bbfile) + bb_data = self.databuilder.mcdata[mc].createCopy() + bb_data.setVar("__ONLYFINALISE", virtual or "default") + datastores = parse_recipe(bb_data, bbfile, appends, mc) + return datastores + bb_data = self.data.createCopy() + datastores = parse_recipe(bb_data, bbfile, appends) -class Cache(object): + for mc in self.databuilder.mcdata: + if not mc: + continue + bb_data = self.databuilder.mcdata[mc].createCopy() + newstores = parse_recipe(bb_data, bbfile, appends, mc) + for ns in newstores: + datastores["multiconfig:%s:%s" % (mc, ns)] = newstores[ns] + + return datastores + +class Cache(NoCache): """ BitBake Cache implementation """ - def __init__(self, data, data_hash, caches_array): + def __init__(self, databuilder, data_hash, caches_array): + super().__init__(databuilder) + data = databuilder.data + # Pass caches_array information into Cache Constructor # It will be used later for deciding whether we # need extra cache file dump/load support @@ -266,7 +382,6 @@ class Cache(object): self.clean = set() self.checked = set() self.depends_cache = {} - self.data = None self.data_fn = None self.cacheclean = True self.data_hash = data_hash @@ -286,72 +401,74 @@ class Cache(object): cache_ok = True if self.caches_array: for cache_class in self.caches_array: - if type(cache_class) is type and issubclass(cache_class, RecipeInfoCommon): - cachefile = getCacheFile(self.cachedir, cache_class.cachefile, self.data_hash) - cache_ok = cache_ok and os.path.exists(cachefile) - cache_class.init_cacheData(self) + cachefile = getCacheFile(self.cachedir, cache_class.cachefile, self.data_hash) + cache_ok = cache_ok and os.path.exists(cachefile) + cache_class.init_cacheData(self) if cache_ok: self.load_cachefile() elif os.path.isfile(self.cachefile): logger.info("Out of date cache found, rebuilding...") def load_cachefile(self): - # Firstly, using core cache file information for - # valid checking - with open(self.cachefile, "rb") as cachefile: - pickled = pickle.Unpickler(cachefile) - try: - cache_ver = pickled.load() - bitbake_ver = pickled.load() - except Exception: - logger.info('Invalid cache, rebuilding...') - return - - if cache_ver != __cache_version__: - logger.info('Cache version mismatch, rebuilding...') - return - elif bitbake_ver != bb.__version__: - logger.info('Bitbake version mismatch, rebuilding...') - return - - cachesize = 0 previous_progress = 0 previous_percent = 0 # Calculate the correct cachesize of all those cache files for cache_class in self.caches_array: - if type(cache_class) is type and issubclass(cache_class, RecipeInfoCommon): - cachefile = getCacheFile(self.cachedir, cache_class.cachefile, self.data_hash) - with open(cachefile, "rb") as cachefile: - cachesize += os.fstat(cachefile.fileno()).st_size + cachefile = getCacheFile(self.cachedir, cache_class.cachefile, self.data_hash) + with open(cachefile, "rb") as cachefile: + cachesize += os.fstat(cachefile.fileno()).st_size bb.event.fire(bb.event.CacheLoadStarted(cachesize), self.data) for cache_class in self.caches_array: - if type(cache_class) is type and issubclass(cache_class, RecipeInfoCommon): - cachefile = getCacheFile(self.cachedir, cache_class.cachefile, self.data_hash) - with open(cachefile, "rb") as cachefile: - pickled = pickle.Unpickler(cachefile) - while cachefile: - try: - key = pickled.load() - value = pickled.load() - except Exception: - break - if self.depends_cache.has_key(key): - self.depends_cache[key].append(value) - else: - self.depends_cache[key] = [value] - # only fire events on even percentage boundaries - current_progress = cachefile.tell() + previous_progress - current_percent = 100 * current_progress / cachesize - if current_percent > previous_percent: - previous_percent = current_percent - bb.event.fire(bb.event.CacheLoadProgress(current_progress, cachesize), - self.data) - - previous_progress += current_progress + cachefile = getCacheFile(self.cachedir, cache_class.cachefile, self.data_hash) + with open(cachefile, "rb") as cachefile: + pickled = pickle.Unpickler(cachefile) + # Check cache version information + try: + cache_ver = pickled.load() + bitbake_ver = pickled.load() + except Exception: + logger.info('Invalid cache, rebuilding...') + return + + if cache_ver != __cache_version__: + logger.info('Cache version mismatch, rebuilding...') + return + elif bitbake_ver != bb.__version__: + logger.info('Bitbake version mismatch, rebuilding...') + return + + # Load the rest of the cache file + current_progress = 0 + while cachefile: + try: + key = pickled.load() + value = pickled.load() + except Exception: + break + if not isinstance(key, str): + bb.warn("%s from extras cache is not a string?" % key) + break + if not isinstance(value, RecipeInfoCommon): + bb.warn("%s from extras cache is not a RecipeInfoCommon class?" % value) + break + + if key in self.depends_cache: + self.depends_cache[key].append(value) + else: + self.depends_cache[key] = [value] + # only fire events on even percentage boundaries + current_progress = cachefile.tell() + previous_progress + current_percent = 100 * current_progress / cachesize + if current_percent > previous_percent: + previous_percent = current_percent + bb.event.fire(bb.event.CacheLoadProgress(current_progress, cachesize), + self.data) + + previous_progress += current_progress # Note: depends cache number is corresponding to the parsing file numbers. # The same file has several caches, still regarded as one item in the cache @@ -359,69 +476,33 @@ class Cache(object): len(self.depends_cache)), self.data) - - @staticmethod - def virtualfn2realfn(virtualfn): - """ - Convert a virtual file name to a real one + the associated subclass keyword - """ - - fn = virtualfn - cls = "" - if virtualfn.startswith('virtual:'): - elems = virtualfn.split(':') - cls = ":".join(elems[1:-1]) - fn = elems[-1] - return (fn, cls) - - @staticmethod - def realfn2virtual(realfn, cls): - """ - Convert a real filename + the associated subclass keyword to a virtual filename - """ - if cls == "": - return realfn - return "virtual:" + cls + ":" + realfn - - @classmethod - def loadDataFull(cls, virtualfn, appends, cfgData): - """ - Return a complete set of data for fn. - To do this, we need to parse the file. - """ - - (fn, virtual) = cls.virtualfn2realfn(virtualfn) - - logger.debug(1, "Parsing %s (full)", fn) - - cfgData.setVar("__ONLYFINALISE", virtual or "default") - bb_data = cls.load_bbfile(fn, appends, cfgData) - return bb_data[virtual] - - @classmethod - def parse(cls, filename, appends, configdata, caches_array): + def parse(self, filename, appends): """Parse the specified filename, returning the recipe information""" + logger.debug(1, "Parsing %s", filename) infos = [] - datastores = cls.load_bbfile(filename, appends, configdata) + datastores = self.load_bbfile(filename, appends) depends = [] - for variant, data in sorted(datastores.iteritems(), + variants = [] + # Process the "real" fn last so we can store variants list + for variant, data in sorted(datastores.items(), key=lambda i: i[0], reverse=True): - virtualfn = cls.realfn2virtual(filename, variant) + virtualfn = variant2virtual(filename, variant) + variants.append(variant) depends = depends + (data.getVar("__depends", False) or []) if depends and not variant: data.setVar("__depends", depends) - + if virtualfn == filename: + data.setVar("__VARIANTS", " ".join(variants)) info_array = [] - for cache_class in caches_array: - if type(cache_class) is type and issubclass(cache_class, RecipeInfoCommon): - info = cache_class(filename, data) - info_array.append(info) + for cache_class in self.caches_array: + info = cache_class(filename, data) + info_array.append(info) infos.append((virtualfn, info_array)) return infos - def load(self, filename, appends, configdata): + def load(self, filename, appends): """Obtain the recipe information for the specified filename, using cached values if available, otherwise parsing. @@ -435,21 +516,20 @@ class Cache(object): # info_array item is a list of [CoreRecipeInfo, XXXRecipeInfo] info_array = self.depends_cache[filename] for variant in info_array[0].variants: - virtualfn = self.realfn2virtual(filename, variant) + virtualfn = variant2virtual(filename, variant) infos.append((virtualfn, self.depends_cache[virtualfn])) else: - logger.debug(1, "Parsing %s", filename) return self.parse(filename, appends, configdata, self.caches_array) return cached, infos - def loadData(self, fn, appends, cfgData, cacheData): + def loadData(self, fn, appends, cacheData): """Load the recipe info for the specified filename, parsing and adding to the cache if necessary, and adding the recipe information to the supplied CacheData instance.""" skipped, virtuals = 0, 0 - cached, infos = self.load(fn, appends, cfgData) + cached, infos = self.load(fn, appends) for virtualfn, info_array in infos: if info_array[0].skipped: logger.debug(1, "Skipping %s: %s", virtualfn, info_array[0].skipreason) @@ -557,16 +637,19 @@ class Cache(object): invalid = False for cls in info_array[0].variants: - virtualfn = self.realfn2virtual(fn, cls) + virtualfn = variant2virtual(fn, cls) self.clean.add(virtualfn) if virtualfn not in self.depends_cache: logger.debug(2, "Cache: %s is not cached", virtualfn) invalid = True + elif len(self.depends_cache[virtualfn]) != len(self.caches_array): + logger.debug(2, "Cache: Extra caches missing for %s?" % virtualfn) + invalid = True # If any one of the variants is not present, mark as invalid for all if invalid: for cls in info_array[0].variants: - virtualfn = self.realfn2virtual(fn, cls) + virtualfn = variant2virtual(fn, cls) if virtualfn in self.clean: logger.debug(2, "Cache: Removing %s from cache", virtualfn) self.clean.remove(virtualfn) @@ -603,30 +686,19 @@ class Cache(object): logger.debug(2, "Cache is clean, not saving.") return - file_dict = {} - pickler_dict = {} for cache_class in self.caches_array: - if type(cache_class) is type and issubclass(cache_class, RecipeInfoCommon): - cache_class_name = cache_class.__name__ - cachefile = getCacheFile(self.cachedir, cache_class.cachefile, self.data_hash) - file_dict[cache_class_name] = open(cachefile, "wb") - pickler_dict[cache_class_name] = pickle.Pickler(file_dict[cache_class_name], pickle.HIGHEST_PROTOCOL) - - pickler_dict['CoreRecipeInfo'].dump(__cache_version__) - pickler_dict['CoreRecipeInfo'].dump(bb.__version__) - - try: - for key, info_array in self.depends_cache.iteritems(): - for info in info_array: - if isinstance(info, RecipeInfoCommon): - cache_class_name = info.__class__.__name__ - pickler_dict[cache_class_name].dump(key) - pickler_dict[cache_class_name].dump(info) - finally: - for cache_class in self.caches_array: - if type(cache_class) is type and issubclass(cache_class, RecipeInfoCommon): - cache_class_name = cache_class.__name__ - file_dict[cache_class_name].close() + cache_class_name = cache_class.__name__ + cachefile = getCacheFile(self.cachedir, cache_class.cachefile, self.data_hash) + with open(cachefile, "wb") as f: + p = pickle.Pickler(f, pickle.HIGHEST_PROTOCOL) + p.dump(__cache_version__) + p.dump(bb.__version__) + + for key, info_array in self.depends_cache.items(): + for info in info_array: + if isinstance(info, RecipeInfoCommon) and info.__class__.__name__ == cache_class_name: + p.dump(key) + p.dump(info) del self.depends_cache @@ -654,50 +726,13 @@ class Cache(object): Save data we need into the cache """ - realfn = self.virtualfn2realfn(file_name)[0] + realfn = virtualfn2realfn(file_name)[0] info_array = [] for cache_class in self.caches_array: - if type(cache_class) is type and issubclass(cache_class, RecipeInfoCommon): - info_array.append(cache_class(realfn, data)) + info_array.append(cache_class(realfn, data)) self.add_info(file_name, info_array, cacheData, parsed) - @staticmethod - def load_bbfile(bbfile, appends, config): - """ - Load and parse one .bb build file - Return the data and whether parsing resulted in the file being skipped - """ - chdir_back = False - - from bb import parse - - # expand tmpdir to include this topdir - config.setVar('TMPDIR', config.getVar('TMPDIR', True) or "") - bbfile_loc = os.path.abspath(os.path.dirname(bbfile)) - oldpath = os.path.abspath(os.getcwd()) - parse.cached_mtime_noerror(bbfile_loc) - bb_data = config.createCopy() - # The ConfHandler first looks if there is a TOPDIR and if not - # then it would call getcwd(). - # Previously, we chdir()ed to bbfile_loc, called the handler - # and finally chdir()ed back, a couple of thousand times. We now - # just fill in TOPDIR to point to bbfile_loc if there is no TOPDIR yet. - if not bb_data.getVar('TOPDIR', False): - chdir_back = True - bb_data.setVar('TOPDIR', bbfile_loc) - try: - if appends: - bb_data.setVar('__BBAPPEND', " ".join(appends)) - bb_data = parse.handle(bbfile, bb_data) - if chdir_back: - os.chdir(oldpath) - return bb_data - except: - if chdir_back: - os.chdir(oldpath) - raise - def init(cooker): """ @@ -727,8 +762,9 @@ class CacheData(object): def __init__(self, caches_array): self.caches_array = caches_array for cache_class in self.caches_array: - if type(cache_class) is type and issubclass(cache_class, RecipeInfoCommon): - cache_class.init_cacheData(self) + if not issubclass(cache_class, RecipeInfoCommon): + bb.error("Extra cache data class %s should subclass RecipeInfoCommon class" % cache_class) + cache_class.init_cacheData(self) # Direct cache variables self.task_queues = {} diff --git a/import-layers/yocto-poky/bitbake/lib/bb/checksum.py b/import-layers/yocto-poky/bitbake/lib/bb/checksum.py index 2ec964d73..84289208f 100644 --- a/import-layers/yocto-poky/bitbake/lib/bb/checksum.py +++ b/import-layers/yocto-poky/bitbake/lib/bb/checksum.py @@ -19,20 +19,13 @@ import glob import operator import os import stat +import pickle import bb.utils import logging from bb.cache import MultiProcessCache logger = logging.getLogger("BitBake.Cache") -try: - import cPickle as pickle -except ImportError: - import pickle - logger.info("Importing cPickle failed. " - "Falling back to a very slow implementation.") - - # mtime cache (non-persistent) # based upon the assumption that files do not change during bitbake run class FileMtimeCache(object): @@ -127,13 +120,15 @@ class FileChecksumCache(MultiProcessCache): checksums.extend(checksum_dir(f)) else: checksum = checksum_file(f) - checksums.append((f, checksum)) + if checksum: + checksums.append((f, checksum)) elif os.path.isdir(pth): if not os.path.islink(pth): checksums.extend(checksum_dir(pth)) else: checksum = checksum_file(pth) - checksums.append((pth, checksum)) + if checksum: + checksums.append((pth, checksum)) checksums.sort(key=operator.itemgetter(1)) return checksums diff --git a/import-layers/yocto-poky/bitbake/lib/bb/codeparser.py b/import-layers/yocto-poky/bitbake/lib/bb/codeparser.py index 3ee4d5622..25938d658 100644 --- a/import-layers/yocto-poky/bitbake/lib/bb/codeparser.py +++ b/import-layers/yocto-poky/bitbake/lib/bb/codeparser.py @@ -1,21 +1,20 @@ import ast +import sys import codegen import logging +import pickle +import bb.pysh as pysh import os.path import bb.utils, bb.data +import hashlib from itertools import chain -from pysh import pyshyacc, pyshlex, sherrors +from bb.pysh import pyshyacc, pyshlex, sherrors from bb.cache import MultiProcessCache - logger = logging.getLogger('BitBake.CodeParser') -try: - import cPickle as pickle -except ImportError: - import pickle - logger.info('Importing cPickle failed. Falling back to a very slow implementation.') - +def bbhash(s): + return hashlib.md5(s.encode("utf-8")).hexdigest() def check_indent(codestr): """If the code is indented, add a top level piece of code to 'remove' the indentation""" @@ -68,11 +67,12 @@ class SetCache(object): new = [] for i in items: - new.append(intern(i)) + new.append(sys.intern(i)) s = frozenset(new) - if hash(s) in self.setcache: - return self.setcache[hash(s)] - self.setcache[hash(s)] = s + h = hash(s) + if h in self.setcache: + return self.setcache[h] + self.setcache[h] = s return s codecache = SetCache() @@ -117,7 +117,7 @@ class shellCacheLine(object): class CodeParserCache(MultiProcessCache): cache_file_name = "bb_codeparser.dat" - CACHE_VERSION = 7 + CACHE_VERSION = 8 def __init__(self): MultiProcessCache.__init__(self) @@ -191,6 +191,7 @@ class BufferedLogger(Logger): class PythonParser(): getvars = (".getVar", ".appendVar", ".prependVar") + getvarflags = (".getVarFlag", ".appendVarFlag", ".prependVarFlag") containsfuncs = ("bb.utils.contains", "base_contains", "bb.utils.contains_any") execfuncs = ("bb.build.exec_func", "bb.build.exec_task") @@ -210,15 +211,20 @@ class PythonParser(): def visit_Call(self, node): name = self.called_node_name(node.func) - if name and name.endswith(self.getvars) or name in self.containsfuncs: + if name and (name.endswith(self.getvars) or name.endswith(self.getvarflags) or name in self.containsfuncs): if isinstance(node.args[0], ast.Str): varname = node.args[0].s if name in self.containsfuncs and isinstance(node.args[1], ast.Str): if varname not in self.contains: self.contains[varname] = set() self.contains[varname].add(node.args[1].s) - else: - self.references.add(node.args[0].s) + elif name.endswith(self.getvarflags): + if isinstance(node.args[1], ast.Str): + self.references.add('%s[%s]' % (varname, node.args[1].s)) + else: + self.warn(node.func, node.args[1]) + else: + self.references.add(varname) else: self.warn(node.func, node.args[0]) elif name and name.endswith(".expand"): @@ -268,7 +274,7 @@ class PythonParser(): if not node or not node.strip(): return - h = hash(str(node)) + h = bbhash(str(node)) if h in codeparsercache.pythoncache: self.references = set(codeparsercache.pythoncache[h].refs) @@ -313,7 +319,7 @@ class ShellParser(): commands it executes. """ - h = hash(str(value)) + h = bbhash(str(value)) if h in codeparsercache.shellcache: self.execs = set(codeparsercache.shellcache[h].execs) diff --git a/import-layers/yocto-poky/bitbake/lib/bb/command.py b/import-layers/yocto-poky/bitbake/lib/bb/command.py index 0559ffc07..caa3e4d45 100644 --- a/import-layers/yocto-poky/bitbake/lib/bb/command.py +++ b/import-layers/yocto-poky/bitbake/lib/bb/command.py @@ -110,7 +110,7 @@ class Command: return False except SystemExit as exc: arg = exc.args[0] - if isinstance(arg, basestring): + if isinstance(arg, str): self.finishAsyncCommand(arg) else: self.finishAsyncCommand("Exited with %s" % arg) diff --git a/import-layers/yocto-poky/bitbake/lib/bb/cooker.py b/import-layers/yocto-poky/bitbake/lib/bb/cooker.py index 9b565fc37..42831e277 100644 --- a/import-layers/yocto-poky/bitbake/lib/bb/cooker.py +++ b/import-layers/yocto-poky/bitbake/lib/bb/cooker.py @@ -22,7 +22,7 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -from __future__ import print_function + import sys, os, glob, os.path, re, time import atexit import itertools @@ -30,18 +30,21 @@ import logging import multiprocessing import sre_constants import threading -from cStringIO import StringIO +from io import StringIO, UnsupportedOperation from contextlib import closing from functools import wraps -from collections import defaultdict +from collections import defaultdict, namedtuple import bb, bb.exceptions, bb.command from bb import utils, data, parse, event, cache, providers, taskdata, runqueue, build -import Queue +import queue import signal import subprocess import errno import prserv.serv import pyinotify +import json +import pickle +import codecs logger = logging.getLogger("BitBake") collectlog = logging.getLogger("BitBake.Collection") @@ -65,7 +68,7 @@ class CollectionError(bb.BBHandledException): """ class state: - initial, parsing, running, shutdown, forceshutdown, stopped, error = range(7) + initial, parsing, running, shutdown, forceshutdown, stopped, error = list(range(7)) @classmethod def get_name(cls, code): @@ -93,7 +96,7 @@ class SkippedPackage: class CookerFeatures(object): - _feature_list = [HOB_EXTRA_CACHES, SEND_DEPENDS_TREE, BASEDATASTORE_TRACKING, SEND_SANITYEVENTS] = range(4) + _feature_list = [HOB_EXTRA_CACHES, BASEDATASTORE_TRACKING, SEND_SANITYEVENTS] = list(range(3)) def __init__(self): self._features=set() @@ -110,9 +113,49 @@ class CookerFeatures(object): def __iter__(self): return self._features.__iter__() - def next(self): - return self._features.next() + def __next__(self): + return next(self._features) + + +class EventWriter: + def __init__(self, cooker, eventfile): + self.file_inited = None + self.cooker = cooker + self.eventfile = eventfile + self.event_queue = [] + + def write_event(self, event): + with open(self.eventfile, "a") as f: + try: + str_event = codecs.encode(pickle.dumps(event), 'base64').decode('utf-8') + f.write("%s\n" % json.dumps({"class": event.__module__ + "." + event.__class__.__name__, + "vars": str_event})) + except Exception as err: + import traceback + print(err, traceback.format_exc()) + + def send(self, event): + if self.file_inited: + # we have the file, just write the event + self.write_event(event) + else: + # init on bb.event.BuildStarted + name = "%s.%s" % (event.__module__, event.__class__.__name__) + if name in ("bb.event.BuildStarted", "bb.cooker.CookerExit"): + with open(self.eventfile, "w") as f: + f.write("%s\n" % json.dumps({ "allvariables" : self.cooker.getAllKeysWithFlags(["doc", "func"])})) + self.file_inited = True + + # write pending events + for evt in self.event_queue: + self.write_event(evt) + + # also write the current event + self.write_event(event) + else: + # queue all events until the file is inited + self.event_queue.append(event) #============================================================================# # BBCooker @@ -123,7 +166,7 @@ class BBCooker: """ def __init__(self, configuration, featureSet=None): - self.recipecache = None + self.recipecaches = None self.skiplist = {} self.featureset = CookerFeatures() if featureSet: @@ -151,6 +194,13 @@ class BBCooker: self.initConfigurationData() + # we log all events to a file if so directed + if self.configuration.writeeventlog: + # register the log file writer as UI Handler + writer = EventWriter(self, self.configuration.writeeventlog) + EventLogWriteHandler = namedtuple('EventLogWriteHandler', ['event']) + bb.event.register_UIHhandler(EventLogWriteHandler(writer)) + self.inotify_modified_files = [] def _process_inotify_updates(server, notifier_list, abort): @@ -180,14 +230,17 @@ class BBCooker: pass # TOSTOP must not be set or our children will hang when they output - fd = sys.stdout.fileno() - if os.isatty(fd): - import termios - tcattr = termios.tcgetattr(fd) - if tcattr[3] & termios.TOSTOP: - buildlog.info("The terminal had the TOSTOP bit set, clearing...") - tcattr[3] = tcattr[3] & ~termios.TOSTOP - termios.tcsetattr(fd, termios.TCSANOW, tcattr) + try: + fd = sys.stdout.fileno() + if os.isatty(fd): + import termios + tcattr = termios.tcgetattr(fd) + if tcattr[3] & termios.TOSTOP: + buildlog.info("The terminal had the TOSTOP bit set, clearing...") + tcattr[3] = tcattr[3] & ~termios.TOSTOP + termios.tcsetattr(fd, termios.TCSANOW, tcattr) + except UnsupportedOperation: + pass self.command = bb.command.Command(self) self.state = state.initial @@ -301,74 +354,6 @@ class BBCooker: if consolelog: self.data.setVar("BB_CONSOLELOG", consolelog) - # we log all events to a file if so directed - if self.configuration.writeeventlog: - import json, pickle - DEFAULT_EVENTFILE = self.configuration.writeeventlog - class EventLogWriteHandler(): - - class EventWriter(): - def __init__(self, cooker): - self.file_inited = None - self.cooker = cooker - self.event_queue = [] - - def init_file(self): - try: - # delete the old log - os.remove(DEFAULT_EVENTFILE) - except: - pass - - # write current configuration data - with open(DEFAULT_EVENTFILE, "w") as f: - f.write("%s\n" % json.dumps({ "allvariables" : self.cooker.getAllKeysWithFlags(["doc", "func"])})) - - def write_event(self, event): - with open(DEFAULT_EVENTFILE, "a") as f: - try: - f.write("%s\n" % json.dumps({"class":event.__module__ + "." + event.__class__.__name__, "vars":json.dumps(pickle.dumps(event)) })) - except Exception as e: - import traceback - print(e, traceback.format_exc(e)) - - - def send(self, event): - event_class = event.__module__ + "." + event.__class__.__name__ - - # init on bb.event.BuildStarted - if self.file_inited is None: - if event_class == "bb.event.BuildStarted": - self.init_file() - self.file_inited = True - - # write pending events - for e in self.event_queue: - self.write_event(e) - - # also write the current event - self.write_event(event) - - else: - # queue all events until the file is inited - self.event_queue.append(event) - - else: - # we have the file, just write the event - self.write_event(event) - - # set our handler's event processor - event = EventWriter(self) # self is the cooker here - - - # set up cooker features for this mock UI handler - - # we need to write the dependency tree in the log - self.featureset.setFeature(CookerFeatures.SEND_DEPENDS_TREE) - # register the log file writer as UI Handler - bb.event.register_UIHhandler(EventLogWriteHandler()) - - # # Copy of the data store which has been expanded. # Used for firing events and accessing variables where expansion needs to be accounted for @@ -539,11 +524,14 @@ class BBCooker: nice = int(nice) - curnice buildlog.verbose("Renice to %s " % os.nice(nice)) - if self.recipecache: - del self.recipecache - self.recipecache = bb.cache.CacheData(self.caches_array) + if self.recipecaches: + del self.recipecaches + self.multiconfigs = self.databuilder.mcdata.keys() + self.recipecaches = {} + for mc in self.multiconfigs: + self.recipecaches[mc] = bb.cache.CacheData(self.caches_array) - self.handleCollections( self.data.getVar("BBFILE_COLLECTIONS", True) ) + self.handleCollections(self.data.getVar("BBFILE_COLLECTIONS", True)) def updateConfigOpts(self, options, environment): clean = True @@ -587,8 +575,8 @@ class BBCooker: def showVersions(self): - pkg_pn = self.recipecache.pkg_pn - (latest_versions, preferred_versions) = bb.providers.findProviders(self.data, self.recipecache, pkg_pn) + pkg_pn = self.recipecaches[''].pkg_pn + (latest_versions, preferred_versions) = bb.providers.findProviders(self.data, self.recipecaches[''], pkg_pn) logger.plain("%-35s %25s %25s", "Recipe Name", "Latest Version", "Preferred Version") logger.plain("%-35s %25s %25s\n", "===========", "==============", "=================") @@ -619,25 +607,25 @@ class BBCooker: # this showEnvironment() code path doesn't use the cache self.parseConfiguration() - fn, cls = bb.cache.Cache.virtualfn2realfn(buildfile) + fn, cls, mc = bb.cache.virtualfn2realfn(buildfile) fn = self.matchFile(fn) - fn = bb.cache.Cache.realfn2virtual(fn, cls) + fn = bb.cache.realfn2virtual(fn, cls, mc) elif len(pkgs_to_build) == 1: ignore = self.expanded_data.getVar("ASSUME_PROVIDED", True) or "" if pkgs_to_build[0] in set(ignore.split()): bb.fatal("%s is in ASSUME_PROVIDED" % pkgs_to_build[0]) - taskdata, runlist, pkgs_to_build = self.buildTaskData(pkgs_to_build, None, self.configuration.abort, allowincomplete=True) + taskdata, runlist = self.buildTaskData(pkgs_to_build, None, self.configuration.abort, allowincomplete=True) - targetid = taskdata.getbuild_id(pkgs_to_build[0]) - fnid = taskdata.build_targets[targetid][0] - fn = taskdata.fn_index[fnid] + mc = runlist[0][0] + fn = runlist[0][3] else: envdata = self.data if fn: try: - envdata = bb.cache.Cache.loadDataFull(fn, self.collection.get_file_appends(fn), self.data) + bb_cache = bb.cache.Cache(self.databuilder, self.data_hash, self.caches_array) + envdata = bb_cache.loadDataFull(fn, self.collection.get_file_appends(fn)) except Exception as e: parselog.exception("Unable to read %s", fn) raise @@ -656,7 +644,7 @@ class BBCooker: # emit the metadata which isnt valid shell data.expandKeys(envdata) for e in envdata.keys(): - if data.getVarFlag( e, 'python', envdata ): + if envdata.getVarFlag(e, 'func', False) and envdata.getVarFlag(e, 'python', False): logger.plain("\npython %s () {\n%s}\n", e, envdata.getVar(e, False)) @@ -670,30 +658,44 @@ class BBCooker: if task is None: task = self.configuration.cmd - fulltargetlist = self.checkPackages(pkgs_to_build) + fulltargetlist = self.checkPackages(pkgs_to_build, task) + taskdata = {} + localdata = {} - localdata = data.createCopy(self.data) - bb.data.update_data(localdata) - bb.data.expandKeys(localdata) - taskdata = bb.taskdata.TaskData(abort, skiplist=self.skiplist, allowincomplete=allowincomplete) + for mc in self.multiconfigs: + taskdata[mc] = bb.taskdata.TaskData(abort, skiplist=self.skiplist, allowincomplete=allowincomplete) + localdata[mc] = data.createCopy(self.databuilder.mcdata[mc]) + bb.data.update_data(localdata[mc]) + bb.data.expandKeys(localdata[mc]) current = 0 runlist = [] for k in fulltargetlist: + mc = "" + if k.startswith("multiconfig:"): + mc = k.split(":")[1] + k = ":".join(k.split(":")[2:]) ktask = task if ":do_" in k: k2 = k.split(":do_") k = k2[0] ktask = k2[1] - taskdata.add_provider(localdata, self.recipecache, k) + taskdata[mc].add_provider(localdata[mc], self.recipecaches[mc], k) current += 1 if not ktask.startswith("do_"): ktask = "do_%s" % ktask - runlist.append([k, ktask]) + if k not in taskdata[mc].build_targets or not taskdata[mc].build_targets[k]: + # e.g. in ASSUME_PROVIDED + continue + fn = taskdata[mc].build_targets[k][0] + runlist.append([mc, k, ktask, fn]) bb.event.fire(bb.event.TreeDataPreparationProgress(current, len(fulltargetlist)), self.data) - taskdata.add_unresolved(localdata, self.recipecache) + + for mc in self.multiconfigs: + taskdata[mc].add_unresolved(localdata[mc], self.recipecaches[mc]) + bb.event.fire(bb.event.TreeDataPreparationCompleted(len(fulltargetlist)), self.data) - return taskdata, runlist, fulltargetlist + return taskdata, runlist def prepareTreeData(self, pkgs_to_build, task): """ @@ -702,7 +704,7 @@ class BBCooker: # We set abort to False here to prevent unbuildable targets raising # an exception when we're just generating data - taskdata, runlist, pkgs_to_build = self.buildTaskData(pkgs_to_build, task, False, allowincomplete=True) + taskdata, runlist = self.buildTaskData(pkgs_to_build, task, False, allowincomplete=True) return runlist, taskdata @@ -714,13 +716,18 @@ class BBCooker: information. """ runlist, taskdata = self.prepareTreeData(pkgs_to_build, task) - rq = bb.runqueue.RunQueue(self, self.data, self.recipecache, taskdata, runlist) + rq = bb.runqueue.RunQueue(self, self.data, self.recipecaches, taskdata, runlist) rq.rqdata.prepare() return self.buildDependTree(rq, taskdata) + @staticmethod + def add_mc_prefix(mc, pn): + if mc: + return "multiconfig:%s.%s" % (mc, pn) + return pn def buildDependTree(self, rq, taskdata): - seen_fnids = [] + seen_fns = [] depend_tree = {} depend_tree["depends"] = {} depend_tree["tdepends"] = {} @@ -730,25 +737,26 @@ class BBCooker: depend_tree["rdepends-pkg"] = {} depend_tree["rrecs-pkg"] = {} depend_tree['providermap'] = {} - depend_tree["layer-priorities"] = self.recipecache.bbfile_config_priorities - - for name, fn in taskdata.get_providermap().iteritems(): - pn = self.recipecache.pkg_fn[fn] - if name != pn: - version = "%s:%s-%s" % self.recipecache.pkg_pepvpr[fn] - depend_tree['providermap'][name] = (pn, version) - - for task in xrange(len(rq.rqdata.runq_fnid)): - taskname = rq.rqdata.runq_task[task] - fnid = rq.rqdata.runq_fnid[task] - fn = taskdata.fn_index[fnid] - pn = self.recipecache.pkg_fn[fn] - version = "%s:%s-%s" % self.recipecache.pkg_pepvpr[fn] + depend_tree["layer-priorities"] = self.bbfile_config_priorities + + for mc in taskdata: + for name, fn in list(taskdata[mc].get_providermap().items()): + pn = self.recipecaches[mc].pkg_fn[fn] + pn = self.add_mc_prefix(mc, pn) + if name != pn: + version = "%s:%s-%s" % self.recipecaches[mc].pkg_pepvpr[fn] + depend_tree['providermap'][name] = (pn, version) + + for tid in rq.rqdata.runtaskentries: + (mc, fn, taskname, taskfn) = bb.runqueue.split_tid_mcfn(tid) + pn = self.recipecaches[mc].pkg_fn[taskfn] + pn = self.add_mc_prefix(mc, pn) + version = "%s:%s-%s" % self.recipecaches[mc].pkg_pepvpr[taskfn] if pn not in depend_tree["pn"]: depend_tree["pn"][pn] = {} - depend_tree["pn"][pn]["filename"] = fn + depend_tree["pn"][pn]["filename"] = taskfn depend_tree["pn"][pn]["version"] = version - depend_tree["pn"][pn]["inherits"] = self.recipecache.inherits.get(fn, None) + depend_tree["pn"][pn]["inherits"] = self.recipecaches[mc].inherits.get(taskfn, None) # if we have extra caches, list all attributes they bring in extra_info = [] @@ -759,36 +767,36 @@ class BBCooker: # for all attributes stored, add them to the dependency tree for ei in extra_info: - depend_tree["pn"][pn][ei] = vars(self.recipecache)[ei][fn] + depend_tree["pn"][pn][ei] = vars(self.recipecaches[mc])[ei][taskfn] - for dep in rq.rqdata.runq_depends[task]: - depfn = taskdata.fn_index[rq.rqdata.runq_fnid[dep]] - deppn = self.recipecache.pkg_fn[depfn] - dotname = "%s.%s" % (pn, rq.rqdata.runq_task[task]) + for dep in rq.rqdata.runtaskentries[tid].depends: + (depmc, depfn, deptaskname, deptaskfn) = bb.runqueue.split_tid_mcfn(dep) + deppn = self.recipecaches[mc].pkg_fn[deptaskfn] + dotname = "%s.%s" % (pn, bb.runqueue.taskname_from_tid(tid)) if not dotname in depend_tree["tdepends"]: depend_tree["tdepends"][dotname] = [] - depend_tree["tdepends"][dotname].append("%s.%s" % (deppn, rq.rqdata.runq_task[dep])) - if fnid not in seen_fnids: - seen_fnids.append(fnid) + depend_tree["tdepends"][dotname].append("%s.%s" % (deppn, bb.runqueue.taskname_from_tid(dep))) + if taskfn not in seen_fns: + seen_fns.append(taskfn) packages = [] depend_tree["depends"][pn] = [] - for dep in taskdata.depids[fnid]: - depend_tree["depends"][pn].append(taskdata.build_names_index[dep]) + for dep in taskdata[mc].depids[taskfn]: + depend_tree["depends"][pn].append(dep) depend_tree["rdepends-pn"][pn] = [] - for rdep in taskdata.rdepids[fnid]: - depend_tree["rdepends-pn"][pn].append(taskdata.run_names_index[rdep]) + for rdep in taskdata[mc].rdepids[taskfn]: + depend_tree["rdepends-pn"][pn].append(rdep) - rdepends = self.recipecache.rundeps[fn] + rdepends = self.recipecaches[mc].rundeps[taskfn] for package in rdepends: depend_tree["rdepends-pkg"][package] = [] for rdepend in rdepends[package]: depend_tree["rdepends-pkg"][package].append(rdepend) packages.append(package) - rrecs = self.recipecache.runrecs[fn] + rrecs = self.recipecaches[mc].runrecs[taskfn] for package in rrecs: depend_tree["rrecs-pkg"][package] = [] for rdepend in rrecs[package]: @@ -800,7 +808,7 @@ class BBCooker: if package not in depend_tree["packages"]: depend_tree["packages"][package] = {} depend_tree["packages"][package]["pn"] = pn - depend_tree["packages"][package]["filename"] = fn + depend_tree["packages"][package]["filename"] = taskfn depend_tree["packages"][package]["version"] = version return depend_tree @@ -811,12 +819,8 @@ class BBCooker: Create a dependency tree of pkgs_to_build, returning the data. """ _, taskdata = self.prepareTreeData(pkgs_to_build, task) - tasks_fnid = [] - if len(taskdata.tasks_name) != 0: - for task in xrange(len(taskdata.tasks_name)): - tasks_fnid.append(taskdata.tasks_fnid[task]) - seen_fnids = [] + seen_fns = [] depend_tree = {} depend_tree["depends"] = {} depend_tree["pn"] = {} @@ -831,51 +835,53 @@ class BBCooker: cachefields = getattr(cache_class, 'cachefields', []) extra_info = extra_info + cachefields - for task in xrange(len(tasks_fnid)): - fnid = tasks_fnid[task] - fn = taskdata.fn_index[fnid] - pn = self.recipecache.pkg_fn[fn] + tids = [] + for mc in taskdata: + for tid in taskdata[mc].taskentries: + tids.append(tid) + + for tid in tids: + (mc, fn, taskname, taskfn) = bb.runqueue.split_tid_mcfn(tid) + + pn = self.recipecaches[mc].pkg_fn[taskfn] + pn = self.add_mc_prefix(mc, pn) if pn not in depend_tree["pn"]: depend_tree["pn"][pn] = {} - depend_tree["pn"][pn]["filename"] = fn - version = "%s:%s-%s" % self.recipecache.pkg_pepvpr[fn] + depend_tree["pn"][pn]["filename"] = taskfn + version = "%s:%s-%s" % self.recipecaches[mc].pkg_pepvpr[taskfn] depend_tree["pn"][pn]["version"] = version - rdepends = self.recipecache.rundeps[fn] - rrecs = self.recipecache.runrecs[fn] - depend_tree["pn"][pn]["inherits"] = self.recipecache.inherits.get(fn, None) + rdepends = self.recipecaches[mc].rundeps[taskfn] + rrecs = self.recipecaches[mc].runrecs[taskfn] + depend_tree["pn"][pn]["inherits"] = self.recipecaches[mc].inherits.get(taskfn, None) # for all extra attributes stored, add them to the dependency tree for ei in extra_info: - depend_tree["pn"][pn][ei] = vars(self.recipecache)[ei][fn] + depend_tree["pn"][pn][ei] = vars(self.recipecaches[mc])[ei][taskfn] - if fnid not in seen_fnids: - seen_fnids.append(fnid) + if taskfn not in seen_fns: + seen_fns.append(taskfn) depend_tree["depends"][pn] = [] - for dep in taskdata.depids[fnid]: - item = taskdata.build_names_index[dep] + for item in taskdata[mc].depids[taskfn]: pn_provider = "" - targetid = taskdata.getbuild_id(item) - if targetid in taskdata.build_targets and taskdata.build_targets[targetid]: - id = taskdata.build_targets[targetid][0] - fn_provider = taskdata.fn_index[id] - pn_provider = self.recipecache.pkg_fn[fn_provider] + if dep in taskdata[mc].build_targets and taskdata[mc].build_targets[dep]: + fn_provider = taskdata[mc].build_targets[dep][0] + pn_provider = self.recipecaches[mc].pkg_fn[fn_provider] else: pn_provider = item + pn_provider = self.add_mc_prefix(mc, pn_provider) depend_tree["depends"][pn].append(pn_provider) depend_tree["rdepends-pn"][pn] = [] - for rdep in taskdata.rdepids[fnid]: - item = taskdata.run_names_index[rdep] + for rdep in taskdata[mc].rdepids[taskfn]: pn_rprovider = "" - targetid = taskdata.getrun_id(item) - if targetid in taskdata.run_targets and taskdata.run_targets[targetid]: - id = taskdata.run_targets[targetid][0] - fn_rprovider = taskdata.fn_index[id] - pn_rprovider = self.recipecache.pkg_fn[fn_rprovider] + if rdep in taskdata[mc].run_targets and taskdata[mc].run_targets[rdep]: + fn_rprovider = taskdata[mc].run_targets[rdep][0] + pn_rprovider = self.recipecaches[mc].pkg_fn[fn_rprovider] else: - pn_rprovider = item + pn_rprovider = rdep + pn_rprovider = self.add_mc_prefix(mc, pn_rprovider) depend_tree["rdepends-pn"][pn].append(pn_rprovider) depend_tree["rdepends-pkg"].update(rdepends) @@ -900,8 +906,8 @@ class BBCooker: depgraph = self.generateTaskDepTreeData(pkgs_to_build, task) # Prints a flattened form of package-depends below where subpackages of a package are merged into the main pn - depends_file = file('pn-depends.dot', 'w' ) - buildlist_file = file('pn-buildlist', 'w' ) + depends_file = open('pn-depends.dot', 'w' ) + buildlist_file = open('pn-buildlist', 'w' ) print("digraph depends {", file=depends_file) for pn in depgraph["pn"]: fn = depgraph["pn"][pn]["filename"] @@ -917,9 +923,10 @@ class BBCooker: for rdepend in depgraph["rdepends-pn"][pn]: print('"%s" -> "%s" [style=dashed]' % (pn, rdepend), file=depends_file) print("}", file=depends_file) + depends_file.close() logger.info("PN dependencies saved to 'pn-depends.dot'") - depends_file = file('package-depends.dot', 'w' ) + depends_file = open('package-depends.dot', 'w' ) print("digraph depends {", file=depends_file) for package in depgraph["packages"]: pn = depgraph["packages"][package]["pn"] @@ -938,9 +945,10 @@ class BBCooker: for rdepend in depgraph["rrecs-pkg"][package]: print('"%s" -> "%s" [style=dotted]' % (package, rdepend), file=depends_file) print("}", file=depends_file) + depends_file.close() logger.info("Package dependencies saved to 'package-depends.dot'") - tdepends_file = file('task-depends.dot', 'w' ) + tdepends_file = open('task-depends.dot', 'w' ) print("digraph depends {", file=tdepends_file) for task in depgraph["tdepends"]: (pn, taskname) = task.rsplit(".", 1) @@ -950,13 +958,14 @@ class BBCooker: for dep in depgraph["tdepends"][task]: print('"%s" -> "%s"' % (task, dep), file=tdepends_file) print("}", file=tdepends_file) + tdepends_file.close() logger.info("Task dependencies saved to 'task-depends.dot'") def show_appends_with_no_recipes(self): # Determine which bbappends haven't been applied # First get list of recipes, including skipped - recipefns = self.recipecache.pkg_fn.keys() + recipefns = list(self.recipecaches[''].pkg_fn.keys()) recipefns.extend(self.skiplist.keys()) # Work out list of bbappends that have been applied @@ -980,20 +989,21 @@ class BBCooker: def handlePrefProviders(self): - localdata = data.createCopy(self.data) - bb.data.update_data(localdata) - bb.data.expandKeys(localdata) + for mc in self.multiconfigs: + localdata = data.createCopy(self.databuilder.mcdata[mc]) + bb.data.update_data(localdata) + bb.data.expandKeys(localdata) - # Handle PREFERRED_PROVIDERS - for p in (localdata.getVar('PREFERRED_PROVIDERS', True) or "").split(): - try: - (providee, provider) = p.split(':') - except: - providerlog.critical("Malformed option in PREFERRED_PROVIDERS variable: %s" % p) - continue - if providee in self.recipecache.preferred and self.recipecache.preferred[providee] != provider: - providerlog.error("conflicting preferences for %s: both %s and %s specified", providee, provider, self.recipecache.preferred[providee]) - self.recipecache.preferred[providee] = provider + # Handle PREFERRED_PROVIDERS + for p in (localdata.getVar('PREFERRED_PROVIDERS', True) or "").split(): + try: + (providee, provider) = p.split(':') + except: + providerlog.critical("Malformed option in PREFERRED_PROVIDERS variable: %s" % p) + continue + if providee in self.recipecaches[mc].preferred and self.recipecaches[mc].preferred[providee] != provider: + providerlog.error("conflicting preferences for %s: both %s and %s specified", providee, provider, self.recipecaches[mc].preferred[providee]) + self.recipecaches[mc].preferred[providee] = provider def findCoreBaseFiles(self, subdir, configfile): corebase = self.data.getVar('COREBASE', True) or "" @@ -1088,10 +1098,10 @@ class BBCooker: """ pkg_list = [] - for pfn in self.recipecache.pkg_fn: - inherits = self.recipecache.inherits.get(pfn, None) + for pfn in self.recipecaches[''].pkg_fn: + inherits = self.recipecaches[''].inherits.get(pfn, None) if inherits and klass in inherits: - pkg_list.append(self.recipecache.pkg_fn[pfn]) + pkg_list.append(self.recipecaches[''].pkg_fn[pfn]) return pkg_list @@ -1124,16 +1134,18 @@ class BBCooker: shell.start( self ) - def handleCollections( self, collections ): + def handleCollections(self, collections): """Handle collections""" errors = False - self.recipecache.bbfile_config_priorities = [] + self.bbfile_config_priorities = [] if collections: collection_priorities = {} collection_depends = {} collection_list = collections.split() min_prio = 0 for c in collection_list: + bb.debug(1,'Processing %s in collection list' % (c)) + # Get collection priority if defined explicitly priority = self.data.getVar("BBFILE_PRIORITY_%s" % c, True) if priority: @@ -1152,10 +1164,10 @@ class BBCooker: deps = self.data.getVar("LAYERDEPENDS_%s" % c, True) if deps: try: - deplist = bb.utils.explode_dep_versions2(deps) + depDict = bb.utils.explode_dep_versions2(deps) except bb.utils.VersionStringException as vse: bb.fatal('Error parsing LAYERDEPENDS_%s: %s' % (c, str(vse))) - for dep, oplist in deplist.iteritems(): + for dep, oplist in list(depDict.items()): if dep in collection_list: for opstr in oplist: layerver = self.data.getVar("LAYERVERSION_%s" % dep, True) @@ -1174,10 +1186,39 @@ class BBCooker: else: parselog.error("Layer '%s' depends on layer '%s', but this layer is not enabled in your configuration", c, dep) errors = True - collection_depends[c] = deplist.keys() + collection_depends[c] = list(depDict.keys()) else: collection_depends[c] = [] + # Check recommends and store information for priority calculation + recs = self.data.getVar("LAYERRECOMMENDS_%s" % c, True) + if recs: + try: + recDict = bb.utils.explode_dep_versions2(recs) + except bb.utils.VersionStringException as vse: + bb.fatal('Error parsing LAYERRECOMMENDS_%s: %s' % (c, str(vse))) + for rec, oplist in list(recDict.items()): + if rec in collection_list: + if oplist: + opstr = oplist[0] + layerver = self.data.getVar("LAYERVERSION_%s" % rec, True) + if layerver: + (op, recver) = opstr.split() + try: + res = bb.utils.vercmp_string_op(layerver, recver, op) + except bb.utils.VersionStringException as vse: + bb.fatal('Error parsing LAYERRECOMMENDS_%s: %s' % (c, str(vse))) + if not res: + parselog.debug(3,"Layer '%s' recommends version %s of layer '%s', but version %s is currently enabled in your configuration. Check that you are using the correct matching versions/branches of these two layers.", c, opstr, rec, layerver) + continue + else: + parselog.debug(3,"Layer '%s' recommends version %s of layer '%s', which exists in your configuration but does not specify a version. Check that you are using the correct matching versions/branches of these two layers.", c, opstr, rec) + continue + parselog.debug(3,"Layer '%s' recommends layer '%s', so we are adding it", c, rec) + collection_depends[c].append(rec) + else: + parselog.debug(3,"Layer '%s' recommends layer '%s', but this layer is not enabled in your configuration", c, rec) + # Recursively work out collection priorities based on dependencies def calc_layer_priority(collection): if not collection_priorities[collection]: @@ -1205,7 +1246,7 @@ class BBCooker: parselog.error("BBFILE_PATTERN_%s \"%s\" is not a valid regular expression", c, regex) errors = True continue - self.recipecache.bbfile_config_priorities.append((c, regex, cre, collection_priorities[c])) + self.bbfile_config_priorities.append((c, regex, cre, collection_priorities[c])) if errors: # We've already printed the actual error(s) raise CollectionError("Errors during parsing layer configuration") @@ -1228,7 +1269,7 @@ class BBCooker: if bf.startswith("/") or bf.startswith("../"): bf = os.path.abspath(bf) - self.collection = CookerCollectFiles(self.recipecache.bbfile_config_priorities) + self.collection = CookerCollectFiles(self.bbfile_config_priorities) filelist, masked = self.collection.collect_bbfiles(self.data, self.expanded_data) try: os.stat(bf) @@ -1264,6 +1305,7 @@ class BBCooker: """ Build the file matching regexp buildfile """ + bb.event.fire(bb.event.BuildInit(), self.expanded_data) # Too many people use -b because they think it's how you normally # specify a target to be built, so show a warning @@ -1277,17 +1319,17 @@ class BBCooker: if (task == None): task = self.configuration.cmd - fn, cls = bb.cache.Cache.virtualfn2realfn(buildfile) + fn, cls, mc = bb.cache.virtualfn2realfn(buildfile) fn = self.matchFile(fn) self.buildSetVars() - infos = bb.cache.Cache.parse(fn, self.collection.get_file_appends(fn), \ - self.data, - self.caches_array) + bb_cache = bb.cache.Cache(self.databuilder, self.data_hash, self.caches_array) + + infos = bb_cache.parse(fn, self.collection.get_file_appends(fn)) infos = dict(infos) - fn = bb.cache.Cache.realfn2virtual(fn, cls) + fn = bb.cache.realfn2virtual(fn, cls, mc) try: info_array = infos[fn] except KeyError: @@ -1296,29 +1338,30 @@ class BBCooker: if info_array[0].skipped: bb.fatal("%s was skipped: %s" % (fn, info_array[0].skipreason)) - self.recipecache.add_from_recipeinfo(fn, info_array) + self.recipecaches[mc].add_from_recipeinfo(fn, info_array) # Tweak some variables item = info_array[0].pn - self.recipecache.ignored_dependencies = set() - self.recipecache.bbfile_priority[fn] = 1 + self.recipecaches[mc].ignored_dependencies = set() + self.recipecaches[mc].bbfile_priority[fn] = 1 # Remove external dependencies - self.recipecache.task_deps[fn]['depends'] = {} - self.recipecache.deps[fn] = [] - self.recipecache.rundeps[fn] = [] - self.recipecache.runrecs[fn] = [] + self.recipecaches[mc].task_deps[fn]['depends'] = {} + self.recipecaches[mc].deps[fn] = [] + self.recipecaches[mc].rundeps[fn] = [] + self.recipecaches[mc].runrecs[fn] = [] # Invalidate task for target if force mode active if self.configuration.force: logger.verbose("Invalidate task %s, %s", task, fn) if not task.startswith("do_"): task = "do_%s" % task - bb.parse.siggen.invalidate_task(task, self.recipecache, fn) + bb.parse.siggen.invalidate_task(task, self.recipecaches[mc], fn) # Setup taskdata structure - taskdata = bb.taskdata.TaskData(self.configuration.abort) - taskdata.add_provider(self.data, self.recipecache, item) + taskdata = {} + taskdata[mc] = bb.taskdata.TaskData(self.configuration.abort) + taskdata[mc].add_provider(self.data, self.recipecaches[mc], item) buildname = self.data.getVar("BUILDNAME", True) bb.event.fire(bb.event.BuildStarted(buildname, [item]), self.expanded_data) @@ -1326,9 +1369,9 @@ class BBCooker: # Execute the runqueue if not task.startswith("do_"): task = "do_%s" % task - runlist = [[item, task]] + runlist = [[mc, item, task, fn]] - rq = bb.runqueue.RunQueue(self, self.data, self.recipecache, taskdata, runlist) + rq = bb.runqueue.RunQueue(self, self.data, self.recipecaches, taskdata, runlist) def buildFileIdle(server, rq, abort): @@ -1353,7 +1396,7 @@ class BBCooker: return False if not retval: - bb.event.fire(bb.event.BuildCompleted(len(rq.rqdata.runq_fnid), buildname, item, failures, interrupted), self.expanded_data) + bb.event.fire(bb.event.BuildCompleted(len(rq.rqdata.runtaskentries), buildname, item, failures, interrupted), self.expanded_data) self.command.finishAsyncCommand(msg) return False if retval is True: @@ -1389,7 +1432,7 @@ class BBCooker: return False if not retval: - bb.event.fire(bb.event.BuildCompleted(len(rq.rqdata.runq_fnid), buildname, targets, failures, interrupted), self.data) + bb.event.fire(bb.event.BuildCompleted(len(rq.rqdata.runtaskentries), buildname, targets, failures, interrupted), self.data) self.command.finishAsyncCommand(msg) return False if retval is True: @@ -1406,23 +1449,24 @@ class BBCooker: if not task.startswith("do_"): task = "do_%s" % task - taskdata, runlist, fulltargetlist = self.buildTaskData(targets, task, self.configuration.abort) + packages = [target if ':' in target else '%s:%s' % (target, task) for target in targets] + + bb.event.fire(bb.event.BuildInit(packages), self.expanded_data) + + taskdata, runlist = self.buildTaskData(targets, task, self.configuration.abort) buildname = self.data.getVar("BUILDNAME", False) # make targets to always look as <target>:do_<task> ntargets = [] - for target in fulltargetlist: - if ":" in target: - if ":do_" not in target: - target = "%s:do_%s" % tuple(target.split(":", 1)) - else: - target = "%s:%s" % (target, task) - ntargets.append(target) + for target in runlist: + if target[0]: + ntargets.append("multiconfig:%s:%s:%s" % (target[0], target[1], target[2])) + ntargets.append("%s:%s" % (target[1], target[2])) bb.event.fire(bb.event.BuildStarted(buildname, ntargets), self.data) - rq = bb.runqueue.RunQueue(self, self.data, self.recipecache, taskdata, runlist) + rq = bb.runqueue.RunQueue(self, self.data, self.recipecaches, taskdata, runlist) if 'universe' in targets: rq.rqdata.warn_multi_bb = True @@ -1537,13 +1581,14 @@ class BBCooker: if CookerFeatures.SEND_SANITYEVENTS in self.featureset: bb.event.fire(bb.event.SanityCheck(False), self.data) - ignore = self.expanded_data.getVar("ASSUME_PROVIDED", True) or "" - self.recipecache.ignored_dependencies = set(ignore.split()) + for mc in self.multiconfigs: + ignore = self.databuilder.mcdata[mc].getVar("ASSUME_PROVIDED", True) or "" + self.recipecaches[mc].ignored_dependencies = set(ignore.split()) - for dep in self.configuration.extra_assume_provided: - self.recipecache.ignored_dependencies.add(dep) + for dep in self.configuration.extra_assume_provided: + self.recipecaches[mc].ignored_dependencies.add(dep) - self.collection = CookerCollectFiles(self.recipecache.bbfile_config_priorities) + self.collection = CookerCollectFiles(self.bbfile_config_priorities) (filelist, masked) = self.collection.collect_bbfiles(self.data, self.expanded_data) self.parser = CookerParser(self, filelist, masked) @@ -1557,18 +1602,20 @@ class BBCooker: raise bb.BBHandledException() self.show_appends_with_no_recipes() self.handlePrefProviders() - self.recipecache.bbfile_priority = self.collection.collection_priorities(self.recipecache.pkg_fn, self.data) + for mc in self.multiconfigs: + self.recipecaches[mc].bbfile_priority = self.collection.collection_priorities(self.recipecaches[mc].pkg_fn, self.data) self.state = state.running # Send an event listing all stamps reachable after parsing # which the metadata may use to clean up stale data - event = bb.event.ReachableStamps(self.recipecache.stamp) - bb.event.fire(event, self.expanded_data) + for mc in self.multiconfigs: + event = bb.event.ReachableStamps(self.recipecaches[mc].stamp) + bb.event.fire(event, self.databuilder.mcdata[mc]) return None return True - def checkPackages(self, pkgs_to_build): + def checkPackages(self, pkgs_to_build, task=None): # Return a copy, don't modify the original pkgs_to_build = pkgs_to_build[:] @@ -1579,26 +1626,29 @@ class BBCooker: ignore = (self.expanded_data.getVar("ASSUME_PROVIDED", True) or "").split() for pkg in pkgs_to_build: if pkg in ignore: - parselog.warn("Explicit target \"%s\" is in ASSUME_PROVIDED, ignoring" % pkg) + parselog.warning("Explicit target \"%s\" is in ASSUME_PROVIDED, ignoring" % pkg) if 'world' in pkgs_to_build: - bb.providers.buildWorldTargetList(self.recipecache) pkgs_to_build.remove('world') - for t in self.recipecache.world_target: - pkgs_to_build.append(t) + for mc in self.multiconfigs: + bb.providers.buildWorldTargetList(self.recipecaches[mc], task) + for t in self.recipecaches[mc].world_target: + if mc: + t = "multiconfig:" + mc + ":" + t + pkgs_to_build.append(t) if 'universe' in pkgs_to_build: - parselog.warn("The \"universe\" target is only intended for testing and may produce errors.") + parselog.warning("The \"universe\" target is only intended for testing and may produce errors.") parselog.debug(1, "collating packages for \"universe\"") pkgs_to_build.remove('universe') - for t in self.recipecache.universe_target: - pkgs_to_build.append(t) + for mc in self.multiconfigs: + for t in self.recipecaches[mc].universe_target: + if mc: + t = "multiconfig:" + mc + ":" + t + pkgs_to_build.append(t) return pkgs_to_build - - - def pre_serve(self): # Empty the environment. The environment will be populated as # necessary from the data store. @@ -1847,7 +1897,7 @@ class CookerCollectFiles(object): # Calculate priorities for each file matched = set() for p in pkgfns: - realfn, cls = bb.cache.Cache.virtualfn2realfn(p) + realfn, cls, mc = bb.cache.virtualfn2realfn(p) priorities[p] = self.calc_bbfile_priority(realfn, matched) # Don't show the warning if the BBFILE_PATTERN did match .bbappend files @@ -1870,7 +1920,7 @@ class CookerCollectFiles(object): for collection, pattern, regex, _ in self.bbfile_config_priorities: if regex in unmatched: if d.getVar('BBFILE_PATTERN_IGNORE_EMPTY_%s' % collection, True) != '1': - collectlog.warn("No bb files matched BBFILE_PATTERN_%s '%s'" % (collection, pattern)) + collectlog.warning("No bb files matched BBFILE_PATTERN_%s '%s'" % (collection, pattern)) return priorities @@ -1891,7 +1941,7 @@ class Feeder(multiprocessing.Process): while True: try: quit = self.quit.get_nowait() - except Queue.Empty: + except queue.Empty: pass else: if quit == 'cancel': @@ -1905,7 +1955,7 @@ class Feeder(multiprocessing.Process): try: self.to_parsers.put(job, timeout=0.5) - except Queue.Full: + except queue.Full: self.jobs.insert(0, job) continue @@ -1945,7 +1995,7 @@ class Parser(multiprocessing.Process): while True: try: self.quit.get_nowait() - except Queue.Empty: + except queue.Empty: pass else: self.results.cancel_join_thread() @@ -1956,7 +2006,7 @@ class Parser(multiprocessing.Process): else: try: job = self.jobs.get(timeout=0.25) - except Queue.Empty: + except queue.Empty: continue if job is None: @@ -1965,10 +2015,10 @@ class Parser(multiprocessing.Process): try: self.results.put(result, timeout=0.25) - except Queue.Full: + except queue.Full: pending.append(result) - def parse(self, filename, appends, caches_array): + def parse(self, filename, appends): try: # Record the filename we're parsing into any events generated def parse_filter(self, record): @@ -1981,7 +2031,7 @@ class Parser(multiprocessing.Process): bb.event.set_class_handlers(self.handlers.copy()) bb.event.LogHandler.filter = parse_filter - return True, bb.cache.Cache.parse(filename, appends, self.cfg, caches_array) + return True, self.bb_cache.parse(filename, appends) except Exception as exc: tb = sys.exc_info()[2] exc.recipe = filename @@ -1999,6 +2049,7 @@ class CookerParser(object): self.cooker = cooker self.cfgdata = cooker.data self.cfghash = cooker.data_hash + self.cfgbuilder = cooker.databuilder # Accounting statistics self.parsed = 0 @@ -2013,17 +2064,17 @@ class CookerParser(object): self.current = 0 self.process_names = [] - self.bb_cache = bb.cache.Cache(self.cfgdata, self.cfghash, cooker.caches_array) + self.bb_cache = bb.cache.Cache(self.cfgbuilder, self.cfghash, cooker.caches_array) self.fromcache = [] self.willparse = [] for filename in self.filelist: appends = self.cooker.collection.get_file_appends(filename) if not self.bb_cache.cacheValid(filename, appends): - self.willparse.append((filename, appends, cooker.caches_array)) + self.willparse.append((filename, appends)) else: self.fromcache.append((filename, appends)) self.toparse = self.total - len(self.fromcache) - self.progress_chunk = max(self.toparse / 100, 1) + self.progress_chunk = int(max(self.toparse / 100, 1)) self.num_processes = min(int(self.cfgdata.getVar("BB_NUMBER_PARSE_THREADS", True) or multiprocessing.cpu_count()), len(self.willparse)) @@ -2037,7 +2088,7 @@ class CookerParser(object): if self.toparse: bb.event.fire(bb.event.ParseStarted(self.toparse), self.cfgdata) def init(): - Parser.cfg = self.cfgdata + Parser.bb_cache = self.bb_cache bb.utils.set_process_name(multiprocessing.current_process().name) multiprocessing.util.Finalize(None, bb.codeparser.parser_cache_save, exitpriority=1) multiprocessing.util.Finalize(None, bb.fetch.fetcher_parse_save, exitpriority=1) @@ -2108,7 +2159,7 @@ class CookerParser(object): def load_cached(self): for filename, appends in self.fromcache: - cached, infos = self.bb_cache.load(filename, appends, self.cfgdata) + cached, infos = self.bb_cache.load(filename, appends) yield not cached, infos def parse_generator(self): @@ -2118,7 +2169,7 @@ class CookerParser(object): try: result = self.result_queue.get(timeout=0.25) - except Queue.Empty: + except queue.Empty: pass else: value = result[1] @@ -2131,7 +2182,7 @@ class CookerParser(object): result = [] parsed = None try: - parsed, result = self.results.next() + parsed, result = next(self.results) except StopIteration: self.shutdown() return False @@ -2153,15 +2204,18 @@ class CookerParser(object): return False except bb.data_smart.ExpansionError as exc: self.error += 1 - _, value, _ = sys.exc_info() - logger.error('ExpansionError during parsing %s: %s', value.recipe, str(exc)) + bbdir = os.path.dirname(__file__) + os.sep + etype, value, _ = sys.exc_info() + tb = list(itertools.dropwhile(lambda e: e.filename.startswith(bbdir), exc.traceback)) + logger.error('ExpansionError during parsing %s', value.recipe, + exc_info=(etype, value, tb)) self.shutdown(clean=False) return False except Exception as exc: self.error += 1 etype, value, tb = sys.exc_info() if hasattr(value, "recipe"): - logger.error('Unable to parse %s', value.recipe, + logger.error('Unable to parse %s' % value.recipe, exc_info=(etype, value, exc.traceback)) else: # Most likely, an exception occurred during raising an exception @@ -2184,13 +2238,13 @@ class CookerParser(object): if info_array[0].skipped: self.skipped += 1 self.cooker.skiplist[virtualfn] = SkippedPackage(info_array[0]) - self.bb_cache.add_info(virtualfn, info_array, self.cooker.recipecache, + (fn, cls, mc) = bb.cache.virtualfn2realfn(virtualfn) + self.bb_cache.add_info(virtualfn, info_array, self.cooker.recipecaches[mc], parsed=parsed, watcher = self.cooker.add_filewatch) return True def reparse(self, filename): - infos = self.bb_cache.parse(filename, - self.cooker.collection.get_file_appends(filename), - self.cfgdata, self.cooker.caches_array) + infos = self.bb_cache.parse(filename, self.cooker.collection.get_file_appends(filename)) for vfn, info_array in infos: - self.cooker.recipecache.add_from_recipeinfo(vfn, info_array) + (fn, cls, mc) = bb.cache.virtualfn2realfn(vfn) + self.cooker.recipecaches[mc].add_from_recipeinfo(vfn, info_array) diff --git a/import-layers/yocto-poky/bitbake/lib/bb/cookerdata.py b/import-layers/yocto-poky/bitbake/lib/bb/cookerdata.py index 50259a9a0..b07c26643 100644 --- a/import-layers/yocto-poky/bitbake/lib/bb/cookerdata.py +++ b/import-layers/yocto-poky/bitbake/lib/bb/cookerdata.py @@ -22,9 +22,11 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -import os, sys -from functools import wraps import logging +import os +import re +import sys +from functools import wraps import bb from bb import data import bb.parse @@ -192,7 +194,8 @@ def catch_parse_error(func): fn, _, _, _ = traceback.extract_tb(tb, 1)[0] if not fn.startswith(bbdir): break - parselog.critical("Unable to parse %s", fn, exc_info=(exc_class, exc, tb)) + parselog.critical("Unable to parse %s" % fn, exc_info=(exc_class, exc, tb)) + sys.exit(1) except bb.parse.ParseError as exc: parselog.critical(str(exc)) sys.exit(1) @@ -234,9 +237,9 @@ class CookerDataBuilder(object): bb.utils.set_context(bb.utils.clean_context()) bb.event.set_class_handlers(bb.event.clean_class_handlers()) - self.data = bb.data.init() + self.basedata = bb.data.init() if self.tracking: - self.data.enableTracking() + self.basedata.enableTracking() # Keep a datastore of the initial environment variables and their # values from when BitBake was launched to enable child processes @@ -247,16 +250,49 @@ class CookerDataBuilder(object): self.savedenv.setVar(k, cookercfg.env[k]) filtered_keys = bb.utils.approved_variables() - bb.data.inheritFromOS(self.data, self.savedenv, filtered_keys) - self.data.setVar("BB_ORIGENV", self.savedenv) + bb.data.inheritFromOS(self.basedata, self.savedenv, filtered_keys) + self.basedata.setVar("BB_ORIGENV", self.savedenv) if worker: - self.data.setVar("BB_WORKERCONTEXT", "1") + self.basedata.setVar("BB_WORKERCONTEXT", "1") + + self.data = self.basedata + self.mcdata = {} def parseBaseConfiguration(self): try: - self.parseConfigurationFiles(self.prefiles, self.postfiles) - except SyntaxError: + bb.parse.init_parser(self.basedata) + self.data = self.parseConfigurationFiles(self.prefiles, self.postfiles) + + if self.data.getVar("BB_WORKERCONTEXT", False) is None: + bb.fetch.fetcher_init(self.data) + bb.codeparser.parser_cache_init(self.data) + + bb.event.fire(bb.event.ConfigParsed(), self.data) + + reparse_cnt = 0 + while self.data.getVar("BB_INVALIDCONF", False) is True: + if reparse_cnt > 20: + logger.error("Configuration has been re-parsed over 20 times, " + "breaking out of the loop...") + raise Exception("Too deep config re-parse loop. Check locations where " + "BB_INVALIDCONF is being set (ConfigParsed event handlers)") + self.data.setVar("BB_INVALIDCONF", False) + self.data = self.parseConfigurationFiles(self.prefiles, self.postfiles) + reparse_cnt += 1 + bb.event.fire(bb.event.ConfigParsed(), self.data) + + bb.parse.init_parser(self.data) + self.data_hash = self.data.get_hash() + self.mcdata[''] = self.data + + multiconfig = (self.data.getVar("BBMULTICONFIG", True) or "").split() + for config in multiconfig: + mcdata = self.parseConfigurationFiles(['conf/multiconfig/%s.conf' % config] + self.prefiles, self.postfiles) + bb.event.fire(bb.event.ConfigParsed(), mcdata) + self.mcdata[config] = mcdata + + except (SyntaxError, bb.BBHandledException): raise bb.BBHandledException except bb.data_smart.ExpansionError as e: logger.error(str(e)) @@ -269,8 +305,7 @@ class CookerDataBuilder(object): return findConfigFile("bblayers.conf", data) def parseConfigurationFiles(self, prefiles, postfiles): - data = self.data - bb.parse.init_parser(data) + data = bb.data.createCopy(self.basedata) # Parse files for loading *before* bitbake.conf and any includes for f in prefiles: @@ -289,15 +324,22 @@ class CookerDataBuilder(object): data = bb.data.createCopy(data) approved = bb.utils.approved_variables() for layer in layers: + if not os.path.isdir(layer): + parselog.critical("Layer directory '%s' does not exist! " + "Please check BBLAYERS in %s" % (layer, layerconf)) + sys.exit(1) parselog.debug(2, "Adding layer %s", layer) if 'HOME' in approved and '~' in layer: layer = os.path.expanduser(layer) if layer.endswith('/'): layer = layer.rstrip('/') data.setVar('LAYERDIR', layer) + data.setVar('LAYERDIR_RE', re.escape(layer)) data = parse_config_file(os.path.join(layer, "conf", "layer.conf"), data) data.expandVarref('LAYERDIR') + data.expandVarref('LAYERDIR_RE') + data.delVar('LAYERDIR_RE') data.delVar('LAYERDIR') if not data.getVar("BBPATH", True): @@ -323,23 +365,13 @@ class CookerDataBuilder(object): # We register any handlers we've found so far here... for var in data.getVar('__BBHANDLERS', False) or []: handlerfn = data.getVarFlag(var, "filename", False) + if not handlerfn: + parselog.critical("Undefined event handler function '%s'" % var) + sys.exit(1) handlerln = int(data.getVarFlag(var, "lineno", False)) bb.event.register(var, data.getVar(var, False), (data.getVarFlag(var, "eventmask", True) or "").split(), handlerfn, handlerln) - if data.getVar("BB_WORKERCONTEXT", False) is None: - bb.fetch.fetcher_init(data) - bb.codeparser.parser_cache_init(data) - bb.event.fire(bb.event.ConfigParsed(), data) - - if data.getVar("BB_INVALIDCONF", False) is True: - data.setVar("BB_INVALIDCONF", False) - self.parseConfigurationFiles(self.prefiles, self.postfiles) - return - - bb.parse.init_parser(data) data.setVar('BBINCLUDED',bb.parse.get_file_depends(data)) - self.data = data - self.data_hash = data.get_hash() - + return data diff --git a/import-layers/yocto-poky/bitbake/lib/bb/daemonize.py b/import-layers/yocto-poky/bitbake/lib/bb/daemonize.py index 346a61858..ab4a95462 100644 --- a/import-layers/yocto-poky/bitbake/lib/bb/daemonize.py +++ b/import-layers/yocto-poky/bitbake/lib/bb/daemonize.py @@ -178,8 +178,8 @@ def createDaemon(function, logfile): # os.dup2(0, 2) # standard error (2) - si = file('/dev/null', 'r') - so = file(logfile, 'w') + si = open('/dev/null', 'r') + so = open(logfile, 'w') se = so diff --git a/import-layers/yocto-poky/bitbake/lib/bb/data.py b/import-layers/yocto-poky/bitbake/lib/bb/data.py index dbc6dea68..c1f27cd0c 100644 --- a/import-layers/yocto-poky/bitbake/lib/bb/data.py +++ b/import-layers/yocto-poky/bitbake/lib/bb/data.py @@ -182,12 +182,12 @@ def inheritFromOS(d, savedenv, permitted): def emit_var(var, o=sys.__stdout__, d = init(), all=False): """Emit a variable to be sourced by a shell.""" - if d.getVarFlag(var, "python", False): + func = d.getVarFlag(var, "func", False) + if d.getVarFlag(var, 'python', False) and func: return False export = d.getVarFlag(var, "export", False) unexport = d.getVarFlag(var, "unexport", False) - func = d.getVarFlag(var, "func", False) if not all and not export and not unexport and not func: return False @@ -339,7 +339,7 @@ def build_dependencies(key, keys, shelldeps, varflagsexcl, d): deps |= parser.references deps = deps | (keys & parser.execs) return deps, value - varflags = d.getVarFlags(key, ["vardeps", "vardepvalue", "vardepsexclude", "vardepvalueexclude", "postfuncs", "prefuncs", "lineno", "filename"]) or {} + varflags = d.getVarFlags(key, ["vardeps", "vardepvalue", "vardepsexclude", "exports", "postfuncs", "prefuncs", "lineno", "filename"]) or {} vardeps = varflags.get("vardeps") value = d.getVar(key, False) @@ -364,7 +364,7 @@ def build_dependencies(key, keys, shelldeps, varflagsexcl, d): if varflags.get("python"): parser = bb.codeparser.PythonParser(key, logger) if value and "\t" in value: - logger.warn("Variable %s contains tabs, please remove these (%s)" % (key, d.getVar("FILE", True))) + logger.warning("Variable %s contains tabs, please remove these (%s)" % (key, d.getVar("FILE", True))) parser.parse_python(value, filename=varflags.get("filename"), lineno=varflags.get("lineno")) deps = deps | parser.references deps = deps | (keys & parser.execs) @@ -383,6 +383,8 @@ def build_dependencies(key, keys, shelldeps, varflagsexcl, d): deps = deps | set(varflags["prefuncs"].split()) if "postfuncs" in varflags: deps = deps | set(varflags["postfuncs"].split()) + if "exports" in varflags: + deps = deps | set(varflags["exports"].split()) else: parser = d.expandWithRefs(value, key) deps |= parser.references diff --git a/import-layers/yocto-poky/bitbake/lib/bb/data_smart.py b/import-layers/yocto-poky/bitbake/lib/bb/data_smart.py index fa1e79427..f100446dc 100644 --- a/import-layers/yocto-poky/bitbake/lib/bb/data_smart.py +++ b/import-layers/yocto-poky/bitbake/lib/bb/data_smart.py @@ -135,7 +135,7 @@ class VariableParse: self.contains[k] = parser.contains[k].copy() else: self.contains[k].update(parser.contains[k]) - value = utils.better_eval(codeobj, DataContext(self.d)) + value = utils.better_eval(codeobj, DataContext(self.d), {'d' : self.d}) return str(value) @@ -372,7 +372,7 @@ class DataSmart(MutableMapping): def expandWithRefs(self, s, varname): - if not isinstance(s, basestring): # sanity check + if not isinstance(s, str): # sanity check return VariableParse(varname, self, s) if varname and varname in self.expand_cache: @@ -397,8 +397,7 @@ class DataSmart(MutableMapping): except bb.parse.SkipRecipe: raise except Exception as exc: - exc_class, exc, tb = sys.exc_info() - raise ExpansionError, ExpansionError(varname, s, exc), tb + raise ExpansionError(varname, s, exc) from exc varparse.value = s @@ -917,7 +916,7 @@ class DataSmart(MutableMapping): yield k def __len__(self): - return len(frozenset(self)) + return len(frozenset(iter(self))) def __getitem__(self, item): value = self.getVar(item, False) @@ -966,4 +965,4 @@ class DataSmart(MutableMapping): data.update({i:value}) data_str = str([(k, data[k]) for k in sorted(data.keys())]) - return hashlib.md5(data_str).hexdigest() + return hashlib.md5(data_str.encode("utf-8")).hexdigest() diff --git a/import-layers/yocto-poky/bitbake/lib/bb/event.py b/import-layers/yocto-poky/bitbake/lib/bb/event.py index 5ffe89eae..6f1cb101f 100644 --- a/import-layers/yocto-poky/bitbake/lib/bb/event.py +++ b/import-layers/yocto-poky/bitbake/lib/bb/event.py @@ -24,14 +24,13 @@ BitBake build tools. import os, sys import warnings -try: - import cPickle as pickle -except ImportError: - import pickle +import pickle import logging import atexit import traceback import ast +import threading + import bb.utils import bb.compat import bb.exceptions @@ -71,12 +70,27 @@ _event_handler_map = {} _catchall_handlers = {} _eventfilter = None _uiready = False +_thread_lock = threading.Lock() +_thread_lock_enabled = False + +if hasattr(__builtins__, '__setitem__'): + builtins = __builtins__ +else: + builtins = __builtins__.__dict__ + +def enable_threadlock(): + global _thread_lock_enabled + _thread_lock_enabled = True + +def disable_threadlock(): + global _thread_lock_enabled + _thread_lock_enabled = False def execute_handler(name, handler, event, d): event.data = d addedd = False - if 'd' not in __builtins__: - __builtins__['d'] = d + if 'd' not in builtins: + builtins['d'] = d addedd = True try: ret = handler(event) @@ -94,7 +108,7 @@ def execute_handler(name, handler, event, d): finally: del event.data if addedd: - del __builtins__['d'] + del builtins['d'] def fire_class_handlers(event, d): if isinstance(event, logging.LogRecord): @@ -102,7 +116,7 @@ def fire_class_handlers(event, d): eid = str(event.__class__)[8:-2] evt_hmap = _event_handler_map.get(eid, {}) - for name, handler in _handlers.iteritems(): + for name, handler in list(_handlers.items()): if name in _catchall_handlers or name in evt_hmap: if _eventfilter: if not _eventfilter(name, handler, event, d): @@ -117,31 +131,44 @@ def print_ui_queue(): logger = logging.getLogger("BitBake") if not _uiready: from bb.msg import BBLogFormatter - console = logging.StreamHandler(sys.stdout) - console.setFormatter(BBLogFormatter("%(levelname)s: %(message)s")) - logger.handlers = [console] + stdout = logging.StreamHandler(sys.stdout) + stderr = logging.StreamHandler(sys.stderr) + formatter = BBLogFormatter("%(levelname)s: %(message)s") + stdout.setFormatter(formatter) + stderr.setFormatter(formatter) # First check to see if we have any proper messages msgprint = False - for event in ui_queue: + for event in ui_queue[:]: if isinstance(event, logging.LogRecord): if event.levelno > logging.DEBUG: + if event.levelno >= logging.WARNING: + logger.addHandler(stderr) + else: + logger.addHandler(stdout) logger.handle(event) msgprint = True if msgprint: return # Nope, so just print all of the messages we have (including debug messages) - for event in ui_queue: + logger.addHandler(stdout) + for event in ui_queue[:]: if isinstance(event, logging.LogRecord): logger.handle(event) def fire_ui_handlers(event, d): + global _thread_lock + global _thread_lock_enabled + if not _uiready: # No UI handlers registered yet, queue up the messages ui_queue.append(event) return + if _thread_lock_enabled: + _thread_lock.acquire() + errors = [] for h in _ui_handlers: #print "Sending event %s" % event @@ -160,6 +187,9 @@ def fire_ui_handlers(event, d): for h in errors: del _ui_handlers[h] + if _thread_lock_enabled: + _thread_lock.release() + def fire(event, d): """Fire off an Event""" @@ -187,7 +217,7 @@ def register(name, handler, mask=None, filename=None, lineno=None): if handler is not None: # handle string containing python code - if isinstance(handler, basestring): + if isinstance(handler, str): tmp = "def %s(e):\n%s" % (name, handler) try: code = bb.methodpool.compile_cache(tmp) @@ -225,6 +255,13 @@ def remove(name, handler): """Remove an Event handler""" _handlers.pop(name) +def get_handlers(): + return _handlers + +def set_handlers(handlers): + global _handlers + _handlers = handlers + def set_eventfilter(func): global _eventfilter _eventfilter = func @@ -373,7 +410,11 @@ class BuildBase(Event): - +class BuildInit(BuildBase): + """buildFile or buildTargets was invoked""" + def __init__(self, p=[]): + name = None + BuildBase.__init__(self, name, p) class BuildStarted(BuildBase, OperationStarted): """bbmake build run started""" @@ -605,8 +646,9 @@ class LogHandler(logging.Handler): if hasattr(tb, 'tb_next'): tb = list(bb.exceptions.extract_traceback(tb, context=3)) # Need to turn the value into something the logging system can pickle - value = str(value) record.bb_exc_info = (etype, value, tb) + record.bb_exc_formatted = bb.exceptions.format_exception(etype, value, tb, limit=5) + value = str(value) record.exc_info = None fire(record, None) @@ -637,6 +679,33 @@ class MetadataEvent(Event): self.type = eventtype self._localdata = eventdata +class ProcessStarted(Event): + """ + Generic process started event (usually part of the initial startup) + where further progress events will be delivered + """ + def __init__(self, processname, total): + Event.__init__(self) + self.processname = processname + self.total = total + +class ProcessProgress(Event): + """ + Generic process progress event (usually part of the initial startup) + """ + def __init__(self, processname, progress): + Event.__init__(self) + self.processname = processname + self.progress = progress + +class ProcessFinished(Event): + """ + Generic process finished event (usually part of the initial startup) + """ + def __init__(self, processname): + Event.__init__(self) + self.processname = processname + class SanityCheck(Event): """ Event to run sanity checks, either raise errors or generate events as return status. diff --git a/import-layers/yocto-poky/bitbake/lib/bb/exceptions.py b/import-layers/yocto-poky/bitbake/lib/bb/exceptions.py index f182c8fd6..cd713439e 100644 --- a/import-layers/yocto-poky/bitbake/lib/bb/exceptions.py +++ b/import-layers/yocto-poky/bitbake/lib/bb/exceptions.py @@ -1,4 +1,4 @@ -from __future__ import absolute_import + import inspect import traceback import bb.namedtuple_with_abc @@ -86,6 +86,6 @@ def format_exception(etype, value, tb, context=1, limit=None, formatter=None): def to_string(exc): if isinstance(exc, SystemExit): - if not isinstance(exc.code, basestring): + if not isinstance(exc.code, str): return 'Exited with "%d"' % exc.code return str(exc) diff --git a/import-layers/yocto-poky/bitbake/lib/bb/fetch2/__init__.py b/import-layers/yocto-poky/bitbake/lib/bb/fetch2/__init__.py index 1fa67020c..cd7362c44 100644 --- a/import-layers/yocto-poky/bitbake/lib/bb/fetch2/__init__.py +++ b/import-layers/yocto-poky/bitbake/lib/bb/fetch2/__init__.py @@ -25,31 +25,26 @@ BitBake build tools. # # Based on functions from the base bb module, Copyright 2003 Holger Schurig -from __future__ import absolute_import -from __future__ import print_function import os, re import signal import logging -import urllib -import urlparse +import urllib.request, urllib.parse, urllib.error +if 'git' not in urllib.parse.uses_netloc: + urllib.parse.uses_netloc.append('git') +import operator +import collections +import subprocess +import pickle import bb.persist_data, bb.utils import bb.checksum from bb import data import bb.process -import subprocess __version__ = "2" _checksum_cache = bb.checksum.FileChecksumCache() logger = logging.getLogger("BitBake.Fetcher") -try: - import cPickle as pickle -except ImportError: - import pickle - logger.info("Importing cPickle failed. " - "Falling back to a very slow implementation.") - class BBFetchException(Exception): """Class all fetch exceptions inherit from""" def __init__(self, message): @@ -231,14 +226,14 @@ class URI(object): # them are not quite RFC compliant. uri, param_str = (uri.split(";", 1) + [None])[:2] - urlp = urlparse.urlparse(uri) + urlp = urllib.parse.urlparse(uri) self.scheme = urlp.scheme reparse = 0 # Coerce urlparse to make URI scheme use netloc - if not self.scheme in urlparse.uses_netloc: - urlparse.uses_params.append(self.scheme) + if not self.scheme in urllib.parse.uses_netloc: + urllib.parse.uses_params.append(self.scheme) reparse = 1 # Make urlparse happy(/ier) by converting local resources @@ -249,7 +244,7 @@ class URI(object): reparse = 1 if reparse: - urlp = urlparse.urlparse(uri) + urlp = urllib.parse.urlparse(uri) # Identify if the URI is relative or not if urlp.scheme in self._relative_schemes and \ @@ -265,7 +260,7 @@ class URI(object): if urlp.password: self.userinfo += ':%s' % urlp.password - self.path = urllib.unquote(urlp.path) + self.path = urllib.parse.unquote(urlp.path) if param_str: self.params = self._param_str_split(param_str, ";") @@ -297,7 +292,7 @@ class URI(object): if self.query else '') def _param_str_split(self, string, elmdelim, kvdelim="="): - ret = {} + ret = collections.OrderedDict() for k, v in [x.split(kvdelim, 1) for x in string.split(elmdelim)]: ret[k] = v return ret @@ -313,11 +308,11 @@ class URI(object): @property def path_quoted(self): - return urllib.quote(self.path) + return urllib.parse.quote(self.path) @path_quoted.setter def path_quoted(self, path): - self.path = urllib.unquote(path) + self.path = urllib.parse.unquote(path) @property def path(self): @@ -390,7 +385,7 @@ def decodeurl(url): user = '' pswd = '' - p = {} + p = collections.OrderedDict() if parm: for s in parm.split(';'): if s: @@ -399,7 +394,7 @@ def decodeurl(url): s1, s2 = s.split('=') p[s1] = s2 - return type, host, urllib.unquote(path), user, pswd, p + return type, host, urllib.parse.unquote(path), user, pswd, p def encodeurl(decoded): """Encodes a URL from tokens (scheme, network location, path, @@ -423,7 +418,7 @@ def encodeurl(decoded): # Standardise path to ensure comparisons work while '//' in path: path = path.replace("//", "/") - url += "%s" % urllib.quote(path) + url += "%s" % urllib.parse.quote(path) if p: for parm in p: url += ";%s=%s" % (parm, p[parm]) @@ -586,12 +581,12 @@ def verify_checksum(ud, d, precomputed={}): raise NoChecksumError('Missing SRC_URI checksum', ud.url) # Log missing sums so user can more easily add them - logger.warn('Missing md5 SRC_URI checksum for %s, consider adding to the recipe:\n' - 'SRC_URI[%s] = "%s"', - ud.localpath, ud.md5_name, md5data) - logger.warn('Missing sha256 SRC_URI checksum for %s, consider adding to the recipe:\n' - 'SRC_URI[%s] = "%s"', - ud.localpath, ud.sha256_name, sha256data) + logger.warning('Missing md5 SRC_URI checksum for %s, consider adding to the recipe:\n' + 'SRC_URI[%s] = "%s"', + ud.localpath, ud.md5_name, md5data) + logger.warning('Missing sha256 SRC_URI checksum for %s, consider adding to the recipe:\n' + 'SRC_URI[%s] = "%s"', + ud.localpath, ud.sha256_name, sha256data) # We want to alert the user if a checksum is defined in the recipe but # it does not match. @@ -659,9 +654,9 @@ def verify_donestamp(ud, d, origud=None): # files to those containing the checksums. if not isinstance(e, EOFError): # Ignore errors, they aren't fatal - logger.warn("Couldn't load checksums from donestamp %s: %s " - "(msg: %s)" % (ud.donestamp, type(e).__name__, - str(e))) + logger.warning("Couldn't load checksums from donestamp %s: %s " + "(msg: %s)" % (ud.donestamp, type(e).__name__, + str(e))) try: checksums = verify_checksum(ud, d, precomputed_checksums) @@ -669,14 +664,14 @@ def verify_donestamp(ud, d, origud=None): # as an upgrade path from the previous done stamp file format. if checksums != precomputed_checksums: with open(ud.donestamp, "wb") as cachefile: - p = pickle.Pickler(cachefile, pickle.HIGHEST_PROTOCOL) + p = pickle.Pickler(cachefile, 2) p.dump(checksums) return True except ChecksumError as e: # Checksums failed to verify, trigger re-download and remove the # incorrect stamp file. - logger.warn("Checksum mismatch for local file %s\n" - "Cleaning and trying again." % ud.localpath) + logger.warning("Checksum mismatch for local file %s\n" + "Cleaning and trying again." % ud.localpath) if os.path.exists(ud.localpath): rename_bad_checksum(ud, e.checksum) bb.utils.remove(ud.donestamp) @@ -703,13 +698,13 @@ def update_stamp(ud, d): checksums = verify_checksum(ud, d) # Store the checksums for later re-verification against the recipe with open(ud.donestamp, "wb") as cachefile: - p = pickle.Pickler(cachefile, pickle.HIGHEST_PROTOCOL) + p = pickle.Pickler(cachefile, 2) p.dump(checksums) except ChecksumError as e: # Checksums failed to verify, trigger re-download and remove the # incorrect stamp file. - logger.warn("Checksum mismatch for local file %s\n" - "Cleaning and trying again." % ud.localpath) + logger.warning("Checksum mismatch for local file %s\n" + "Cleaning and trying again." % ud.localpath) if os.path.exists(ud.localpath): rename_bad_checksum(ud, e.checksum) bb.utils.remove(ud.donestamp) @@ -766,6 +761,7 @@ def get_srcrev(d, method_name='sortable_revision'): if not format: raise FetchError("The SRCREV_FORMAT variable must be set when multiple SCMs are used.") + name_to_rev = {} seenautoinc = False for scm in scms: ud = urldata[scm] @@ -774,7 +770,16 @@ def get_srcrev(d, method_name='sortable_revision'): seenautoinc = seenautoinc or autoinc if len(rev) > 10: rev = rev[:10] - format = format.replace(name, rev) + name_to_rev[name] = rev + # Replace names by revisions in the SRCREV_FORMAT string. The approach used + # here can handle names being prefixes of other names and names appearing + # as substrings in revisions (in which case the name should not be + # expanded). The '|' regular expression operator tries matches from left to + # right, so we need to sort the names with the longest ones first. + names_descending_len = sorted(name_to_rev, key=len, reverse=True) + name_to_rev_re = "|".join(re.escape(name) for name in names_descending_len) + format = re.sub(name_to_rev_re, lambda match: name_to_rev[match.group(0)], format) + if seenautoinc: format = "AUTOINC+" + format @@ -784,7 +789,7 @@ def localpath(url, d): fetcher = bb.fetch2.Fetch([url], d) return fetcher.localpath(url) -def runfetchcmd(cmd, d, quiet=False, cleanup=None): +def runfetchcmd(cmd, d, quiet=False, cleanup=None, log=None, workdir=None): """ Run cmd returning the command output Raise an error if interrupted or cmd fails @@ -807,13 +812,16 @@ def runfetchcmd(cmd, d, quiet=False, cleanup=None): 'GIT_SSL_CAINFO', 'GIT_SMART_HTTP', 'SSH_AUTH_SOCK', 'SSH_AGENT_PID', - 'SOCKS5_USER', 'SOCKS5_PASSWD'] + 'SOCKS5_USER', 'SOCKS5_PASSWD', + 'DBUS_SESSION_BUS_ADDRESS', + 'P4CONFIG'] if not cleanup: cleanup = [] + origenv = d.getVar("BB_ORIGENV", False) for var in exportvars: - val = d.getVar(var, True) + val = d.getVar(var, True) or (origenv and origenv.getVar(var, True)) if val: cmd = 'export ' + var + '=\"%s\"; %s' % (val, cmd) @@ -823,7 +831,7 @@ def runfetchcmd(cmd, d, quiet=False, cleanup=None): error_message = "" try: - (output, errors) = bb.process.run(cmd, shell=True, stderr=subprocess.PIPE) + (output, errors) = bb.process.run(cmd, log=log, shell=True, stderr=subprocess.PIPE, cwd=workdir) success = True except bb.process.NotFoundError as e: error_message = "Fetch command %s" % (e.command) @@ -834,7 +842,7 @@ def runfetchcmd(cmd, d, quiet=False, cleanup=None): output = "output:\n%s" % e.stderr else: output = "no output" - error_message = "Fetch command failed with exit code %s, %s" % (e.exitcode, output) + error_message = "Fetch command %s failed with exit code %s, %s" % (e.command, e.exitcode, output) except bb.process.CmdError as e: error_message = "Fetch command %s could not be run:\n%s" % (e.command, e.msg) if not success: @@ -937,8 +945,6 @@ def try_mirror_url(fetch, origud, ud, ld, check = False): return found return False - os.chdir(ld.getVar("DL_DIR", True)) - if not verify_donestamp(ud, ld, origud) or ud.method.need_update(ud, ld): ud.method.download(ud, ld) if hasattr(ud.method,"build_mirror_data"): @@ -982,8 +988,8 @@ def try_mirror_url(fetch, origud, ud, ld, check = False): except bb.fetch2.BBFetchException as e: if isinstance(e, ChecksumError): - logger.warn("Mirror checksum failure for url %s (original url: %s)\nCleaning and trying again." % (ud.url, origud.url)) - logger.warn(str(e)) + logger.warning("Mirror checksum failure for url %s (original url: %s)\nCleaning and trying again." % (ud.url, origud.url)) + logger.warning(str(e)) if os.path.exists(ud.localpath): rename_bad_checksum(ud, e.checksum) elif isinstance(e, NoChecksumError): @@ -1198,7 +1204,7 @@ class FetchData(object): raise NonLocalMethod() if self.parm.get("proto", None) and "protocol" not in self.parm: - logger.warn('Consider updating %s recipe to use "protocol" not "proto" in SRC_URI.', d.getVar('PN', True)) + logger.warning('Consider updating %s recipe to use "protocol" not "proto" in SRC_URI.', d.getVar('PN', True)) self.parm["protocol"] = self.parm.get("proto", None) if hasattr(self.method, "urldata_init"): @@ -1395,7 +1401,18 @@ class FetchMethod(object): else: cmd = 'rpm2cpio.sh %s | cpio -id' % (file) elif file.endswith('.deb') or file.endswith('.ipk'): - cmd = 'ar -p %s data.tar.gz | zcat | tar --no-same-owner -xpf -' % file + output = subprocess.check_output('ar -t %s' % file, preexec_fn=subprocess_setup, shell=True) + datafile = None + if output: + for line in output.decode().splitlines(): + if line.startswith('data.tar.'): + datafile = line + break + else: + raise UnpackError("Unable to unpack deb/ipk package - does not contain data.tar.* file", urldata.url) + else: + raise UnpackError("Unable to unpack deb/ipk package - could not list contents", urldata.url) + cmd = 'ar x %s %s && tar --no-same-owner -xpf %s && rm %s' % (file, datafile, datafile, datafile) elif file.endswith('.tar.7z'): cmd = '7z x -so %s | tar xf - ' % file elif file.endswith('.7z'): @@ -1403,7 +1420,13 @@ class FetchMethod(object): # If 'subdir' param exists, create a dir and use it as destination for unpack cmd if 'subdir' in urldata.parm: - unpackdir = '%s/%s' % (rootdir, urldata.parm.get('subdir')) + subdir = urldata.parm.get('subdir') + if os.path.isabs(subdir): + if not os.path.realpath(subdir).startswith(os.path.realpath(rootdir)): + raise UnpackError("subdir argument isn't a subdirectory of unpack root %s" % rootdir, urldata.url) + unpackdir = subdir + else: + unpackdir = os.path.join(rootdir, subdir) bb.utils.mkdirhier(unpackdir) else: unpackdir = rootdir @@ -1422,22 +1445,16 @@ class FetchMethod(object): if urlpath.find("/") != -1: destdir = urlpath.rsplit("/", 1)[0] + '/' bb.utils.mkdirhier("%s/%s" % (unpackdir, destdir)) - cmd = 'cp -fpPR %s %s' % (file, destdir) + cmd = 'cp -fpPRH %s %s' % (file, destdir) if not cmd: return - # Change to unpackdir before executing command - save_cwd = os.getcwd(); - os.chdir(unpackdir) - path = data.getVar('PATH', True) if path: cmd = "PATH=\"%s\" %s" % (path, cmd) - bb.note("Unpacking %s to %s/" % (file, os.getcwd())) - ret = subprocess.call(cmd, preexec_fn=subprocess_setup, shell=True) - - os.chdir(save_cwd) + bb.note("Unpacking %s to %s/" % (file, unpackdir)) + ret = subprocess.call(cmd, preexec_fn=subprocess_setup, shell=True, cwd=unpackdir) if ret != 0: raise UnpackError("Unpack command %s failed with return value %s" % (cmd, ret), urldata.url) @@ -1505,8 +1522,9 @@ class Fetch(object): self.connection_cache = connection_cache fn = d.getVar('FILE', True) - if cache and fn and fn in urldata_cache: - self.ud = urldata_cache[fn] + mc = d.getVar('__BBMULTICONFIG', True) or "" + if cache and fn and mc + fn in urldata_cache: + self.ud = urldata_cache[mc + fn] for url in urls: if url not in self.ud: @@ -1518,7 +1536,7 @@ class Fetch(object): pass if fn and cache: - urldata_cache[fn] = self.ud + urldata_cache[mc + fn] = self.ud def localpath(self, url): if url not in self.urls: @@ -1572,8 +1590,6 @@ class Fetch(object): if premirroronly: self.d.setVar("BB_NO_NETWORK", "1") - os.chdir(self.d.getVar("DL_DIR", True)) - firsterr = None verified_stamp = verify_donestamp(ud, self.d) if not localpath and (not verified_stamp or m.need_update(ud, self.d)): @@ -1594,14 +1610,14 @@ class Fetch(object): except BBFetchException as e: if isinstance(e, ChecksumError): - logger.warn("Checksum failure encountered with download of %s - will attempt other sources if available" % u) + logger.warning("Checksum failure encountered with download of %s - will attempt other sources if available" % u) logger.debug(1, str(e)) if os.path.exists(ud.localpath): rename_bad_checksum(ud, e.checksum) elif isinstance(e, NoChecksumError): raise else: - logger.warn('Failed to fetch URL %s, attempting MIRRORS if available' % u) + logger.warning('Failed to fetch URL %s, attempting MIRRORS if available' % u) logger.debug(1, str(e)) firsterr = e # Remove any incomplete fetch @@ -1734,7 +1750,7 @@ class FetchConnectionCache(object): del self.cache[cn] def close_connections(self): - for cn in self.cache.keys(): + for cn in list(self.cache.keys()): self.cache[cn].close() del self.cache[cn] diff --git a/import-layers/yocto-poky/bitbake/lib/bb/fetch2/bzr.py b/import-layers/yocto-poky/bitbake/lib/bb/fetch2/bzr.py index 03e9ac461..72264afb5 100644 --- a/import-layers/yocto-poky/bitbake/lib/bb/fetch2/bzr.py +++ b/import-layers/yocto-poky/bitbake/lib/bb/fetch2/bzr.py @@ -88,28 +88,25 @@ class Bzr(FetchMethod): bzrcmd = self._buildbzrcommand(ud, d, "update") logger.debug(1, "BZR Update %s", ud.url) bb.fetch2.check_network_access(d, bzrcmd, ud.url) - os.chdir(os.path.join (ud.pkgdir, os.path.basename(ud.path))) - runfetchcmd(bzrcmd, d) + runfetchcmd(bzrcmd, d, workdir=os.path.join(ud.pkgdir, os.path.basename(ud.path))) else: bb.utils.remove(os.path.join(ud.pkgdir, os.path.basename(ud.pkgdir)), True) bzrcmd = self._buildbzrcommand(ud, d, "fetch") bb.fetch2.check_network_access(d, bzrcmd, ud.url) logger.debug(1, "BZR Checkout %s", ud.url) bb.utils.mkdirhier(ud.pkgdir) - os.chdir(ud.pkgdir) logger.debug(1, "Running %s", bzrcmd) - runfetchcmd(bzrcmd, d) - - os.chdir(ud.pkgdir) + runfetchcmd(bzrcmd, d, workdir=ud.pkgdir) scmdata = ud.parm.get("scmdata", "") if scmdata == "keep": tar_flags = "" else: - tar_flags = "--exclude '.bzr' --exclude '.bzrtags'" + tar_flags = "--exclude='.bzr' --exclude='.bzrtags'" # tar them up to a defined filename - runfetchcmd("tar %s -czf %s %s" % (tar_flags, ud.localpath, os.path.basename(ud.pkgdir)), d, cleanup = [ud.localpath]) + runfetchcmd("tar %s -czf %s %s" % (tar_flags, ud.localpath, os.path.basename(ud.pkgdir)), + d, cleanup=[ud.localpath], workdir=ud.pkgdir) def supports_srcrev(self): return True diff --git a/import-layers/yocto-poky/bitbake/lib/bb/fetch2/clearcase.py b/import-layers/yocto-poky/bitbake/lib/bb/fetch2/clearcase.py index ba83e7cb6..70e280a8d 100644 --- a/import-layers/yocto-poky/bitbake/lib/bb/fetch2/clearcase.py +++ b/import-layers/yocto-poky/bitbake/lib/bb/fetch2/clearcase.py @@ -202,11 +202,10 @@ class ClearCase(FetchMethod): def _remove_view(self, ud, d): if os.path.exists(ud.viewdir): - os.chdir(ud.ccasedir) cmd = self._build_ccase_command(ud, 'rmview'); logger.info("cleaning up [VOB=%s label=%s view=%s]", ud.vob, ud.label, ud.viewname) bb.fetch2.check_network_access(d, cmd, ud.url) - output = runfetchcmd(cmd, d) + output = runfetchcmd(cmd, d, workdir=ud.ccasedir) logger.info("rmview output: %s", output) def need_update(self, ud, d): @@ -241,11 +240,10 @@ class ClearCase(FetchMethod): raise e # Set configspec: Setting the configspec effectively fetches the files as defined in the configspec - os.chdir(ud.viewdir) cmd = self._build_ccase_command(ud, 'setcs'); logger.info("fetching data [VOB=%s label=%s view=%s]", ud.vob, ud.label, ud.viewname) bb.fetch2.check_network_access(d, cmd, ud.url) - output = runfetchcmd(cmd, d) + output = runfetchcmd(cmd, d, workdir=ud.viewdir) logger.info("%s", output) # Copy the configspec to the viewdir so we have it in our source tarball later diff --git a/import-layers/yocto-poky/bitbake/lib/bb/fetch2/cvs.py b/import-layers/yocto-poky/bitbake/lib/bb/fetch2/cvs.py index d27d96f68..5ff70ba92 100644 --- a/import-layers/yocto-poky/bitbake/lib/bb/fetch2/cvs.py +++ b/import-layers/yocto-poky/bitbake/lib/bb/fetch2/cvs.py @@ -123,22 +123,23 @@ class Cvs(FetchMethod): pkg = d.getVar('PN', True) pkgdir = os.path.join(d.getVar('CVSDIR', True), pkg) moddir = os.path.join(pkgdir, localdir) + workdir = None if os.access(os.path.join(moddir, 'CVS'), os.R_OK): logger.info("Update " + ud.url) bb.fetch2.check_network_access(d, cvsupdatecmd, ud.url) # update sources there - os.chdir(moddir) + workdir = moddir cmd = cvsupdatecmd else: logger.info("Fetch " + ud.url) # check out sources there bb.utils.mkdirhier(pkgdir) - os.chdir(pkgdir) + workdir = pkgdir logger.debug(1, "Running %s", cvscmd) bb.fetch2.check_network_access(d, cvscmd, ud.url) cmd = cvscmd - runfetchcmd(cmd, d, cleanup = [moddir]) + runfetchcmd(cmd, d, cleanup=[moddir], workdir=workdir) if not os.access(moddir, os.R_OK): raise FetchError("Directory %s was not readable despite sucessful fetch?!" % moddir, ud.url) @@ -147,18 +148,18 @@ class Cvs(FetchMethod): if scmdata == "keep": tar_flags = "" else: - tar_flags = "--exclude 'CVS'" + tar_flags = "--exclude='CVS'" # tar them up to a defined filename + workdir = None if 'fullpath' in ud.parm: - os.chdir(pkgdir) + workdir = pkgdir cmd = "tar %s -czf %s %s" % (tar_flags, ud.localpath, localdir) else: - os.chdir(moddir) - os.chdir('..') + workdir = os.path.dirname(os.path.realpath(moddir)) cmd = "tar %s -czf %s %s" % (tar_flags, ud.localpath, os.path.basename(moddir)) - runfetchcmd(cmd, d, cleanup = [ud.localpath]) + runfetchcmd(cmd, d, cleanup=[ud.localpath], workdir=workdir) def clean(self, ud, d): """ Clean CVS Files and tarballs """ diff --git a/import-layers/yocto-poky/bitbake/lib/bb/fetch2/git.py b/import-layers/yocto-poky/bitbake/lib/bb/fetch2/git.py index 526668bc2..1bec60ab7 100644 --- a/import-layers/yocto-poky/bitbake/lib/bb/fetch2/git.py +++ b/import-layers/yocto-poky/bitbake/lib/bb/fetch2/git.py @@ -49,6 +49,10 @@ Supported SRC_URI options are: referring to commit which is valid in tag instead of branch. The default is "0", set nobranch=1 if needed. +- usehead + For local git:// urls to use the current branch HEAD as the revsion for use with + AUTOREV. Implies nobranch. + """ #Copyright (C) 2005 Richard Purdie @@ -71,11 +75,53 @@ import os import re import bb import errno +import bb.progress from bb import data from bb.fetch2 import FetchMethod from bb.fetch2 import runfetchcmd from bb.fetch2 import logger + +class GitProgressHandler(bb.progress.LineFilterProgressHandler): + """Extract progress information from git output""" + def __init__(self, d): + self._buffer = '' + self._count = 0 + super(GitProgressHandler, self).__init__(d) + # Send an initial progress event so the bar gets shown + self._fire_progress(-1) + + def write(self, string): + self._buffer += string + stages = ['Counting objects', 'Compressing objects', 'Receiving objects', 'Resolving deltas'] + stage_weights = [0.2, 0.05, 0.5, 0.25] + stagenum = 0 + for i, stage in reversed(list(enumerate(stages))): + if stage in self._buffer: + stagenum = i + self._buffer = '' + break + self._status = stages[stagenum] + percs = re.findall(r'(\d+)%', string) + if percs: + progress = int(round((int(percs[-1]) * stage_weights[stagenum]) + (sum(stage_weights[:stagenum]) * 100))) + rates = re.findall(r'([\d.]+ [a-zA-Z]*/s+)', string) + if rates: + rate = rates[-1] + else: + rate = None + self.update(progress, rate) + else: + if stagenum == 0: + percs = re.findall(r': (\d+)', string) + if percs: + count = int(percs[-1]) + if count > self._count: + self._count = count + self._fire_progress(-count) + super(GitProgressHandler, self).write(string) + + class Git(FetchMethod): """Class to fetch a module or modules from git repositories""" def init(self, d): @@ -111,6 +157,13 @@ class Git(FetchMethod): ud.nobranch = ud.parm.get("nobranch","0") == "1" + # usehead implies nobranch + ud.usehead = ud.parm.get("usehead","0") == "1" + if ud.usehead: + if ud.proto != "file": + raise bb.fetch2.ParameterError("The usehead option is only for use with local ('protocol=file') git repositories", ud.url) + ud.nobranch = 1 + # bareclone implies nocheckout ud.bareclone = ud.parm.get("bareclone","0") == "1" if ud.bareclone: @@ -126,6 +179,9 @@ class Git(FetchMethod): ud.branches[name] = branch ud.unresolvedrev[name] = branch + if ud.usehead: + ud.unresolvedrev['default'] = 'HEAD' + ud.basecmd = data.getVar("FETCHCMD_git", d, True) or "git -c core.fsyncobjectfiles=0" ud.write_tarballs = ((data.getVar("BB_GENERATE_MIRROR_TARBALLS", d, True) or "0") != "0") or ud.rebaseable @@ -163,9 +219,8 @@ class Git(FetchMethod): def need_update(self, ud, d): if not os.path.exists(ud.clonedir): return True - os.chdir(ud.clonedir) for name in ud.names: - if not self._contains_ref(ud, d, name): + if not self._contains_ref(ud, d, name, ud.clonedir): return True if ud.write_tarballs and not os.path.exists(ud.fullmirror): return True @@ -186,8 +241,7 @@ class Git(FetchMethod): # If the checkout doesn't exist and the mirror tarball does, extract it if not os.path.exists(ud.clonedir) and os.path.exists(ud.fullmirror): bb.utils.mkdirhier(ud.clonedir) - os.chdir(ud.clonedir) - runfetchcmd("tar -xzf %s" % (ud.fullmirror), d) + runfetchcmd("tar -xzf %s" % (ud.fullmirror), d, workdir=ud.clonedir) repourl = self._get_repo_url(ud) @@ -196,38 +250,38 @@ class Git(FetchMethod): # We do this since git will use a "-l" option automatically for local urls where possible if repourl.startswith("file://"): repourl = repourl[7:] - clone_cmd = "%s clone --bare --mirror %s %s" % (ud.basecmd, repourl, ud.clonedir) + clone_cmd = "LANG=C %s clone --bare --mirror %s %s --progress" % (ud.basecmd, repourl, ud.clonedir) if ud.proto.lower() != 'file': bb.fetch2.check_network_access(d, clone_cmd) - runfetchcmd(clone_cmd, d) + progresshandler = GitProgressHandler(d) + runfetchcmd(clone_cmd, d, log=progresshandler) - os.chdir(ud.clonedir) # Update the checkout if needed needupdate = False for name in ud.names: - if not self._contains_ref(ud, d, name): + if not self._contains_ref(ud, d, name, ud.clonedir): needupdate = True if needupdate: try: - runfetchcmd("%s remote rm origin" % ud.basecmd, d) + runfetchcmd("%s remote rm origin" % ud.basecmd, d, workdir=ud.clonedir) except bb.fetch2.FetchError: logger.debug(1, "No Origin") - runfetchcmd("%s remote add --mirror=fetch origin %s" % (ud.basecmd, repourl), d) - fetch_cmd = "%s fetch -f --prune %s refs/*:refs/*" % (ud.basecmd, repourl) + runfetchcmd("%s remote add --mirror=fetch origin %s" % (ud.basecmd, repourl), d, workdir=ud.clonedir) + fetch_cmd = "LANG=C %s fetch -f --prune --progress %s refs/*:refs/*" % (ud.basecmd, repourl) if ud.proto.lower() != 'file': bb.fetch2.check_network_access(d, fetch_cmd, ud.url) - runfetchcmd(fetch_cmd, d) - runfetchcmd("%s prune-packed" % ud.basecmd, d) - runfetchcmd("%s pack-redundant --all | xargs -r rm" % ud.basecmd, d) + progresshandler = GitProgressHandler(d) + runfetchcmd(fetch_cmd, d, log=progresshandler, workdir=ud.clonedir) + runfetchcmd("%s prune-packed" % ud.basecmd, d, workdir=ud.clonedir) + runfetchcmd("%s pack-redundant --all | xargs -r rm" % ud.basecmd, d, workdir=ud.clonedir) try: os.unlink(ud.fullmirror) except OSError as exc: if exc.errno != errno.ENOENT: raise - os.chdir(ud.clonedir) for name in ud.names: - if not self._contains_ref(ud, d, name): + if not self._contains_ref(ud, d, name, ud.clonedir): raise bb.fetch2.FetchError("Unable to find revision %s in branch %s even from upstream" % (ud.revisions[name], ud.branches[name])) def build_mirror_data(self, ud, d): @@ -237,10 +291,9 @@ class Git(FetchMethod): if os.path.islink(ud.fullmirror): os.unlink(ud.fullmirror) - os.chdir(ud.clonedir) logger.info("Creating tarball of git repository") - runfetchcmd("tar -czf %s %s" % (ud.fullmirror, os.path.join(".") ), d) - runfetchcmd("touch %s.done" % (ud.fullmirror), d) + runfetchcmd("tar -czf %s %s" % (ud.fullmirror, os.path.join(".") ), d, workdir=ud.clonedir) + runfetchcmd("touch %s.done" % (ud.fullmirror), d, workdir=ud.clonedir) def unpack(self, ud, destdir, d): """ unpack the downloaded src to destdir""" @@ -263,21 +316,21 @@ class Git(FetchMethod): cloneflags += " --mirror" runfetchcmd("%s clone %s %s/ %s" % (ud.basecmd, cloneflags, ud.clonedir, destdir), d) - os.chdir(destdir) repourl = self._get_repo_url(ud) - runfetchcmd("%s remote set-url origin %s" % (ud.basecmd, repourl), d) + runfetchcmd("%s remote set-url origin %s" % (ud.basecmd, repourl), d, workdir=destdir) if not ud.nocheckout: if subdir != "": - runfetchcmd("%s read-tree %s%s" % (ud.basecmd, ud.revisions[ud.names[0]], readpathspec), d) - runfetchcmd("%s checkout-index -q -f -a" % ud.basecmd, d) + runfetchcmd("%s read-tree %s%s" % (ud.basecmd, ud.revisions[ud.names[0]], readpathspec), d, + workdir=destdir) + runfetchcmd("%s checkout-index -q -f -a" % ud.basecmd, d, workdir=destdir) elif not ud.nobranch: branchname = ud.branches[ud.names[0]] runfetchcmd("%s checkout -B %s %s" % (ud.basecmd, branchname, \ - ud.revisions[ud.names[0]]), d) + ud.revisions[ud.names[0]]), d, workdir=destdir) runfetchcmd("%s branch --set-upstream %s origin/%s" % (ud.basecmd, branchname, \ - branchname), d) + branchname), d, workdir=destdir) else: - runfetchcmd("%s checkout %s" % (ud.basecmd, ud.revisions[ud.names[0]]), d) + runfetchcmd("%s checkout %s" % (ud.basecmd, ud.revisions[ud.names[0]]), d, workdir=destdir) return True @@ -291,7 +344,7 @@ class Git(FetchMethod): def supports_srcrev(self): return True - def _contains_ref(self, ud, d, name): + def _contains_ref(self, ud, d, name, wd): cmd = "" if ud.nobranch: cmd = "%s log --pretty=oneline -n 1 %s -- 2> /dev/null | wc -l" % ( @@ -300,7 +353,7 @@ class Git(FetchMethod): cmd = "%s branch --contains %s --list %s 2> /dev/null | wc -l" % ( ud.basecmd, ud.revisions[name], ud.branches[name]) try: - output = runfetchcmd(cmd, d, quiet=True) + output = runfetchcmd(cmd, d, quiet=True, workdir=wd) except bb.fetch2.FetchError: return False if len(output.split()) > 1: @@ -343,16 +396,17 @@ class Git(FetchMethod): """ output = self._lsremote(ud, d, "") # Tags of the form ^{} may not work, need to fallback to other form - if ud.unresolvedrev[name][:5] == "refs/": + if ud.unresolvedrev[name][:5] == "refs/" or ud.usehead: head = ud.unresolvedrev[name] tag = ud.unresolvedrev[name] else: head = "refs/heads/%s" % ud.unresolvedrev[name] tag = "refs/tags/%s" % ud.unresolvedrev[name] for s in [head, tag + "^{}", tag]: - for l in output.split('\n'): - if s in l: - return l.split()[0] + for l in output.strip().split('\n'): + sha1, ref = l.split() + if s == ref: + return sha1 raise bb.fetch2.FetchError("Unable to resolve '%s' in upstream git repository in git ls-remote output for %s" % \ (ud.unresolvedrev[name], ud.host+ud.path)) diff --git a/import-layers/yocto-poky/bitbake/lib/bb/fetch2/gitannex.py b/import-layers/yocto-poky/bitbake/lib/bb/fetch2/gitannex.py index 0f3789745..4937a1089 100644 --- a/import-layers/yocto-poky/bitbake/lib/bb/fetch2/gitannex.py +++ b/import-layers/yocto-poky/bitbake/lib/bb/fetch2/gitannex.py @@ -34,43 +34,42 @@ class GitANNEX(Git): """ return ud.type in ['gitannex'] - def uses_annex(self, ud, d): + def uses_annex(self, ud, d, wd): for name in ud.names: try: - runfetchcmd("%s rev-list git-annex" % (ud.basecmd), d, quiet=True) + runfetchcmd("%s rev-list git-annex" % (ud.basecmd), d, quiet=True, workdir=wd) return True except bb.fetch.FetchError: pass return False - def update_annex(self, ud, d): + def update_annex(self, ud, d, wd): try: - runfetchcmd("%s annex get --all" % (ud.basecmd), d, quiet=True) + runfetchcmd("%s annex get --all" % (ud.basecmd), d, quiet=True, workdir=wd) except bb.fetch.FetchError: return False - runfetchcmd("chmod u+w -R %s/annex" % (ud.clonedir), d, quiet=True) + runfetchcmd("chmod u+w -R %s/annex" % (ud.clonedir), d, quiet=True, workdir=wd) return True def download(self, ud, d): Git.download(self, ud, d) - os.chdir(ud.clonedir) - annex = self.uses_annex(ud, d) + annex = self.uses_annex(ud, d, ud.clonedir) if annex: - self.update_annex(ud, d) + self.update_annex(ud, d, ud.clonedir) def unpack(self, ud, destdir, d): Git.unpack(self, ud, destdir, d) - os.chdir(ud.destdir) try: - runfetchcmd("%s annex sync" % (ud.basecmd), d) + runfetchcmd("%s annex init" % (ud.basecmd), d, workdir=ud.destdir) except bb.fetch.FetchError: pass - annex = self.uses_annex(ud, d) + annex = self.uses_annex(ud, d, ud.destdir) if annex: - runfetchcmd("%s annex get" % (ud.basecmd), d) - runfetchcmd("chmod u+w -R %s/.git/annex" % (ud.destdir), d, quiet=True) + runfetchcmd("%s annex get" % (ud.basecmd), d, workdir=ud.destdir) + runfetchcmd("chmod u+w -R %s/.git/annex" % (ud.destdir), d, quiet=True, workdir=ud.destdir) + diff --git a/import-layers/yocto-poky/bitbake/lib/bb/fetch2/gitsm.py b/import-layers/yocto-poky/bitbake/lib/bb/fetch2/gitsm.py index 752f1d3c1..661376204 100644 --- a/import-layers/yocto-poky/bitbake/lib/bb/fetch2/gitsm.py +++ b/import-layers/yocto-poky/bitbake/lib/bb/fetch2/gitsm.py @@ -43,10 +43,10 @@ class GitSM(Git): """ return ud.type in ['gitsm'] - def uses_submodules(self, ud, d): + def uses_submodules(self, ud, d, wd): for name in ud.names: try: - runfetchcmd("%s show %s:.gitmodules" % (ud.basecmd, ud.revisions[name]), d, quiet=True) + runfetchcmd("%s show %s:.gitmodules" % (ud.basecmd, ud.revisions[name]), d, quiet=True, workdir=wd) return True except bb.fetch.FetchError: pass @@ -107,28 +107,25 @@ class GitSM(Git): os.mkdir(tmpclonedir) os.rename(ud.clonedir, gitdir) runfetchcmd("sed " + gitdir + "/config -i -e 's/bare.*=.*true/bare = false/'", d) - os.chdir(tmpclonedir) - runfetchcmd(ud.basecmd + " reset --hard", d) - runfetchcmd(ud.basecmd + " checkout " + ud.revisions[ud.names[0]], d) - runfetchcmd(ud.basecmd + " submodule update --init --recursive", d) + runfetchcmd(ud.basecmd + " reset --hard", d, workdir=tmpclonedir) + runfetchcmd(ud.basecmd + " checkout " + ud.revisions[ud.names[0]], d, workdir=tmpclonedir) + runfetchcmd(ud.basecmd + " submodule update --init --recursive", d, workdir=tmpclonedir) self._set_relative_paths(tmpclonedir) - runfetchcmd("sed " + gitdir + "/config -i -e 's/bare.*=.*false/bare = true/'", d) + runfetchcmd("sed " + gitdir + "/config -i -e 's/bare.*=.*false/bare = true/'", d, workdir=tmpclonedir) os.rename(gitdir, ud.clonedir,) bb.utils.remove(tmpclonedir, True) def download(self, ud, d): Git.download(self, ud, d) - os.chdir(ud.clonedir) - submodules = self.uses_submodules(ud, d) + submodules = self.uses_submodules(ud, d, ud.clonedir) if submodules: self.update_submodules(ud, d) def unpack(self, ud, destdir, d): Git.unpack(self, ud, destdir, d) - os.chdir(ud.destdir) - submodules = self.uses_submodules(ud, d) + submodules = self.uses_submodules(ud, d, ud.destdir) if submodules: - runfetchcmd(ud.basecmd + " checkout " + ud.revisions[ud.names[0]], d) - runfetchcmd(ud.basecmd + " submodule update --init --recursive", d) + runfetchcmd(ud.basecmd + " checkout " + ud.revisions[ud.names[0]], d, workdir=ud.destdir) + runfetchcmd(ud.basecmd + " submodule update --init --recursive", d, workdir=ud.destdir) diff --git a/import-layers/yocto-poky/bitbake/lib/bb/fetch2/hg.py b/import-layers/yocto-poky/bitbake/lib/bb/fetch2/hg.py index 3b743ff51..20df8016d 100644 --- a/import-layers/yocto-poky/bitbake/lib/bb/fetch2/hg.py +++ b/import-layers/yocto-poky/bitbake/lib/bb/fetch2/hg.py @@ -169,25 +169,22 @@ class Hg(FetchMethod): # If the checkout doesn't exist and the mirror tarball does, extract it if not os.path.exists(ud.pkgdir) and os.path.exists(ud.fullmirror): bb.utils.mkdirhier(ud.pkgdir) - os.chdir(ud.pkgdir) - runfetchcmd("tar -xzf %s" % (ud.fullmirror), d) + runfetchcmd("tar -xzf %s" % (ud.fullmirror), d, workdir=ud.pkgdir) if os.access(os.path.join(ud.moddir, '.hg'), os.R_OK): # Found the source, check whether need pull updatecmd = self._buildhgcommand(ud, d, "update") - os.chdir(ud.moddir) logger.debug(1, "Running %s", updatecmd) try: - runfetchcmd(updatecmd, d) + runfetchcmd(updatecmd, d, workdir=ud.moddir) except bb.fetch2.FetchError: # Runnning pull in the repo pullcmd = self._buildhgcommand(ud, d, "pull") logger.info("Pulling " + ud.url) # update sources there - os.chdir(ud.moddir) logger.debug(1, "Running %s", pullcmd) bb.fetch2.check_network_access(d, pullcmd, ud.url) - runfetchcmd(pullcmd, d) + runfetchcmd(pullcmd, d, workdir=ud.moddir) try: os.unlink(ud.fullmirror) except OSError as exc: @@ -200,17 +197,15 @@ class Hg(FetchMethod): logger.info("Fetch " + ud.url) # check out sources there bb.utils.mkdirhier(ud.pkgdir) - os.chdir(ud.pkgdir) logger.debug(1, "Running %s", fetchcmd) bb.fetch2.check_network_access(d, fetchcmd, ud.url) - runfetchcmd(fetchcmd, d) + runfetchcmd(fetchcmd, d, workdir=ud.pkgdir) # Even when we clone (fetch), we still need to update as hg's clone # won't checkout the specified revision if its on a branch updatecmd = self._buildhgcommand(ud, d, "update") - os.chdir(ud.moddir) logger.debug(1, "Running %s", updatecmd) - runfetchcmd(updatecmd, d) + runfetchcmd(updatecmd, d, workdir=ud.moddir) def clean(self, ud, d): """ Clean the hg dir """ @@ -246,10 +241,9 @@ class Hg(FetchMethod): if os.path.islink(ud.fullmirror): os.unlink(ud.fullmirror) - os.chdir(ud.pkgdir) logger.info("Creating tarball of hg repository") - runfetchcmd("tar -czf %s %s" % (ud.fullmirror, ud.module), d) - runfetchcmd("touch %s.done" % (ud.fullmirror), d) + runfetchcmd("tar -czf %s %s" % (ud.fullmirror, ud.module), d, workdir=ud.pkgdir) + runfetchcmd("touch %s.done" % (ud.fullmirror), d, workdir=ud.pkgdir) def localpath(self, ud, d): return ud.pkgdir @@ -269,10 +263,8 @@ class Hg(FetchMethod): logger.debug(2, "Unpack: creating new hg repository in '" + codir + "'") runfetchcmd("%s init %s" % (ud.basecmd, codir), d) logger.debug(2, "Unpack: updating source in '" + codir + "'") - os.chdir(codir) - runfetchcmd("%s pull %s" % (ud.basecmd, ud.moddir), d) - runfetchcmd("%s up -C %s" % (ud.basecmd, revflag), d) + runfetchcmd("%s pull %s" % (ud.basecmd, ud.moddir), d, workdir=codir) + runfetchcmd("%s up -C %s" % (ud.basecmd, revflag), d, workdir=codir) else: logger.debug(2, "Unpack: extracting source to '" + codir + "'") - os.chdir(ud.moddir) - runfetchcmd("%s archive -t files %s %s" % (ud.basecmd, revflag, codir), d) + runfetchcmd("%s archive -t files %s %s" % (ud.basecmd, revflag, codir), d, workdir=ud.moddir) diff --git a/import-layers/yocto-poky/bitbake/lib/bb/fetch2/local.py b/import-layers/yocto-poky/bitbake/lib/bb/fetch2/local.py index 303a52b63..51ca78d12 100644 --- a/import-layers/yocto-poky/bitbake/lib/bb/fetch2/local.py +++ b/import-layers/yocto-poky/bitbake/lib/bb/fetch2/local.py @@ -26,7 +26,7 @@ BitBake build tools. # Based on functions from the base bb module, Copyright 2003 Holger Schurig import os -import urllib +import urllib.request, urllib.parse, urllib.error import bb import bb.utils from bb import data @@ -42,7 +42,7 @@ class Local(FetchMethod): def urldata_init(self, ud, d): # We don't set localfile as for this fetcher the file is already local! - ud.decodedurl = urllib.unquote(ud.url.split("://")[1].split(";")[0]) + ud.decodedurl = urllib.parse.unquote(ud.url.split("://")[1].split(";")[0]) ud.basename = os.path.basename(ud.decodedurl) ud.basepath = ud.decodedurl ud.needdonestamp = False diff --git a/import-layers/yocto-poky/bitbake/lib/bb/fetch2/npm.py b/import-layers/yocto-poky/bitbake/lib/bb/fetch2/npm.py index e8d9b1109..699ae72e0 100644 --- a/import-layers/yocto-poky/bitbake/lib/bb/fetch2/npm.py +++ b/import-layers/yocto-poky/bitbake/lib/bb/fetch2/npm.py @@ -13,14 +13,14 @@ Usage in the recipe: - name - version - npm://registry.npmjs.org/${PN}/-/${PN}-${PV}.tgz would become npm://registry.npmjs.org;name=${PN};ver=${PV} + npm://registry.npmjs.org/${PN}/-/${PN}-${PV}.tgz would become npm://registry.npmjs.org;name=${PN};version=${PV} The fetcher all triggers off the existence of ud.localpath. If that exists and has the ".done" stamp, its assumed the fetch is good/done """ import os import sys -import urllib +import urllib.request, urllib.parse, urllib.error import json import subprocess import signal @@ -88,7 +88,7 @@ class Npm(FetchMethod): ud.localpath = d.expand("${DL_DIR}/npm/%s" % ud.bbnpmmanifest) self.basecmd = d.getVar("FETCHCMD_wget", True) or "/usr/bin/env wget -O -t 2 -T 30 -nv --passive-ftp --no-check-certificate " - self.basecmd += " --directory-prefix=%s " % prefixdir + ud.prefixdir = prefixdir ud.write_tarballs = ((data.getVar("BB_GENERATE_MIRROR_TARBALLS", d, True) or "0") != "0") ud.mirrortarball = 'npm_%s-%s.tar.xz' % (ud.pkgname, ud.version) @@ -102,7 +102,8 @@ class Npm(FetchMethod): def _runwget(self, ud, d, command, quiet): logger.debug(2, "Fetching %s using command '%s'" % (ud.url, command)) bb.fetch2.check_network_access(d, command) - runfetchcmd(command, d, quiet) + dldir = d.getVar("DL_DIR", True) + runfetchcmd(command, d, quiet, workdir=dldir) def _unpackdep(self, ud, pkg, data, destdir, dldir, d): file = data[pkg]['tgz'] @@ -113,16 +114,13 @@ class Npm(FetchMethod): bb.fatal("NPM package %s downloaded not a tarball!" % file) # Change to subdir before executing command - save_cwd = os.getcwd() if not os.path.exists(destdir): os.makedirs(destdir) - os.chdir(destdir) path = d.getVar('PATH', True) if path: cmd = "PATH=\"%s\" %s" % (path, cmd) - bb.note("Unpacking %s to %s/" % (file, os.getcwd())) - ret = subprocess.call(cmd, preexec_fn=subprocess_setup, shell=True) - os.chdir(save_cwd) + bb.note("Unpacking %s to %s/" % (file, destdir)) + ret = subprocess.call(cmd, preexec_fn=subprocess_setup, shell=True, cwd=destdir) if ret != 0: raise UnpackError("Unpack command %s failed with return value %s" % (cmd, ret), ud.url) @@ -140,7 +138,12 @@ class Npm(FetchMethod): workobj = json.load(datafile) dldir = "%s/%s" % (os.path.dirname(ud.localpath), ud.pkgname) - self._unpackdep(ud, ud.pkgname, workobj, "%s/npmpkg" % destdir, dldir, d) + if 'subdir' in ud.parm: + unpackdir = '%s/%s' % (destdir, ud.parm.get('subdir')) + else: + unpackdir = '%s/npmpkg' % destdir + + self._unpackdep(ud, ud.pkgname, workobj, unpackdir, dldir, d) def _parse_view(self, output): ''' @@ -162,7 +165,9 @@ class Npm(FetchMethod): pdata = json.loads('\n'.join(datalines)) return pdata - def _getdependencies(self, pkg, data, version, d, ud, optional=False): + def _getdependencies(self, pkg, data, version, d, ud, optional=False, fetchedlist=None): + if fetchedlist is None: + fetchedlist = [] pkgfullname = pkg if version != '*' and not '/' in version: pkgfullname += "@'%s'" % version @@ -184,7 +189,9 @@ class Npm(FetchMethod): outputurl = pdata['dist']['tarball'] data[pkg] = {} data[pkg]['tgz'] = os.path.basename(outputurl) - self._runwget(ud, d, "%s %s" % (self.basecmd, outputurl), False) + if not outputurl in fetchedlist: + self._runwget(ud, d, "%s --directory-prefix=%s %s" % (self.basecmd, ud.prefixdir, outputurl), False) + fetchedlist.append(outputurl) dependencies = pdata.get('dependencies', {}) optionalDependencies = pdata.get('optionalDependencies', {}) @@ -196,13 +203,20 @@ class Npm(FetchMethod): optdepsfound[dep] = dependencies[dep] else: depsfound[dep] = dependencies[dep] - for dep, version in optdepsfound.iteritems(): - self._getdependencies(dep, data[pkg]['deps'], version, d, ud, optional=True) - for dep, version in depsfound.iteritems(): - self._getdependencies(dep, data[pkg]['deps'], version, d, ud) + for dep, version in optdepsfound.items(): + self._getdependencies(dep, data[pkg]['deps'], version, d, ud, optional=True, fetchedlist=fetchedlist) + for dep, version in depsfound.items(): + self._getdependencies(dep, data[pkg]['deps'], version, d, ud, fetchedlist=fetchedlist) - def _getshrinkeddependencies(self, pkg, data, version, d, ud, lockdown, manifest): + def _getshrinkeddependencies(self, pkg, data, version, d, ud, lockdown, manifest, toplevel=True): logger.debug(2, "NPM shrinkwrap file is %s" % data) + if toplevel: + name = data.get('name', None) + if name and name != pkg: + for obj in data.get('dependencies', []): + if obj == pkg: + self._getshrinkeddependencies(obj, data['dependencies'][obj], data['dependencies'][obj]['version'], d, ud, lockdown, manifest, False) + return outputurl = "invalid" if ('resolved' not in data) or (not data['resolved'].startswith('http')): # will be the case for ${PN} @@ -211,7 +225,7 @@ class Npm(FetchMethod): outputurl = runfetchcmd(fetchcmd, d, True) else: outputurl = data['resolved'] - self._runwget(ud, d, "%s %s" % (self.basecmd, outputurl), False) + self._runwget(ud, d, "%s --directory-prefix=%s %s" % (self.basecmd, ud.prefixdir, outputurl), False) manifest[pkg] = {} manifest[pkg]['tgz'] = os.path.basename(outputurl).rstrip() manifest[pkg]['deps'] = {} @@ -228,7 +242,7 @@ class Npm(FetchMethod): if 'dependencies' in data: for obj in data['dependencies']: logger.debug(2, "Found dep is %s" % str(obj)) - self._getshrinkeddependencies(obj, data['dependencies'][obj], data['dependencies'][obj]['version'], d, ud, lockdown, manifest[pkg]['deps']) + self._getshrinkeddependencies(obj, data['dependencies'][obj], data['dependencies'][obj]['version'], d, ud, lockdown, manifest[pkg]['deps'], False) def download(self, ud, d): """Fetch url""" @@ -239,10 +253,7 @@ class Npm(FetchMethod): if not os.listdir(ud.pkgdatadir) and os.path.exists(ud.fullmirror): dest = d.getVar("DL_DIR", True) bb.utils.mkdirhier(dest) - save_cwd = os.getcwd() - os.chdir(dest) - runfetchcmd("tar -xJf %s" % (ud.fullmirror), d) - os.chdir(save_cwd) + runfetchcmd("tar -xJf %s" % (ud.fullmirror), d, workdir=dest) return shwrf = d.getVar('NPM_SHRINKWRAP', True) @@ -251,14 +262,14 @@ class Npm(FetchMethod): with open(shwrf) as datafile: shrinkobj = json.load(datafile) except: - logger.warn('Missing shrinkwrap file in NPM_SHRINKWRAP for %s, this will lead to unreliable builds!' % ud.pkgname) + logger.warning('Missing shrinkwrap file in NPM_SHRINKWRAP for %s, this will lead to unreliable builds!' % ud.pkgname) lckdf = d.getVar('NPM_LOCKDOWN', True) logger.debug(2, "NPM lockdown file is %s" % lckdf) try: with open(lckdf) as datafile: lockdown = json.load(datafile) except: - logger.warn('Missing lockdown file in NPM_LOCKDOWN for %s, this will lead to unreproducible builds!' % ud.pkgname) + logger.warning('Missing lockdown file in NPM_LOCKDOWN for %s, this will lead to unreproducible builds!' % ud.pkgname) if ('name' not in shrinkobj): self._getdependencies(ud.pkgname, jsondepobj, ud.version, d, ud) @@ -275,10 +286,8 @@ class Npm(FetchMethod): if os.path.islink(ud.fullmirror): os.unlink(ud.fullmirror) - save_cwd = os.getcwd() - os.chdir(d.getVar("DL_DIR", True)) + dldir = d.getVar("DL_DIR", True) logger.info("Creating tarball of npm data") - runfetchcmd("tar -cJf %s npm/%s npm/%s" % (ud.fullmirror, ud.bbnpmmanifest, ud.pkgname), d) - runfetchcmd("touch %s.done" % (ud.fullmirror), d) - os.chdir(save_cwd) - + runfetchcmd("tar -cJf %s npm/%s npm/%s" % (ud.fullmirror, ud.bbnpmmanifest, ud.pkgname), d, + workdir=dldir) + runfetchcmd("touch %s.done" % (ud.fullmirror), d, workdir=dldir) diff --git a/import-layers/yocto-poky/bitbake/lib/bb/fetch2/osc.py b/import-layers/yocto-poky/bitbake/lib/bb/fetch2/osc.py index d051dfdaf..295abf953 100644 --- a/import-layers/yocto-poky/bitbake/lib/bb/fetch2/osc.py +++ b/import-layers/yocto-poky/bitbake/lib/bb/fetch2/osc.py @@ -88,23 +88,21 @@ class Osc(FetchMethod): oscupdatecmd = self._buildosccommand(ud, d, "update") logger.info("Update "+ ud.url) # update sources there - os.chdir(ud.moddir) logger.debug(1, "Running %s", oscupdatecmd) bb.fetch2.check_network_access(d, oscupdatecmd, ud.url) - runfetchcmd(oscupdatecmd, d) + runfetchcmd(oscupdatecmd, d, workdir=ud.moddir) else: oscfetchcmd = self._buildosccommand(ud, d, "fetch") logger.info("Fetch " + ud.url) # check out sources there bb.utils.mkdirhier(ud.pkgdir) - os.chdir(ud.pkgdir) logger.debug(1, "Running %s", oscfetchcmd) bb.fetch2.check_network_access(d, oscfetchcmd, ud.url) - runfetchcmd(oscfetchcmd, d) + runfetchcmd(oscfetchcmd, d, workdir=ud.pkgdir) - os.chdir(os.path.join(ud.pkgdir + ud.path)) # tar them up to a defined filename - runfetchcmd("tar -czf %s %s" % (ud.localpath, ud.module), d, cleanup = [ud.localpath]) + runfetchcmd("tar -czf %s %s" % (ud.localpath, ud.module), d, + cleanup=[ud.localpath], workdir=os.path.join(ud.pkgdir + ud.path)) def supports_srcrev(self): return False diff --git a/import-layers/yocto-poky/bitbake/lib/bb/fetch2/perforce.py b/import-layers/yocto-poky/bitbake/lib/bb/fetch2/perforce.py index 3a10c7ca3..50cb47909 100644 --- a/import-layers/yocto-poky/bitbake/lib/bb/fetch2/perforce.py +++ b/import-layers/yocto-poky/bitbake/lib/bb/fetch2/perforce.py @@ -1,14 +1,12 @@ # ex:ts=4:sw=4:sts=4:et # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- """ -BitBake 'Fetch' implementations - -Classes for obtaining upstream sources for the -BitBake build tools. +BitBake 'Fetch' implementation for perforce """ # Copyright (C) 2003, 2004 Chris Larson +# Copyright (C) 2016 Kodak Alaris, Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 as @@ -25,9 +23,7 @@ BitBake build tools. # # Based on functions from the base bb module, Copyright 2003 Holger Schurig -from future_builtins import zip import os -import subprocess import logging import bb from bb import data @@ -37,151 +33,178 @@ from bb.fetch2 import logger from bb.fetch2 import runfetchcmd class Perforce(FetchMethod): + """ Class to fetch from perforce repositories """ def supports(self, ud, d): + """ Check to see if a given url can be fetched with perforce. """ return ud.type in ['p4'] - def doparse(url, d): - parm = {} - path = url.split("://")[1] - delim = path.find("@"); + def urldata_init(self, ud, d): + """ + Initialize perforce specific variables within url data. If P4CONFIG is + provided by the env, use it. If P4PORT is specified by the recipe, use + its values, which may override the settings in P4CONFIG. + """ + ud.basecmd = d.getVar('FETCHCMD_p4', True) + if not ud.basecmd: + ud.basecmd = "/usr/bin/env p4" + + ud.dldir = d.getVar('P4DIR', True) + if not ud.dldir: + ud.dldir = '%s/%s' % (d.getVar('DL_DIR', True), 'p4') + + path = ud.url.split('://')[1] + path = path.split(';')[0] + delim = path.find('@'); if delim != -1: - (user, pswd, host, port) = path.split('@')[0].split(":") - path = path.split('@')[1] + (ud.user, ud.pswd) = path.split('@')[0].split(':') + ud.path = path.split('@')[1] else: - (host, port) = d.getVar('P4PORT', False).split(':') - user = "" - pswd = "" - - if path.find(";") != -1: - keys=[] - values=[] - plist = path.split(';') - for item in plist: - if item.count('='): - (key, value) = item.split('=') - keys.append(key) - values.append(value) - - parm = dict(zip(keys, values)) - path = "//" + path.split(';')[0] - host += ":%s" % (port) - parm["cset"] = Perforce.getcset(d, path, host, user, pswd, parm) - - return host, path, user, pswd, parm - doparse = staticmethod(doparse) - - def getcset(d, depot, host, user, pswd, parm): - p4opt = "" - if "cset" in parm: - return parm["cset"]; - if user: - p4opt += " -u %s" % (user) - if pswd: - p4opt += " -P %s" % (pswd) - if host: - p4opt += " -p %s" % (host) - - p4date = d.getVar("P4DATE", True) - if "revision" in parm: - depot += "#%s" % (parm["revision"]) - elif "label" in parm: - depot += "@%s" % (parm["label"]) - elif p4date: - depot += "@%s" % (p4date) - - p4cmd = d.getVar('FETCHCMD_p4', True) or "p4" - logger.debug(1, "Running %s%s changes -m 1 %s", p4cmd, p4opt, depot) - p4file, errors = bb.process.run("%s%s changes -m 1 %s" % (p4cmd, p4opt, depot)) - cset = p4file.strip() - logger.debug(1, "READ %s", cset) - if not cset: - return -1 - - return cset.split(' ')[1] - getcset = staticmethod(getcset) + ud.path = path - def urldata_init(self, ud, d): - (host, path, user, pswd, parm) = Perforce.doparse(ud.url, d) + ud.usingp4config = False + p4port = d.getVar('P4PORT', True) - base_path = path.replace('/...', '') - base_path = self._strip_leading_slashes(base_path) - - if "label" in parm: - version = parm["label"] + if p4port: + logger.debug(1, 'Using recipe provided P4PORT: %s' % p4port) + ud.host = p4port + else: + logger.debug(1, 'Trying to use P4CONFIG to automatically set P4PORT...') + ud.usingp4config = True + p4cmd = '%s info | grep "Server address"' % ud.basecmd + bb.fetch2.check_network_access(d, p4cmd) + ud.host = runfetchcmd(p4cmd, d, True) + ud.host = ud.host.split(': ')[1].strip() + logger.debug(1, 'Determined P4PORT to be: %s' % ud.host) + if not ud.host: + raise FetchError('Could not determine P4PORT from P4CONFIG') + + if ud.path.find('/...') >= 0: + ud.pathisdir = True else: - version = Perforce.getcset(d, path, host, user, pswd, parm) + ud.pathisdir = False - ud.localfile = data.expand('%s+%s+%s.tar.gz' % (host, base_path.replace('/', '.'), version), d) + cleanedpath = ud.path.replace('/...', '').replace('/', '.') + cleanedhost = ud.host.replace(':', '.') + ud.pkgdir = os.path.join(ud.dldir, cleanedhost, cleanedpath) - def download(self, ud, d): + ud.setup_revisons(d) + + ud.localfile = data.expand('%s_%s_%s.tar.gz' % (cleanedhost, cleanedpath, ud.revision), d) + + def _buildp4command(self, ud, d, command, depot_filename=None): """ - Fetch urls + Build a p4 commandline. Valid commands are "changes", "print", and + "files". depot_filename is the full path to the file in the depot + including the trailing '#rev' value. """ + p4opt = "" + + if ud.user: + p4opt += ' -u "%s"' % (ud.user) - (host, depot, user, pswd, parm) = Perforce.doparse(ud.url, d) + if ud.pswd: + p4opt += ' -P "%s"' % (ud.pswd) - if depot.find('/...') != -1: - path = depot[:depot.find('/...')] + if ud.host and not ud.usingp4config: + p4opt += ' -p %s' % (ud.host) + + if hasattr(ud, 'revision') and ud.revision: + pathnrev = '%s@%s' % (ud.path, ud.revision) + else: + pathnrev = '%s' % (ud.path) + + if depot_filename: + if ud.pathisdir: # Remove leading path to obtain filename + filename = depot_filename[len(ud.path)-1:] + else: + filename = depot_filename[depot_filename.rfind('/'):] + filename = filename[:filename.find('#')] # Remove trailing '#rev' + + if command == 'changes': + p4cmd = '%s%s changes -m 1 //%s' % (ud.basecmd, p4opt, pathnrev) + elif command == 'print': + if depot_filename != None: + p4cmd = '%s%s print -o "p4/%s" "%s"' % (ud.basecmd, p4opt, filename, depot_filename) + else: + raise FetchError('No depot file name provided to p4 %s' % command, ud.url) + elif command == 'files': + p4cmd = '%s%s files //%s' % (ud.basecmd, p4opt, pathnrev) else: - path = depot[:depot.rfind('/')] + raise FetchError('Invalid p4 command %s' % command, ud.url) - module = parm.get('module', os.path.basename(path)) + return p4cmd - # Get the p4 command - p4opt = "" - if user: - p4opt += " -u %s" % (user) + def _p4listfiles(self, ud, d): + """ + Return a list of the file names which are present in the depot using the + 'p4 files' command, including trailing '#rev' file revision indicator + """ + p4cmd = self._buildp4command(ud, d, 'files') + bb.fetch2.check_network_access(d, p4cmd) + p4fileslist = runfetchcmd(p4cmd, d, True) + p4fileslist = [f.rstrip() for f in p4fileslist.splitlines()] + + if not p4fileslist: + raise FetchError('Unable to fetch listing of p4 files from %s@%s' % (ud.host, ud.path)) + + count = 0 + filelist = [] - if pswd: - p4opt += " -P %s" % (pswd) + for filename in p4fileslist: + item = filename.split(' - ') + lastaction = item[1].split() + logger.debug(1, 'File: %s Last Action: %s' % (item[0], lastaction[0])) + if lastaction[0] == 'delete': + continue + filelist.append(item[0]) - if host: - p4opt += " -p %s" % (host) + return filelist - p4cmd = d.getVar('FETCHCMD_p4', True) or "p4" + def download(self, ud, d): + """ Get the list of files, fetch each one """ + filelist = self._p4listfiles(ud, d) + if not filelist: + raise FetchError('No files found in depot %s@%s' % (ud.host, ud.path)) - # create temp directory - logger.debug(2, "Fetch: creating temporary directory") - bb.utils.mkdirhier(d.expand('${WORKDIR}')) - mktemp = d.getVar("FETCHCMD_p4mktemp", True) or d.expand("mktemp -d -q '${WORKDIR}/oep4.XXXXXX'") - tmpfile, errors = bb.process.run(mktemp) - tmpfile = tmpfile.strip() - if not tmpfile: - raise FetchError("Fetch: unable to create temporary directory.. make sure 'mktemp' is in the PATH.", ud.url) + bb.utils.remove(ud.pkgdir, True) + bb.utils.mkdirhier(ud.pkgdir) - if "label" in parm: - depot = "%s@%s" % (depot, parm["label"]) - else: - cset = Perforce.getcset(d, depot, host, user, pswd, parm) - depot = "%s@%s" % (depot, cset) + for afile in filelist: + p4fetchcmd = self._buildp4command(ud, d, 'print', afile) + bb.fetch2.check_network_access(d, p4fetchcmd) + runfetchcmd(p4fetchcmd, d, workdir=ud.pkgdir) - os.chdir(tmpfile) - logger.info("Fetch " + ud.url) - logger.info("%s%s files %s", p4cmd, p4opt, depot) - p4file, errors = bb.process.run("%s%s files %s" % (p4cmd, p4opt, depot)) - p4file = [f.rstrip() for f in p4file.splitlines()] + runfetchcmd('tar -czf %s p4' % (ud.localpath), d, cleanup=[ud.localpath], workdir=ud.pkgdir) - if not p4file: - raise FetchError("Fetch: unable to get the P4 files from %s" % depot, ud.url) + def clean(self, ud, d): + """ Cleanup p4 specific files and dirs""" + bb.utils.remove(ud.localpath) + bb.utils.remove(ud.pkgdir, True) - count = 0 + def supports_srcrev(self): + return True - for file in p4file: - list = file.split() + def _revision_key(self, ud, d, name): + """ Return a unique key for the url """ + return 'p4:%s' % ud.pkgdir - if list[2] == "delete": - continue + def _latest_revision(self, ud, d, name): + """ Return the latest upstream scm revision number """ + p4cmd = self._buildp4command(ud, d, "changes") + bb.fetch2.check_network_access(d, p4cmd) + tip = runfetchcmd(p4cmd, d, True) + + if not tip: + raise FetchError('Could not determine the latest perforce changelist') - dest = list[0][len(path)+1:] - where = dest.find("#") + tipcset = tip.split(' ')[1] + logger.debug(1, 'p4 tip found to be changelist %s' % tipcset) + return tipcset - subprocess.call("%s%s print -o %s/%s %s" % (p4cmd, p4opt, module, dest[:where], list[0]), shell=True) - count = count + 1 + def sortable_revision(self, ud, d, name): + """ Return a sortable revision number """ + return False, self._build_revision(ud, d) - if count == 0: - logger.error() - raise FetchError("Fetch: No files gathered from the P4 fetch", ud.url) + def _build_revision(self, ud, d): + return ud.revision - runfetchcmd("tar -czf %s %s" % (ud.localpath, module), d, cleanup = [ud.localpath]) - # cleanup - bb.utils.prunedir(tmpfile) diff --git a/import-layers/yocto-poky/bitbake/lib/bb/fetch2/repo.py b/import-layers/yocto-poky/bitbake/lib/bb/fetch2/repo.py index 21678eb7d..ecc6e68e9 100644 --- a/import-layers/yocto-poky/bitbake/lib/bb/fetch2/repo.py +++ b/import-layers/yocto-poky/bitbake/lib/bb/fetch2/repo.py @@ -69,24 +69,23 @@ class Repo(FetchMethod): else: username = "" - bb.utils.mkdirhier(os.path.join(codir, "repo")) - os.chdir(os.path.join(codir, "repo")) - if not os.path.exists(os.path.join(codir, "repo", ".repo")): + repodir = os.path.join(codir, "repo") + bb.utils.mkdirhier(repodir) + if not os.path.exists(os.path.join(repodir, ".repo")): bb.fetch2.check_network_access(d, "repo init -m %s -b %s -u %s://%s%s%s" % (ud.manifest, ud.branch, ud.proto, username, ud.host, ud.path), ud.url) - runfetchcmd("repo init -m %s -b %s -u %s://%s%s%s" % (ud.manifest, ud.branch, ud.proto, username, ud.host, ud.path), d) + runfetchcmd("repo init -m %s -b %s -u %s://%s%s%s" % (ud.manifest, ud.branch, ud.proto, username, ud.host, ud.path), d, workdir=repodir) bb.fetch2.check_network_access(d, "repo sync %s" % ud.url, ud.url) - runfetchcmd("repo sync", d) - os.chdir(codir) + runfetchcmd("repo sync", d, workdir=repodir) scmdata = ud.parm.get("scmdata", "") if scmdata == "keep": tar_flags = "" else: - tar_flags = "--exclude '.repo' --exclude '.git'" + tar_flags = "--exclude='.repo' --exclude='.git'" # Create a cache - runfetchcmd("tar %s -czf %s %s" % (tar_flags, ud.localpath, os.path.join(".", "*") ), d) + runfetchcmd("tar %s -czf %s %s" % (tar_flags, ud.localpath, os.path.join(".", "*") ), d, workdir=codir) def supports_srcrev(self): return False diff --git a/import-layers/yocto-poky/bitbake/lib/bb/fetch2/sftp.py b/import-layers/yocto-poky/bitbake/lib/bb/fetch2/sftp.py index cb2f753a8..7989fccc7 100644 --- a/import-layers/yocto-poky/bitbake/lib/bb/fetch2/sftp.py +++ b/import-layers/yocto-poky/bitbake/lib/bb/fetch2/sftp.py @@ -61,8 +61,7 @@ SRC_URI = "sftp://user@host.example.com/dir/path.file.txt" import os import bb -import urllib -import commands +import urllib.request, urllib.parse, urllib.error from bb import data from bb.fetch2 import URI from bb.fetch2 import FetchMethod @@ -93,7 +92,7 @@ class SFTP(FetchMethod): else: ud.basename = os.path.basename(ud.path) - ud.localfile = data.expand(urllib.unquote(ud.basename), d) + ud.localfile = data.expand(urllib.parse.unquote(ud.basename), d) def download(self, ud, d): """Fetch urls""" @@ -121,8 +120,7 @@ class SFTP(FetchMethod): remote = '%s%s:%s' % (user, urlo.hostname, path) - cmd = '%s %s %s %s' % (basecmd, port, commands.mkarg(remote), - commands.mkarg(lpath)) + cmd = '%s %s %s %s' % (basecmd, port, remote, lpath) bb.fetch2.check_network_access(d, cmd, ud.url) runfetchcmd(cmd, d) diff --git a/import-layers/yocto-poky/bitbake/lib/bb/fetch2/ssh.py b/import-layers/yocto-poky/bitbake/lib/bb/fetch2/ssh.py index 635578a71..56f9b7eb3 100644 --- a/import-layers/yocto-poky/bitbake/lib/bb/fetch2/ssh.py +++ b/import-layers/yocto-poky/bitbake/lib/bb/fetch2/ssh.py @@ -114,12 +114,10 @@ class SSH(FetchMethod): fr = host fr += ':%s' % path - - import commands cmd = 'scp -B -r %s %s %s/' % ( portarg, - commands.mkarg(fr), - commands.mkarg(dldir) + fr, + dldir ) bb.fetch2.check_network_access(d, cmd, urldata.url) diff --git a/import-layers/yocto-poky/bitbake/lib/bb/fetch2/svn.py b/import-layers/yocto-poky/bitbake/lib/bb/fetch2/svn.py index 8a291935c..6ca79d35d 100644 --- a/import-layers/yocto-poky/bitbake/lib/bb/fetch2/svn.py +++ b/import-layers/yocto-poky/bitbake/lib/bb/fetch2/svn.py @@ -126,35 +126,32 @@ class Svn(FetchMethod): if os.access(os.path.join(ud.moddir, '.svn'), os.R_OK): svnupdatecmd = self._buildsvncommand(ud, d, "update") logger.info("Update " + ud.url) - # update sources there - os.chdir(ud.moddir) # We need to attempt to run svn upgrade first in case its an older working format try: - runfetchcmd(ud.basecmd + " upgrade", d) + runfetchcmd(ud.basecmd + " upgrade", d, workdir=ud.moddir) except FetchError: pass logger.debug(1, "Running %s", svnupdatecmd) bb.fetch2.check_network_access(d, svnupdatecmd, ud.url) - runfetchcmd(svnupdatecmd, d) + runfetchcmd(svnupdatecmd, d, workdir=ud.moddir) else: svnfetchcmd = self._buildsvncommand(ud, d, "fetch") logger.info("Fetch " + ud.url) # check out sources there bb.utils.mkdirhier(ud.pkgdir) - os.chdir(ud.pkgdir) logger.debug(1, "Running %s", svnfetchcmd) bb.fetch2.check_network_access(d, svnfetchcmd, ud.url) - runfetchcmd(svnfetchcmd, d) + runfetchcmd(svnfetchcmd, d, workdir=ud.pkgdir) scmdata = ud.parm.get("scmdata", "") if scmdata == "keep": tar_flags = "" else: - tar_flags = "--exclude '.svn'" + tar_flags = "--exclude='.svn'" - os.chdir(ud.pkgdir) # tar them up to a defined filename - runfetchcmd("tar %s -czf %s %s" % (tar_flags, ud.localpath, ud.path_spec), d, cleanup = [ud.localpath]) + runfetchcmd("tar %s -czf %s %s" % (tar_flags, ud.localpath, ud.path_spec), d, + cleanup=[ud.localpath], workdir=ud.pkgdir) def clean(self, ud, d): """ Clean SVN specific files and dirs """ diff --git a/import-layers/yocto-poky/bitbake/lib/bb/fetch2/wget.py b/import-layers/yocto-poky/bitbake/lib/bb/fetch2/wget.py index 8bc9e93ca..ecb946aa8 100644 --- a/import-layers/yocto-poky/bitbake/lib/bb/fetch2/wget.py +++ b/import-layers/yocto-poky/bitbake/lib/bb/fetch2/wget.py @@ -31,7 +31,8 @@ import subprocess import os import logging import bb -import urllib +import bb.progress +import urllib.request, urllib.parse, urllib.error from bb import data from bb.fetch2 import FetchMethod from bb.fetch2 import FetchError @@ -41,6 +42,27 @@ from bb.utils import export_proxies from bs4 import BeautifulSoup from bs4 import SoupStrainer +class WgetProgressHandler(bb.progress.LineFilterProgressHandler): + """ + Extract progress information from wget output. + Note: relies on --progress=dot (with -v or without -q/-nv) being + specified on the wget command line. + """ + def __init__(self, d): + super(WgetProgressHandler, self).__init__(d) + # Send an initial progress event so the bar gets shown + self._fire_progress(0) + + def writeline(self, line): + percs = re.findall(r'(\d+)%\s+([\d.]+[A-Z])', line) + if percs: + progress = int(percs[-1][0]) + rate = percs[-1][1] + '/s' + self.update(progress, rate) + return False + return True + + class Wget(FetchMethod): """Class to fetch urls via 'wget'""" def supports(self, ud, d): @@ -62,17 +84,19 @@ class Wget(FetchMethod): else: ud.basename = os.path.basename(ud.path) - ud.localfile = data.expand(urllib.unquote(ud.basename), d) + ud.localfile = data.expand(urllib.parse.unquote(ud.basename), d) if not ud.localfile: - ud.localfile = data.expand(urllib.unquote(ud.host + ud.path).replace("/", "."), d) + ud.localfile = data.expand(urllib.parse.unquote(ud.host + ud.path).replace("/", "."), d) - self.basecmd = d.getVar("FETCHCMD_wget", True) or "/usr/bin/env wget -t 2 -T 30 -nv --passive-ftp --no-check-certificate" + self.basecmd = d.getVar("FETCHCMD_wget", True) or "/usr/bin/env wget -t 2 -T 30 --passive-ftp --no-check-certificate" def _runwget(self, ud, d, command, quiet): + progresshandler = WgetProgressHandler(d) + logger.debug(2, "Fetching %s using command '%s'" % (ud.url, command)) bb.fetch2.check_network_access(d, command) - runfetchcmd(command, d, quiet) + runfetchcmd(command + ' --progress=dot -v', d, quiet, log=progresshandler) def download(self, ud, d): """Fetch urls""" @@ -84,6 +108,10 @@ class Wget(FetchMethod): bb.utils.mkdirhier(os.path.dirname(dldir + os.sep + ud.localfile)) fetchcmd += " -O " + dldir + os.sep + ud.localfile + if ud.user: + up = ud.user.split(":") + fetchcmd += " --user=%s --password=%s --auth-no-challenge" % (up[0],up[1]) + uri = ud.url.split(";")[0] if os.path.exists(ud.localpath): # file exists, but we didnt complete it.. trying again.. @@ -104,12 +132,12 @@ class Wget(FetchMethod): return True - def checkstatus(self, fetch, ud, d): - import urllib2, socket, httplib - from urllib import addinfourl + def checkstatus(self, fetch, ud, d, try_again=True): + import urllib.request, urllib.error, urllib.parse, socket, http.client + from urllib.response import addinfourl from bb.fetch2 import FetchConnectionCache - class HTTPConnectionCache(httplib.HTTPConnection): + class HTTPConnectionCache(http.client.HTTPConnection): if fetch.connection_cache: def connect(self): """Connect to the host and port specified in __init__.""" @@ -125,7 +153,7 @@ class Wget(FetchMethod): if self._tunnel_host: self._tunnel() - class CacheHTTPHandler(urllib2.HTTPHandler): + class CacheHTTPHandler(urllib.request.HTTPHandler): def http_open(self, req): return self.do_open(HTTPConnectionCache, req) @@ -139,7 +167,7 @@ class Wget(FetchMethod): - geturl(): return the original request URL - code: HTTP status code """ - host = req.get_host() + host = req.host if not host: raise urlllib2.URLError('no host given') @@ -147,7 +175,7 @@ class Wget(FetchMethod): h.set_debuglevel(self._debuglevel) headers = dict(req.unredirected_hdrs) - headers.update(dict((k, v) for k, v in req.headers.items() + headers.update(dict((k, v) for k, v in list(req.headers.items()) if k not in headers)) # We want to make an HTTP/1.1 request, but the addinfourl @@ -164,7 +192,7 @@ class Wget(FetchMethod): headers["Connection"] = "Keep-Alive" # Works for HTTP/1.0 headers = dict( - (name.title(), val) for name, val in headers.items()) + (name.title(), val) for name, val in list(headers.items())) if req._tunnel_host: tunnel_headers = {} @@ -177,12 +205,12 @@ class Wget(FetchMethod): h.set_tunnel(req._tunnel_host, headers=tunnel_headers) try: - h.request(req.get_method(), req.get_selector(), req.data, headers) - except socket.error, err: # XXX what error? + h.request(req.get_method(), req.selector, req.data, headers) + except socket.error as err: # XXX what error? # Don't close connection when cache is enabled. if fetch.connection_cache is None: h.close() - raise urllib2.URLError(err) + raise urllib.error.URLError(err) else: try: r = h.getresponse(buffering=True) @@ -222,7 +250,7 @@ class Wget(FetchMethod): return resp - class HTTPMethodFallback(urllib2.BaseHandler): + class HTTPMethodFallback(urllib.request.BaseHandler): """ Fallback to GET if HEAD is not allowed (405 HTTP error) """ @@ -230,11 +258,11 @@ class Wget(FetchMethod): fp.read() fp.close() - newheaders = dict((k,v) for k,v in req.headers.items() + newheaders = dict((k,v) for k,v in list(req.headers.items()) if k.lower() not in ("content-length", "content-type")) - return self.parent.open(urllib2.Request(req.get_full_url(), + return self.parent.open(urllib.request.Request(req.get_full_url(), headers=newheaders, - origin_req_host=req.get_origin_req_host(), + origin_req_host=req.origin_req_host, unverifiable=True)) """ @@ -249,38 +277,49 @@ class Wget(FetchMethod): """ http_error_406 = http_error_405 - class FixedHTTPRedirectHandler(urllib2.HTTPRedirectHandler): + class FixedHTTPRedirectHandler(urllib.request.HTTPRedirectHandler): """ urllib2.HTTPRedirectHandler resets the method to GET on redirect, when we want to follow redirects using the original method. """ def redirect_request(self, req, fp, code, msg, headers, newurl): - newreq = urllib2.HTTPRedirectHandler.redirect_request(self, req, fp, code, msg, headers, newurl) + newreq = urllib.request.HTTPRedirectHandler.redirect_request(self, req, fp, code, msg, headers, newurl) newreq.get_method = lambda: req.get_method() return newreq exported_proxies = export_proxies(d) handlers = [FixedHTTPRedirectHandler, HTTPMethodFallback] if export_proxies: - handlers.append(urllib2.ProxyHandler()) + handlers.append(urllib.request.ProxyHandler()) handlers.append(CacheHTTPHandler()) # XXX: Since Python 2.7.9 ssl cert validation is enabled by default # see PEP-0476, this causes verification errors on some https servers # so disable by default. import ssl if hasattr(ssl, '_create_unverified_context'): - handlers.append(urllib2.HTTPSHandler(context=ssl._create_unverified_context())) - opener = urllib2.build_opener(*handlers) + handlers.append(urllib.request.HTTPSHandler(context=ssl._create_unverified_context())) + opener = urllib.request.build_opener(*handlers) try: uri = ud.url.split(";")[0] - r = urllib2.Request(uri) + r = urllib.request.Request(uri) r.get_method = lambda: "HEAD" + + if ud.user: + import base64 + encodeuser = base64.b64encode(ud.user.encode('utf-8')).decode("utf-8") + authheader = "Basic %s" % encodeuser + r.add_header("Authorization", authheader) + opener.open(r) - except urllib2.URLError as e: - # debug for now to avoid spamming the logs in e.g. remote sstate searches - logger.debug(2, "checkstatus() urlopen failed: %s" % e) - return False + except urllib.error.URLError as e: + if try_again: + logger.debug(2, "checkstatus: trying again") + return self.checkstatus(fetch, ud, d, False) + else: + # debug for now to avoid spamming the logs in e.g. remote sstate searches + logger.debug(2, "checkstatus() urlopen failed: %s" % e) + return False return True def _parse_path(self, regex, s): diff --git a/import-layers/yocto-poky/bitbake/lib/bb/main.py b/import-layers/yocto-poky/bitbake/lib/bb/main.py index e30217369..f2f59f670 100755 --- a/import-layers/yocto-poky/bitbake/lib/bb/main.py +++ b/import-layers/yocto-poky/bitbake/lib/bb/main.py @@ -27,6 +27,7 @@ import sys import logging import optparse import warnings +import fcntl import bb from bb import event @@ -100,11 +101,12 @@ def import_extension_module(pkg, modulename, checkattr): # Dynamically load the UI based on the ui name. Although we # suggest a fixed set this allows you to have flexibility in which # ones are available. - module = __import__(pkg.__name__, fromlist = [modulename]) + module = __import__(pkg.__name__, fromlist=[modulename]) return getattr(module, modulename) except AttributeError: - raise BBMainException('FATAL: Unable to import extension module "%s" from %s. Valid extension modules: %s' % (modulename, pkg.__name__, present_options(list_extension_modules(pkg, checkattr)))) - + modules = present_options(list_extension_modules(pkg, checkattr)) + raise BBMainException('FATAL: Unable to import extension module "%s" from %s. ' + 'Valid extension modules: %s' % (modulename, pkg.__name__, modules)) # Display bitbake/OE warnings via the BitBake.Warnings logger, ignoring others""" warnlog = logging.getLogger("BitBake.Warnings") @@ -115,7 +117,7 @@ def _showwarning(message, category, filename, lineno, file=None, line=None): _warnings_showwarning(message, category, filename, lineno, file, line) else: s = warnings.formatwarning(message, category, filename, lineno) - warnlog.warn(s) + warnlog.warning(s) warnings.showwarning = _showwarning warnings.filterwarnings("ignore") @@ -129,136 +131,189 @@ class BitBakeConfigParameters(cookerdata.ConfigParameters): def parseCommandLine(self, argv=sys.argv): parser = optparse.OptionParser( - formatter = BitbakeHelpFormatter(), - version = "BitBake Build Tool Core version %s" % bb.__version__, - usage = """%prog [options] [recipename/target recipe:do_task ...] + formatter=BitbakeHelpFormatter(), + version="BitBake Build Tool Core version %s" % bb.__version__, + usage="""%prog [options] [recipename/target recipe:do_task ...] Executes the specified task (default is 'build') for a given set of target recipes (.bb files). It is assumed there is a conf/bblayers.conf available in cwd or in BBPATH which will provide the layer, BBFILES and other configuration information.""") - parser.add_option("-b", "--buildfile", help = "Execute tasks from a specific .bb recipe directly. WARNING: Does not handle any dependencies from other recipes.", - action = "store", dest = "buildfile", default = None) + parser.add_option("-b", "--buildfile", action="store", dest="buildfile", default=None, + help="Execute tasks from a specific .bb recipe directly. WARNING: Does " + "not handle any dependencies from other recipes.") + + parser.add_option("-k", "--continue", action="store_false", dest="abort", default=True, + help="Continue as much as possible after an error. While the target that " + "failed and anything depending on it cannot be built, as much as " + "possible will be built before stopping.") - parser.add_option("-k", "--continue", help = "Continue as much as possible after an error. While the target that failed and anything depending on it cannot be built, as much as possible will be built before stopping.", - action = "store_false", dest = "abort", default = True) + parser.add_option("-a", "--tryaltconfigs", action="store_true", + dest="tryaltconfigs", default=False, + help="Continue with builds by trying to use alternative providers " + "where possible.") - parser.add_option("-a", "--tryaltconfigs", help = "Continue with builds by trying to use alternative providers where possible.", - action = "store_true", dest = "tryaltconfigs", default = False) + parser.add_option("-f", "--force", action="store_true", dest="force", default=False, + help="Force the specified targets/task to run (invalidating any " + "existing stamp file).") - parser.add_option("-f", "--force", help = "Force the specified targets/task to run (invalidating any existing stamp file).", - action = "store_true", dest = "force", default = False) + parser.add_option("-c", "--cmd", action="store", dest="cmd", + help="Specify the task to execute. The exact options available " + "depend on the metadata. Some examples might be 'compile'" + " or 'populate_sysroot' or 'listtasks' may give a list of " + "the tasks available.") - parser.add_option("-c", "--cmd", help = "Specify the task to execute. The exact options available depend on the metadata. Some examples might be 'compile' or 'populate_sysroot' or 'listtasks' may give a list of the tasks available.", - action = "store", dest = "cmd") + parser.add_option("-C", "--clear-stamp", action="store", dest="invalidate_stamp", + help="Invalidate the stamp for the specified task such as 'compile' " + "and then run the default task for the specified target(s).") - parser.add_option("-C", "--clear-stamp", help = "Invalidate the stamp for the specified task such as 'compile' and then run the default task for the specified target(s).", - action = "store", dest = "invalidate_stamp") + parser.add_option("-r", "--read", action="append", dest="prefile", default=[], + help="Read the specified file before bitbake.conf.") - parser.add_option("-r", "--read", help = "Read the specified file before bitbake.conf.", - action = "append", dest = "prefile", default = []) + parser.add_option("-R", "--postread", action="append", dest="postfile", default=[], + help="Read the specified file after bitbake.conf.") - parser.add_option("-R", "--postread", help = "Read the specified file after bitbake.conf.", - action = "append", dest = "postfile", default = []) + parser.add_option("-v", "--verbose", action="store_true", dest="verbose", default=False, + help="Output more log message data to the terminal.") - parser.add_option("-v", "--verbose", help = "Output more log message data to the terminal.", - action = "store_true", dest = "verbose", default = False) + parser.add_option("-D", "--debug", action="count", dest="debug", default=0, + help="Increase the debug level. You can specify this more than once.") - parser.add_option("-D", "--debug", help = "Increase the debug level. You can specify this more than once.", - action = "count", dest="debug", default = 0) + parser.add_option("-q", "--quiet", action="store_true", dest="quiet", default=False, + help="Output less log message data to the terminal.") - parser.add_option("-n", "--dry-run", help = "Don't execute, just go through the motions.", - action = "store_true", dest = "dry_run", default = False) + parser.add_option("-n", "--dry-run", action="store_true", dest="dry_run", default=False, + help="Don't execute, just go through the motions.") - parser.add_option("-S", "--dump-signatures", help = "Dump out the signature construction information, with no task execution. The SIGNATURE_HANDLER parameter is passed to the handler. Two common values are none and printdiff but the handler may define more/less. none means only dump the signature, printdiff means compare the dumped signature with the cached one.", - action = "append", dest = "dump_signatures", default = [], metavar="SIGNATURE_HANDLER") + parser.add_option("-S", "--dump-signatures", action="append", dest="dump_signatures", + default=[], metavar="SIGNATURE_HANDLER", + help="Dump out the signature construction information, with no task " + "execution. The SIGNATURE_HANDLER parameter is passed to the " + "handler. Two common values are none and printdiff but the handler " + "may define more/less. none means only dump the signature, printdiff" + " means compare the dumped signature with the cached one.") - parser.add_option("-p", "--parse-only", help = "Quit after parsing the BB recipes.", - action = "store_true", dest = "parse_only", default = False) + parser.add_option("-p", "--parse-only", action="store_true", + dest="parse_only", default=False, + help="Quit after parsing the BB recipes.") - parser.add_option("-s", "--show-versions", help = "Show current and preferred versions of all recipes.", - action = "store_true", dest = "show_versions", default = False) + parser.add_option("-s", "--show-versions", action="store_true", + dest="show_versions", default=False, + help="Show current and preferred versions of all recipes.") - parser.add_option("-e", "--environment", help = "Show the global or per-recipe environment complete with information about where variables were set/changed.", - action = "store_true", dest = "show_environment", default = False) + parser.add_option("-e", "--environment", action="store_true", + dest="show_environment", default=False, + help="Show the global or per-recipe environment complete with information" + " about where variables were set/changed.") - parser.add_option("-g", "--graphviz", help = "Save dependency tree information for the specified targets in the dot syntax.", - action = "store_true", dest = "dot_graph", default = False) + parser.add_option("-g", "--graphviz", action="store_true", dest="dot_graph", default=False, + help="Save dependency tree information for the specified " + "targets in the dot syntax.") - parser.add_option("-I", "--ignore-deps", help = """Assume these dependencies don't exist and are already provided (equivalent to ASSUME_PROVIDED). Useful to make dependency graphs more appealing""", - action = "append", dest = "extra_assume_provided", default = []) + parser.add_option("-I", "--ignore-deps", action="append", + dest="extra_assume_provided", default=[], + help="Assume these dependencies don't exist and are already provided " + "(equivalent to ASSUME_PROVIDED). Useful to make dependency " + "graphs more appealing") - parser.add_option("-l", "--log-domains", help = """Show debug logging for the specified logging domains""", - action = "append", dest = "debug_domains", default = []) + parser.add_option("-l", "--log-domains", action="append", dest="debug_domains", default=[], + help="Show debug logging for the specified logging domains") - parser.add_option("-P", "--profile", help = "Profile the command and save reports.", - action = "store_true", dest = "profile", default = False) + parser.add_option("-P", "--profile", action="store_true", dest="profile", default=False, + help="Profile the command and save reports.") - env_ui = os.environ.get('BITBAKE_UI', None) - default_ui = env_ui or 'knotty' # @CHOICES@ is substituted out by BitbakeHelpFormatter above - parser.add_option("-u", "--ui", help = "The user interface to use (@CHOICES@ - default %default).", - action="store", dest="ui", default=default_ui) + parser.add_option("-u", "--ui", action="store", dest="ui", + default=os.environ.get('BITBAKE_UI', 'knotty'), + help="The user interface to use (@CHOICES@ - default %default).") # @CHOICES@ is substituted out by BitbakeHelpFormatter above - parser.add_option("-t", "--servertype", help = "Choose which server type to use (@CHOICES@ - default %default).", - action = "store", dest = "servertype", default = "process") + parser.add_option("-t", "--servertype", action="store", dest="servertype", + default=["process", "xmlrpc"]["BBSERVER" in os.environ], + help="Choose which server type to use (@CHOICES@ - default %default).") + + parser.add_option("", "--token", action="store", dest="xmlrpctoken", + default=os.environ.get("BBTOKEN"), + help="Specify the connection token to be used when connecting " + "to a remote server.") - parser.add_option("", "--token", help = "Specify the connection token to be used when connecting to a remote server.", - action = "store", dest = "xmlrpctoken") + parser.add_option("", "--revisions-changed", action="store_true", + dest="revisions_changed", default=False, + help="Set the exit code depending on whether upstream floating " + "revisions have changed or not.") - parser.add_option("", "--revisions-changed", help = "Set the exit code depending on whether upstream floating revisions have changed or not.", - action = "store_true", dest = "revisions_changed", default = False) + parser.add_option("", "--server-only", action="store_true", + dest="server_only", default=False, + help="Run bitbake without a UI, only starting a server " + "(cooker) process.") - parser.add_option("", "--server-only", help = "Run bitbake without a UI, only starting a server (cooker) process.", - action = "store_true", dest = "server_only", default = False) + parser.add_option("", "--foreground", action="store_true", + help="Run bitbake server in foreground.") - parser.add_option("-B", "--bind", help = "The name/address for the bitbake server to bind to.", - action = "store", dest = "bind", default = False) + parser.add_option("-B", "--bind", action="store", dest="bind", default=False, + help="The name/address for the bitbake server to bind to.") - parser.add_option("", "--no-setscene", help = "Do not run any setscene tasks. sstate will be ignored and everything needed, built.", - action = "store_true", dest = "nosetscene", default = False) + parser.add_option("-T", "--idle-timeout", type=int, + default=int(os.environ.get("BBTIMEOUT", "0")), + help="Set timeout to unload bitbake server due to inactivity") - parser.add_option("", "--setscene-only", help = "Only run setscene tasks, don't run any real tasks.", - action = "store_true", dest = "setsceneonly", default = False) + parser.add_option("", "--no-setscene", action="store_true", + dest="nosetscene", default=False, + help="Do not run any setscene tasks. sstate will be ignored and " + "everything needed, built.") - parser.add_option("", "--remote-server", help = "Connect to the specified server.", - action = "store", dest = "remote_server", default = False) + parser.add_option("", "--setscene-only", action="store_true", + dest="setsceneonly", default=False, + help="Only run setscene tasks, don't run any real tasks.") - parser.add_option("-m", "--kill-server", help = "Terminate the remote server.", - action = "store_true", dest = "kill_server", default = False) + parser.add_option("", "--remote-server", action="store", dest="remote_server", + default=os.environ.get("BBSERVER"), + help="Connect to the specified server.") - parser.add_option("", "--observe-only", help = "Connect to a server as an observing-only client.", - action = "store_true", dest = "observe_only", default = False) + parser.add_option("-m", "--kill-server", action="store_true", + dest="kill_server", default=False, + help="Terminate the remote server.") - parser.add_option("", "--status-only", help = "Check the status of the remote bitbake server.", - action = "store_true", dest = "status_only", default = False) + parser.add_option("", "--observe-only", action="store_true", + dest="observe_only", default=False, + help="Connect to a server as an observing-only client.") - parser.add_option("-w", "--write-log", help = "Writes the event log of the build to a bitbake event json file. Use '' (empty string) to assign the name automatically.", - action = "store", dest = "writeeventlog") + parser.add_option("", "--status-only", action="store_true", + dest="status_only", default=False, + help="Check the status of the remote bitbake server.") + + parser.add_option("-w", "--write-log", action="store", dest="writeeventlog", + default=os.environ.get("BBEVENTLOG"), + help="Writes the event log of the build to a bitbake event json file. " + "Use '' (empty string) to assign the name automatically.") options, targets = parser.parse_args(argv) - # some environmental variables set also configuration options - if "BBSERVER" in os.environ: - options.servertype = "xmlrpc" - options.remote_server = os.environ["BBSERVER"] + if options.quiet and options.verbose: + parser.error("options --quiet and --verbose are mutually exclusive") + + if options.quiet and options.debug: + parser.error("options --quiet and --debug are mutually exclusive") - if "BBTOKEN" in os.environ: - options.xmlrpctoken = os.environ["BBTOKEN"] + # use configuration files from environment variables + if "BBPRECONF" in os.environ: + options.prefile.append(os.environ["BBPRECONF"]) - if "BBEVENTLOG" in os.environ: - options.writeeventlog = os.environ["BBEVENTLOG"] + if "BBPOSTCONF" in os.environ: + options.postfile.append(os.environ["BBPOSTCONF"]) # fill in proper log name if not supplied if options.writeeventlog is not None and len(options.writeeventlog) == 0: - import datetime - options.writeeventlog = "bitbake_eventlog_%s.json" % datetime.datetime.now().strftime("%Y%m%d%H%M%S") + from datetime import datetime + eventlog = "bitbake_eventlog_%s.json" % datetime.now().strftime("%Y%m%d%H%M%S") + options.writeeventlog = eventlog # if BBSERVER says to autodetect, let's do that if options.remote_server: - [host, port] = options.remote_server.split(":", 2) - port = int(port) + port = -1 + if options.remote_server != 'autostart': + host, port = options.remote_server.split(":", 2) + port = int(port) # use automatic port if port set to -1, means read it from # the bitbake.lock file; this is a bit tricky, but we always expect # to be in the base of the build directory if we need to have a @@ -275,18 +330,20 @@ class BitBakeConfigParameters(cookerdata.ConfigParameters): lf.close() options.remote_server = remotedef except Exception as e: - raise BBMainException("Failed to read bitbake.lock (%s), invalid port" % str(e)) + if options.remote_server != 'autostart': + raise BBMainException("Failed to read bitbake.lock (%s), invalid port" % str(e)) return options, targets[1:] def start_server(servermodule, configParams, configuration, features): server = servermodule.BitBakeServer() - single_use = not configParams.server_only + single_use = not configParams.server_only and os.getenv('BBSERVER') != 'autostart' if configParams.bind: (host, port) = configParams.bind.split(':') - server.initServer((host, int(port)), single_use) - configuration.interface = [ server.serverImpl.host, server.serverImpl.port ] + server.initServer((host, int(port)), single_use=single_use, + idle_timeout=configParams.idle_timeout) + configuration.interface = [server.serverImpl.host, server.serverImpl.port] else: server.initServer(single_use=single_use) configuration.interface = [] @@ -299,20 +356,17 @@ def start_server(servermodule, configParams, configuration, features): server.addcooker(cooker) server.saveConnectionDetails() except Exception as e: - exc_info = sys.exc_info() while hasattr(server, "event_queue"): - try: - import queue - except ImportError: - import Queue as queue + import queue try: event = server.event_queue.get(block=False) except (queue.Empty, IOError): break if isinstance(event, logging.LogRecord): logger.handle(event) - raise exc_info[1], None, exc_info[2] - server.detach() + raise + if not configParams.foreground: + server.detach() cooker.lock.close() return server @@ -328,7 +382,10 @@ def bitbake_main(configParams, configuration): # updates to log files for use with tail try: if sys.stdout.name == '<stdout>': - sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0) + # Reopen with O_SYNC (unbuffered) + fl = fcntl.fcntl(sys.stdout.fileno(), fcntl.F_GETFL) + fl |= os.O_SYNC + fcntl.fcntl(sys.stdout.fileno(), fcntl.F_SETFL, fl) except: pass @@ -345,10 +402,21 @@ def bitbake_main(configParams, configuration): if not configParams.bind: raise BBMainException("FATAL: The '--server-only' option requires a name/address " "to bind to with the -B option.\n") + else: + try: + #Checking that the port is a number + int(configParams.bind.split(":")[1]) + except (ValueError,IndexError): + raise BBMainException( + "FATAL: Malformed host:port bind parameter") if configParams.remote_server: raise BBMainException("FATAL: The '--server-only' option conflicts with %s.\n" % ("the BBSERVER environment variable" if "BBSERVER" in os.environ \ - else "the '--remote-server' option" )) + else "the '--remote-server' option")) + + elif configParams.foreground: + raise BBMainException("FATAL: The '--foreground' option can only be used " + "with --server-only.\n") if configParams.bind and configParams.servertype != "xmlrpc": raise BBMainException("FATAL: If '-B' or '--bind' is defined, we must " @@ -363,7 +431,8 @@ def bitbake_main(configParams, configuration): "connecting to a server.\n") if configParams.kill_server and not configParams.remote_server: - raise BBMainException("FATAL: '--kill-server' can only be used to terminate a remote server") + raise BBMainException("FATAL: '--kill-server' can only be used to " + "terminate a remote server") if "BBDEBUG" in os.environ: level = int(os.environ["BBDEBUG"]) @@ -371,7 +440,7 @@ def bitbake_main(configParams, configuration): configuration.debug = level bb.msg.init_msgconfig(configParams.verbose, configuration.debug, - configuration.debug_domains) + configuration.debug_domains) # Ensure logging messages get sent to the UI as events handler = bb.event.LogHandler() @@ -399,8 +468,17 @@ def bitbake_main(configParams, configuration): server = start_server(servermodule, configParams, configuration, featureset) bb.event.ui_queue = [] else: + if os.getenv('BBSERVER') == 'autostart': + if configParams.remote_server == 'autostart' or \ + not servermodule.check_connection(configParams.remote_server, timeout=2): + configParams.bind = 'localhost:0' + srv = start_server(servermodule, configParams, configuration, featureset) + configParams.remote_server = '%s:%d' % tuple(configuration.interface) + bb.event.ui_queue = [] + # we start a stub server that is actually a XMLRPClient that connects to a real server - server = servermodule.BitBakeXMLRPCClient(configParams.observe_only, configParams.xmlrpctoken) + server = servermodule.BitBakeXMLRPCClient(configParams.observe_only, + configParams.xmlrpctoken) server.saveConnectionDetails(configParams.remote_server) @@ -429,12 +507,16 @@ def bitbake_main(configParams, configuration): return 0 try: - return ui_module.main(server_connection.connection, server_connection.events, configParams) + return ui_module.main(server_connection.connection, server_connection.events, + configParams) finally: bb.event.ui_queue = [] server_connection.terminate() else: - print("Bitbake server address: %s, server port: %s" % (server.serverImpl.host, server.serverImpl.port)) + print("Bitbake server address: %s, server port: %s" % (server.serverImpl.host, + server.serverImpl.port)) + if configParams.foreground: + server.serverImpl.serve_forever() return 0 return 1 diff --git a/import-layers/yocto-poky/bitbake/lib/bb/monitordisk.py b/import-layers/yocto-poky/bitbake/lib/bb/monitordisk.py index 466523c6e..203c40504 100644 --- a/import-layers/yocto-poky/bitbake/lib/bb/monitordisk.py +++ b/import-layers/yocto-poky/bitbake/lib/bb/monitordisk.py @@ -220,7 +220,7 @@ class diskMonitor: if minSpace and freeSpace < minSpace: # Always show warning, the self.checked would always be False if the action is WARN if self.preFreeS[k] == 0 or self.preFreeS[k] - freeSpace > self.spaceInterval and not self.checked[k]: - logger.warn("The free space of %s (%s) is running low (%.3fGB left)" % \ + logger.warning("The free space of %s (%s) is running low (%.3fGB left)" % \ (path, dev, freeSpace / 1024 / 1024 / 1024.0)) self.preFreeS[k] = freeSpace @@ -246,7 +246,7 @@ class diskMonitor: continue # Always show warning, the self.checked would always be False if the action is WARN if self.preFreeI[k] == 0 or self.preFreeI[k] - freeInode > self.inodeInterval and not self.checked[k]: - logger.warn("The free inode of %s (%s) is running low (%.3fK left)" % \ + logger.warning("The free inode of %s (%s) is running low (%.3fK left)" % \ (path, dev, freeInode / 1024.0)) self.preFreeI[k] = freeInode diff --git a/import-layers/yocto-poky/bitbake/lib/bb/msg.py b/import-layers/yocto-poky/bitbake/lib/bb/msg.py index 786b5aef4..b7c39fa13 100644 --- a/import-layers/yocto-poky/bitbake/lib/bb/msg.py +++ b/import-layers/yocto-poky/bitbake/lib/bb/msg.py @@ -57,7 +57,7 @@ class BBLogFormatter(logging.Formatter): } color_enabled = False - BASECOLOR, BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(29,38) + BASECOLOR, BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = list(range(29,38)) COLORS = { DEBUG3 : CYAN, @@ -90,8 +90,9 @@ class BBLogFormatter(logging.Formatter): if self.color_enabled: record = self.colorize(record) msg = logging.Formatter.format(self, record) - - if hasattr(record, 'bb_exc_info'): + if hasattr(record, 'bb_exc_formatted'): + msg += '\n' + ''.join(record.bb_exc_formatted) + elif hasattr(record, 'bb_exc_info'): etype, value, tb = record.bb_exc_info formatted = bb.exceptions.format_exception(etype, value, tb, limit=5) msg += '\n' + ''.join(formatted) @@ -181,9 +182,12 @@ def constructLogOptions(): debug_domains["BitBake.%s" % domainarg] = logging.DEBUG - dlevel + 1 return level, debug_domains -def addDefaultlogFilter(handler, cls = BBLogFilter): +def addDefaultlogFilter(handler, cls = BBLogFilter, forcelevel=None): level, debug_domains = constructLogOptions() + if forcelevel is not None: + level = forcelevel + cls(handler, level, debug_domains) # diff --git a/import-layers/yocto-poky/bitbake/lib/bb/parse/ast.py b/import-layers/yocto-poky/bitbake/lib/bb/parse/ast.py index 5f55af5ef..fa83b1898 100644 --- a/import-layers/yocto-poky/bitbake/lib/bb/parse/ast.py +++ b/import-layers/yocto-poky/bitbake/lib/bb/parse/ast.py @@ -21,8 +21,7 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -from __future__ import absolute_import -from future_builtins import filter + import re import string import logging @@ -70,6 +69,33 @@ class ExportNode(AstNode): def eval(self, data): data.setVarFlag(self.var, "export", 1, op = 'exported') +class UnsetNode(AstNode): + def __init__(self, filename, lineno, var): + AstNode.__init__(self, filename, lineno) + self.var = var + + def eval(self, data): + loginfo = { + 'variable': self.var, + 'file': self.filename, + 'line': self.lineno, + } + data.delVar(self.var,**loginfo) + +class UnsetFlagNode(AstNode): + def __init__(self, filename, lineno, var, flag): + AstNode.__init__(self, filename, lineno) + self.var = var + self.flag = flag + + def eval(self, data): + loginfo = { + 'variable': self.var, + 'file': self.filename, + 'line': self.lineno, + } + data.delVarFlag(self.var, self.flag, **loginfo) + class DataNode(AstNode): """ Various data related updates. For the sake of sanity @@ -139,7 +165,7 @@ class DataNode(AstNode): data.setVar(key, val, parsing=True, **loginfo) class MethodNode(AstNode): - tr_tbl = string.maketrans('/.+-@%&', '_______') + tr_tbl = str.maketrans('/.+-@%&', '_______') def __init__(self, filename, lineno, func_name, body, python, fakeroot): AstNode.__init__(self, filename, lineno) @@ -271,6 +297,12 @@ def handleInclude(statements, filename, lineno, m, force): def handleExport(statements, filename, lineno, m): statements.append(ExportNode(filename, lineno, m.group(1))) +def handleUnset(statements, filename, lineno, m): + statements.append(UnsetNode(filename, lineno, m.group(1))) + +def handleUnsetFlag(statements, filename, lineno, m): + statements.append(UnsetFlagNode(filename, lineno, m.group(1), m.group(2))) + def handleData(statements, filename, lineno, groupd): statements.append(DataNode(filename, lineno, groupd)) @@ -307,10 +339,13 @@ def handleInherit(statements, filename, lineno, m): statements.append(InheritNode(filename, lineno, classes)) def finalize(fn, d, variant = None): - all_handlers = {} + saved_handlers = bb.event.get_handlers().copy() + for var in d.getVar('__BBHANDLERS', False) or []: # try to add the handler handlerfn = d.getVarFlag(var, "filename", False) + if not handlerfn: + bb.fatal("Undefined event handler function '%s'" % var) handlerln = int(d.getVarFlag(var, "lineno", False)) bb.event.register(var, d.getVar(var, False), (d.getVarFlag(var, "eventmask", True) or "").split(), handlerfn, handlerln) @@ -332,6 +367,7 @@ def finalize(fn, d, variant = None): d.setVar('BBINCLUDED', bb.parse.get_file_depends(d)) bb.event.fire(bb.event.RecipeParsed(fn), d) + bb.event.set_handlers(saved_handlers) def _create_variants(datastores, names, function, onlyfinalise): def create_variant(name, orig_d, arg = None): @@ -341,17 +377,17 @@ def _create_variants(datastores, names, function, onlyfinalise): function(arg or name, new_d) datastores[name] = new_d - for variant, variant_d in datastores.items(): + for variant in list(datastores.keys()): for name in names: if not variant: # Based on main recipe - create_variant(name, variant_d) + create_variant(name, datastores[""]) else: - create_variant("%s-%s" % (variant, name), variant_d, name) + create_variant("%s-%s" % (variant, name), datastores[variant], name) def _expand_versions(versions): def expand_one(version, start, end): - for i in xrange(start, end + 1): + for i in range(start, end + 1): ver = _bbversions_re.sub(str(i), version, 1) yield ver @@ -460,17 +496,13 @@ def multi_finalize(fn, d): safe_d.setVar("BBCLASSEXTEND", extended) _create_variants(datastores, extendedmap.keys(), extendfunc, onlyfinalise) - for variant, variant_d in datastores.iteritems(): + for variant in datastores.keys(): if variant: try: if not onlyfinalise or variant in onlyfinalise: - finalize(fn, variant_d, variant) + finalize(fn, datastores[variant], variant) except bb.parse.SkipRecipe as e: - variant_d.setVar("__SKIPPED", e.args[0]) - - if len(datastores) > 1: - variants = filter(None, datastores.iterkeys()) - safe_d.setVar("__VARIANTS", " ".join(variants)) + datastores[variant].setVar("__SKIPPED", e.args[0]) datastores[""] = d return datastores diff --git a/import-layers/yocto-poky/bitbake/lib/bb/parse/parse_py/BBHandler.py b/import-layers/yocto-poky/bitbake/lib/bb/parse/parse_py/BBHandler.py index ef72c3700..c54a07979 100644 --- a/import-layers/yocto-poky/bitbake/lib/bb/parse/parse_py/BBHandler.py +++ b/import-layers/yocto-poky/bitbake/lib/bb/parse/parse_py/BBHandler.py @@ -25,7 +25,7 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -from __future__ import absolute_import + import re, bb, os import logging import bb.build, bb.utils diff --git a/import-layers/yocto-poky/bitbake/lib/bb/parse/parse_py/ConfHandler.py b/import-layers/yocto-poky/bitbake/lib/bb/parse/parse_py/ConfHandler.py index fbd75b14a..875250de4 100644 --- a/import-layers/yocto-poky/bitbake/lib/bb/parse/parse_py/ConfHandler.py +++ b/import-layers/yocto-poky/bitbake/lib/bb/parse/parse_py/ConfHandler.py @@ -57,6 +57,8 @@ __config_regexp__ = re.compile( r""" __include_regexp__ = re.compile( r"include\s+(.+)" ) __require_regexp__ = re.compile( r"require\s+(.+)" ) __export_regexp__ = re.compile( r"export\s+([a-zA-Z0-9\-_+.${}/]+)$" ) +__unset_regexp__ = re.compile( r"unset\s+([a-zA-Z0-9\-_+.${}/]+)$" ) +__unset_flag_regexp__ = re.compile( r"unset\s+([a-zA-Z0-9\-_+.${}/]+)\[([a-zA-Z0-9\-_+.${}/]+)\]$" ) def init(data): topdir = data.getVar('TOPDIR', False) @@ -84,13 +86,13 @@ def include(parentfn, fn, lineno, data, error_out): bbpath = "%s:%s" % (dname, data.getVar("BBPATH", True)) abs_fn, attempts = bb.utils.which(bbpath, fn, history=True) if abs_fn and bb.parse.check_dependency(data, abs_fn): - logger.warn("Duplicate inclusion for %s in %s" % (abs_fn, data.getVar('FILE', True))) + logger.warning("Duplicate inclusion for %s in %s" % (abs_fn, data.getVar('FILE', True))) for af in attempts: bb.parse.mark_dependency(data, af) if abs_fn: fn = abs_fn elif bb.parse.check_dependency(data, fn): - logger.warn("Duplicate inclusion for %s in %s" % (fn, data.getVar('FILE', True))) + logger.warning("Duplicate inclusion for %s in %s" % (fn, data.getVar('FILE', True))) try: bb.parse.handle(fn, data, True) @@ -185,6 +187,16 @@ def feeder(lineno, s, fn, statements): ast.handleExport(statements, fn, lineno, m) return + m = __unset_regexp__.match(s) + if m: + ast.handleUnset(statements, fn, lineno, m) + return + + m = __unset_flag_regexp__.match(s) + if m: + ast.handleUnsetFlag(statements, fn, lineno, m) + return + raise ParseError("unparsed line: '%s'" % s, fn, lineno); # Add us to the handlers list diff --git a/import-layers/yocto-poky/bitbake/lib/bb/persist_data.py b/import-layers/yocto-poky/bitbake/lib/bb/persist_data.py index e45042324..bb6deca52 100644 --- a/import-layers/yocto-poky/bitbake/lib/bb/persist_data.py +++ b/import-layers/yocto-poky/bitbake/lib/bb/persist_data.py @@ -92,9 +92,9 @@ class SQLTable(collections.MutableMapping): self._execute("DELETE from %s where key=?;" % self.table, [key]) def __setitem__(self, key, value): - if not isinstance(key, basestring): + if not isinstance(key, str): raise TypeError('Only string keys are supported') - elif not isinstance(value, basestring): + elif not isinstance(value, str): raise TypeError('Only string values are supported') data = self._execute("SELECT * from %s where key=?;" % @@ -178,7 +178,7 @@ class PersistData(object): """ Return a list of key + value pairs for a domain """ - return self.data[domain].items() + return list(self.data[domain].items()) def getValue(self, domain, key): """ diff --git a/import-layers/yocto-poky/bitbake/lib/bb/process.py b/import-layers/yocto-poky/bitbake/lib/bb/process.py index 1c07f2d9b..c62d7bca4 100644 --- a/import-layers/yocto-poky/bitbake/lib/bb/process.py +++ b/import-layers/yocto-poky/bitbake/lib/bb/process.py @@ -17,7 +17,7 @@ class CmdError(RuntimeError): self.msg = msg def __str__(self): - if not isinstance(self.command, basestring): + if not isinstance(self.command, str): cmd = subprocess.list2cmdline(self.command) else: cmd = self.command @@ -97,6 +97,8 @@ def _logged_communicate(pipe, log, input, extrafiles): try: while pipe.poll() is None: rlist = rin + stdoutbuf = b"" + stderrbuf = b"" try: r,w,e = select.select (rlist, [], [], 1) except OSError as e: @@ -104,16 +106,26 @@ def _logged_communicate(pipe, log, input, extrafiles): raise if pipe.stdout in r: - data = pipe.stdout.read() - if data is not None: - outdata.append(data) - log.write(data) + data = stdoutbuf + pipe.stdout.read() + if data is not None and len(data) > 0: + try: + data = data.decode("utf-8") + outdata.append(data) + log.write(data) + stdoutbuf = b"" + except UnicodeDecodeError: + stdoutbuf = data if pipe.stderr in r: - data = pipe.stderr.read() - if data is not None: - errdata.append(data) - log.write(data) + data = stderrbuf + pipe.stderr.read() + if data is not None and len(data) > 0: + try: + data = data.decode("utf-8") + errdata.append(data) + log.write(data) + stderrbuf = b"" + except UnicodeDecodeError: + stderrbuf = data readextras(r) @@ -135,7 +147,7 @@ def run(cmd, input=None, log=None, extrafiles=None, **options): if not extrafiles: extrafiles = [] - if isinstance(cmd, basestring) and not "shell" in options: + if isinstance(cmd, str) and not "shell" in options: options["shell"] = True try: @@ -150,6 +162,10 @@ def run(cmd, input=None, log=None, extrafiles=None, **options): stdout, stderr = _logged_communicate(pipe, log, input, extrafiles) else: stdout, stderr = pipe.communicate(input) + if stdout: + stdout = stdout.decode("utf-8") + if stderr: + stderr = stderr.decode("utf-8") if pipe.returncode != 0: raise ExecutionError(cmd, pipe.returncode, stdout, stderr) diff --git a/import-layers/yocto-poky/bitbake/lib/bb/progress.py b/import-layers/yocto-poky/bitbake/lib/bb/progress.py new file mode 100644 index 000000000..f54d1c76f --- /dev/null +++ b/import-layers/yocto-poky/bitbake/lib/bb/progress.py @@ -0,0 +1,276 @@ +""" +BitBake progress handling code +""" + +# Copyright (C) 2016 Intel Corporation +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# 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, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import sys +import re +import time +import inspect +import bb.event +import bb.build + +class ProgressHandler(object): + """ + Base class that can pretend to be a file object well enough to be + used to build objects to intercept console output and determine the + progress of some operation. + """ + def __init__(self, d, outfile=None): + self._progress = 0 + self._data = d + self._lastevent = 0 + if outfile: + self._outfile = outfile + else: + self._outfile = sys.stdout + + def _fire_progress(self, taskprogress, rate=None): + """Internal function to fire the progress event""" + bb.event.fire(bb.build.TaskProgress(taskprogress, rate), self._data) + + def write(self, string): + self._outfile.write(string) + + def flush(self): + self._outfile.flush() + + def update(self, progress, rate=None): + ts = time.time() + if progress > 100: + progress = 100 + if progress != self._progress or self._lastevent + 1 < ts: + self._fire_progress(progress, rate) + self._lastevent = ts + self._progress = progress + +class LineFilterProgressHandler(ProgressHandler): + """ + A ProgressHandler variant that provides the ability to filter out + the lines if they contain progress information. Additionally, it + filters out anything before the last line feed on a line. This can + be used to keep the logs clean of output that we've only enabled for + getting progress, assuming that that can be done on a per-line + basis. + """ + def __init__(self, d, outfile=None): + self._linebuffer = '' + super(LineFilterProgressHandler, self).__init__(d, outfile) + + def write(self, string): + self._linebuffer += string + while True: + breakpos = self._linebuffer.find('\n') + 1 + if breakpos == 0: + break + line = self._linebuffer[:breakpos] + self._linebuffer = self._linebuffer[breakpos:] + # Drop any line feeds and anything that precedes them + lbreakpos = line.rfind('\r') + 1 + if lbreakpos: + line = line[lbreakpos:] + if self.writeline(line): + super(LineFilterProgressHandler, self).write(line) + + def writeline(self, line): + return True + +class BasicProgressHandler(ProgressHandler): + def __init__(self, d, regex=r'(\d+)%', outfile=None): + super(BasicProgressHandler, self).__init__(d, outfile) + self._regex = re.compile(regex) + # Send an initial progress event so the bar gets shown + self._fire_progress(0) + + def write(self, string): + percs = self._regex.findall(string) + if percs: + progress = int(percs[-1]) + self.update(progress) + super(BasicProgressHandler, self).write(string) + +class OutOfProgressHandler(ProgressHandler): + def __init__(self, d, regex, outfile=None): + super(OutOfProgressHandler, self).__init__(d, outfile) + self._regex = re.compile(regex) + # Send an initial progress event so the bar gets shown + self._fire_progress(0) + + def write(self, string): + nums = self._regex.findall(string) + if nums: + progress = (float(nums[-1][0]) / float(nums[-1][1])) * 100 + self.update(progress) + super(OutOfProgressHandler, self).write(string) + +class MultiStageProgressReporter(object): + """ + Class which allows reporting progress without the caller + having to know where they are in the overall sequence. Useful + for tasks made up of python code spread across multiple + classes / functions - the progress reporter object can + be passed around or stored at the object level and calls + to next_stage() and update() made whereever needed. + """ + def __init__(self, d, stage_weights, debug=False): + """ + Initialise the progress reporter. + + Parameters: + * d: the datastore (needed for firing the events) + * stage_weights: a list of weight values, one for each stage. + The value is scaled internally so you only need to specify + values relative to other values in the list, so if there + are two stages and the first takes 2s and the second takes + 10s you would specify [2, 10] (or [1, 5], it doesn't matter). + * debug: specify True (and ensure you call finish() at the end) + in order to show a printout of the calculated stage weights + based on timing each stage. Use this to determine what the + weights should be when you're not sure. + """ + self._data = d + total = sum(stage_weights) + self._stage_weights = [float(x)/total for x in stage_weights] + self._stage = -1 + self._base_progress = 0 + # Send an initial progress event so the bar gets shown + self._fire_progress(0) + self._debug = debug + self._finished = False + if self._debug: + self._last_time = time.time() + self._stage_times = [] + self._stage_total = None + self._callers = [] + + def _fire_progress(self, taskprogress): + bb.event.fire(bb.build.TaskProgress(taskprogress), self._data) + + def next_stage(self, stage_total=None): + """ + Move to the next stage. + Parameters: + * stage_total: optional total for progress within the stage, + see update() for details + NOTE: you need to call this before the first stage. + """ + self._stage += 1 + self._stage_total = stage_total + if self._stage == 0: + # First stage + if self._debug: + self._last_time = time.time() + else: + if self._stage < len(self._stage_weights): + self._base_progress = sum(self._stage_weights[:self._stage]) * 100 + if self._debug: + currtime = time.time() + self._stage_times.append(currtime - self._last_time) + self._last_time = currtime + self._callers.append(inspect.getouterframes(inspect.currentframe())[1]) + elif not self._debug: + bb.warn('ProgressReporter: current stage beyond declared number of stages') + self._base_progress = 100 + self._fire_progress(self._base_progress) + + def update(self, stage_progress): + """ + Update progress within the current stage. + Parameters: + * stage_progress: progress value within the stage. If stage_total + was specified when next_stage() was last called, then this + value is considered to be out of stage_total, otherwise it should + be a percentage value from 0 to 100. + """ + if self._stage_total: + stage_progress = (float(stage_progress) / self._stage_total) * 100 + if self._stage < 0: + bb.warn('ProgressReporter: update called before first call to next_stage()') + elif self._stage < len(self._stage_weights): + progress = self._base_progress + (stage_progress * self._stage_weights[self._stage]) + else: + progress = self._base_progress + if progress > 100: + progress = 100 + self._fire_progress(progress) + + def finish(self): + if self._finished: + return + self._finished = True + if self._debug: + import math + self._stage_times.append(time.time() - self._last_time) + mintime = max(min(self._stage_times), 0.01) + self._callers.append(None) + stage_weights = [int(math.ceil(x / mintime)) for x in self._stage_times] + bb.warn('Stage weights: %s' % stage_weights) + out = [] + for stage_weight, caller in zip(stage_weights, self._callers): + if caller: + out.append('Up to %s:%d: %d' % (caller[1], caller[2], stage_weight)) + else: + out.append('Up to finish: %d' % stage_weight) + bb.warn('Stage times:\n %s' % '\n '.join(out)) + +class MultiStageProcessProgressReporter(MultiStageProgressReporter): + """ + Version of MultiStageProgressReporter intended for use with + standalone processes (such as preparing the runqueue) + """ + def __init__(self, d, processname, stage_weights, debug=False): + self._processname = processname + self._started = False + MultiStageProgressReporter.__init__(self, d, stage_weights, debug) + + def start(self): + if not self._started: + bb.event.fire(bb.event.ProcessStarted(self._processname, 100), self._data) + self._started = True + + def _fire_progress(self, taskprogress): + if taskprogress == 0: + self.start() + return + bb.event.fire(bb.event.ProcessProgress(self._processname, taskprogress), self._data) + + def finish(self): + MultiStageProgressReporter.finish(self) + bb.event.fire(bb.event.ProcessFinished(self._processname), self._data) + +class DummyMultiStageProcessProgressReporter(MultiStageProgressReporter): + """ + MultiStageProcessProgressReporter that takes the calls and does nothing + with them (to avoid a bunch of "if progress_reporter:" checks) + """ + def __init__(self): + MultiStageProcessProgressReporter.__init__(self, "", None, []) + + def _fire_progress(self, taskprogress, rate=None): + pass + + def start(self): + pass + + def next_stage(self, stage_total=None): + pass + + def update(self, stage_progress): + pass + + def finish(self): + pass diff --git a/import-layers/yocto-poky/bitbake/lib/bb/providers.py b/import-layers/yocto-poky/bitbake/lib/bb/providers.py index 563a091fd..db02a0b0d 100644 --- a/import-layers/yocto-poky/bitbake/lib/bb/providers.py +++ b/import-layers/yocto-poky/bitbake/lib/bb/providers.py @@ -245,7 +245,7 @@ def _filterProviders(providers, item, cfgData, dataCache): pkg_pn[pn] = [] pkg_pn[pn].append(p) - logger.debug(1, "providers for %s are: %s", item, pkg_pn.keys()) + logger.debug(1, "providers for %s are: %s", item, list(pkg_pn.keys())) # First add PREFERRED_VERSIONS for pn in pkg_pn: @@ -402,7 +402,7 @@ def getRuntimeProviders(dataCache, rdepend): return rproviders -def buildWorldTargetList(dataCache): +def buildWorldTargetList(dataCache, task=None): """ Build package list for "bitbake world" """ @@ -413,6 +413,9 @@ def buildWorldTargetList(dataCache): for f in dataCache.possible_world: terminal = True pn = dataCache.pkg_fn[f] + if task and task not in dataCache.task_deps[f]['tasks']: + logger.debug(2, "World build skipping %s as task %s doesn't exist", f, task) + terminal = False for p in dataCache.pn_provides[pn]: if p.startswith('virtual/'): diff --git a/import-layers/yocto-poky/bitbake/lib/bb/pysh/builtin.py b/import-layers/yocto-poky/bitbake/lib/bb/pysh/builtin.py index b748e4a4f..a8814dc33 100644 --- a/import-layers/yocto-poky/bitbake/lib/bb/pysh/builtin.py +++ b/import-layers/yocto-poky/bitbake/lib/bb/pysh/builtin.py @@ -527,7 +527,7 @@ def utility_sed(name, args, interp, env, stdin, stdout, stderr, debugflags): print interp.log(' '.join([name, str(args), interp['PWD']]) + '\n') # Scan pattern arguments and append a space if necessary - for i in xrange(len(args)): + for i in range(len(args)): if not RE_SED.search(args[i]): continue args[i] = args[i] + ' ' diff --git a/import-layers/yocto-poky/bitbake/lib/bb/pysh/interp.py b/import-layers/yocto-poky/bitbake/lib/bb/pysh/interp.py index 25d8c92ec..d14ecf3c6 100644 --- a/import-layers/yocto-poky/bitbake/lib/bb/pysh/interp.py +++ b/import-layers/yocto-poky/bitbake/lib/bb/pysh/interp.py @@ -474,7 +474,7 @@ class Environment: """ # Save and remove previous arguments prevargs = [] - for i in xrange(int(self._env['#'])): + for i in range(int(self._env['#'])): i = str(i+1) prevargs.append(self._env[i]) del self._env[i] @@ -488,7 +488,7 @@ class Environment: return prevargs def get_positional_args(self): - return [self._env[str(i+1)] for i in xrange(int(self._env['#']))] + return [self._env[str(i+1)] for i in range(int(self._env['#']))] def get_variables(self): return dict(self._env) diff --git a/import-layers/yocto-poky/bitbake/lib/bb/pysh/pyshlex.py b/import-layers/yocto-poky/bitbake/lib/bb/pysh/pyshlex.py index b30123675..fbf094b7a 100644 --- a/import-layers/yocto-poky/bitbake/lib/bb/pysh/pyshlex.py +++ b/import-layers/yocto-poky/bitbake/lib/bb/pysh/pyshlex.py @@ -20,7 +20,7 @@ except NameError: from Set import Set as set from ply import lex -from sherrors import * +from bb.pysh.sherrors import * class NeedMore(Exception): pass diff --git a/import-layers/yocto-poky/bitbake/lib/bb/pysh/pyshyacc.py b/import-layers/yocto-poky/bitbake/lib/bb/pysh/pyshyacc.py index e8e80aac4..ba4cefdcb 100644 --- a/import-layers/yocto-poky/bitbake/lib/bb/pysh/pyshyacc.py +++ b/import-layers/yocto-poky/bitbake/lib/bb/pysh/pyshyacc.py @@ -10,11 +10,11 @@ import os.path import sys -import pyshlex +import bb.pysh.pyshlex as pyshlex tokens = pyshlex.tokens from ply import yacc -import sherrors +import bb.pysh.sherrors as sherrors class IORedirect: def __init__(self, op, filename, io_number=None): diff --git a/import-layers/yocto-poky/bitbake/lib/bb/runqueue.py b/import-layers/yocto-poky/bitbake/lib/bb/runqueue.py index e1b9b2e66..84b268580 100644 --- a/import-layers/yocto-poky/bitbake/lib/bb/runqueue.py +++ b/import-layers/yocto-poky/bitbake/lib/bb/runqueue.py @@ -35,17 +35,44 @@ import bb from bb import msg, data, event from bb import monitordisk import subprocess - -try: - import cPickle as pickle -except ImportError: - import pickle +import pickle bblogger = logging.getLogger("BitBake") logger = logging.getLogger("BitBake.RunQueue") __find_md5__ = re.compile( r'(?i)(?<![a-z0-9])[a-f0-9]{32}(?![a-z0-9])' ) +def fn_from_tid(tid): + return tid.rsplit(":", 1)[0] + +def taskname_from_tid(tid): + return tid.rsplit(":", 1)[1] + +def split_tid(tid): + (mc, fn, taskname, _) = split_tid_mcfn(tid) + return (mc, fn, taskname) + +def split_tid_mcfn(tid): + if tid.startswith('multiconfig:'): + elems = tid.split(':') + mc = elems[1] + fn = ":".join(elems[2:-1]) + taskname = elems[-1] + mcfn = "multiconfig:" + mc + ":" + fn + else: + tid = tid.rsplit(":", 1) + mc = "" + fn = tid[0] + taskname = tid[1] + mcfn = fn + + return (mc, fn, taskname, mcfn) + +def build_tid(mc, fn, taskname): + if mc: + return "multiconfig:" + mc + ":" + fn + ":" + taskname + return fn + ":" + taskname + class RunQueueStats: """ Holds statistics on the tasks handled by the associated runQueue @@ -101,19 +128,17 @@ class RunQueueScheduler(object): """ self.rq = runqueue self.rqdata = rqdata - self.numTasks = len(self.rqdata.runq_fnid) + self.numTasks = len(self.rqdata.runtaskentries) - self.prio_map = [] - self.prio_map.extend(range(self.numTasks)) + self.prio_map = [self.rqdata.runtaskentries.keys()] self.buildable = [] self.stamps = {} - for taskid in xrange(self.numTasks): - fn = self.rqdata.taskData.fn_index[self.rqdata.runq_fnid[taskid]] - taskname = self.rqdata.runq_task[taskid] - self.stamps[taskid] = bb.build.stampfile(taskname, self.rqdata.dataCache, fn) - if self.rq.runq_buildable[taskid] == 1: - self.buildable.append(taskid) + for tid in self.rqdata.runtaskentries: + (mc, fn, taskname, taskfn) = split_tid_mcfn(tid) + self.stamps[tid] = bb.build.stampfile(taskname, self.rqdata.dataCaches[mc], taskfn, noextra=True) + if tid in self.rq.runq_buildable: + self.buildable.append(tid) self.rev_prio_map = None @@ -121,30 +146,30 @@ class RunQueueScheduler(object): """ Return the id of the first task we find that is buildable """ - self.buildable = [x for x in self.buildable if not self.rq.runq_running[x] == 1] + self.buildable = [x for x in self.buildable if x not in self.rq.runq_running] if not self.buildable: return None if len(self.buildable) == 1: - taskid = self.buildable[0] - stamp = self.stamps[taskid] - if stamp not in self.rq.build_stamps.itervalues(): - return taskid + tid = self.buildable[0] + stamp = self.stamps[tid] + if stamp not in self.rq.build_stamps.values(): + return tid if not self.rev_prio_map: - self.rev_prio_map = range(self.numTasks) - for taskid in xrange(self.numTasks): - self.rev_prio_map[self.prio_map[taskid]] = taskid + self.rev_prio_map = {} + for tid in self.rqdata.runtaskentries: + self.rev_prio_map[tid] = self.prio_map.index(tid) best = None bestprio = None - for taskid in self.buildable: - prio = self.rev_prio_map[taskid] + for tid in self.buildable: + prio = self.rev_prio_map[tid] if bestprio is None or bestprio > prio: - stamp = self.stamps[taskid] - if stamp in self.rq.build_stamps.itervalues(): + stamp = self.stamps[tid] + if stamp in self.rq.build_stamps.values(): continue bestprio = prio - best = taskid + best = tid return best @@ -171,14 +196,17 @@ class RunQueueSchedulerSpeed(RunQueueScheduler): """ RunQueueScheduler.__init__(self, runqueue, rqdata) - sortweight = sorted(copy.deepcopy(self.rqdata.runq_weight)) - copyweight = copy.deepcopy(self.rqdata.runq_weight) - self.prio_map = [] + weights = {} + for tid in self.rqdata.runtaskentries: + weight = self.rqdata.runtaskentries[tid].weight + if not weight in weights: + weights[weight] = [] + weights[weight].append(tid) - for weight in sortweight: - idx = copyweight.index(weight) - self.prio_map.append(idx) - copyweight[idx] = -1 + self.prio_map = [] + for weight in sorted(weights): + for w in weights[weight]: + self.prio_map.append(w) self.prio_map.reverse() @@ -195,32 +223,40 @@ class RunQueueSchedulerCompletion(RunQueueSchedulerSpeed): def __init__(self, runqueue, rqdata): RunQueueSchedulerSpeed.__init__(self, runqueue, rqdata) - #FIXME - whilst this groups all fnids together it does not reorder the - #fnid groups optimally. + #FIXME - whilst this groups all fns together it does not reorder the + #fn groups optimally. basemap = copy.deepcopy(self.prio_map) self.prio_map = [] while (len(basemap) > 0): entry = basemap.pop(0) self.prio_map.append(entry) - fnid = self.rqdata.runq_fnid[entry] + fn = fn_from_tid(entry) todel = [] for entry in basemap: - entry_fnid = self.rqdata.runq_fnid[entry] - if entry_fnid == fnid: + entry_fn = fn_from_tid(entry) + if entry_fn == fn: todel.append(basemap.index(entry)) self.prio_map.append(entry) todel.reverse() for idx in todel: del basemap[idx] +class RunTaskEntry(object): + def __init__(self): + self.depends = set() + self.revdeps = set() + self.hash = None + self.task = None + self.weight = 1 + class RunQueueData: """ BitBake Run Queue implementation """ - def __init__(self, rq, cooker, cfgData, dataCache, taskData, targets): + def __init__(self, rq, cooker, cfgData, dataCaches, taskData, targets): self.cooker = cooker - self.dataCache = dataCache + self.dataCaches = dataCaches self.taskData = taskData self.targets = targets self.rq = rq @@ -228,52 +264,36 @@ class RunQueueData: self.stampwhitelist = cfgData.getVar("BB_STAMP_WHITELIST", True) or "" self.multi_provider_whitelist = (cfgData.getVar("MULTI_PROVIDER_WHITELIST", True) or "").split() + self.setscenewhitelist = get_setscene_enforce_whitelist(cfgData) + self.setscenewhitelist_checked = False + self.init_progress_reporter = bb.progress.DummyMultiStageProcessProgressReporter() self.reset() def reset(self): - self.runq_fnid = [] - self.runq_task = [] - self.runq_depends = [] - self.runq_revdeps = [] - self.runq_hash = [] + self.runtaskentries = {} def runq_depends_names(self, ids): import re ret = [] - for id in self.runq_depends[ids]: - nam = os.path.basename(self.get_user_idstring(id)) + for id in ids: + nam = os.path.basename(id) nam = re.sub("_[^,]*,", ",", nam) ret.extend([nam]) return ret - def get_task_name(self, task): - return self.runq_task[task] - - def get_task_file(self, task): - return self.taskData.fn_index[self.runq_fnid[task]] + def get_task_hash(self, tid): + return self.runtaskentries[tid].hash - def get_task_hash(self, task): - return self.runq_hash[task] - - def get_user_idstring(self, task, task_name_suffix = ""): - fn = self.taskData.fn_index[self.runq_fnid[task]] - taskname = self.runq_task[task] + task_name_suffix - return "%s, %s" % (fn, taskname) + def get_user_idstring(self, tid, task_name_suffix = ""): + return tid + task_name_suffix def get_short_user_idstring(self, task, task_name_suffix = ""): - fn = self.taskData.fn_index[self.runq_fnid[task]] - pn = self.dataCache.pkg_fn[fn] - taskname = self.runq_task[task] + task_name_suffix + (mc, fn, taskname, _) = split_tid_mcfn(task) + pn = self.dataCaches[mc].pkg_fn[fn] + taskname = taskname_from_tid(task) + task_name_suffix return "%s:%s" % (pn, taskname) - - def get_task_id(self, fnid, taskname): - for listid in xrange(len(self.runq_fnid)): - if self.runq_fnid[listid] == fnid and self.runq_task[listid] == taskname: - return listid - return None - def circular_depchains_handler(self, tasks): """ Some tasks aren't buildable, likely due to circular dependency issues. @@ -291,7 +311,7 @@ class RunQueueData: """ lowest = 0 new_chain = [] - for entry in xrange(len(chain)): + for entry in range(len(chain)): if chain[entry] < chain[lowest]: lowest = entry new_chain.extend(chain[lowest:]) @@ -304,7 +324,7 @@ class RunQueueData: """ if len(chain1) != len(chain2): return False - for index in xrange(len(chain1)): + for index in range(len(chain1)): if chain1[index] != chain2[index]: return False return True @@ -318,11 +338,11 @@ class RunQueueData: return True return False - def find_chains(taskid, prev_chain): - prev_chain.append(taskid) + def find_chains(tid, prev_chain): + prev_chain.append(tid) total_deps = [] - total_deps.extend(self.runq_revdeps[taskid]) - for revdep in self.runq_revdeps[taskid]: + total_deps.extend(self.runtaskentries[tid].revdeps) + for revdep in self.runtaskentries[tid].revdeps: if revdep in prev_chain: idx = prev_chain.index(revdep) # To prevent duplicates, reorder the chain to start with the lowest taskid @@ -333,7 +353,7 @@ class RunQueueData: valid_chains.append(new_chain) msgs.append("Dependency loop #%d found:\n" % len(valid_chains)) for dep in new_chain: - msgs.append(" Task %s (%s) (dependent Tasks %s)\n" % (dep, self.get_user_idstring(dep), self.runq_depends_names(dep))) + msgs.append(" Task %s (dependent Tasks %s)\n" % (dep, self.runq_depends_names(self.runtaskentries[dep].depends))) msgs.append("\n") if len(valid_chains) > 10: msgs.append("Aborted dependency loops search after 10 matches.\n") @@ -354,7 +374,7 @@ class RunQueueData: if dep not in total_deps: total_deps.append(dep) - explored_deps[taskid] = total_deps + explored_deps[tid] = total_deps for task in tasks: find_chains(task, []) @@ -370,25 +390,25 @@ class RunQueueData: possible to execute due to circular dependencies. """ - numTasks = len(self.runq_fnid) - weight = [] - deps_left = [] - task_done = [] + numTasks = len(self.runtaskentries) + weight = {} + deps_left = {} + task_done = {} - for listid in xrange(numTasks): - task_done.append(False) - weight.append(1) - deps_left.append(len(self.runq_revdeps[listid])) + for tid in self.runtaskentries: + task_done[tid] = False + weight[tid] = 1 + deps_left[tid] = len(self.runtaskentries[tid].revdeps) - for listid in endpoints: - weight[listid] = 10 - task_done[listid] = True + for tid in endpoints: + weight[tid] = 10 + task_done[tid] = True while True: next_points = [] - for listid in endpoints: - for revdep in self.runq_depends[listid]: - weight[revdep] = weight[revdep] + weight[listid] + for tid in endpoints: + for revdep in self.runtaskentries[tid].depends: + weight[revdep] = weight[revdep] + weight[tid] deps_left[revdep] = deps_left[revdep] - 1 if deps_left[revdep] == 0: next_points.append(revdep) @@ -399,14 +419,15 @@ class RunQueueData: # Circular dependency sanity check problem_tasks = [] - for task in xrange(numTasks): - if task_done[task] is False or deps_left[task] != 0: - problem_tasks.append(task) - logger.debug(2, "Task %s (%s) is not buildable", task, self.get_user_idstring(task)) - logger.debug(2, "(Complete marker was %s and the remaining dependency count was %s)\n", task_done[task], deps_left[task]) + for tid in self.runtaskentries: + if task_done[tid] is False or deps_left[tid] != 0: + problem_tasks.append(tid) + logger.debug(2, "Task %s is not buildable", tid) + logger.debug(2, "(Complete marker was %s and the remaining dependency count was %s)\n", task_done[tid], deps_left[tid]) + self.runtaskentries[tid].weight = weight[tid] if problem_tasks: - message = "Unbuildable tasks were found.\n" + message = "%s unbuildable tasks were found.\n" % len(problem_tasks) message = message + "These are usually caused by circular dependencies and any circular dependency chains found will be printed below. Increase the debug level to see a list of unbuildable tasks.\n\n" message = message + "Identifying dependency loops (this may take a short while)...\n" logger.error(message) @@ -426,18 +447,24 @@ class RunQueueData: to optimise the execution order. """ - runq_build = [] + runq_build = {} recursivetasks = {} recursiveitasks = {} recursivetasksselfref = set() taskData = self.taskData - if len(taskData.tasks_name) == 0: + found = False + for mc in self.taskData: + if len(taskData[mc].taskentries) > 0: + found = True + break + if not found: # Nothing to do return 0 - logger.info("Preparing RunQueue") + self.init_progress_reporter.start() + self.init_progress_reporter.next_stage() # Step A - Work out a list of tasks to run # @@ -450,161 +477,173 @@ class RunQueueData: # process is repeated for each type of dependency (tdepends, deptask, # rdeptast, recrdeptask, idepends). - def add_build_dependencies(depids, tasknames, depends): - for depid in depids: + def add_build_dependencies(depids, tasknames, depends, mc): + for depname in depids: # Won't be in build_targets if ASSUME_PROVIDED - if depid not in taskData.build_targets: + if depname not in taskData[mc].build_targets or not taskData[mc].build_targets[depname]: continue - depdata = taskData.build_targets[depid][0] + depdata = taskData[mc].build_targets[depname][0] if depdata is None: continue for taskname in tasknames: - taskid = taskData.gettask_id_fromfnid(depdata, taskname) - if taskid is not None: - depends.add(taskid) + t = depdata + ":" + taskname + if t in taskData[mc].taskentries: + depends.add(t) - def add_runtime_dependencies(depids, tasknames, depends): - for depid in depids: - if depid not in taskData.run_targets: + def add_runtime_dependencies(depids, tasknames, depends, mc): + for depname in depids: + if depname not in taskData[mc].run_targets or not taskData[mc].run_targets[depname]: continue - depdata = taskData.run_targets[depid][0] + depdata = taskData[mc].run_targets[depname][0] if depdata is None: continue for taskname in tasknames: - taskid = taskData.gettask_id_fromfnid(depdata, taskname) - if taskid is not None: - depends.add(taskid) + t = depdata + ":" + taskname + if t in taskData[mc].taskentries: + depends.add(t) - def add_resolved_dependencies(depids, tasknames, depends): - for depid in depids: - for taskname in tasknames: - taskid = taskData.gettask_id_fromfnid(depid, taskname) - if taskid is not None: - depends.add(taskid) + def add_resolved_dependencies(mc, fn, tasknames, depends): + for taskname in tasknames: + tid = build_tid(mc, fn, taskname) + if tid in self.runtaskentries: + depends.add(tid) - for task in xrange(len(taskData.tasks_name)): - depends = set() - fnid = taskData.tasks_fnid[task] - fn = taskData.fn_index[fnid] - task_deps = self.dataCache.task_deps[fn] + for mc in taskData: + for tid in taskData[mc].taskentries: - #logger.debug(2, "Processing %s:%s", fn, taskData.tasks_name[task]) + (mc, fn, taskname, taskfn) = split_tid_mcfn(tid) + #runtid = build_tid(mc, fn, taskname) - if fnid not in taskData.failed_fnids: + #logger.debug(2, "Processing %s,%s:%s", mc, fn, taskname) + + depends = set() + task_deps = self.dataCaches[mc].task_deps[taskfn] + + self.runtaskentries[tid] = RunTaskEntry() + + if fn in taskData[mc].failed_fns: + continue # Resolve task internal dependencies # # e.g. addtask before X after Y - depends = set(taskData.tasks_tdepends[task]) + for t in taskData[mc].taskentries[tid].tdepends: + (_, depfn, deptaskname, _) = split_tid_mcfn(t) + depends.add(build_tid(mc, depfn, deptaskname)) # Resolve 'deptask' dependencies # # e.g. do_sometask[deptask] = "do_someothertask" # (makes sure sometask runs after someothertask of all DEPENDS) - if 'deptask' in task_deps and taskData.tasks_name[task] in task_deps['deptask']: - tasknames = task_deps['deptask'][taskData.tasks_name[task]].split() - add_build_dependencies(taskData.depids[fnid], tasknames, depends) + if 'deptask' in task_deps and taskname in task_deps['deptask']: + tasknames = task_deps['deptask'][taskname].split() + add_build_dependencies(taskData[mc].depids[taskfn], tasknames, depends, mc) # Resolve 'rdeptask' dependencies # # e.g. do_sometask[rdeptask] = "do_someothertask" # (makes sure sometask runs after someothertask of all RDEPENDS) - if 'rdeptask' in task_deps and taskData.tasks_name[task] in task_deps['rdeptask']: - tasknames = task_deps['rdeptask'][taskData.tasks_name[task]].split() - add_runtime_dependencies(taskData.rdepids[fnid], tasknames, depends) + if 'rdeptask' in task_deps and taskname in task_deps['rdeptask']: + tasknames = task_deps['rdeptask'][taskname].split() + add_runtime_dependencies(taskData[mc].rdepids[taskfn], tasknames, depends, mc) # Resolve inter-task dependencies # # e.g. do_sometask[depends] = "targetname:do_someothertask" # (makes sure sometask runs after targetname's someothertask) - idepends = taskData.tasks_idepends[task] - for (depid, idependtask) in idepends: - if depid in taskData.build_targets and not depid in taskData.failed_deps: + idepends = taskData[mc].taskentries[tid].idepends + for (depname, idependtask) in idepends: + if depname in taskData[mc].build_targets and taskData[mc].build_targets[depname] and not depname in taskData[mc].failed_deps: # Won't be in build_targets if ASSUME_PROVIDED - depdata = taskData.build_targets[depid][0] + depdata = taskData[mc].build_targets[depname][0] if depdata is not None: - taskid = taskData.gettask_id_fromfnid(depdata, idependtask) - if taskid is None: - bb.msg.fatal("RunQueue", "Task %s in %s depends upon non-existent task %s in %s" % (taskData.tasks_name[task], fn, idependtask, taskData.fn_index[depdata])) - depends.add(taskid) - irdepends = taskData.tasks_irdepends[task] - for (depid, idependtask) in irdepends: - if depid in taskData.run_targets: + t = depdata + ":" + idependtask + depends.add(t) + if t not in taskData[mc].taskentries: + bb.msg.fatal("RunQueue", "Task %s in %s depends upon non-existent task %s in %s" % (taskname, fn, idependtask, depdata)) + irdepends = taskData[mc].taskentries[tid].irdepends + for (depname, idependtask) in irdepends: + if depname in taskData[mc].run_targets: # Won't be in run_targets if ASSUME_PROVIDED - depdata = taskData.run_targets[depid][0] + depdata = taskData[mc].run_targets[depname][0] if depdata is not None: - taskid = taskData.gettask_id_fromfnid(depdata, idependtask) - if taskid is None: - bb.msg.fatal("RunQueue", "Task %s in %s rdepends upon non-existent task %s in %s" % (taskData.tasks_name[task], fn, idependtask, taskData.fn_index[depdata])) - depends.add(taskid) + t = depdata + ":" + idependtask + depends.add(t) + if t not in taskData[mc].taskentries: + bb.msg.fatal("RunQueue", "Task %s in %s rdepends upon non-existent task %s in %s" % (taskname, fn, idependtask, depdata)) # Resolve recursive 'recrdeptask' dependencies (Part A) # # e.g. do_sometask[recrdeptask] = "do_someothertask" # (makes sure sometask runs after someothertask of all DEPENDS, RDEPENDS and intertask dependencies, recursively) # We cover the recursive part of the dependencies below - if 'recrdeptask' in task_deps and taskData.tasks_name[task] in task_deps['recrdeptask']: - tasknames = task_deps['recrdeptask'][taskData.tasks_name[task]].split() - recursivetasks[task] = tasknames - add_build_dependencies(taskData.depids[fnid], tasknames, depends) - add_runtime_dependencies(taskData.rdepids[fnid], tasknames, depends) - if taskData.tasks_name[task] in tasknames: - recursivetasksselfref.add(task) - - if 'recideptask' in task_deps and taskData.tasks_name[task] in task_deps['recideptask']: - recursiveitasks[task] = [] - for t in task_deps['recideptask'][taskData.tasks_name[task]].split(): - newdep = taskData.gettask_id_fromfnid(fnid, t) - recursiveitasks[task].append(newdep) - - self.runq_fnid.append(taskData.tasks_fnid[task]) - self.runq_task.append(taskData.tasks_name[task]) - self.runq_depends.append(depends) - self.runq_revdeps.append(set()) - self.runq_hash.append("") - - runq_build.append(0) + if 'recrdeptask' in task_deps and taskname in task_deps['recrdeptask']: + tasknames = task_deps['recrdeptask'][taskname].split() + recursivetasks[tid] = tasknames + add_build_dependencies(taskData[mc].depids[taskfn], tasknames, depends, mc) + add_runtime_dependencies(taskData[mc].rdepids[taskfn], tasknames, depends, mc) + if taskname in tasknames: + recursivetasksselfref.add(tid) + + if 'recideptask' in task_deps and taskname in task_deps['recideptask']: + recursiveitasks[tid] = [] + for t in task_deps['recideptask'][taskname].split(): + newdep = build_tid(mc, fn, t) + recursiveitasks[tid].append(newdep) + + self.runtaskentries[tid].depends = depends + + #self.dump_data() # Resolve recursive 'recrdeptask' dependencies (Part B) # # e.g. do_sometask[recrdeptask] = "do_someothertask" # (makes sure sometask runs after someothertask of all DEPENDS, RDEPENDS and intertask dependencies, recursively) - # We need to do this separately since we need all of self.runq_depends to be complete before this is processed + # We need to do this separately since we need all of runtaskentries[*].depends to be complete before this is processed + self.init_progress_reporter.next_stage(len(recursivetasks)) extradeps = {} - for task in recursivetasks: - extradeps[task] = set(self.runq_depends[task]) - tasknames = recursivetasks[task] + for taskcounter, tid in enumerate(recursivetasks): + extradeps[tid] = set(self.runtaskentries[tid].depends) + + tasknames = recursivetasks[tid] seendeps = set() - seenfnid = [] def generate_recdeps(t): newdeps = set() - add_resolved_dependencies([taskData.tasks_fnid[t]], tasknames, newdeps) - extradeps[task].update(newdeps) + (mc, fn, taskname, _) = split_tid_mcfn(t) + add_resolved_dependencies(mc, fn, tasknames, newdeps) + extradeps[tid].update(newdeps) seendeps.add(t) newdeps.add(t) for i in newdeps: - for n in self.runq_depends[i]: + task = self.runtaskentries[i].task + for n in self.runtaskentries[i].depends: if n not in seendeps: - generate_recdeps(n) - generate_recdeps(task) + generate_recdeps(n) + generate_recdeps(tid) - if task in recursiveitasks: - for dep in recursiveitasks[task]: + if tid in recursiveitasks: + for dep in recursiveitasks[tid]: generate_recdeps(dep) + self.init_progress_reporter.update(taskcounter) # Remove circular references so that do_a[recrdeptask] = "do_a do_b" can work - for task in recursivetasks: - extradeps[task].difference_update(recursivetasksselfref) + for tid in recursivetasks: + extradeps[tid].difference_update(recursivetasksselfref) - for task in xrange(len(taskData.tasks_name)): + for tid in self.runtaskentries: + task = self.runtaskentries[tid].task # Add in extra dependencies - if task in extradeps: - self.runq_depends[task] = extradeps[task] + if tid in extradeps: + self.runtaskentries[tid].depends = extradeps[tid] # Remove all self references - if task in self.runq_depends[task]: - logger.debug(2, "Task %s (%s %s) contains self reference! %s", task, taskData.fn_index[taskData.tasks_fnid[task]], taskData.tasks_name[task], self.runq_depends[task]) - self.runq_depends[task].remove(task) + if tid in self.runtaskentries[tid].depends: + logger.debug(2, "Task %s contains self reference!", tid) + self.runtaskentries[tid].depends.remove(tid) + + self.init_progress_reporter.next_stage() + + #self.dump_data() # Step B - Mark all active tasks # @@ -614,148 +653,146 @@ class RunQueueData: logger.verbose("Marking Active Tasks") - def mark_active(listid, depth): + def mark_active(tid, depth): """ Mark an item as active along with its depends (calls itself recursively) """ - if runq_build[listid] == 1: + if tid in runq_build: return - runq_build[listid] = 1 + runq_build[tid] = 1 - depends = self.runq_depends[listid] + depends = self.runtaskentries[tid].depends for depend in depends: mark_active(depend, depth+1) - self.target_pairs = [] - for target in self.targets: - targetid = taskData.getbuild_id(target[0]) + self.target_tids = [] + for (mc, target, task, fn) in self.targets: - if targetid not in taskData.build_targets: + if target not in taskData[mc].build_targets or not taskData[mc].build_targets[target]: continue - if targetid in taskData.failed_deps: + if target in taskData[mc].failed_deps: continue - fnid = taskData.build_targets[targetid][0] - fn = taskData.fn_index[fnid] - task = target[1] parents = False if task.endswith('-'): parents = True task = task[:-1] - self.target_pairs.append((fn, task)) - - if fnid in taskData.failed_fnids: + if fn in taskData[mc].failed_fns: continue - if task not in taskData.tasks_lookup[fnid]: + # fn already has mc prefix + tid = fn + ":" + task + self.target_tids.append(tid) + if tid not in taskData[mc].taskentries: import difflib - close_matches = difflib.get_close_matches(task, taskData.tasks_lookup[fnid], cutoff=0.7) + tasks = [] + for x in taskData[mc].taskentries: + if x.startswith(fn + ":"): + tasks.append(taskname_from_tid(x)) + close_matches = difflib.get_close_matches(task, tasks, cutoff=0.7) if close_matches: extra = ". Close matches:\n %s" % "\n ".join(close_matches) else: extra = "" - bb.msg.fatal("RunQueue", "Task %s does not exist for target %s%s" % (task, target[0], extra)) - + bb.msg.fatal("RunQueue", "Task %s does not exist for target %s (%s)%s" % (task, target, tid, extra)) + # For tasks called "XXXX-", ony run their dependencies - listid = taskData.tasks_lookup[fnid][task] if parents: - for i in self.runq_depends[listid]: + for i in self.runtaskentries[tid].depends: mark_active(i, 1) else: - mark_active(listid, 1) + mark_active(tid, 1) + + self.init_progress_reporter.next_stage() # Step C - Prune all inactive tasks # # Once all active tasks are marked, prune the ones we don't need. - maps = [] delcount = 0 - for listid in xrange(len(self.runq_fnid)): - if runq_build[listid-delcount] == 1: - maps.append(listid-delcount) - else: - del self.runq_fnid[listid-delcount] - del self.runq_task[listid-delcount] - del self.runq_depends[listid-delcount] - del runq_build[listid-delcount] - del self.runq_revdeps[listid-delcount] - del self.runq_hash[listid-delcount] - delcount = delcount + 1 - maps.append(-1) + for tid in list(self.runtaskentries.keys()): + if tid not in runq_build: + del self.runtaskentries[tid] + delcount += 1 + + self.init_progress_reporter.next_stage() # # Step D - Sanity checks and computation # # Check to make sure we still have tasks to run - if len(self.runq_fnid) == 0: - if not taskData.abort: + if len(self.runtaskentries) == 0: + if not taskData[''].abort: bb.msg.fatal("RunQueue", "All buildable tasks have been run but the build is incomplete (--continue mode). Errors for the tasks that failed will have been printed above.") else: bb.msg.fatal("RunQueue", "No active tasks and not in --continue mode?! Please report this bug.") - logger.verbose("Pruned %s inactive tasks, %s left", delcount, len(self.runq_fnid)) - - # Remap the dependencies to account for the deleted tasks - # Check we didn't delete a task we depend on - for listid in xrange(len(self.runq_fnid)): - newdeps = [] - origdeps = self.runq_depends[listid] - for origdep in origdeps: - if maps[origdep] == -1: - bb.msg.fatal("RunQueue", "Invalid mapping - Should never happen!") - newdeps.append(maps[origdep]) - self.runq_depends[listid] = set(newdeps) + logger.verbose("Pruned %s inactive tasks, %s left", delcount, len(self.runtaskentries)) logger.verbose("Assign Weightings") + self.init_progress_reporter.next_stage() + # Generate a list of reverse dependencies to ease future calculations - for listid in xrange(len(self.runq_fnid)): - for dep in self.runq_depends[listid]: - self.runq_revdeps[dep].add(listid) + for tid in self.runtaskentries: + for dep in self.runtaskentries[tid].depends: + self.runtaskentries[dep].revdeps.add(tid) + + self.init_progress_reporter.next_stage() # Identify tasks at the end of dependency chains # Error on circular dependency loops (length two) endpoints = [] - for listid in xrange(len(self.runq_fnid)): - revdeps = self.runq_revdeps[listid] + for tid in self.runtaskentries: + revdeps = self.runtaskentries[tid].revdeps if len(revdeps) == 0: - endpoints.append(listid) + endpoints.append(tid) for dep in revdeps: - if dep in self.runq_depends[listid]: - #self.dump_data(taskData) - bb.msg.fatal("RunQueue", "Task %s (%s) has circular dependency on %s (%s)" % (taskData.fn_index[self.runq_fnid[dep]], self.runq_task[dep], taskData.fn_index[self.runq_fnid[listid]], self.runq_task[listid])) + if dep in self.runtaskentries[tid].depends: + bb.msg.fatal("RunQueue", "Task %s has circular dependency on %s" % (tid, dep)) + logger.verbose("Compute totals (have %s endpoint(s))", len(endpoints)) + self.init_progress_reporter.next_stage() + # Calculate task weights # Check of higher length circular dependencies self.runq_weight = self.calculate_task_weights(endpoints) + self.init_progress_reporter.next_stage() + # Sanity Check - Check for multiple tasks building the same provider - prov_list = {} - seen_fn = [] - for task in xrange(len(self.runq_fnid)): - fn = taskData.fn_index[self.runq_fnid[task]] - if fn in seen_fn: - continue - seen_fn.append(fn) - for prov in self.dataCache.fn_provides[fn]: - if prov not in prov_list: - prov_list[prov] = [fn] - elif fn not in prov_list[prov]: - prov_list[prov].append(fn) - for prov in prov_list: - if len(prov_list[prov]) > 1 and prov not in self.multi_provider_whitelist: + for mc in self.dataCaches: + prov_list = {} + seen_fn = [] + for tid in self.runtaskentries: + (tidmc, fn, taskname, taskfn) = split_tid_mcfn(tid) + if taskfn in seen_fn: + continue + if mc != tidmc: + continue + seen_fn.append(taskfn) + for prov in self.dataCaches[mc].fn_provides[taskfn]: + if prov not in prov_list: + prov_list[prov] = [taskfn] + elif taskfn not in prov_list[prov]: + prov_list[prov].append(taskfn) + for prov in prov_list: + if len(prov_list[prov]) < 2: + continue + if prov in self.multi_provider_whitelist: + continue seen_pn = [] # If two versions of the same PN are being built its fatal, we don't support it. for fn in prov_list[prov]: - pn = self.dataCache.pkg_fn[fn] + pn = self.dataCaches[mc].pkg_fn[fn] if pn not in seen_pn: seen_pn.append(pn) else: @@ -770,15 +807,15 @@ class RunQueueData: commondeps = None for provfn in prov_list[prov]: deps = set() - for task, fnid in enumerate(self.runq_fnid): - fn = taskData.fn_index[fnid] + for tid in self.runtaskentries: + fn = fn_from_tid(tid) if fn != provfn: continue - for dep in self.runq_revdeps[task]: - fn = taskData.fn_index[self.runq_fnid[dep]] + for dep in self.runtaskentries[tid].revdeps: + fn = fn_from_tid(dep) if fn == provfn: continue - deps.add(self.get_short_user_idstring(dep)) + deps.add(dep) if not commondeps: commondeps = set(deps) else: @@ -796,16 +833,16 @@ class RunQueueData: commonprovs = None commonrprovs = None for provfn in prov_list[prov]: - provides = set(self.dataCache.fn_provides[provfn]) + provides = set(self.dataCaches[mc].fn_provides[provfn]) rprovides = set() - for rprovide in self.dataCache.rproviders: - if provfn in self.dataCache.rproviders[rprovide]: + for rprovide in self.dataCaches[mc].rproviders: + if provfn in self.dataCaches[mc].rproviders[rprovide]: rprovides.add(rprovide) - for package in self.dataCache.packages: - if provfn in self.dataCache.packages[package]: + for package in self.dataCaches[mc].packages: + if provfn in self.dataCaches[mc].packages[package]: rprovides.add(package) - for package in self.dataCache.packages_dynamic: - if provfn in self.dataCache.packages_dynamic[package]: + for package in self.dataCaches[mc].packages_dynamic: + if provfn in self.dataCaches[mc].packages_dynamic[package]: rprovides.add(package) if not commonprovs: commonprovs = set(provides) @@ -824,35 +861,39 @@ class RunQueueData: msg += "\n%s has unique rprovides:\n %s" % (provfn, "\n ".join(rprovide_results[provfn] - commonrprovs)) if self.warn_multi_bb: - logger.warn(msg) + logger.warning(msg) else: logger.error(msg) + self.init_progress_reporter.next_stage() + # Create a whitelist usable by the stamp checks - stampfnwhitelist = [] - for entry in self.stampwhitelist.split(): - entryid = self.taskData.getbuild_id(entry) - if entryid not in self.taskData.build_targets: - continue - fnid = self.taskData.build_targets[entryid][0] - fn = self.taskData.fn_index[fnid] - stampfnwhitelist.append(fn) - self.stampfnwhitelist = stampfnwhitelist + self.stampfnwhitelist = {} + for mc in self.taskData: + self.stampfnwhitelist[mc] = [] + for entry in self.stampwhitelist.split(): + if entry not in self.taskData[mc].build_targets: + continue + fn = self.taskData.build_targets[entry][0] + self.stampfnwhitelist[mc].append(fn) + + self.init_progress_reporter.next_stage() # Iterate over the task list looking for tasks with a 'setscene' function - self.runq_setscene = [] + self.runq_setscene_tids = [] if not self.cooker.configuration.nosetscene: - for task in range(len(self.runq_fnid)): - setscene = taskData.gettask_id(self.taskData.fn_index[self.runq_fnid[task]], self.runq_task[task] + "_setscene", False) - if not setscene: + for tid in self.runtaskentries: + (mc, fn, taskname, _) = split_tid_mcfn(tid) + setscenetid = fn + ":" + taskname + "_setscene" + if setscenetid not in taskData[mc].taskentries: continue - self.runq_setscene.append(task) + self.runq_setscene_tids.append(tid) - def invalidate_task(fn, taskname, error_nostamp): - taskdep = self.dataCache.task_deps[fn] - fnid = self.taskData.getfn_id(fn) - if taskname not in taskData.tasks_lookup[fnid]: - logger.warn("Task %s does not exist, invalidating this task will have no effect" % taskname) + def invalidate_task(tid, error_nostamp): + (mc, fn, taskname, _) = split_tid_mcfn(tid) + taskdep = self.dataCaches[mc].task_deps[fn] + if fn + ":" + taskname not in taskData[mc].taskentries: + logger.warning("Task %s does not exist, invalidating this task will have no effect" % taskname) if 'nostamp' in taskdep and taskname in taskdep['nostamp']: if error_nostamp: bb.fatal("Task %s is marked nostamp, cannot invalidate this task" % taskname) @@ -860,80 +901,84 @@ class RunQueueData: bb.debug(1, "Task %s is marked nostamp, cannot invalidate this task" % taskname) else: logger.verbose("Invalidate task %s, %s", taskname, fn) - bb.parse.siggen.invalidate_task(taskname, self.dataCache, fn) + bb.parse.siggen.invalidate_task(taskname, self.dataCaches[mc], fn) + + self.init_progress_reporter.next_stage() # Invalidate task if force mode active if self.cooker.configuration.force: - for (fn, target) in self.target_pairs: - invalidate_task(fn, target, False) + for tid in self.target_tids: + invalidate_task(tid, False) # Invalidate task if invalidate mode active if self.cooker.configuration.invalidate_stamp: - for (fn, target) in self.target_pairs: + for tid in self.target_tids: + fn = fn_from_tid(tid) for st in self.cooker.configuration.invalidate_stamp.split(','): if not st.startswith("do_"): st = "do_%s" % st - invalidate_task(fn, st, True) + invalidate_task(fn + ":" + st, True) + + self.init_progress_reporter.next_stage() # Create and print to the logs a virtual/xxxx -> PN (fn) table - virtmap = taskData.get_providermap(prefix="virtual/") - virtpnmap = {} - for v in virtmap: - virtpnmap[v] = self.dataCache.pkg_fn[virtmap[v]] - bb.debug(2, "%s resolved to: %s (%s)" % (v, virtpnmap[v], virtmap[v])) - if hasattr(bb.parse.siggen, "tasks_resolved"): - bb.parse.siggen.tasks_resolved(virtmap, virtpnmap, self.dataCache) + for mc in taskData: + virtmap = taskData[mc].get_providermap(prefix="virtual/") + virtpnmap = {} + for v in virtmap: + virtpnmap[v] = self.dataCaches[mc].pkg_fn[virtmap[v]] + bb.debug(2, "%s resolved to: %s (%s)" % (v, virtpnmap[v], virtmap[v])) + if hasattr(bb.parse.siggen, "tasks_resolved"): + bb.parse.siggen.tasks_resolved(virtmap, virtpnmap, self.dataCaches[mc]) + + self.init_progress_reporter.next_stage() # Iterate over the task list and call into the siggen code dealtwith = set() - todeal = set(range(len(self.runq_fnid))) + todeal = set(self.runtaskentries) while len(todeal) > 0: - for task in todeal.copy(): - if len(self.runq_depends[task] - dealtwith) == 0: - dealtwith.add(task) - todeal.remove(task) + for tid in todeal.copy(): + if len(self.runtaskentries[tid].depends - dealtwith) == 0: + dealtwith.add(tid) + todeal.remove(tid) procdep = [] - for dep in self.runq_depends[task]: - procdep.append(self.taskData.fn_index[self.runq_fnid[dep]] + "." + self.runq_task[dep]) - self.runq_hash[task] = bb.parse.siggen.get_taskhash(self.taskData.fn_index[self.runq_fnid[task]], self.runq_task[task], procdep, self.dataCache) + for dep in self.runtaskentries[tid].depends: + procdep.append(fn_from_tid(dep) + "." + taskname_from_tid(dep)) + (mc, fn, taskname, taskfn) = split_tid_mcfn(tid) + self.runtaskentries[tid].hash = bb.parse.siggen.get_taskhash(taskfn, taskname, procdep, self.dataCaches[mc]) + task = self.runtaskentries[tid].task bb.parse.siggen.writeout_file_checksum_cache() - return len(self.runq_fnid) - def dump_data(self, taskQueue): + #self.dump_data() + return len(self.runtaskentries) + + def dump_data(self): """ Dump some debug information on the internal data structures """ logger.debug(3, "run_tasks:") - for task in xrange(len(self.rqdata.runq_task)): - logger.debug(3, " (%s)%s - %s: %s Deps %s RevDeps %s", task, - taskQueue.fn_index[self.rqdata.runq_fnid[task]], - self.rqdata.runq_task[task], - self.rqdata.runq_weight[task], - self.rqdata.runq_depends[task], - self.rqdata.runq_revdeps[task]) - - logger.debug(3, "sorted_tasks:") - for task1 in xrange(len(self.rqdata.runq_task)): - if task1 in self.prio_map: - task = self.prio_map[task1] - logger.debug(3, " (%s)%s - %s: %s Deps %s RevDeps %s", task, - taskQueue.fn_index[self.rqdata.runq_fnid[task]], - self.rqdata.runq_task[task], - self.rqdata.runq_weight[task], - self.rqdata.runq_depends[task], - self.rqdata.runq_revdeps[task]) + for tid in self.runtaskentries: + logger.debug(3, " %s: %s Deps %s RevDeps %s", tid, + self.runtaskentries[tid].weight, + self.runtaskentries[tid].depends, + self.runtaskentries[tid].revdeps) + +class RunQueueWorker(): + def __init__(self, process, pipe): + self.process = process + self.pipe = pipe class RunQueue: - def __init__(self, cooker, cfgData, dataCache, taskData, targets): + def __init__(self, cooker, cfgData, dataCaches, taskData, targets): self.cooker = cooker self.cfgData = cfgData - self.rqdata = RunQueueData(self, cooker, cfgData, dataCache, taskData, targets) + self.rqdata = RunQueueData(self, cooker, cfgData, dataCaches, taskData, targets) self.stamppolicy = cfgData.getVar("BB_STAMP_POLICY", True) or "perfile" self.hashvalidate = cfgData.getVar("BB_HASHCHECK_FUNCTION", True) or None - self.setsceneverify = cfgData.getVar("BB_SETSCENE_VERIFY_FUNCTION", True) or None + self.setsceneverify = cfgData.getVar("BB_SETSCENE_VERIFY_FUNCTION2", True) or None self.depvalidate = cfgData.getVar("BB_SETSCENE_DEPVALID", True) or None self.state = runQueuePrepare @@ -942,12 +987,10 @@ class RunQueue: self.dm = monitordisk.diskMonitor(cfgData) self.rqexe = None - self.worker = None - self.workerpipe = None - self.fakeworker = None - self.fakeworkerpipe = None + self.worker = {} + self.fakeworker = {} - def _start_worker(self, fakeroot = False, rqexec = None): + def _start_worker(self, mc, fakeroot = False, rqexec = None): logger.debug(1, "Starting bitbake-worker") magic = "decafbad" if self.cooker.configuration.profile: @@ -965,13 +1008,17 @@ class RunQueue: bb.utils.nonblockingfd(worker.stdout) workerpipe = runQueuePipe(worker.stdout, None, self.cfgData, self, rqexec) + runqhash = {} + for tid in self.rqdata.runtaskentries: + runqhash[tid] = self.rqdata.runtaskentries[tid].hash + workerdata = { - "taskdeps" : self.rqdata.dataCache.task_deps, - "fakerootenv" : self.rqdata.dataCache.fakerootenv, - "fakerootdirs" : self.rqdata.dataCache.fakerootdirs, - "fakerootnoenv" : self.rqdata.dataCache.fakerootnoenv, + "taskdeps" : self.rqdata.dataCaches[mc].task_deps, + "fakerootenv" : self.rqdata.dataCaches[mc].fakerootenv, + "fakerootdirs" : self.rqdata.dataCaches[mc].fakerootdirs, + "fakerootnoenv" : self.rqdata.dataCaches[mc].fakerootnoenv, "sigdata" : bb.parse.siggen.get_taskdata(), - "runq_hash" : self.rqdata.runq_hash, + "runq_hash" : runqhash, "logdefaultdebug" : bb.msg.loggerDefaultDebugLevel, "logdefaultverbose" : bb.msg.loggerDefaultVerbose, "logdefaultverboselogs" : bb.msg.loggerVerboseLogs, @@ -982,61 +1029,65 @@ class RunQueue: "time" : self.cfgData.getVar("TIME", True), } - worker.stdin.write("<cookerconfig>" + pickle.dumps(self.cooker.configuration) + "</cookerconfig>") - worker.stdin.write("<workerdata>" + pickle.dumps(workerdata) + "</workerdata>") + worker.stdin.write(b"<cookerconfig>" + pickle.dumps(self.cooker.configuration) + b"</cookerconfig>") + worker.stdin.write(b"<workerdata>" + pickle.dumps(workerdata) + b"</workerdata>") worker.stdin.flush() - return worker, workerpipe + return RunQueueWorker(worker, workerpipe) - def _teardown_worker(self, worker, workerpipe): + def _teardown_worker(self, worker): if not worker: return logger.debug(1, "Teardown for bitbake-worker") try: - worker.stdin.write("<quit></quit>") - worker.stdin.flush() + worker.process.stdin.write(b"<quit></quit>") + worker.process.stdin.flush() + worker.process.stdin.close() except IOError: pass - while worker.returncode is None: - workerpipe.read() - worker.poll() - while workerpipe.read(): + while worker.process.returncode is None: + worker.pipe.read() + worker.process.poll() + while worker.pipe.read(): continue - workerpipe.close() + worker.pipe.close() def start_worker(self): if self.worker: self.teardown_workers() self.teardown = False - self.worker, self.workerpipe = self._start_worker() + for mc in self.rqdata.dataCaches: + self.worker[mc] = self._start_worker(mc) def start_fakeworker(self, rqexec): if not self.fakeworker: - self.fakeworker, self.fakeworkerpipe = self._start_worker(True, rqexec) + for mc in self.rqdata.dataCaches: + self.fakeworker[mc] = self._start_worker(mc, True, rqexec) def teardown_workers(self): self.teardown = True - self._teardown_worker(self.worker, self.workerpipe) - self.worker = None - self.workerpipe = None - self._teardown_worker(self.fakeworker, self.fakeworkerpipe) - self.fakeworker = None - self.fakeworkerpipe = None + for mc in self.worker: + self._teardown_worker(self.worker[mc]) + self.worker = {} + for mc in self.fakeworker: + self._teardown_worker(self.fakeworker[mc]) + self.fakeworker = {} def read_workers(self): - self.workerpipe.read() - if self.fakeworkerpipe: - self.fakeworkerpipe.read() + for mc in self.worker: + self.worker[mc].pipe.read() + for mc in self.fakeworker: + self.fakeworker[mc].pipe.read() def active_fds(self): fds = [] - if self.workerpipe: - fds.append(self.workerpipe.input) - if self.fakeworkerpipe: - fds.append(self.fakeworkerpipe.input) + for mc in self.worker: + fds.append(self.worker[mc].pipe.input) + for mc in self.fakeworker: + fds.append(self.fakeworker[mc].pipe.input) return fds - def check_stamp_task(self, task, taskname = None, recurse = False, cache = None): + def check_stamp_task(self, tid, taskname = None, recurse = False, cache = None): def get_timestamp(f): try: if not os.access(f, os.F_OK): @@ -1045,26 +1096,26 @@ class RunQueue: except: return None + (mc, fn, tn, taskfn) = split_tid_mcfn(tid) + if taskname is None: + taskname = tn + if self.stamppolicy == "perfile": fulldeptree = False else: fulldeptree = True stampwhitelist = [] if self.stamppolicy == "whitelist": - stampwhitelist = self.rqdata.stampfnwhitelist - - fn = self.rqdata.taskData.fn_index[self.rqdata.runq_fnid[task]] - if taskname is None: - taskname = self.rqdata.runq_task[task] + stampwhitelist = self.rqdata.stampfnwhitelist[mc] - stampfile = bb.build.stampfile(taskname, self.rqdata.dataCache, fn) + stampfile = bb.build.stampfile(taskname, self.rqdata.dataCaches[mc], taskfn) # If the stamp is missing, it's not current if not os.access(stampfile, os.F_OK): logger.debug(2, "Stampfile %s not available", stampfile) return False # If it's a 'nostamp' task, it's not current - taskdep = self.rqdata.dataCache.task_deps[fn] + taskdep = self.rqdata.dataCaches[mc].task_deps[taskfn] if 'nostamp' in taskdep and taskname in taskdep['nostamp']: logger.debug(2, "%s.%s is nostamp\n", fn, taskname) return False @@ -1077,23 +1128,26 @@ class RunQueue: iscurrent = True t1 = get_timestamp(stampfile) - for dep in self.rqdata.runq_depends[task]: + for dep in self.rqdata.runtaskentries[tid].depends: if iscurrent: - fn2 = self.rqdata.taskData.fn_index[self.rqdata.runq_fnid[dep]] - taskname2 = self.rqdata.runq_task[dep] - stampfile2 = bb.build.stampfile(taskname2, self.rqdata.dataCache, fn2) - stampfile3 = bb.build.stampfile(taskname2 + "_setscene", self.rqdata.dataCache, fn2) + (mc2, fn2, taskname2, taskfn2) = split_tid_mcfn(dep) + stampfile2 = bb.build.stampfile(taskname2, self.rqdata.dataCaches[mc2], taskfn2) + stampfile3 = bb.build.stampfile(taskname2 + "_setscene", self.rqdata.dataCaches[mc2], taskfn2) t2 = get_timestamp(stampfile2) t3 = get_timestamp(stampfile3) + if t3 and not t2: + continue if t3 and t3 > t2: - continue + continue if fn == fn2 or (fulldeptree and fn2 not in stampwhitelist): if not t2: logger.debug(2, 'Stampfile %s does not exist', stampfile2) iscurrent = False + break if t1 < t2: logger.debug(2, 'Stampfile %s < %s', stampfile, stampfile2) iscurrent = False + break if recurse and iscurrent: if dep in cache: iscurrent = cache[dep] @@ -1103,7 +1157,7 @@ class RunQueue: iscurrent = self.check_stamp_task(dep, recurse=True, cache=cache) cache[dep] = iscurrent if recurse: - cache[task] = iscurrent + cache[tid] = iscurrent return iscurrent def _execute_runqueue(self): @@ -1117,19 +1171,31 @@ class RunQueue: if self.state is runQueuePrepare: self.rqexe = RunQueueExecuteDummy(self) + # NOTE: if you add, remove or significantly refactor the stages of this + # process then you should recalculate the weightings here. This is quite + # easy to do - just change the next line temporarily to pass debug=True as + # the last parameter and you'll get a printout of the weightings as well + # as a map to the lines where next_stage() was called. Of course this isn't + # critical, but it helps to keep the progress reporting accurate. + self.rqdata.init_progress_reporter = bb.progress.MultiStageProcessProgressReporter(self.cooker.data, + "Initialising tasks", + [43, 967, 4, 3, 1, 5, 3, 7, 13, 1, 2, 1, 1, 246, 35, 1, 38, 1, 35, 2, 338, 204, 142, 3, 3, 37, 244]) if self.rqdata.prepare() == 0: self.state = runQueueComplete else: self.state = runQueueSceneInit + self.rqdata.init_progress_reporter.next_stage() - # we are ready to run, see if any UI client needs the dependency info - if bb.cooker.CookerFeatures.SEND_DEPENDS_TREE in self.cooker.featureset: - depgraph = self.cooker.buildDependTree(self, self.rqdata.taskData) - bb.event.fire(bb.event.DepTreeGenerated(depgraph), self.cooker.data) + # we are ready to run, emit dependency info to any UI or class which + # needs it + depgraph = self.cooker.buildDependTree(self, self.rqdata.taskData) + self.rqdata.init_progress_reporter.next_stage() + bb.event.fire(bb.event.DepTreeGenerated(depgraph), self.cooker.data) if self.state is runQueueSceneInit: dump = self.cooker.configuration.dump_signatures if dump: + self.rqdata.init_progress_reporter.finish() if 'printdiff' in dump: invalidtasks = self.print_diffscenetasks() self.dump_signatures(dump) @@ -1137,7 +1203,9 @@ class RunQueue: self.write_diffscenetasks(invalidtasks) self.state = runQueueComplete else: + self.rqdata.init_progress_reporter.next_stage() self.start_worker() + self.rqdata.init_progress_reporter.next_stage() self.rqexe = RunQueueExecuteScenequeue(self) if self.state in [runQueueSceneRun, runQueueRunning, runQueueCleanUp]: @@ -1150,6 +1218,8 @@ class RunQueue: if self.cooker.configuration.setsceneonly: self.state = runQueueComplete else: + # Just in case we didn't setscene + self.rqdata.init_progress_reporter.finish() logger.info("Executing RunQueue Tasks") self.rqexe = RunQueueExecuteTasks(self) self.state = runQueueRunning @@ -1169,10 +1239,11 @@ class RunQueue: logger.info("Tasks Summary: Attempted %d tasks of which %d didn't need to be rerun and all succeeded.", self.rqexe.stats.completed, self.rqexe.stats.skipped) if self.state is runQueueFailed: - if not self.rqdata.taskData.tryaltconfigs: - raise bb.runqueue.TaskFailure(self.rqexe.failed_fnids) - for fnid in self.rqexe.failed_fnids: - self.rqdata.taskData.fail_fnid(fnid) + if not self.rqdata.taskData[''].tryaltconfigs: + raise bb.runqueue.TaskFailure(self.rqexe.failed_tids) + for tid in self.rqexe.failed_tids: + (mc, fn, tn, _) = split_tid_mcfn(tid) + self.rqdata.taskData[mc].fail_fn(fn) self.rqdata.reset() if self.state is runQueueComplete: @@ -1197,8 +1268,8 @@ class RunQueue: pass self.state = runQueueComplete raise - except: - logger.error("An uncaught exception occured in runqueue, please see the failure below:") + except Exception as err: + logger.exception("An uncaught exception occurred in runqueue") try: self.teardown_workers() except: @@ -1219,13 +1290,14 @@ class RunQueue: def dump_signatures(self, options): done = set() bb.note("Reparsing files to collect dependency data") - for task in range(len(self.rqdata.runq_fnid)): - if self.rqdata.runq_fnid[task] not in done: - fn = self.rqdata.taskData.fn_index[self.rqdata.runq_fnid[task]] - the_data = bb.cache.Cache.loadDataFull(fn, self.cooker.collection.get_file_appends(fn), self.cooker.data) - done.add(self.rqdata.runq_fnid[task]) + bb_cache = bb.cache.NoCache(self.cooker.databuilder) + for tid in self.rqdata.runtaskentries: + fn = fn_from_tid(tid) + if fn not in done: + the_data = bb_cache.loadDataFull(fn, self.cooker.collection.get_file_appends(fn)) + done.add(fn) - bb.parse.siggen.dump_sigs(self.rqdata.dataCache, options) + bb.parse.siggen.dump_sigs(self.rqdata.dataCaches, options) return @@ -1241,20 +1313,19 @@ class RunQueue: stamppresent = [] valid_new = set() - for task in xrange(len(self.rqdata.runq_fnid)): - fn = self.rqdata.taskData.fn_index[self.rqdata.runq_fnid[task]] - taskname = self.rqdata.runq_task[task] - taskdep = self.rqdata.dataCache.task_deps[fn] + for tid in self.rqdata.runtaskentries: + (mc, fn, taskname, taskfn) = split_tid_mcfn(tid) + taskdep = self.rqdata.dataCaches[mc].task_deps[taskfn] if 'noexec' in taskdep and taskname in taskdep['noexec']: - noexec.append(task) + noexec.append(tid) continue sq_fn.append(fn) - sq_hashfn.append(self.rqdata.dataCache.hashfn[fn]) - sq_hash.append(self.rqdata.runq_hash[task]) + sq_hashfn.append(self.rqdata.dataCaches[mc].hashfn[fn]) + sq_hash.append(self.rqdata.runtaskentries[tid].hash) sq_taskname.append(taskname) - sq_task.append(task) + sq_task.append(tid) locs = { "sq_fn" : sq_fn, "sq_task" : sq_taskname, "sq_hash" : sq_hash, "sq_hashfn" : sq_hashfn, "d" : self.cooker.expanded_data } try: call = self.hashvalidate + "(sq_fn, sq_task, sq_hash, sq_hashfn, d, siginfo=True)" @@ -1269,13 +1340,13 @@ class RunQueue: # Tasks which are both setscene and noexec never care about dependencies # We therefore find tasks which are setscene and noexec and mark their # unique dependencies as valid. - for task in noexec: - if task not in self.rqdata.runq_setscene: + for tid in noexec: + if tid not in self.rqdata.runq_setscene_tids: continue - for dep in self.rqdata.runq_depends[task]: + for dep in self.rqdata.runtaskentries[tid].depends: hasnoexecparents = True - for dep2 in self.rqdata.runq_revdeps[dep]: - if dep2 in self.rqdata.runq_setscene and dep2 in noexec: + for dep2 in self.rqdata.runtaskentries[dep].revdeps: + if dep2 in self.rqdata.runq_setscene_tids and dep2 in noexec: continue hasnoexecparents = False break @@ -1283,30 +1354,30 @@ class RunQueue: valid_new.add(dep) invalidtasks = set() - for task in xrange(len(self.rqdata.runq_fnid)): - if task not in valid_new and task not in noexec: - invalidtasks.add(task) + for tid in self.rqdata.runtaskentries: + if tid not in valid_new and tid not in noexec: + invalidtasks.add(tid) found = set() processed = set() - for task in invalidtasks: - toprocess = set([task]) + for tid in invalidtasks: + toprocess = set([tid]) while toprocess: next = set() for t in toprocess: - for dep in self.rqdata.runq_depends[t]: + for dep in self.rqdata.runtaskentries[t].depends: if dep in invalidtasks: - found.add(task) + found.add(tid) if dep not in processed: processed.add(dep) next.add(dep) toprocess = next - if task in found: + if tid in found: toprocess = set() tasklist = [] - for task in invalidtasks.difference(found): - tasklist.append(self.rqdata.get_user_idstring(task)) + for tid in invalidtasks.difference(found): + tasklist.append(tid) if tasklist: bb.plain("The differences between the current build and any cached tasks start at the following tasks:\n" + "\n".join(tasklist)) @@ -1330,11 +1401,10 @@ class RunQueue: return recout - for task in invalidtasks: - fn = self.rqdata.taskData.fn_index[self.rqdata.runq_fnid[task]] - pn = self.rqdata.dataCache.pkg_fn[fn] - taskname = self.rqdata.runq_task[task] - h = self.rqdata.runq_hash[task] + for tid in invalidtasks: + (mc, fn, taskname, _) = split_tid_mcfn(tid) + pn = self.rqdata.dataCaches[mc].pkg_fn[fn] + h = self.rqdata.runtaskentries[tid].hash matches = bb.siggen.find_siginfo(pn, taskname, [], self.cfgData) match = None for m in matches: @@ -1342,7 +1412,7 @@ class RunQueue: match = m if match is None: bb.fatal("Can't find a task we're supposed to have written out? (hash: %s)?" % h) - matches = {k : v for k, v in matches.iteritems() if h not in k} + matches = {k : v for k, v in iter(matches.items()) if h not in k} if matches: latestmatch = sorted(matches.keys(), key=lambda f: matches[f])[-1] prevh = __find_md5__.search(latestmatch).group(0) @@ -1360,19 +1430,20 @@ class RunQueueExecute: self.number_tasks = int(self.cfgData.getVar("BB_NUMBER_THREADS", True) or 1) self.scheduler = self.cfgData.getVar("BB_SCHEDULER", True) or "speed" - self.runq_buildable = [] - self.runq_running = [] - self.runq_complete = [] + self.runq_buildable = set() + self.runq_running = set() + self.runq_complete = set() self.build_stamps = {} self.build_stamps2 = [] - self.failed_fnids = [] + self.failed_tids = [] self.stampcache = {} - rq.workerpipe.setrunqueueexec(self) - if rq.fakeworkerpipe: - rq.fakeworkerpipe.setrunqueueexec(self) + for mc in rq.worker: + rq.worker[mc].pipe.setrunqueueexec(self) + for mc in rq.fakeworker: + rq.fakeworker[mc].pipe.setrunqueueexec(self) if self.number_tasks <= 0: bb.fatal("Invalid BB_NUMBER_THREADS %s" % self.number_tasks) @@ -1391,18 +1462,22 @@ class RunQueueExecute: return True def finish_now(self): - - for worker in [self.rq.worker, self.rq.fakeworker]: - if not worker: - continue + for mc in self.rq.worker: + try: + self.rq.worker[mc].process.stdin.write(b"<finishnow></finishnow>") + self.rq.worker[mc].process.stdin.flush() + except IOError: + # worker must have died? + pass + for mc in self.rq.fakeworker: try: - worker.stdin.write("<finishnow></finishnow>") - worker.stdin.flush() + self.rq.fakeworker[mc].process.stdin.write(b"<finishnow></finishnow>") + self.rq.fakeworker[mc].process.stdin.flush() except IOError: # worker must have died? pass - if len(self.failed_fnids) != 0: + if len(self.failed_tids) != 0: self.rq.state = runQueueFailed return @@ -1417,7 +1492,7 @@ class RunQueueExecute: self.rq.read_workers() return self.rq.active_fds() - if len(self.failed_fnids) != 0: + if len(self.failed_tids) != 0: self.rq.state = runQueueFailed return True @@ -1431,13 +1506,8 @@ class RunQueueExecute: taskdata = {} taskdeps.add(task) for dep in taskdeps: - if setscene: - depid = self.rqdata.runq_setscene[dep] - else: - depid = dep - fn = self.rqdata.taskData.fn_index[self.rqdata.runq_fnid[depid]] - pn = self.rqdata.dataCache.pkg_fn[fn] - taskname = self.rqdata.runq_task[depid] + (mc, fn, taskname, _) = split_tid_mcfn(dep) + pn = self.rqdata.dataCaches[mc].pkg_fn[fn] taskdata[dep] = [pn, taskname, fn] call = self.rq.depvalidate + "(task, taskdata, notneeded, d)" locs = { "task" : task, "taskdata" : taskdata, "notneeded" : self.scenequeue_notneeded, "d" : self.cooker.expanded_data } @@ -1457,34 +1527,32 @@ class RunQueueExecuteTasks(RunQueueExecute): def __init__(self, rq): RunQueueExecute.__init__(self, rq) - self.stats = RunQueueStats(len(self.rqdata.runq_fnid)) + self.stats = RunQueueStats(len(self.rqdata.runtaskentries)) self.stampcache = {} initial_covered = self.rq.scenequeue_covered.copy() # Mark initial buildable tasks - for task in xrange(self.stats.total): - self.runq_running.append(0) - self.runq_complete.append(0) - if len(self.rqdata.runq_depends[task]) == 0: - self.runq_buildable.append(1) - else: - self.runq_buildable.append(0) - if len(self.rqdata.runq_revdeps[task]) > 0 and self.rqdata.runq_revdeps[task].issubset(self.rq.scenequeue_covered): - self.rq.scenequeue_covered.add(task) + for tid in self.rqdata.runtaskentries: + if len(self.rqdata.runtaskentries[tid].depends) == 0: + self.runq_buildable.add(tid) + if len(self.rqdata.runtaskentries[tid].revdeps) > 0 and self.rqdata.runtaskentries[tid].revdeps.issubset(self.rq.scenequeue_covered): + self.rq.scenequeue_covered.add(tid) found = True while found: found = False - for task in xrange(self.stats.total): - if task in self.rq.scenequeue_covered: + for tid in self.rqdata.runtaskentries: + if tid in self.rq.scenequeue_covered: continue - logger.debug(1, 'Considering %s (%s): %s' % (task, self.rqdata.get_user_idstring(task), str(self.rqdata.runq_revdeps[task]))) + logger.debug(1, 'Considering %s: %s' % (tid, str(self.rqdata.runtaskentries[tid].revdeps))) - if len(self.rqdata.runq_revdeps[task]) > 0 and self.rqdata.runq_revdeps[task].issubset(self.rq.scenequeue_covered): + if len(self.rqdata.runtaskentries[tid].revdeps) > 0 and self.rqdata.runtaskentries[tid].revdeps.issubset(self.rq.scenequeue_covered): + if tid in self.rq.scenequeue_notcovered: + continue found = True - self.rq.scenequeue_covered.add(task) + self.rq.scenequeue_covered.add(tid) logger.debug(1, 'Skip list (pre setsceneverify) %s', sorted(self.rq.scenequeue_covered)) @@ -1492,35 +1560,32 @@ class RunQueueExecuteTasks(RunQueueExecute): covered_remove = set() if self.rq.setsceneverify: invalidtasks = [] - for task in xrange(len(self.rqdata.runq_task)): - fn = self.rqdata.taskData.fn_index[self.rqdata.runq_fnid[task]] - taskname = self.rqdata.runq_task[task] - taskdep = self.rqdata.dataCache.task_deps[fn] - + tasknames = {} + fns = {} + for tid in self.rqdata.runtaskentries: + (mc, fn, taskname, taskfn) = split_tid_mcfn(tid) + taskdep = self.rqdata.dataCaches[mc].task_deps[taskfn] + fns[tid] = taskfn + tasknames[tid] = taskname if 'noexec' in taskdep and taskname in taskdep['noexec']: continue - if self.rq.check_stamp_task(task, taskname + "_setscene", cache=self.stampcache): - logger.debug(2, 'Setscene stamp current for task %s(%s)', task, self.rqdata.get_user_idstring(task)) + if self.rq.check_stamp_task(tid, taskname + "_setscene", cache=self.stampcache): + logger.debug(2, 'Setscene stamp current for task %s', tid) continue - if self.rq.check_stamp_task(task, taskname, recurse = True, cache=self.stampcache): - logger.debug(2, 'Normal stamp current for task %s(%s)', task, self.rqdata.get_user_idstring(task)) + if self.rq.check_stamp_task(tid, taskname, recurse = True, cache=self.stampcache): + logger.debug(2, 'Normal stamp current for task %s', tid) continue - invalidtasks.append(task) + invalidtasks.append(tid) - call = self.rq.setsceneverify + "(covered, tasknames, fnids, fns, d, invalidtasks=invalidtasks)" - call2 = self.rq.setsceneverify + "(covered, tasknames, fnids, fns, d)" - locs = { "covered" : self.rq.scenequeue_covered, "tasknames" : self.rqdata.runq_task, "fnids" : self.rqdata.runq_fnid, "fns" : self.rqdata.taskData.fn_index, "d" : self.cooker.expanded_data, "invalidtasks" : invalidtasks } - # Backwards compatibility with older versions without invalidtasks - try: - covered_remove = bb.utils.better_eval(call, locs) - except TypeError: - covered_remove = bb.utils.better_eval(call2, locs) + call = self.rq.setsceneverify + "(covered, tasknames, fns, d, invalidtasks=invalidtasks)" + locs = { "covered" : self.rq.scenequeue_covered, "tasknames" : tasknames, "fns" : fns, "d" : self.cooker.expanded_data, "invalidtasks" : invalidtasks } + covered_remove = bb.utils.better_eval(call, locs) - def removecoveredtask(task): - fn = self.rqdata.taskData.fn_index[self.rqdata.runq_fnid[task]] - taskname = self.rqdata.runq_task[task] + '_setscene' - bb.build.del_stamp(taskname, self.rqdata.dataCache, fn) - self.rq.scenequeue_covered.remove(task) + def removecoveredtask(tid): + (mc, fn, taskname, taskfn) = split_tid_mcfn(tid) + taskname = taskname + '_setscene' + bb.build.del_stamp(taskname, self.rqdata.dataCaches[mc], taskfn) + self.rq.scenequeue_covered.remove(tid) toremove = covered_remove for task in toremove: @@ -1529,7 +1594,7 @@ class RunQueueExecuteTasks(RunQueueExecute): covered_remove = [] for task in toremove: removecoveredtask(task) - for deptask in self.rqdata.runq_depends[task]: + for deptask in self.rqdata.runtaskentries[task].depends: if deptask not in self.rq.scenequeue_covered: continue if deptask in toremove or deptask in covered_remove or deptask in initial_covered: @@ -1540,7 +1605,15 @@ class RunQueueExecuteTasks(RunQueueExecute): logger.debug(1, 'Full skip list %s', self.rq.scenequeue_covered) - event.fire(bb.event.StampUpdate(self.rqdata.target_pairs, self.rqdata.dataCache.stamp), self.cfgData) + + for mc in self.rqdata.dataCaches: + target_pairs = [] + for tid in self.rqdata.target_tids: + (tidmc, fn, taskname, _) = split_tid_mcfn(tid) + if tidmc == mc: + target_pairs.append((fn, taskname)) + + event.fire(bb.event.StampUpdate(target_pairs, self.rqdata.dataCaches[mc].stamp), self.cfgData) schedulers = self.get_schedulers() for scheduler in schedulers: @@ -1575,7 +1648,7 @@ class RunQueueExecuteTasks(RunQueueExecute): return schedulers def setbuildable(self, task): - self.runq_buildable[task] = 1 + self.runq_buildable.add(task) self.sched.newbuilable(task) def task_completeoutright(self, task): @@ -1584,21 +1657,21 @@ class RunQueueExecuteTasks(RunQueueExecute): Look at the reverse dependencies and mark any task with completed dependencies as buildable """ - self.runq_complete[task] = 1 - for revdep in self.rqdata.runq_revdeps[task]: - if self.runq_running[revdep] == 1: + self.runq_complete.add(task) + for revdep in self.rqdata.runtaskentries[task].revdeps: + if revdep in self.runq_running: continue - if self.runq_buildable[revdep] == 1: + if revdep in self.runq_buildable: continue alldeps = 1 - for dep in self.rqdata.runq_depends[revdep]: - if self.runq_complete[dep] != 1: + for dep in self.rqdata.runtaskentries[revdep].depends: + if dep not in self.runq_complete: alldeps = 0 if alldeps == 1: self.setbuildable(revdep) - fn = self.rqdata.taskData.fn_index[self.rqdata.runq_fnid[revdep]] - taskname = self.rqdata.runq_task[revdep] - logger.debug(1, "Marking task %s (%s, %s) as buildable", revdep, fn, taskname) + fn = fn_from_tid(revdep) + taskname = taskname_from_tid(revdep) + logger.debug(1, "Marking task %s as buildable", revdep) def task_complete(self, task): self.stats.taskCompleted() @@ -1611,14 +1684,13 @@ class RunQueueExecuteTasks(RunQueueExecute): Updates the state engine with the failure """ self.stats.taskFailed() - fnid = self.rqdata.runq_fnid[task] - self.failed_fnids.append(fnid) + self.failed_tids.append(task) bb.event.fire(runQueueTaskFailed(task, self.stats, exitcode, self.rq), self.cfgData) - if self.rqdata.taskData.abort: + if self.rqdata.taskData[''].abort: self.rq.state = runQueueCleanUp def task_skip(self, task, reason): - self.runq_running[task] = 1 + self.runq_running.add(task) self.setbuildable(task) bb.event.fire(runQueueTaskSkipped(task, self.stats, self.rq, reason), self.cfgData) self.task_completeoutright(task) @@ -1630,8 +1702,52 @@ class RunQueueExecuteTasks(RunQueueExecute): Run the tasks in a queue prepared by rqdata.prepare() """ + if self.rqdata.setscenewhitelist and not self.rqdata.setscenewhitelist_checked: + self.rqdata.setscenewhitelist_checked = True + + # Check tasks that are going to run against the whitelist + def check_norun_task(tid, showerror=False): + (mc, fn, taskname, _) = split_tid_mcfn(tid) + # Ignore covered tasks + if tid in self.rq.scenequeue_covered: + return False + # Ignore stamped tasks + if self.rq.check_stamp_task(tid, taskname, cache=self.stampcache): + return False + # Ignore noexec tasks + taskdep = self.rqdata.dataCaches[mc].task_deps[fn] + if 'noexec' in taskdep and taskname in taskdep['noexec']: + return False + + pn = self.rqdata.dataCaches[mc].pkg_fn[fn] + if not check_setscene_enforce_whitelist(pn, taskname, self.rqdata.setscenewhitelist): + if showerror: + if tid in self.rqdata.runq_setscene_tids: + logger.error('Task %s.%s attempted to execute unexpectedly and should have been setscened' % (pn, taskname)) + else: + logger.error('Task %s.%s attempted to execute unexpectedly' % (pn, taskname)) + return True + return False + # Look to see if any tasks that we think shouldn't run are going to + unexpected = False + for tid in self.rqdata.runtaskentries: + if check_norun_task(tid): + unexpected = True + break + if unexpected: + # Run through the tasks in the rough order they'd have executed and print errors + # (since the order can be useful - usually missing sstate for the last few tasks + # is the cause of the problem) + task = self.sched.next() + while task is not None: + check_norun_task(task, showerror=True) + self.task_skip(task, 'Setscene enforcement check') + task = self.sched.next() + + self.rq.state = runQueueCleanUp + return True + self.rq.read_workers() - if self.stats.total == 0: # nothing to do @@ -1639,30 +1755,28 @@ class RunQueueExecuteTasks(RunQueueExecute): task = self.sched.next() if task is not None: - fn = self.rqdata.taskData.fn_index[self.rqdata.runq_fnid[task]] - taskname = self.rqdata.runq_task[task] + (mc, fn, taskname, taskfn) = split_tid_mcfn(task) if task in self.rq.scenequeue_covered: - logger.debug(2, "Setscene covered task %s (%s)", task, - self.rqdata.get_user_idstring(task)) + logger.debug(2, "Setscene covered task %s", task) self.task_skip(task, "covered") return True if self.rq.check_stamp_task(task, taskname, cache=self.stampcache): - logger.debug(2, "Stamp current task %s (%s)", task, - self.rqdata.get_user_idstring(task)) + logger.debug(2, "Stamp current task %s", task) + self.task_skip(task, "existing") return True - taskdep = self.rqdata.dataCache.task_deps[fn] + taskdep = self.rqdata.dataCaches[mc].task_deps[taskfn] if 'noexec' in taskdep and taskname in taskdep['noexec']: startevent = runQueueTaskStarted(task, self.stats, self.rq, noexec=True) bb.event.fire(startevent, self.cfgData) - self.runq_running[task] = 1 + self.runq_running.add(task) self.stats.taskActive() if not self.cooker.configuration.dry_run: - bb.build.make_stamp(taskname, self.rqdata.dataCache, fn) + bb.build.make_stamp(taskname, self.rqdata.dataCaches[mc], taskfn) self.task_complete(task) return True else: @@ -1671,24 +1785,25 @@ class RunQueueExecuteTasks(RunQueueExecute): taskdepdata = self.build_taskdepdata(task) - taskdep = self.rqdata.dataCache.task_deps[fn] + taskdep = self.rqdata.dataCaches[mc].task_deps[taskfn] if 'fakeroot' in taskdep and taskname in taskdep['fakeroot'] and not self.cooker.configuration.dry_run: if not self.rq.fakeworker: try: self.rq.start_fakeworker(self) except OSError as exc: - logger.critical("Failed to spawn fakeroot worker to run %s:%s: %s" % (fn, taskname, str(exc))) + logger.critical("Failed to spawn fakeroot worker to run %s: %s" % (task, str(exc))) self.rq.state = runQueueFailed + self.stats.taskFailed() return True - self.rq.fakeworker.stdin.write("<runtask>" + pickle.dumps((fn, task, taskname, False, self.cooker.collection.get_file_appends(fn), taskdepdata)) + "</runtask>") - self.rq.fakeworker.stdin.flush() + self.rq.fakeworker[mc].process.stdin.write(b"<runtask>" + pickle.dumps((taskfn, task, taskname, False, self.cooker.collection.get_file_appends(fn), taskdepdata)) + b"</runtask>") + self.rq.fakeworker[mc].process.stdin.flush() else: - self.rq.worker.stdin.write("<runtask>" + pickle.dumps((fn, task, taskname, False, self.cooker.collection.get_file_appends(fn), taskdepdata)) + "</runtask>") - self.rq.worker.stdin.flush() + self.rq.worker[mc].process.stdin.write(b"<runtask>" + pickle.dumps((taskfn, task, taskname, False, self.cooker.collection.get_file_appends(taskfn), taskdepdata)) + b"</runtask>") + self.rq.worker[mc].process.stdin.flush() - self.build_stamps[task] = bb.build.stampfile(taskname, self.rqdata.dataCache, fn) - self.build_stamps2.append(self.build_stamps[task]) - self.runq_running[task] = 1 + self.build_stamps[task] = bb.build.stampfile(taskname, self.rqdata.dataCaches[mc], taskfn, noextra=True) + self.build_stamps2.append(self.build_stamps[task]) + self.runq_running.add(task) self.stats.taskActive() if self.stats.active < self.number_tasks: return True @@ -1697,17 +1812,17 @@ class RunQueueExecuteTasks(RunQueueExecute): self.rq.read_workers() return self.rq.active_fds() - if len(self.failed_fnids) != 0: + if len(self.failed_tids) != 0: self.rq.state = runQueueFailed return True # Sanity Checks - for task in xrange(self.stats.total): - if self.runq_buildable[task] == 0: + for task in self.rqdata.runtaskentries: + if task not in self.runq_buildable: logger.error("Task %s never buildable!", task) - if self.runq_running[task] == 0: + if task not in self.runq_running: logger.error("Task %s never ran!", task) - if self.runq_complete[task] == 0: + if task not in self.runq_complete: logger.error("Task %s never completed!", task) self.rq.state = runQueueComplete @@ -1715,16 +1830,15 @@ class RunQueueExecuteTasks(RunQueueExecute): def build_taskdepdata(self, task): taskdepdata = {} - next = self.rqdata.runq_depends[task] + next = self.rqdata.runtaskentries[task].depends next.add(task) while next: additional = [] for revdep in next: - fn = self.rqdata.taskData.fn_index[self.rqdata.runq_fnid[revdep]] - pn = self.rqdata.dataCache.pkg_fn[fn] - taskname = self.rqdata.runq_task[revdep] - deps = self.rqdata.runq_depends[revdep] - provides = self.rqdata.dataCache.fn_provides[fn] + (mc, fn, taskname, taskfn) = split_tid_mcfn(revdep) + pn = self.rqdata.dataCaches[mc].pkg_fn[taskfn] + deps = self.rqdata.runtaskentries[revdep].depends + provides = self.rqdata.dataCaches[mc].fn_provides[taskfn] taskdepdata[revdep] = [pn, taskname, fn, deps, provides] for revdep2 in deps: if revdep2 not in taskdepdata: @@ -1743,16 +1857,16 @@ class RunQueueExecuteScenequeue(RunQueueExecute): self.scenequeue_notneeded = set() # If we don't have any setscene functions, skip this step - if len(self.rqdata.runq_setscene) == 0: + if len(self.rqdata.runq_setscene_tids) == 0: rq.scenequeue_covered = set() rq.state = runQueueRunInit return - self.stats = RunQueueStats(len(self.rqdata.runq_setscene)) + self.stats = RunQueueStats(len(self.rqdata.runq_setscene_tids)) - sq_revdeps = [] - sq_revdeps_new = [] - sq_revdeps_squash = [] + sq_revdeps = {} + sq_revdeps_new = {} + sq_revdeps_squash = {} self.sq_harddeps = {} # We need to construct a dependency graph for the setscene functions. Intermediate @@ -1760,25 +1874,29 @@ class RunQueueExecuteScenequeue(RunQueueExecute): # therefore aims to collapse the huge runqueue dependency tree into a smaller one # only containing the setscene functions. - for task in xrange(self.stats.total): - self.runq_running.append(0) - self.runq_complete.append(0) - self.runq_buildable.append(0) + self.rqdata.init_progress_reporter.next_stage() # First process the chains up to the first setscene task. endpoints = {} - for task in xrange(len(self.rqdata.runq_fnid)): - sq_revdeps.append(copy.copy(self.rqdata.runq_revdeps[task])) - sq_revdeps_new.append(set()) - if (len(self.rqdata.runq_revdeps[task]) == 0) and task not in self.rqdata.runq_setscene: - endpoints[task] = set() + for tid in self.rqdata.runtaskentries: + sq_revdeps[tid] = copy.copy(self.rqdata.runtaskentries[tid].revdeps) + sq_revdeps_new[tid] = set() + if (len(sq_revdeps[tid]) == 0) and tid not in self.rqdata.runq_setscene_tids: + #bb.warn("Added endpoint %s" % (tid)) + endpoints[tid] = set() + + self.rqdata.init_progress_reporter.next_stage() # Secondly process the chains between setscene tasks. - for task in self.rqdata.runq_setscene: - for dep in self.rqdata.runq_depends[task]: + for tid in self.rqdata.runq_setscene_tids: + #bb.warn("Added endpoint 2 %s" % (tid)) + for dep in self.rqdata.runtaskentries[tid].depends: if dep not in endpoints: endpoints[dep] = set() - endpoints[dep].add(task) + #bb.warn(" Added endpoint 3 %s" % (dep)) + endpoints[dep].add(tid) + + self.rqdata.init_progress_reporter.next_stage() def process_endpoints(endpoints): newendpoints = {} @@ -1789,26 +1907,28 @@ class RunQueueExecuteScenequeue(RunQueueExecute): if sq_revdeps_new[point]: tasks |= sq_revdeps_new[point] sq_revdeps_new[point] = set() - if point in self.rqdata.runq_setscene: + if point in self.rqdata.runq_setscene_tids: sq_revdeps_new[point] = tasks tasks = set() - for dep in self.rqdata.runq_depends[point]: + for dep in self.rqdata.runtaskentries[point].depends: if point in sq_revdeps[dep]: sq_revdeps[dep].remove(point) if tasks: sq_revdeps_new[dep] |= tasks - if (len(sq_revdeps[dep]) == 0 or len(sq_revdeps_new[dep]) != 0) and dep not in self.rqdata.runq_setscene: + if (len(sq_revdeps[dep]) == 0 or len(sq_revdeps_new[dep]) != 0) and dep not in self.rqdata.runq_setscene_tids: newendpoints[dep] = task if len(newendpoints) != 0: process_endpoints(newendpoints) process_endpoints(endpoints) + self.rqdata.init_progress_reporter.next_stage() + # Build a list of setscene tasks which are "unskippable" # These are direct endpoints referenced by the build endpoints2 = {} - sq_revdeps2 = [] - sq_revdeps_new2 = [] + sq_revdeps2 = {} + sq_revdeps_new2 = {} def process_endpoints2(endpoints): newendpoints = {} for point, task in endpoints.items(): @@ -1818,84 +1938,99 @@ class RunQueueExecuteScenequeue(RunQueueExecute): if sq_revdeps_new2[point]: tasks |= sq_revdeps_new2[point] sq_revdeps_new2[point] = set() - if point in self.rqdata.runq_setscene: + if point in self.rqdata.runq_setscene_tids: sq_revdeps_new2[point] = tasks - for dep in self.rqdata.runq_depends[point]: + for dep in self.rqdata.runtaskentries[point].depends: if point in sq_revdeps2[dep]: sq_revdeps2[dep].remove(point) if tasks: sq_revdeps_new2[dep] |= tasks - if (len(sq_revdeps2[dep]) == 0 or len(sq_revdeps_new2[dep]) != 0) and dep not in self.rqdata.runq_setscene: + if (len(sq_revdeps2[dep]) == 0 or len(sq_revdeps_new2[dep]) != 0) and dep not in self.rqdata.runq_setscene_tids: newendpoints[dep] = tasks if len(newendpoints) != 0: process_endpoints2(newendpoints) - for task in xrange(len(self.rqdata.runq_fnid)): - sq_revdeps2.append(copy.copy(self.rqdata.runq_revdeps[task])) - sq_revdeps_new2.append(set()) - if (len(self.rqdata.runq_revdeps[task]) == 0) and task not in self.rqdata.runq_setscene: - endpoints2[task] = set() + for tid in self.rqdata.runtaskentries: + sq_revdeps2[tid] = copy.copy(self.rqdata.runtaskentries[tid].revdeps) + sq_revdeps_new2[tid] = set() + if (len(sq_revdeps2[tid]) == 0) and tid not in self.rqdata.runq_setscene_tids: + endpoints2[tid] = set() process_endpoints2(endpoints2) self.unskippable = [] - for task in self.rqdata.runq_setscene: - if sq_revdeps_new2[task]: - self.unskippable.append(self.rqdata.runq_setscene.index(task)) + for tid in self.rqdata.runq_setscene_tids: + if sq_revdeps_new2[tid]: + self.unskippable.append(tid) - for task in xrange(len(self.rqdata.runq_fnid)): - if task in self.rqdata.runq_setscene: + self.rqdata.init_progress_reporter.next_stage(len(self.rqdata.runtaskentries)) + + for taskcounter, tid in enumerate(self.rqdata.runtaskentries): + if tid in self.rqdata.runq_setscene_tids: deps = set() - for dep in sq_revdeps_new[task]: - deps.add(self.rqdata.runq_setscene.index(dep)) - sq_revdeps_squash.append(deps) - elif len(sq_revdeps_new[task]) != 0: + for dep in sq_revdeps_new[tid]: + deps.add(dep) + sq_revdeps_squash[tid] = deps + elif len(sq_revdeps_new[tid]) != 0: bb.msg.fatal("RunQueue", "Something went badly wrong during scenequeue generation, aborting. Please report this problem.") + self.rqdata.init_progress_reporter.update(taskcounter) + + self.rqdata.init_progress_reporter.next_stage() # Resolve setscene inter-task dependencies # e.g. do_sometask_setscene[depends] = "targetname:do_someothertask_setscene" # Note that anything explicitly depended upon will have its reverse dependencies removed to avoid circular dependencies - for task in self.rqdata.runq_setscene: - realid = self.rqdata.taskData.gettask_id(self.rqdata.taskData.fn_index[self.rqdata.runq_fnid[task]], self.rqdata.runq_task[task] + "_setscene", False) - idepends = self.rqdata.taskData.tasks_idepends[realid] - for (depid, idependtask) in idepends: - if depid not in self.rqdata.taskData.build_targets: + for tid in self.rqdata.runq_setscene_tids: + (mc, fn, taskname, _) = split_tid_mcfn(tid) + realtid = fn + ":" + taskname + "_setscene" + idepends = self.rqdata.taskData[mc].taskentries[realtid].idepends + for (depname, idependtask) in idepends: + + if depname not in self.rqdata.taskData[mc].build_targets: continue - depdata = self.rqdata.taskData.build_targets[depid][0] - if depdata is None: + depfn = self.rqdata.taskData[mc].build_targets[depname][0] + if depfn is None: continue - dep = self.rqdata.taskData.fn_index[depdata] - taskid = self.rqdata.get_task_id(self.rqdata.taskData.getfn_id(dep), idependtask.replace("_setscene", "")) - if taskid is None: - bb.msg.fatal("RunQueue", "Task %s_setscene depends upon non-existent task %s:%s" % (self.rqdata.get_user_idstring(task), dep, idependtask)) + deptid = depfn + ":" + idependtask.replace("_setscene", "") + if deptid not in self.rqdata.runtaskentries: + bb.msg.fatal("RunQueue", "Task %s depends upon non-existent task %s:%s" % (realtid, depfn, idependtask)) - if not self.rqdata.runq_setscene.index(taskid) in self.sq_harddeps: - self.sq_harddeps[self.rqdata.runq_setscene.index(taskid)] = set() - self.sq_harddeps[self.rqdata.runq_setscene.index(taskid)].add(self.rqdata.runq_setscene.index(task)) + if not deptid in self.sq_harddeps: + self.sq_harddeps[deptid] = set() + self.sq_harddeps[deptid].add(tid) - sq_revdeps_squash[self.rqdata.runq_setscene.index(task)].add(self.rqdata.runq_setscene.index(taskid)) + sq_revdeps_squash[tid].add(deptid) # Have to zero this to avoid circular dependencies - sq_revdeps_squash[self.rqdata.runq_setscene.index(taskid)] = set() + sq_revdeps_squash[deptid] = set() + + self.rqdata.init_progress_reporter.next_stage() for task in self.sq_harddeps: for dep in self.sq_harddeps[task]: sq_revdeps_squash[dep].add(task) - #for task in xrange(len(sq_revdeps_squash)): - # realtask = self.rqdata.runq_setscene[task] - # bb.warn("Task %s: %s_setscene is %s " % (task, self.rqdata.get_user_idstring(realtask) , sq_revdeps_squash[task])) + self.rqdata.init_progress_reporter.next_stage() + + #for tid in sq_revdeps_squash: + # for dep in sq_revdeps_squash[tid]: + # data = data + "\n %s" % dep + # bb.warn("Task %s_setscene: is %s " % (tid, data - self.sq_deps = [] + self.sq_deps = {} self.sq_revdeps = sq_revdeps_squash self.sq_revdeps2 = copy.deepcopy(self.sq_revdeps) - for task in xrange(len(self.sq_revdeps)): - self.sq_deps.append(set()) - for task in xrange(len(self.sq_revdeps)): - for dep in self.sq_revdeps[task]: - self.sq_deps[dep].add(task) + for tid in self.sq_revdeps: + self.sq_deps[tid] = set() + for tid in self.sq_revdeps: + for dep in self.sq_revdeps[tid]: + self.sq_deps[dep].add(tid) + + self.rqdata.init_progress_reporter.next_stage() + + for tid in self.sq_revdeps: + if len(self.sq_revdeps[tid]) == 0: + self.runq_buildable.add(tid) - for task in xrange(len(self.sq_revdeps)): - if len(self.sq_revdeps[task]) == 0: - self.runq_buildable[task] = 1 + self.rqdata.init_progress_reporter.finish() self.outrightfail = [] if self.rq.hashvalidate: @@ -1906,35 +2041,34 @@ class RunQueueExecuteScenequeue(RunQueueExecute): sq_task = [] noexec = [] stamppresent = [] - for task in xrange(len(self.sq_revdeps)): - realtask = self.rqdata.runq_setscene[task] - fn = self.rqdata.taskData.fn_index[self.rqdata.runq_fnid[realtask]] - taskname = self.rqdata.runq_task[realtask] - taskdep = self.rqdata.dataCache.task_deps[fn] + for tid in self.sq_revdeps: + (mc, fn, taskname, taskfn) = split_tid_mcfn(tid) + + taskdep = self.rqdata.dataCaches[mc].task_deps[fn] if 'noexec' in taskdep and taskname in taskdep['noexec']: - noexec.append(task) - self.task_skip(task) - bb.build.make_stamp(taskname + "_setscene", self.rqdata.dataCache, fn) + noexec.append(tid) + self.task_skip(tid) + bb.build.make_stamp(taskname + "_setscene", self.rqdata.dataCaches[mc], taskfn) continue - if self.rq.check_stamp_task(realtask, taskname + "_setscene", cache=self.stampcache): - logger.debug(2, 'Setscene stamp current for task %s(%s)', task, self.rqdata.get_user_idstring(realtask)) - stamppresent.append(task) - self.task_skip(task) + if self.rq.check_stamp_task(tid, taskname + "_setscene", cache=self.stampcache): + logger.debug(2, 'Setscene stamp current for task %s', tid) + stamppresent.append(tid) + self.task_skip(tid) continue - if self.rq.check_stamp_task(realtask, taskname, recurse = True, cache=self.stampcache): - logger.debug(2, 'Normal stamp current for task %s(%s)', task, self.rqdata.get_user_idstring(realtask)) - stamppresent.append(task) - self.task_skip(task) + if self.rq.check_stamp_task(tid, taskname, recurse = True, cache=self.stampcache): + logger.debug(2, 'Normal stamp current for task %s', tid) + stamppresent.append(tid) + self.task_skip(tid) continue sq_fn.append(fn) - sq_hashfn.append(self.rqdata.dataCache.hashfn[fn]) - sq_hash.append(self.rqdata.runq_hash[realtask]) + sq_hashfn.append(self.rqdata.dataCaches[mc].hashfn[fn]) + sq_hash.append(self.rqdata.runtaskentries[tid].hash) sq_taskname.append(taskname) - sq_task.append(task) + sq_task.append(tid) call = self.rq.hashvalidate + "(sq_fn, sq_task, sq_hash, sq_hashfn, d)" locs = { "sq_fn" : sq_fn, "sq_task" : sq_taskname, "sq_hash" : sq_hash, "sq_hashfn" : sq_hashfn, "d" : self.cooker.expanded_data } valid = bb.utils.better_eval(call, locs) @@ -1943,12 +2077,10 @@ class RunQueueExecuteScenequeue(RunQueueExecute): for v in valid: valid_new.append(sq_task[v]) - for task in xrange(len(self.sq_revdeps)): - if task not in valid_new and task not in noexec: - realtask = self.rqdata.runq_setscene[task] - logger.debug(2, 'No package found, so skipping setscene task %s', - self.rqdata.get_user_idstring(realtask)) - self.outrightfail.append(task) + for tid in self.sq_revdeps: + if tid not in valid_new and tid not in noexec: + logger.debug(2, 'No package found, so skipping setscene task %s', tid) + self.outrightfail.append(tid) logger.info('Executing SetScene Tasks') @@ -1957,9 +2089,7 @@ class RunQueueExecuteScenequeue(RunQueueExecute): def scenequeue_updatecounters(self, task, fail = False): for dep in self.sq_deps[task]: if fail and task in self.sq_harddeps and dep in self.sq_harddeps[task]: - realtask = self.rqdata.runq_setscene[task] - realdep = self.rqdata.runq_setscene[dep] - logger.debug(2, "%s was unavailable and is a hard dependency of %s so skipping" % (self.rqdata.get_user_idstring(realtask), self.rqdata.get_user_idstring(realdep))) + logger.debug(2, "%s was unavailable and is a hard dependency of %s so skipping" % (task, dep)) self.scenequeue_updatecounters(dep, fail) continue if task not in self.sq_revdeps2[dep]: @@ -1967,7 +2097,7 @@ class RunQueueExecuteScenequeue(RunQueueExecute): continue self.sq_revdeps2[dep].remove(task) if len(self.sq_revdeps2[dep]) == 0: - self.runq_buildable[dep] = 1 + self.runq_buildable.add(dep) def task_completeoutright(self, task): """ @@ -1976,13 +2106,19 @@ class RunQueueExecuteScenequeue(RunQueueExecute): completed dependencies as buildable """ - index = self.rqdata.runq_setscene[task] - logger.debug(1, 'Found task %s which could be accelerated', - self.rqdata.get_user_idstring(index)) - + logger.debug(1, 'Found task %s which could be accelerated', task) self.scenequeue_covered.add(task) self.scenequeue_updatecounters(task) + def check_taskfail(self, task): + if self.rqdata.setscenewhitelist: + realtask = task.split('_setscene')[0] + (mc, fn, taskname, _) = split_tid_mcfn(realtask) + pn = self.rqdata.dataCaches[mc].pkg_fn[fn] + if not check_setscene_enforce_whitelist(pn, taskname, self.rqdata.setscenewhitelist): + logger.error('Task %s.%s failed' % (pn, taskname + "_setscene")) + self.rq.state = runQueueCleanUp + def task_complete(self, task): self.stats.taskCompleted() bb.event.fire(sceneQueueTaskCompleted(task, self.stats, self.rq), self.cfgData) @@ -1993,19 +2129,19 @@ class RunQueueExecuteScenequeue(RunQueueExecute): bb.event.fire(sceneQueueTaskFailed(task, self.stats, result, self), self.cfgData) self.scenequeue_notcovered.add(task) self.scenequeue_updatecounters(task, True) + self.check_taskfail(task) def task_failoutright(self, task): - self.runq_running[task] = 1 - self.runq_buildable[task] = 1 + self.runq_running.add(task) + self.runq_buildable.add(task) self.stats.taskCompleted() self.stats.taskSkipped() - index = self.rqdata.runq_setscene[task] self.scenequeue_notcovered.add(task) self.scenequeue_updatecounters(task, True) def task_skip(self, task): - self.runq_running[task] = 1 - self.runq_buildable[task] = 1 + self.runq_running.add(task) + self.runq_buildable.add(task) self.task_completeoutright(task) self.stats.taskCompleted() self.stats.taskSkipped() @@ -2020,20 +2156,18 @@ class RunQueueExecuteScenequeue(RunQueueExecute): task = None if self.stats.active < self.number_tasks: # Find the next setscene to run - for nexttask in xrange(self.stats.total): - if self.runq_buildable[nexttask] == 1 and self.runq_running[nexttask] != 1: + for nexttask in self.rqdata.runq_setscene_tids: + if nexttask in self.runq_buildable and nexttask not in self.runq_running: if nexttask in self.unskippable: - logger.debug(2, "Setscene task %s is unskippable" % self.rqdata.get_user_idstring(self.rqdata.runq_setscene[nexttask])) + logger.debug(2, "Setscene task %s is unskippable" % nexttask) if nexttask not in self.unskippable and len(self.sq_revdeps[nexttask]) > 0 and self.sq_revdeps[nexttask].issubset(self.scenequeue_covered) and self.check_dependencies(nexttask, self.sq_revdeps[nexttask], True): - realtask = self.rqdata.runq_setscene[nexttask] - fn = self.rqdata.taskData.fn_index[self.rqdata.runq_fnid[realtask]] + fn = fn_from_tid(nexttask) foundtarget = False - for target in self.rqdata.target_pairs: - if target[0] == fn and target[1] == self.rqdata.runq_task[realtask]: - foundtarget = True - break + + if nexttask in self.rqdata.target_tids: + foundtarget = True if not foundtarget: - logger.debug(2, "Skipping setscene for task %s" % self.rqdata.get_user_idstring(self.rqdata.runq_setscene[nexttask])) + logger.debug(2, "Skipping setscene for task %s" % nexttask) self.task_skip(nexttask) self.scenequeue_notneeded.add(nexttask) return True @@ -2043,42 +2177,37 @@ class RunQueueExecuteScenequeue(RunQueueExecute): task = nexttask break if task is not None: - realtask = self.rqdata.runq_setscene[task] - fn = self.rqdata.taskData.fn_index[self.rqdata.runq_fnid[realtask]] - - taskname = self.rqdata.runq_task[realtask] + "_setscene" - if self.rq.check_stamp_task(realtask, self.rqdata.runq_task[realtask], recurse = True, cache=self.stampcache): - logger.debug(2, 'Stamp for underlying task %s(%s) is current, so skipping setscene variant', - task, self.rqdata.get_user_idstring(realtask)) + (mc, fn, taskname, taskfn) = split_tid_mcfn(task) + taskname = taskname + "_setscene" + if self.rq.check_stamp_task(task, taskname_from_tid(task), recurse = True, cache=self.stampcache): + logger.debug(2, 'Stamp for underlying task %s is current, so skipping setscene variant', task) self.task_failoutright(task) return True if self.cooker.configuration.force: - for target in self.rqdata.target_pairs: - if target[0] == fn and target[1] == self.rqdata.runq_task[realtask]: - self.task_failoutright(task) - return True + if task in self.rqdata.target_tids: + self.task_failoutright(task) + return True - if self.rq.check_stamp_task(realtask, taskname, cache=self.stampcache): - logger.debug(2, 'Setscene stamp current task %s(%s), so skip it and its dependencies', - task, self.rqdata.get_user_idstring(realtask)) + if self.rq.check_stamp_task(task, taskname, cache=self.stampcache): + logger.debug(2, 'Setscene stamp current task %s, so skip it and its dependencies', task) self.task_skip(task) return True startevent = sceneQueueTaskStarted(task, self.stats, self.rq) bb.event.fire(startevent, self.cfgData) - taskdep = self.rqdata.dataCache.task_deps[fn] - if 'fakeroot' in taskdep and taskname in taskdep['fakeroot']: + taskdep = self.rqdata.dataCaches[mc].task_deps[taskfn] + if 'fakeroot' in taskdep and taskname in taskdep['fakeroot'] and not self.cooker.configuration.dry_run: if not self.rq.fakeworker: self.rq.start_fakeworker(self) - self.rq.fakeworker.stdin.write("<runtask>" + pickle.dumps((fn, realtask, taskname, True, self.cooker.collection.get_file_appends(fn), None)) + "</runtask>") - self.rq.fakeworker.stdin.flush() + self.rq.fakeworker[mc].process.stdin.write(b"<runtask>" + pickle.dumps((taskfn, task, taskname, True, self.cooker.collection.get_file_appends(taskfn), None)) + b"</runtask>") + self.rq.fakeworker[mc].process.stdin.flush() else: - self.rq.worker.stdin.write("<runtask>" + pickle.dumps((fn, realtask, taskname, True, self.cooker.collection.get_file_appends(fn), None)) + "</runtask>") - self.rq.worker.stdin.flush() + self.rq.worker[mc].process.stdin.write(b"<runtask>" + pickle.dumps((taskfn, task, taskname, True, self.cooker.collection.get_file_appends(taskfn), None)) + b"</runtask>") + self.rq.worker[mc].process.stdin.flush() - self.runq_running[task] = 1 + self.runq_running.add(task) self.stats.taskActive() if self.stats.active < self.number_tasks: return True @@ -2087,17 +2216,14 @@ class RunQueueExecuteScenequeue(RunQueueExecute): self.rq.read_workers() return self.rq.active_fds() - #for task in xrange(self.stats.total): - # if self.runq_running[task] != 1: - # buildable = self.runq_buildable[task] - # revdeps = self.sq_revdeps[task] - # bb.warn("Found we didn't run %s %s %s %s" % (task, buildable, str(revdeps), self.rqdata.get_user_idstring(self.rqdata.runq_setscene[task]))) + #for tid in self.sq_revdeps: + # if tid not in self.runq_running: + # buildable = tid in self.runq_buildable + # revdeps = self.sq_revdeps[tid] + # bb.warn("Found we didn't run %s %s %s" % (tid, buildable, str(revdeps))) - # Convert scenequeue_covered task numbers into full taskgraph ids - oldcovered = self.scenequeue_covered - self.rq.scenequeue_covered = set() - for task in oldcovered: - self.rq.scenequeue_covered.add(self.rqdata.runq_setscene[task]) + self.rq.scenequeue_covered = self.scenequeue_covered + self.rq.scenequeue_notcovered = self.scenequeue_notcovered logger.debug(1, 'We can skip tasks %s', sorted(self.rq.scenequeue_covered)) @@ -2109,8 +2235,6 @@ class RunQueueExecuteScenequeue(RunQueueExecute): return True def runqueue_process_waitpid(self, task, status): - task = self.rq.rqdata.runq_setscene.index(task) - RunQueueExecute.runqueue_process_waitpid(self, task, status) class TaskFailure(Exception): @@ -2137,9 +2261,9 @@ class runQueueEvent(bb.event.Event): """ def __init__(self, task, stats, rq): self.taskid = task - self.taskstring = rq.rqdata.get_user_idstring(task) - self.taskname = rq.rqdata.get_task_name(task) - self.taskfile = rq.rqdata.get_task_file(task) + self.taskstring = task + self.taskname = taskname_from_tid(task) + self.taskfile = fn_from_tid(task) self.taskhash = rq.rqdata.get_task_hash(task) self.stats = stats.copy() bb.event.Event.__init__(self) @@ -2150,11 +2274,10 @@ class sceneQueueEvent(runQueueEvent): """ def __init__(self, task, stats, rq, noexec=False): runQueueEvent.__init__(self, task, stats, rq) - realtask = rq.rqdata.runq_setscene[task] - self.taskstring = rq.rqdata.get_user_idstring(realtask, "_setscene") - self.taskname = rq.rqdata.get_task_name(realtask) + "_setscene" - self.taskfile = rq.rqdata.get_task_file(realtask) - self.taskhash = rq.rqdata.get_task_hash(realtask) + self.taskstring = task + "_setscene" + self.taskname = taskname_from_tid(task) + "_setscene" + self.taskfile = fn_from_tid(task) + self.taskhash = rq.rqdata.get_task_hash(task) class runQueueTaskStarted(runQueueEvent): """ @@ -2223,7 +2346,7 @@ class runQueuePipe(): if pipeout: pipeout.close() bb.utils.nonblockingfd(self.input) - self.queue = "" + self.queue = b"" self.d = d self.rq = rq self.rqexec = rqexec @@ -2232,22 +2355,16 @@ class runQueuePipe(): self.rqexec = rqexec def read(self): - for w in [self.rq.worker, self.rq.fakeworker]: - if not w: - continue - w.poll() - if w.returncode is not None and not self.rq.teardown: - name = None - if self.rq.worker and w.pid == self.rq.worker.pid: - name = "Worker" - elif self.rq.fakeworker and w.pid == self.rq.fakeworker.pid: - name = "Fakeroot" - bb.error("%s process (%s) exited unexpectedly (%s), shutting down..." % (name, w.pid, str(w.returncode))) - self.rq.finish_runqueue(True) + for workers, name in [(self.rq.worker, "Worker"), (self.rq.fakeworker, "Fakeroot")]: + for worker in workers.values(): + worker.process.poll() + if worker.process.returncode is not None and not self.rq.teardown: + bb.error("%s process (%s) exited unexpectedly (%s), shutting down..." % (name, worker.process.pid, str(worker.process.returncode))) + self.rq.finish_runqueue(True) start = len(self.queue) try: - self.queue = self.queue + self.input.read(102400) + self.queue = self.queue + (self.input.read(102400) or b"") except (OSError, IOError) as e: if e.errno != errno.EAGAIN: raise @@ -2255,8 +2372,8 @@ class runQueuePipe(): found = True while found and len(self.queue): found = False - index = self.queue.find("</event>") - while index != -1 and self.queue.startswith("<event>"): + index = self.queue.find(b"</event>") + while index != -1 and self.queue.startswith(b"<event>"): try: event = pickle.loads(self.queue[7:index]) except ValueError as e: @@ -2264,9 +2381,9 @@ class runQueuePipe(): bb.event.fire_from_worker(event, self.d) found = True self.queue = self.queue[index+8:] - index = self.queue.find("</event>") - index = self.queue.find("</exitcode>") - while index != -1 and self.queue.startswith("<exitcode>"): + index = self.queue.find(b"</event>") + index = self.queue.find(b"</exitcode>") + while index != -1 and self.queue.startswith(b"<exitcode>"): try: task, status = pickle.loads(self.queue[10:index]) except ValueError as e: @@ -2274,7 +2391,7 @@ class runQueuePipe(): self.rqexec.runqueue_process_waitpid(task, status) found = True self.queue = self.queue[index+11:] - index = self.queue.find("</exitcode>") + index = self.queue.find(b"</exitcode>") return (end > start) def close(self): @@ -2283,3 +2400,27 @@ class runQueuePipe(): if len(self.queue) > 0: print("Warning, worker left partial message: %s" % self.queue) self.input.close() + +def get_setscene_enforce_whitelist(d): + if d.getVar('BB_SETSCENE_ENFORCE', True) != '1': + return None + whitelist = (d.getVar("BB_SETSCENE_ENFORCE_WHITELIST", True) or "").split() + outlist = [] + for item in whitelist[:]: + if item.startswith('%:'): + for target in sys.argv[1:]: + if not target.startswith('-'): + outlist.append(target.split(':')[0] + ':' + item.split(':')[1]) + else: + outlist.append(item) + return outlist + +def check_setscene_enforce_whitelist(pn, taskname, whitelist): + import fnmatch + if whitelist: + item = '%s:%s' % (pn, taskname) + for whitelist_item in whitelist: + if fnmatch.fnmatch(item, whitelist_item): + return True + return False + return True diff --git a/import-layers/yocto-poky/bitbake/lib/bb/server/process.py b/import-layers/yocto-poky/bitbake/lib/bb/server/process.py index a3078a873..982fcf71c 100644 --- a/import-layers/yocto-poky/bitbake/lib/bb/server/process.py +++ b/import-layers/yocto-poky/bitbake/lib/bb/server/process.py @@ -30,7 +30,7 @@ import signal import sys import time import select -from Queue import Empty +from queue import Empty from multiprocessing import Event, Process, util, Queue, Pipe, queues, Manager from . import BitBakeBaseServer, BitBakeBaseServerConnection, BaseImplServer @@ -137,7 +137,7 @@ class ProcessServer(Process, BaseImplServer): if not fds: fds = [] - for function, data in self._idlefuns.items(): + for function, data in list(self._idlefuns.items()): try: retval = function(self, data, False) if retval is False: @@ -145,7 +145,7 @@ class ProcessServer(Process, BaseImplServer): nextsleep = None elif retval is True: nextsleep = None - elif isinstance(retval, float): + elif isinstance(retval, float) and nextsleep: if (retval < nextsleep): nextsleep = retval elif nextsleep is None: @@ -213,7 +213,7 @@ class BitBakeProcessServerConnection(BitBakeBaseServerConnection): # Wrap Queue to provide API which isn't server implementation specific class ProcessEventQueue(multiprocessing.queues.Queue): def __init__(self, maxsize): - multiprocessing.queues.Queue.__init__(self, maxsize) + multiprocessing.queues.Queue.__init__(self, maxsize, ctx=multiprocessing.get_context()) self.exit = False bb.utils.set_process_name("ProcessEQueue") @@ -222,11 +222,10 @@ class ProcessEventQueue(multiprocessing.queues.Queue): def waitEvent(self, timeout): if self.exit: - sys.exit(1) + return self.getEvent() try: if not self.server.is_alive(): - self.setexit() - return None + return self.getEvent() return self.get(True, timeout) except Empty: return None @@ -235,9 +234,10 @@ class ProcessEventQueue(multiprocessing.queues.Queue): try: if not self.server.is_alive(): self.setexit() - return None return self.get(False) except Empty: + if self.exit: + sys.exit(1) return None diff --git a/import-layers/yocto-poky/bitbake/lib/bb/server/xmlrpc.py b/import-layers/yocto-poky/bitbake/lib/bb/server/xmlrpc.py index ace1cf646..452f14bb3 100644 --- a/import-layers/yocto-poky/bitbake/lib/bb/server/xmlrpc.py +++ b/import-layers/yocto-poky/bitbake/lib/bb/server/xmlrpc.py @@ -31,31 +31,33 @@ in the server's main loop. """ +import os +import sys + +import hashlib +import time +import socket +import signal +import threading +import pickle +import inspect +import select +import http.client +import xmlrpc.client +from xmlrpc.server import SimpleXMLRPCServer, SimpleXMLRPCRequestHandler + import bb -import xmlrpclib, sys from bb import daemonize from bb.ui import uievent -import hashlib, time -import socket -import os, signal -import threading -try: - import cPickle as pickle -except ImportError: - import pickle +from . import BitBakeBaseServer, BitBakeBaseServerConnection, BaseImplServer DEBUG = False -from SimpleXMLRPCServer import SimpleXMLRPCServer, SimpleXMLRPCRequestHandler -import inspect, select, httplib - -from . import BitBakeBaseServer, BitBakeBaseServerConnection, BaseImplServer - -class BBTransport(xmlrpclib.Transport): +class BBTransport(xmlrpc.client.Transport): def __init__(self, timeout): self.timeout = timeout self.connection_token = None - xmlrpclib.Transport.__init__(self) + xmlrpc.client.Transport.__init__(self) # Modified from default to pass timeout to HTTPConnection def make_connection(self, host): @@ -67,7 +69,7 @@ class BBTransport(xmlrpclib.Transport): # create a HTTP connection object from a host descriptor chost, self._extra_headers, x509 = self.get_host_info(host) #store the host argument along with the connection object - self._connection = host, httplib.HTTPConnection(chost, timeout=self.timeout) + self._connection = host, http.client.HTTPConnection(chost, timeout=self.timeout) return self._connection[1] def set_connection_token(self, token): @@ -76,13 +78,30 @@ class BBTransport(xmlrpclib.Transport): def send_content(self, h, body): if self.connection_token: h.putheader("Bitbake-token", self.connection_token) - xmlrpclib.Transport.send_content(self, h, body) + xmlrpc.client.Transport.send_content(self, h, body) def _create_server(host, port, timeout = 60): t = BBTransport(timeout) - s = xmlrpclib.ServerProxy("http://%s:%d/" % (host, port), transport=t, allow_none=True) + s = xmlrpc.client.ServerProxy("http://%s:%d/" % (host, port), transport=t, allow_none=True, use_builtin_types=True) return s, t +def check_connection(remote, timeout): + try: + host, port = remote.split(":") + port = int(port) + except Exception as e: + bb.warn("Failed to read remote definition (%s)" % str(e)) + raise e + + server, _transport = _create_server(host, port, timeout) + try: + ret, err = server.runCommand(['getVariable', 'TOPDIR']) + if err or not ret: + return False + except ConnectionError: + return False + return True + class BitBakeServerCommands(): def __init__(self, server): @@ -128,7 +147,7 @@ class BitBakeServerCommands(): def addClient(self): if self.has_client: return None - token = hashlib.md5(str(time.time())).hexdigest() + token = hashlib.md5(str(time.time()).encode("utf-8")).hexdigest() self.server.set_connection_token(token) self.has_client = True return token @@ -178,7 +197,7 @@ class XMLRPCProxyServer(BaseImplServer): """ not a real working server, but a stub for a proxy server connection """ - def __init__(self, host, port): + def __init__(self, host, port, use_builtin_types=True): self.host = host self.port = port @@ -186,7 +205,7 @@ class XMLRPCServer(SimpleXMLRPCServer, BaseImplServer): # remove this when you're done with debugging # allow_reuse_address = True - def __init__(self, interface, single_use=False): + def __init__(self, interface, single_use=False, idle_timeout=0): """ Constructor """ @@ -204,6 +223,10 @@ class XMLRPCServer(SimpleXMLRPCServer, BaseImplServer): self.commands = BitBakeServerCommands(self) self.autoregister_all_functions(self.commands, "") self.interface = interface + self.time = time.time() + self.idle_timeout = idle_timeout + if idle_timeout: + self.register_idle_function(self.handle_idle_timeout, self) def addcooker(self, cooker): BaseImplServer.addcooker(self, cooker) @@ -219,6 +242,12 @@ class XMLRPCServer(SimpleXMLRPCServer, BaseImplServer): if name.startswith(prefix): self.register_function(method, name[len(prefix):]) + def handle_idle_timeout(self, server, data, abort): + if not abort: + if time.time() - server.time > server.idle_timeout: + server.quit = True + print("Server idle timeout expired") + return [] def serve_forever(self): # Start the actual XMLRPC server @@ -232,7 +261,7 @@ class XMLRPCServer(SimpleXMLRPCServer, BaseImplServer): while not self.quit: fds = [self] nextsleep = 0.1 - for function, data in self._idlefuns.items(): + for function, data in list(self._idlefuns.items()): retval = None try: retval = function(self, data, False) @@ -261,13 +290,15 @@ class XMLRPCServer(SimpleXMLRPCServer, BaseImplServer): try: fd_sets = select.select(fds, [], [], socktimeout) if fd_sets[0] and self in fd_sets[0]: + if self.idle_timeout: + self.time = time.time() self._handle_request_noblock() except IOError: # we ignore interrupted calls pass # Tell idle functions we're exiting - for function, data in self._idlefuns.items(): + for function, data in list(self._idlefuns.items()): try: retval = function(self, data, True) except: @@ -332,9 +363,10 @@ class BitBakeXMLRPCServerConnection(BitBakeBaseServerConnection): pass class BitBakeServer(BitBakeBaseServer): - def initServer(self, interface = ("localhost", 0), single_use = False): + def initServer(self, interface = ("localhost", 0), + single_use = False, idle_timeout=0): self.interface = interface - self.serverImpl = XMLRPCServer(interface, single_use) + self.serverImpl = XMLRPCServer(interface, single_use, idle_timeout) def detach(self): daemonize.createDaemon(self.serverImpl.serve_forever, "bitbake-cookerdaemon.log") @@ -379,7 +411,7 @@ class BitBakeXMLRPCClient(BitBakeBaseServer): bb.warn("Could not create socket for %s:%s (%s)" % (host, port, str(e))) raise e try: - self.serverImpl = XMLRPCProxyServer(host, port) + self.serverImpl = XMLRPCProxyServer(host, port, use_builtin_types=True) self.connection = BitBakeXMLRPCServerConnection(self.serverImpl, (ip, 0), self.observer_only, featureset) return self.connection.connect(self.token) except Exception as e: diff --git a/import-layers/yocto-poky/bitbake/lib/bb/siggen.py b/import-layers/yocto-poky/bitbake/lib/bb/siggen.py index 88fc0f1d5..3a7dac4cb 100644 --- a/import-layers/yocto-poky/bitbake/lib/bb/siggen.py +++ b/import-layers/yocto-poky/bitbake/lib/bb/siggen.py @@ -3,19 +3,14 @@ import logging import os import re import tempfile +import pickle import bb.data from bb.checksum import FileChecksumCache logger = logging.getLogger('BitBake.SigGen') -try: - import cPickle as pickle -except ImportError: - import pickle - logger.info('Importing cPickle failed. Falling back to a very slow implementation.') - def init(d): - siggens = [obj for obj in globals().itervalues() + siggens = [obj for obj in globals().values() if type(obj) is type and issubclass(obj, SignatureGenerator)] desired = d.getVar("BB_SIGNATURE_HANDLER", True) or "noop" @@ -138,7 +133,7 @@ class SignatureGeneratorBasic(SignatureGenerator): var = lookupcache[dep] if var is not None: data = data + str(var) - self.basehash[fn + "." + task] = hashlib.md5(data).hexdigest() + self.basehash[fn + "." + task] = hashlib.md5(data.encode("utf-8")).hexdigest() taskdeps[task] = alldeps self.taskdeps[fn] = taskdeps @@ -149,8 +144,9 @@ class SignatureGeneratorBasic(SignatureGenerator): def finalise(self, fn, d, variant): - if variant: - fn = "virtual:" + variant + ":" + fn + mc = d.getVar("__BBMULTICONFIG", False) or "" + if variant or mc: + fn = bb.cache.realfn2virtual(fn, variant, mc) try: taskdeps = self._build_data(fn, d) @@ -221,9 +217,9 @@ class SignatureGeneratorBasic(SignatureGenerator): if taint: data = data + taint self.taints[k] = taint - logger.warn("%s is tainted from a forced run" % k) + logger.warning("%s is tainted from a forced run" % k) - h = hashlib.md5(data).hexdigest() + h = hashlib.md5(data.encode("utf-8")).hexdigest() self.taskhash[k] = h #d.setVar("BB_TASKHASH_task-%s" % task, taskhash[task]) return h @@ -287,7 +283,7 @@ class SignatureGeneratorBasic(SignatureGenerator): with os.fdopen(fd, "wb") as stream: p = pickle.dump(data, stream, -1) stream.flush() - os.chmod(tmpfile, 0664) + os.chmod(tmpfile, 0o664) os.rename(tmpfile, sigfile) except (OSError, IOError) as err: try: @@ -298,23 +294,25 @@ class SignatureGeneratorBasic(SignatureGenerator): computed_basehash = calc_basehash(data) if computed_basehash != self.basehash[k]: - bb.error("Basehash mismatch %s verses %s for %s" % (computed_basehash, self.basehash[k], k)) - if k in self.taskhash: + bb.error("Basehash mismatch %s versus %s for %s" % (computed_basehash, self.basehash[k], k)) + if runtime and k in self.taskhash: computed_taskhash = calc_taskhash(data) if computed_taskhash != self.taskhash[k]: - bb.error("Taskhash mismatch %s verses %s for %s" % (computed_taskhash, self.taskhash[k], k)) + bb.error("Taskhash mismatch %s versus %s for %s" % (computed_taskhash, self.taskhash[k], k)) - def dump_sigs(self, dataCache, options): + def dump_sigs(self, dataCaches, options): for fn in self.taskdeps: for task in self.taskdeps[fn]: + tid = fn + ":" + task + (mc, _, _) = bb.runqueue.split_tid(tid) k = fn + "." + task if k not in self.taskhash: continue - if dataCache.basetaskhash[k] != self.basehash[k]: + if dataCaches[mc].basetaskhash[k] != self.basehash[k]: bb.error("Bitbake's cached basehash does not match the one we just generated (%s)!" % k) - bb.error("The mismatched hashes were %s and %s" % (dataCache.basetaskhash[k], self.basehash[k])) - self.dump_sigtask(fn, task, dataCache.stamp[fn], True) + bb.error("The mismatched hashes were %s and %s" % (dataCaches[mc].basetaskhash[k], self.basehash[k])) + self.dump_sigtask(fn, task, dataCaches[mc].stamp[fn], True) class SignatureGeneratorBasicHash(SignatureGeneratorBasic): name = "basichash" @@ -368,10 +366,12 @@ def clean_basepaths_list(a): def compare_sigfiles(a, b, recursecb = None): output = [] - p1 = pickle.Unpickler(open(a, "rb")) - a_data = p1.load() - p2 = pickle.Unpickler(open(b, "rb")) - b_data = p2.load() + with open(a, 'rb') as f: + p1 = pickle.Unpickler(f) + a_data = p1.load() + with open(b, 'rb') as f: + p2 = pickle.Unpickler(f) + b_data = p2.load() def dict_diff(a, b, whitelist=set()): sa = set(a.keys()) @@ -453,6 +453,11 @@ def compare_sigfiles(a, b, recursecb = None): for dep in changed: output.append("Variable %s value changed from '%s' to '%s'" % (dep, a_data['varvals'][dep], b_data['varvals'][dep])) + if not 'file_checksum_values' in a_data: + a_data['file_checksum_values'] = {} + if not 'file_checksum_values' in b_data: + b_data['file_checksum_values'] = {} + changed, added, removed = file_checksums_diff(a_data['file_checksum_values'], b_data['file_checksum_values']) if changed: for f, old, new in changed: @@ -464,6 +469,10 @@ def compare_sigfiles(a, b, recursecb = None): for f in removed: output.append("Dependency on checksum of file %s was removed" % (f)) + if not 'runtaskdeps' in a_data: + a_data['runtaskdeps'] = {} + if not 'runtaskdeps' in b_data: + b_data['runtaskdeps'] = {} if len(a_data['runtaskdeps']) != len(b_data['runtaskdeps']): changed = ["Number of task dependencies changed"] @@ -536,7 +545,7 @@ def calc_basehash(sigdata): if val is not None: basedata = basedata + str(val) - return hashlib.md5(basedata).hexdigest() + return hashlib.md5(basedata.encode("utf-8")).hexdigest() def calc_taskhash(sigdata): data = sigdata['basehash'] @@ -553,14 +562,15 @@ def calc_taskhash(sigdata): else: data = data + sigdata['taint'] - return hashlib.md5(data).hexdigest() + return hashlib.md5(data.encode("utf-8")).hexdigest() def dump_sigfile(a): output = [] - p1 = pickle.Unpickler(open(a, "rb")) - a_data = p1.load() + with open(a, 'rb') as f: + p1 = pickle.Unpickler(f) + a_data = p1.load() output.append("basewhitelist: %s" % (a_data['basewhitelist'])) diff --git a/import-layers/yocto-poky/bitbake/lib/bb/taskdata.py b/import-layers/yocto-poky/bitbake/lib/bb/taskdata.py index 9ae52d77d..d8bdbcabf 100644 --- a/import-layers/yocto-poky/bitbake/lib/bb/taskdata.py +++ b/import-layers/yocto-poky/bitbake/lib/bb/taskdata.py @@ -37,27 +37,24 @@ def re_match_strings(target, strings): return any(name == target or re.match(name, target) for name in strings) +class TaskEntry: + def __init__(self): + self.tdepends = [] + self.idepends = [] + self.irdepends = [] + class TaskData: """ BitBake Task Data implementation """ def __init__(self, abort = True, tryaltconfigs = False, skiplist = None, allowincomplete = False): - self.build_names_index = [] - self.run_names_index = [] - self.fn_index = [] - self.build_targets = {} self.run_targets = {} self.external_targets = [] - self.tasks_fnid = [] - self.tasks_name = [] - self.tasks_tdepends = [] - self.tasks_idepends = [] - self.tasks_irdepends = [] - # Cache to speed up task ID lookups - self.tasks_lookup = {} + self.seenfns = [] + self.taskentries = {} self.depids = {} self.rdepids = {} @@ -66,7 +63,7 @@ class TaskData: self.failed_deps = [] self.failed_rdeps = [] - self.failed_fnids = [] + self.failed_fns = [] self.abort = abort self.tryaltconfigs = tryaltconfigs @@ -74,88 +71,6 @@ class TaskData: self.skiplist = skiplist - def getbuild_id(self, name): - """ - Return an ID number for the build target name. - If it doesn't exist, create one. - """ - if not name in self.build_names_index: - self.build_names_index.append(name) - return len(self.build_names_index) - 1 - - return self.build_names_index.index(name) - - def getrun_id(self, name): - """ - Return an ID number for the run target name. - If it doesn't exist, create one. - """ - if not name in self.run_names_index: - self.run_names_index.append(name) - return len(self.run_names_index) - 1 - - return self.run_names_index.index(name) - - def getfn_id(self, name): - """ - Return an ID number for the filename. - If it doesn't exist, create one. - """ - if not name in self.fn_index: - self.fn_index.append(name) - return len(self.fn_index) - 1 - - return self.fn_index.index(name) - - def gettask_ids(self, fnid): - """ - Return an array of the ID numbers matching a given fnid. - """ - ids = [] - if fnid in self.tasks_lookup: - for task in self.tasks_lookup[fnid]: - ids.append(self.tasks_lookup[fnid][task]) - return ids - - def gettask_id_fromfnid(self, fnid, task): - """ - Return an ID number for the task matching fnid and task. - """ - if fnid in self.tasks_lookup: - if task in self.tasks_lookup[fnid]: - return self.tasks_lookup[fnid][task] - - return None - - def gettask_id(self, fn, task, create = True): - """ - Return an ID number for the task matching fn and task. - If it doesn't exist, create one by default. - Optionally return None instead. - """ - fnid = self.getfn_id(fn) - - if fnid in self.tasks_lookup: - if task in self.tasks_lookup[fnid]: - return self.tasks_lookup[fnid][task] - - if not create: - return None - - self.tasks_name.append(task) - self.tasks_fnid.append(fnid) - self.tasks_tdepends.append([]) - self.tasks_idepends.append([]) - self.tasks_irdepends.append([]) - - listid = len(self.tasks_name) - 1 - - if fnid not in self.tasks_lookup: - self.tasks_lookup[fnid] = {} - self.tasks_lookup[fnid][task] = listid - - return listid - def add_tasks(self, fn, dataCache): """ Add tasks for a given fn to the database @@ -163,29 +78,31 @@ class TaskData: task_deps = dataCache.task_deps[fn] - fnid = self.getfn_id(fn) - - if fnid in self.failed_fnids: + if fn in self.failed_fns: bb.msg.fatal("TaskData", "Trying to re-add a failed file? Something is broken...") # Check if we've already seen this fn - if fnid in self.tasks_fnid: + if fn in self.seenfns: return + self.seenfns.append(fn) + self.add_extra_deps(fn, dataCache) for task in task_deps['tasks']: + tid = "%s:%s" % (fn, task) + self.taskentries[tid] = TaskEntry() + # Work out task dependencies parentids = [] for dep in task_deps['parents'][task]: if dep not in task_deps['tasks']: bb.debug(2, "Not adding dependeny of %s on %s since %s does not exist" % (task, dep, dep)) continue - parentid = self.gettask_id(fn, dep) + parentid = "%s:%s" % (fn, dep) parentids.append(parentid) - taskid = self.gettask_id(fn, task) - self.tasks_tdepends[taskid].extend(parentids) + self.taskentries[tid].tdepends.extend(parentids) # Touch all intertask dependencies if 'depends' in task_deps and task in task_deps['depends']: @@ -194,29 +111,30 @@ class TaskData: if dep: if ":" not in dep: bb.msg.fatal("TaskData", "Error for %s, dependency %s does not contain ':' character\n. Task 'depends' should be specified in the form 'packagename:task'" % (fn, dep)) - ids.append(((self.getbuild_id(dep.split(":")[0])), dep.split(":")[1])) - self.tasks_idepends[taskid].extend(ids) + ids.append(((dep.split(":")[0]), dep.split(":")[1])) + self.seen_build_target(dep.split(":")[0]) + self.taskentries[tid].idepends.extend(ids) if 'rdepends' in task_deps and task in task_deps['rdepends']: ids = [] for dep in task_deps['rdepends'][task].split(): if dep: if ":" not in dep: bb.msg.fatal("TaskData", "Error for %s, dependency %s does not contain ':' character\n. Task 'rdepends' should be specified in the form 'packagename:task'" % (fn, dep)) - ids.append(((self.getrun_id(dep.split(":")[0])), dep.split(":")[1])) - self.tasks_irdepends[taskid].extend(ids) - + ids.append(((dep.split(":")[0]), dep.split(":")[1])) + self.seen_run_target(dep.split(":")[0]) + self.taskentries[tid].irdepends.extend(ids) # Work out build dependencies - if not fnid in self.depids: - dependids = {} + if not fn in self.depids: + dependids = set() for depend in dataCache.deps[fn]: - dependids[self.getbuild_id(depend)] = None - self.depids[fnid] = dependids.keys() + dependids.add(depend) + self.depids[fn] = list(dependids) logger.debug(2, "Added dependencies %s for %s", str(dataCache.deps[fn]), fn) # Work out runtime dependencies - if not fnid in self.rdepids: - rdependids = {} + if not fn in self.rdepids: + rdependids = set() rdepends = dataCache.rundeps[fn] rrecs = dataCache.runrecs[fn] rdependlist = [] @@ -224,24 +142,26 @@ class TaskData: for package in rdepends: for rdepend in rdepends[package]: rdependlist.append(rdepend) - rdependids[self.getrun_id(rdepend)] = None + rdependids.add(rdepend) for package in rrecs: for rdepend in rrecs[package]: rreclist.append(rdepend) - rdependids[self.getrun_id(rdepend)] = None + rdependids.add(rdepend) if rdependlist: logger.debug(2, "Added runtime dependencies %s for %s", str(rdependlist), fn) if rreclist: logger.debug(2, "Added runtime recommendations %s for %s", str(rreclist), fn) - self.rdepids[fnid] = rdependids.keys() + self.rdepids[fn] = list(rdependids) - for dep in self.depids[fnid]: + for dep in self.depids[fn]: + self.seen_build_target(dep) if dep in self.failed_deps: - self.fail_fnid(fnid) + self.fail_fn(fn) return - for dep in self.rdepids[fnid]: + for dep in self.rdepids[fn]: + self.seen_run_target(dep) if dep in self.failed_rdeps: - self.fail_fnid(fnid) + self.fail_fn(fn) return def add_extra_deps(self, fn, dataCache): @@ -263,9 +183,7 @@ class TaskData: """ Have we a build target matching this name? """ - targetid = self.getbuild_id(target) - - if targetid in self.build_targets: + if target in self.build_targets and self.build_targets[target]: return True return False @@ -273,50 +191,54 @@ class TaskData: """ Have we a runtime target matching this name? """ - targetid = self.getrun_id(target) - - if targetid in self.run_targets: + if target in self.run_targets and self.run_targets[target]: return True return False + def seen_build_target(self, name): + """ + Maintain a list of build targets + """ + if name not in self.build_targets: + self.build_targets[name] = [] + def add_build_target(self, fn, item): """ Add a build target. If already present, append the provider fn to the list """ - targetid = self.getbuild_id(item) - fnid = self.getfn_id(fn) - - if targetid in self.build_targets: - if fnid in self.build_targets[targetid]: + if item in self.build_targets: + if fn in self.build_targets[item]: return - self.build_targets[targetid].append(fnid) + self.build_targets[item].append(fn) return - self.build_targets[targetid] = [fnid] + self.build_targets[item] = [fn] + + def seen_run_target(self, name): + """ + Maintain a list of runtime build targets + """ + if name not in self.run_targets: + self.run_targets[name] = [] def add_runtime_target(self, fn, item): """ Add a runtime target. If already present, append the provider fn to the list """ - targetid = self.getrun_id(item) - fnid = self.getfn_id(fn) - - if targetid in self.run_targets: - if fnid in self.run_targets[targetid]: + if item in self.run_targets: + if fn in self.run_targets[item]: return - self.run_targets[targetid].append(fnid) + self.run_targets[item].append(fn) return - self.run_targets[targetid] = [fnid] + self.run_targets[item] = [fn] - def mark_external_target(self, item): + def mark_external_target(self, target): """ Mark a build target as being externally requested """ - targetid = self.getbuild_id(item) - - if targetid not in self.external_targets: - self.external_targets.append(targetid) + if target not in self.external_targets: + self.external_targets.append(target) def get_unresolved_build_targets(self, dataCache): """ @@ -324,12 +246,12 @@ class TaskData: are unknown. """ unresolved = [] - for target in self.build_names_index: + for target in self.build_targets: if re_match_strings(target, dataCache.ignored_dependencies): continue - if self.build_names_index.index(target) in self.failed_deps: + if target in self.failed_deps: continue - if not self.have_build_target(target): + if not self.build_targets[target]: unresolved.append(target) return unresolved @@ -339,12 +261,12 @@ class TaskData: are unknown. """ unresolved = [] - for target in self.run_names_index: + for target in self.run_targets: if re_match_strings(target, dataCache.ignored_dependencies): continue - if self.run_names_index.index(target) in self.failed_rdeps: + if target in self.failed_rdeps: continue - if not self.have_runtime_target(target): + if not self.run_targets[target]: unresolved.append(target) return unresolved @@ -352,50 +274,26 @@ class TaskData: """ Return a list of providers of item """ - targetid = self.getbuild_id(item) + return self.build_targets[item] - return self.build_targets[targetid] - - def get_dependees(self, itemid): + def get_dependees(self, item): """ Return a list of targets which depend on item """ dependees = [] - for fnid in self.depids: - if itemid in self.depids[fnid]: - dependees.append(fnid) - return dependees - - def get_dependees_str(self, item): - """ - Return a list of targets which depend on item as a user readable string - """ - itemid = self.getbuild_id(item) - dependees = [] - for fnid in self.depids: - if itemid in self.depids[fnid]: - dependees.append(self.fn_index[fnid]) + for fn in self.depids: + if item in self.depids[fn]: + dependees.append(fn) return dependees - def get_rdependees(self, itemid): + def get_rdependees(self, item): """ Return a list of targets which depend on runtime item """ dependees = [] - for fnid in self.rdepids: - if itemid in self.rdepids[fnid]: - dependees.append(fnid) - return dependees - - def get_rdependees_str(self, item): - """ - Return a list of targets which depend on runtime item as a user readable string - """ - itemid = self.getrun_id(item) - dependees = [] - for fnid in self.rdepids: - if itemid in self.rdepids[fnid]: - dependees.append(self.fn_index[fnid]) + for fn in self.rdepids: + if item in self.rdepids[fn]: + dependees.append(fn) return dependees def get_reasons(self, item, runtime=False): @@ -431,7 +329,7 @@ class TaskData: except bb.providers.NoProvider: if self.abort: raise - self.remove_buildtarget(self.getbuild_id(item)) + self.remove_buildtarget(item) self.mark_external_target(item) @@ -446,14 +344,14 @@ class TaskData: return if not item in dataCache.providers: - close_matches = self.get_close_matches(item, dataCache.providers.keys()) + close_matches = self.get_close_matches(item, list(dataCache.providers.keys())) # Is it in RuntimeProviders ? all_p = bb.providers.getRuntimeProviders(dataCache, item) for fn in all_p: new = dataCache.pkg_fn[fn] + " RPROVIDES " + item if new not in close_matches: close_matches.append(new) - bb.event.fire(bb.event.NoProvider(item, dependees=self.get_dependees_str(item), reasons=self.get_reasons(item), close_matches=close_matches), cfgData) + bb.event.fire(bb.event.NoProvider(item, dependees=self.get_dependees(item), reasons=self.get_reasons(item), close_matches=close_matches), cfgData) raise bb.providers.NoProvider(item) if self.have_build_target(item): @@ -462,10 +360,10 @@ class TaskData: all_p = dataCache.providers[item] eligible, foundUnique = bb.providers.filterProviders(all_p, item, cfgData, dataCache) - eligible = [p for p in eligible if not self.getfn_id(p) in self.failed_fnids] + eligible = [p for p in eligible if not p in self.failed_fns] if not eligible: - bb.event.fire(bb.event.NoProvider(item, dependees=self.get_dependees_str(item), reasons=["No eligible PROVIDERs exist for '%s'" % item]), cfgData) + bb.event.fire(bb.event.NoProvider(item, dependees=self.get_dependees(item), reasons=["No eligible PROVIDERs exist for '%s'" % item]), cfgData) raise bb.providers.NoProvider(item) if len(eligible) > 1 and foundUnique == False: @@ -477,8 +375,7 @@ class TaskData: self.consider_msgs_cache.append(item) for fn in eligible: - fnid = self.getfn_id(fn) - if fnid in self.failed_fnids: + if fn in self.failed_fns: continue logger.debug(2, "adding %s to satisfy %s", fn, item) self.add_build_target(fn, item) @@ -502,14 +399,14 @@ class TaskData: all_p = bb.providers.getRuntimeProviders(dataCache, item) if not all_p: - bb.event.fire(bb.event.NoProvider(item, runtime=True, dependees=self.get_rdependees_str(item), reasons=self.get_reasons(item, True)), cfgData) + bb.event.fire(bb.event.NoProvider(item, runtime=True, dependees=self.get_rdependees(item), reasons=self.get_reasons(item, True)), cfgData) raise bb.providers.NoRProvider(item) eligible, numberPreferred = bb.providers.filterProvidersRunTime(all_p, item, cfgData, dataCache) - eligible = [p for p in eligible if not self.getfn_id(p) in self.failed_fnids] + eligible = [p for p in eligible if not p in self.failed_fns] if not eligible: - bb.event.fire(bb.event.NoProvider(item, runtime=True, dependees=self.get_rdependees_str(item), reasons=["No eligible RPROVIDERs exist for '%s'" % item]), cfgData) + bb.event.fire(bb.event.NoProvider(item, runtime=True, dependees=self.get_rdependees(item), reasons=["No eligible RPROVIDERs exist for '%s'" % item]), cfgData) raise bb.providers.NoRProvider(item) if len(eligible) > 1 and numberPreferred == 0: @@ -531,82 +428,80 @@ class TaskData: # run through the list until we find one that we can build for fn in eligible: - fnid = self.getfn_id(fn) - if fnid in self.failed_fnids: + if fn in self.failed_fns: continue logger.debug(2, "adding '%s' to satisfy runtime '%s'", fn, item) self.add_runtime_target(fn, item) self.add_tasks(fn, dataCache) - def fail_fnid(self, fnid, missing_list=None): + def fail_fn(self, fn, missing_list=None): """ Mark a file as failed (unbuildable) Remove any references from build and runtime provider lists missing_list, A list of missing requirements for this target """ - if fnid in self.failed_fnids: + if fn in self.failed_fns: return if not missing_list: missing_list = [] - logger.debug(1, "File '%s' is unbuildable, removing...", self.fn_index[fnid]) - self.failed_fnids.append(fnid) + logger.debug(1, "File '%s' is unbuildable, removing...", fn) + self.failed_fns.append(fn) for target in self.build_targets: - if fnid in self.build_targets[target]: - self.build_targets[target].remove(fnid) + if fn in self.build_targets[target]: + self.build_targets[target].remove(fn) if len(self.build_targets[target]) == 0: self.remove_buildtarget(target, missing_list) for target in self.run_targets: - if fnid in self.run_targets[target]: - self.run_targets[target].remove(fnid) + if fn in self.run_targets[target]: + self.run_targets[target].remove(fn) if len(self.run_targets[target]) == 0: self.remove_runtarget(target, missing_list) - def remove_buildtarget(self, targetid, missing_list=None): + def remove_buildtarget(self, target, missing_list=None): """ Mark a build target as failed (unbuildable) Trigger removal of any files that have this as a dependency """ if not missing_list: - missing_list = [self.build_names_index[targetid]] + missing_list = [target] else: - missing_list = [self.build_names_index[targetid]] + missing_list - logger.verbose("Target '%s' is unbuildable, removing...\nMissing or unbuildable dependency chain was: %s", self.build_names_index[targetid], missing_list) - self.failed_deps.append(targetid) - dependees = self.get_dependees(targetid) - for fnid in dependees: - self.fail_fnid(fnid, missing_list) - for taskid in xrange(len(self.tasks_idepends)): - idepends = self.tasks_idepends[taskid] - for (idependid, idependtask) in idepends: - if idependid == targetid: - self.fail_fnid(self.tasks_fnid[taskid], missing_list) - - if self.abort and targetid in self.external_targets: - target = self.build_names_index[targetid] + missing_list = [target] + missing_list + logger.verbose("Target '%s' is unbuildable, removing...\nMissing or unbuildable dependency chain was: %s", target, missing_list) + self.failed_deps.append(target) + dependees = self.get_dependees(target) + for fn in dependees: + self.fail_fn(fn, missing_list) + for tid in self.taskentries: + for (idepend, idependtask) in self.taskentries[tid].idepends: + if idepend == target: + fn = tid.rsplit(":",1)[0] + self.fail_fn(fn, missing_list) + + if self.abort and target in self.external_targets: logger.error("Required build target '%s' has no buildable providers.\nMissing or unbuildable dependency chain was: %s", target, missing_list) raise bb.providers.NoProvider(target) - def remove_runtarget(self, targetid, missing_list=None): + def remove_runtarget(self, target, missing_list=None): """ Mark a run target as failed (unbuildable) Trigger removal of any files that have this as a dependency """ if not missing_list: - missing_list = [self.run_names_index[targetid]] + missing_list = [target] else: - missing_list = [self.run_names_index[targetid]] + missing_list - - logger.info("Runtime target '%s' is unbuildable, removing...\nMissing or unbuildable dependency chain was: %s", self.run_names_index[targetid], missing_list) - self.failed_rdeps.append(targetid) - dependees = self.get_rdependees(targetid) - for fnid in dependees: - self.fail_fnid(fnid, missing_list) - for taskid in xrange(len(self.tasks_irdepends)): - irdepends = self.tasks_irdepends[taskid] - for (idependid, idependtask) in irdepends: - if idependid == targetid: - self.fail_fnid(self.tasks_fnid[taskid], missing_list) + missing_list = [target] + missing_list + + logger.info("Runtime target '%s' is unbuildable, removing...\nMissing or unbuildable dependency chain was: %s", target, missing_list) + self.failed_rdeps.append(target) + dependees = self.get_rdependees(target) + for fn in dependees: + self.fail_fn(fn, missing_list) + for tid in self.taskentries: + for (idepend, idependtask) in self.taskentries[tid].irdepends: + if idepend == target: + fn = tid.rsplit(":",1)[0] + self.fail_fn(fn, missing_list) def add_unresolved(self, cfgData, dataCache): """ @@ -620,17 +515,16 @@ class TaskData: self.add_provider_internal(cfgData, dataCache, target) added = added + 1 except bb.providers.NoProvider: - targetid = self.getbuild_id(target) - if self.abort and targetid in self.external_targets and not self.allowincomplete: + if self.abort and target in self.external_targets and not self.allowincomplete: raise if not self.allowincomplete: - self.remove_buildtarget(targetid) + self.remove_buildtarget(target) for target in self.get_unresolved_run_targets(dataCache): try: self.add_rprovider(cfgData, dataCache, target) added = added + 1 except (bb.providers.NoRProvider, bb.providers.MultipleRProvider): - self.remove_runtarget(self.getrun_id(target)) + self.remove_runtarget(target) logger.debug(1, "Resolved " + str(added) + " extra dependencies") if added == 0: break @@ -638,13 +532,13 @@ class TaskData: def get_providermap(self, prefix=None): provmap = {} - for name in self.build_names_index: + for name in self.build_targets: if prefix and not name.startswith(prefix): continue if self.have_build_target(name): provider = self.get_provider(name) if provider: - provmap[name] = self.fn_index[provider[0]] + provmap[name] = provider[0] return provmap def dump_data(self): @@ -652,39 +546,37 @@ class TaskData: Dump some debug information on the internal data structures """ logger.debug(3, "build_names:") - logger.debug(3, ", ".join(self.build_names_index)) + logger.debug(3, ", ".join(self.build_targets)) logger.debug(3, "run_names:") - logger.debug(3, ", ".join(self.run_names_index)) + logger.debug(3, ", ".join(self.run_targets)) logger.debug(3, "build_targets:") - for buildid in xrange(len(self.build_names_index)): - target = self.build_names_index[buildid] + for target in self.build_targets: targets = "None" - if buildid in self.build_targets: - targets = self.build_targets[buildid] - logger.debug(3, " (%s)%s: %s", buildid, target, targets) + if target in self.build_targets: + targets = self.build_targets[target] + logger.debug(3, " %s: %s", target, targets) logger.debug(3, "run_targets:") - for runid in xrange(len(self.run_names_index)): - target = self.run_names_index[runid] + for target in self.run_targets: targets = "None" - if runid in self.run_targets: - targets = self.run_targets[runid] - logger.debug(3, " (%s)%s: %s", runid, target, targets) + if target in self.run_targets: + targets = self.run_targets[target] + logger.debug(3, " %s: %s", target, targets) logger.debug(3, "tasks:") - for task in xrange(len(self.tasks_name)): - logger.debug(3, " (%s)%s - %s: %s", - task, - self.fn_index[self.tasks_fnid[task]], - self.tasks_name[task], - self.tasks_tdepends[task]) + for tid in self.taskentries: + logger.debug(3, " %s: %s %s %s", + tid, + self.taskentries[tid].idepends, + self.taskentries[tid].irdepends, + self.taskentries[tid].tdepends) logger.debug(3, "dependency ids (per fn):") - for fnid in self.depids: - logger.debug(3, " %s %s: %s", fnid, self.fn_index[fnid], self.depids[fnid]) + for fn in self.depids: + logger.debug(3, " %s: %s", fn, self.depids[fn]) logger.debug(3, "runtime dependency ids (per fn):") - for fnid in self.rdepids: - logger.debug(3, " %s %s: %s", fnid, self.fn_index[fnid], self.rdepids[fnid]) + for fn in self.rdepids: + logger.debug(3, " %s: %s", fn, self.rdepids[fn]) diff --git a/import-layers/yocto-poky/bitbake/lib/bb/tests/codeparser.py b/import-layers/yocto-poky/bitbake/lib/bb/tests/codeparser.py index bb820e403..14f0e2572 100644 --- a/import-layers/yocto-poky/bitbake/lib/bb/tests/codeparser.py +++ b/import-layers/yocto-poky/bitbake/lib/bb/tests/codeparser.py @@ -191,8 +191,8 @@ class PythonReferenceTest(ReferenceTest): if hasattr(bb.utils, "_context"): self.context = bb.utils._context else: - import __builtin__ - self.context = __builtin__.__dict__ + import builtins + self.context = builtins.__dict__ def parseExpression(self, exp): parsedvar = self.d.expandWithRefs(exp, None) @@ -302,7 +302,7 @@ bb.data.getVar(a(), d, False) deps, values = bb.data.build_dependencies("FOO", set(self.d.keys()), set(), set(), self.d) - self.assertEquals(deps, set(["somevar", "bar", "something", "inexpand", "test", "test2", "a"])) + self.assertEqual(deps, set(["somevar", "bar", "something", "inexpand", "test", "test2", "a"])) shelldata = """ @@ -349,7 +349,7 @@ esac deps, values = bb.data.build_dependencies("FOO", set(self.d.keys()), set(), set(), self.d) - self.assertEquals(deps, set(["somevar", "inverted"] + execs)) + self.assertEqual(deps, set(["somevar", "inverted"] + execs)) def test_vardeps(self): @@ -359,7 +359,7 @@ esac deps, values = bb.data.build_dependencies("FOO", set(self.d.keys()), set(), set(), self.d) - self.assertEquals(deps, set(["oe_libinstall"])) + self.assertEqual(deps, set(["oe_libinstall"])) def test_vardeps_expand(self): self.d.setVar("oe_libinstall", "echo test") @@ -368,7 +368,7 @@ esac deps, values = bb.data.build_dependencies("FOO", set(self.d.keys()), set(), set(), self.d) - self.assertEquals(deps, set(["oe_libinstall"])) + self.assertEqual(deps, set(["oe_libinstall"])) #Currently no wildcard support #def test_vardeps_wildcards(self): diff --git a/import-layers/yocto-poky/bitbake/lib/bb/tests/cow.py b/import-layers/yocto-poky/bitbake/lib/bb/tests/cow.py index 35c5841f3..d149d84d0 100644 --- a/import-layers/yocto-poky/bitbake/lib/bb/tests/cow.py +++ b/import-layers/yocto-poky/bitbake/lib/bb/tests/cow.py @@ -34,14 +34,14 @@ class COWTestCase(unittest.TestCase): from bb.COW import COWDictBase a = COWDictBase.copy() - self.assertEquals(False, a.has_key('a')) + self.assertEqual(False, 'a' in a) a['a'] = 'a' a['b'] = 'b' - self.assertEquals(True, a.has_key('a')) - self.assertEquals(True, a.has_key('b')) - self.assertEquals('a', a['a'] ) - self.assertEquals('b', a['b'] ) + self.assertEqual(True, 'a' in a) + self.assertEqual(True, 'b' in a) + self.assertEqual('a', a['a'] ) + self.assertEqual('b', a['b'] ) def testCopyCopy(self): """ @@ -60,31 +60,31 @@ class COWTestCase(unittest.TestCase): c['a'] = 30 # test separation of the two instances - self.assertEquals(False, c.has_key('c')) - self.assertEquals(30, c['a']) - self.assertEquals(10, b['a']) + self.assertEqual(False, 'c' in c) + self.assertEqual(30, c['a']) + self.assertEqual(10, b['a']) # test copy b_2 = b.copy() c_2 = c.copy() - self.assertEquals(False, c_2.has_key('c')) - self.assertEquals(10, b_2['a']) + self.assertEqual(False, 'c' in c_2) + self.assertEqual(10, b_2['a']) b_2['d'] = 40 - self.assertEquals(False, c_2.has_key('d')) - self.assertEquals(True, b_2.has_key('d')) - self.assertEquals(40, b_2['d']) - self.assertEquals(False, b.has_key('d')) - self.assertEquals(False, c.has_key('d')) + self.assertEqual(False, 'd' in c_2) + self.assertEqual(True, 'd' in b_2) + self.assertEqual(40, b_2['d']) + self.assertEqual(False, 'd' in b) + self.assertEqual(False, 'd' in c) c_2['d'] = 30 - self.assertEquals(True, c_2.has_key('d')) - self.assertEquals(True, b_2.has_key('d')) - self.assertEquals(30, c_2['d']) - self.assertEquals(40, b_2['d']) - self.assertEquals(False, b.has_key('d')) - self.assertEquals(False, c.has_key('d')) + self.assertEqual(True, 'd' in c_2) + self.assertEqual(True, 'd' in b_2) + self.assertEqual(30, c_2['d']) + self.assertEqual(40, b_2['d']) + self.assertEqual(False, 'd' in b) + self.assertEqual(False, 'd' in c) # test copy of the copy c_3 = c_2.copy() @@ -92,19 +92,19 @@ class COWTestCase(unittest.TestCase): b_3_2 = b_2.copy() c_3['e'] = 4711 - self.assertEquals(4711, c_3['e']) - self.assertEquals(False, c_2.has_key('e')) - self.assertEquals(False, b_3.has_key('e')) - self.assertEquals(False, b_3_2.has_key('e')) - self.assertEquals(False, b_2.has_key('e')) + self.assertEqual(4711, c_3['e']) + self.assertEqual(False, 'e' in c_2) + self.assertEqual(False, 'e' in b_3) + self.assertEqual(False, 'e' in b_3_2) + self.assertEqual(False, 'e' in b_2) b_3['e'] = 'viel' - self.assertEquals('viel', b_3['e']) - self.assertEquals(4711, c_3['e']) - self.assertEquals(False, c_2.has_key('e')) - self.assertEquals(True, b_3.has_key('e')) - self.assertEquals(False, b_3_2.has_key('e')) - self.assertEquals(False, b_2.has_key('e')) + self.assertEqual('viel', b_3['e']) + self.assertEqual(4711, c_3['e']) + self.assertEqual(False, 'e' in c_2) + self.assertEqual(True, 'e' in b_3) + self.assertEqual(False, 'e' in b_3_2) + self.assertEqual(False, 'e' in b_2) def testCow(self): from bb.COW import COWDictBase @@ -115,12 +115,12 @@ class COWTestCase(unittest.TestCase): copy = c.copy() - self.assertEquals(1027, c['123']) - self.assertEquals(4711, c['other']) - self.assertEquals({'abc':10, 'bcd':20}, c['d']) - self.assertEquals(1027, copy['123']) - self.assertEquals(4711, copy['other']) - self.assertEquals({'abc':10, 'bcd':20}, copy['d']) + self.assertEqual(1027, c['123']) + self.assertEqual(4711, c['other']) + self.assertEqual({'abc':10, 'bcd':20}, c['d']) + self.assertEqual(1027, copy['123']) + self.assertEqual(4711, copy['other']) + self.assertEqual({'abc':10, 'bcd':20}, copy['d']) # cow it now copy['123'] = 1028 @@ -128,9 +128,9 @@ class COWTestCase(unittest.TestCase): copy['d']['abc'] = 20 - self.assertEquals(1027, c['123']) - self.assertEquals(4711, c['other']) - self.assertEquals({'abc':10, 'bcd':20}, c['d']) - self.assertEquals(1028, copy['123']) - self.assertEquals(4712, copy['other']) - self.assertEquals({'abc':20, 'bcd':20}, copy['d']) + self.assertEqual(1027, c['123']) + self.assertEqual(4711, c['other']) + self.assertEqual({'abc':10, 'bcd':20}, c['d']) + self.assertEqual(1028, copy['123']) + self.assertEqual(4712, copy['other']) + self.assertEqual({'abc':20, 'bcd':20}, copy['d']) diff --git a/import-layers/yocto-poky/bitbake/lib/bb/tests/data.py b/import-layers/yocto-poky/bitbake/lib/bb/tests/data.py index 12232305c..b54eb0679 100644 --- a/import-layers/yocto-poky/bitbake/lib/bb/tests/data.py +++ b/import-layers/yocto-poky/bitbake/lib/bb/tests/data.py @@ -147,14 +147,14 @@ class DataExpansions(unittest.TestCase): self.assertEqual(self.d.getVar("foo", False), None) def test_keys(self): - keys = self.d.keys() - self.assertEqual(keys, ['value_of_foo', 'foo', 'bar']) + keys = list(self.d.keys()) + self.assertCountEqual(keys, ['value_of_foo', 'foo', 'bar']) def test_keys_deletion(self): newd = bb.data.createCopy(self.d) newd.delVar("bar") - keys = newd.keys() - self.assertEqual(keys, ['value_of_foo', 'foo']) + keys = list(newd.keys()) + self.assertCountEqual(keys, ['value_of_foo', 'foo']) class TestNestedExpansions(unittest.TestCase): def setUp(self): @@ -334,7 +334,7 @@ class TestOverrides(unittest.TestCase): self.d.setVar("TEST2_bar", "testvalue2") bb.data.update_data(self.d) self.assertEqual(self.d.getVar("TEST2", True), "testvalue2") - self.assertItemsEqual(self.d.keys(), ['TEST', 'TEST2', 'OVERRIDES', 'TEST2_bar']) + self.assertCountEqual(list(self.d.keys()), ['TEST', 'TEST2', 'OVERRIDES', 'TEST2_bar']) def test_multiple_override(self): self.d.setVar("TEST_bar", "testvalue2") @@ -342,7 +342,7 @@ class TestOverrides(unittest.TestCase): self.d.setVar("TEST_foo", "testvalue4") bb.data.update_data(self.d) self.assertEqual(self.d.getVar("TEST", True), "testvalue3") - self.assertItemsEqual(self.d.keys(), ['TEST', 'TEST_foo', 'OVERRIDES', 'TEST_bar', 'TEST_local']) + self.assertCountEqual(list(self.d.keys()), ['TEST', 'TEST_foo', 'OVERRIDES', 'TEST_bar', 'TEST_local']) def test_multiple_combined_overrides(self): self.d.setVar("TEST_local_foo_bar", "testvalue3") diff --git a/import-layers/yocto-poky/bitbake/lib/bb/tests/fetch.py b/import-layers/yocto-poky/bitbake/lib/bb/tests/fetch.py index 4ba688bfe..0fd2c0216 100644 --- a/import-layers/yocto-poky/bitbake/lib/bb/tests/fetch.py +++ b/import-layers/yocto-poky/bitbake/lib/bb/tests/fetch.py @@ -22,6 +22,7 @@ import unittest import tempfile import subprocess +import collections import os from bb.fetch2 import URI from bb.fetch2 import FetchMethod @@ -133,10 +134,10 @@ class URITest(unittest.TestCase): 'userinfo': 'anoncvs:anonymous', 'username': 'anoncvs', 'password': 'anonymous', - 'params': { - 'tag': 'V0-99-81', - 'module': 'familiar/dist/ipkg' - }, + 'params': collections.OrderedDict([ + ('tag', 'V0-99-81'), + ('module', 'familiar/dist/ipkg') + ]), 'query': {}, 'relative': False }, @@ -359,7 +360,10 @@ class FetcherTest(unittest.TestCase): def tearDown(self): os.chdir(self.origdir) - bb.utils.prunedir(self.tempdir) + if os.environ.get("BB_TMPDIR_NOCLEAN") == "yes": + print("Not cleaning up %s. Please remove manually." % self.tempdir) + else: + bb.utils.prunedir(self.tempdir) class MirrorUriTest(FetcherTest): @@ -450,7 +454,7 @@ class MirrorUriTest(FetcherTest): class FetcherLocalTest(FetcherTest): def setUp(self): def touch(fn): - with file(fn, 'a'): + with open(fn, 'a'): os.utime(fn, None) super(FetcherLocalTest, self).setUp() @@ -504,6 +508,15 @@ class FetcherLocalTest(FetcherTest): tree = self.fetchUnpack(['file://dir/subdir/e;subdir=bar']) self.assertEqual(tree, ['bar/dir/subdir/e']) + def test_local_absolutedir(self): + # Unpacking to an absolute path that is a subdirectory of the root + # should work + tree = self.fetchUnpack(['file://a;subdir=%s' % os.path.join(self.unpackdir, 'bar')]) + + # Unpacking to an absolute path outside of the root should fail + with self.assertRaises(bb.fetch2.UnpackError): + self.fetchUnpack(['file://a;subdir=/bin/sh']) + class FetcherNetworkTest(FetcherTest): if os.environ.get("BB_SKIP_NETTESTS") == "yes": @@ -584,6 +597,36 @@ class FetcherNetworkTest(FetcherTest): url1 = url2 = "git://git.openembedded.org/bitbake;rev=270a05b0b4ba0959fe0624d2a4885d7b70426da5;tag=270a05b0b4ba0959fe0624d2a4885d7b70426da5" self.assertRaises(bb.fetch.FetchError, self.gitfetcher, url1, url2) + def test_gitfetch_localusehead(self): + # Create dummy local Git repo + src_dir = tempfile.mkdtemp(dir=self.tempdir, + prefix='gitfetch_localusehead_') + src_dir = os.path.abspath(src_dir) + bb.process.run("git init", cwd=src_dir) + bb.process.run("git commit --allow-empty -m'Dummy commit'", + cwd=src_dir) + # Use other branch than master + bb.process.run("git checkout -b my-devel", cwd=src_dir) + bb.process.run("git commit --allow-empty -m'Dummy commit 2'", + cwd=src_dir) + stdout = bb.process.run("git rev-parse HEAD", cwd=src_dir) + orig_rev = stdout[0].strip() + + # Fetch and check revision + self.d.setVar("SRCREV", "AUTOINC") + url = "git://" + src_dir + ";protocol=file;usehead=1" + fetcher = bb.fetch.Fetch([url], self.d) + fetcher.download() + fetcher.unpack(self.unpackdir) + stdout = bb.process.run("git rev-parse HEAD", + cwd=os.path.join(self.unpackdir, 'git')) + unpack_rev = stdout[0].strip() + self.assertEqual(orig_rev, unpack_rev) + + def test_gitfetch_remoteusehead(self): + url = "git://git.openembedded.org/bitbake;usehead=1" + self.assertRaises(bb.fetch.ParameterError, self.gitfetcher, url, url) + def test_gitfetch_premirror(self): url1 = "git://git.openembedded.org/bitbake" url2 = "git://someserver.org/bitbake" @@ -660,7 +703,7 @@ class URLHandle(unittest.TestCase): datatable = { "http://www.google.com/index.html" : ('http', 'www.google.com', '/index.html', '', '', {}), "cvs://anoncvs@cvs.handhelds.org/cvs;module=familiar/dist/ipkg" : ('cvs', 'cvs.handhelds.org', '/cvs', 'anoncvs', '', {'module': 'familiar/dist/ipkg'}), - "cvs://anoncvs:anonymous@cvs.handhelds.org/cvs;tag=V0-99-81;module=familiar/dist/ipkg" : ('cvs', 'cvs.handhelds.org', '/cvs', 'anoncvs', 'anonymous', {'tag': 'V0-99-81', 'module': 'familiar/dist/ipkg'}), + "cvs://anoncvs:anonymous@cvs.handhelds.org/cvs;tag=V0-99-81;module=familiar/dist/ipkg" : ('cvs', 'cvs.handhelds.org', '/cvs', 'anoncvs', 'anonymous', collections.OrderedDict([('tag', 'V0-99-81'), ('module', 'familiar/dist/ipkg')])), "git://git.openembedded.org/bitbake;branch=@foo" : ('git', 'git.openembedded.org', '/bitbake', '', '', {'branch': '@foo'}), "file://somelocation;someparam=1": ('file', '', 'somelocation', '', '', {'someparam': '1'}), } @@ -767,7 +810,6 @@ class FetchLatestVersionTest(FetcherTest): class FetchCheckStatusTest(FetcherTest): test_wget_uris = ["http://www.cups.org/software/1.7.2/cups-1.7.2-source.tar.bz2", - "http://www.cups.org/software/ipptool/ipptool-20130731-linux-ubuntu-i686.tar.gz", "http://www.cups.org/", "http://downloads.yoctoproject.org/releases/sato/sato-engine-0.1.tar.gz", "http://downloads.yoctoproject.org/releases/sato/sato-engine-0.2.tar.gz", diff --git a/import-layers/yocto-poky/bitbake/lib/bb/tests/parse.py b/import-layers/yocto-poky/bitbake/lib/bb/tests/parse.py index 6beb76a48..0b2706af0 100644 --- a/import-layers/yocto-poky/bitbake/lib/bb/tests/parse.py +++ b/import-layers/yocto-poky/bitbake/lib/bb/tests/parse.py @@ -50,7 +50,7 @@ C = "3" def parsehelper(self, content, suffix = ".bb"): f = tempfile.NamedTemporaryFile(suffix = suffix) - f.write(content) + f.write(bytes(content, "utf-8")) f.flush() os.chdir(os.path.dirname(f.name)) return f @@ -68,6 +68,23 @@ C = "3" with self.assertRaises(bb.parse.ParseError): d = bb.parse.handle(f.name, self.d)[''] + unsettest = """ +A = "1" +B = "2" +B[flag] = "3" + +unset A +unset B[flag] +""" + + def test_parse_unset(self): + f = self.parsehelper(self.unsettest) + d = bb.parse.handle(f.name, self.d)[''] + self.assertEqual(d.getVar("A", True), None) + self.assertEqual(d.getVarFlag("A","flag", True), None) + self.assertEqual(d.getVar("B", True), "2") + + overridetest = """ RRECOMMENDS_${PN} = "a" RRECOMMENDS_${PN}_libc = "b" diff --git a/import-layers/yocto-poky/bitbake/lib/bb/tinfoil.py b/import-layers/yocto-poky/bitbake/lib/bb/tinfoil.py index 7aa653f1a..8899e861c 100644 --- a/import-layers/yocto-poky/bitbake/lib/bb/tinfoil.py +++ b/import-layers/yocto-poky/bitbake/lib/bb/tinfoil.py @@ -59,6 +59,12 @@ class Tinfoil: def register_idle_function(self, function, data): pass + def __enter__(self): + return self + + def __exit__(self, type, value, traceback): + self.shutdown() + def parseRecipes(self): sys.stderr.write("Parsing recipes..") self.logger.setLevel(logging.WARNING) @@ -74,16 +80,52 @@ class Tinfoil: self.logger.setLevel(logging.INFO) sys.stderr.write("done.\n") - self.cooker_data = self.cooker.recipecache + self.cooker_data = self.cooker.recipecaches[''] def prepare(self, config_only = False): if not self.cooker_data: if config_only: self.cooker.parseConfiguration() - self.cooker_data = self.cooker.recipecache + self.cooker_data = self.cooker.recipecaches[''] else: self.parseRecipes() + def parse_recipe_file(self, fn, appends=True, appendlist=None, config_data=None): + """ + Parse the specified recipe file (with or without bbappends) + and return a datastore object representing the environment + for the recipe. + Parameters: + fn: recipe file to parse - can be a file path or virtual + specification + appends: True to apply bbappends, False otherwise + appendlist: optional list of bbappend files to apply, if you + want to filter them + config_data: custom config datastore to use. NOTE: if you + specify config_data then you cannot use a virtual + specification for fn. + """ + if appends and appendlist == []: + appends = False + if appends: + if appendlist: + appendfiles = appendlist + else: + if not hasattr(self.cooker, 'collection'): + raise Exception('You must call tinfoil.prepare() with config_only=False in order to get bbappends') + appendfiles = self.cooker.collection.get_file_appends(fn) + else: + appendfiles = None + if config_data: + # We have to use a different function here if we're passing in a datastore + localdata = bb.data.createCopy(config_data) + envdata = bb.cache.parse_recipe(localdata, fn, appendfiles)[''] + else: + # Use the standard path + parser = bb.cache.NoCache(self.cooker.databuilder) + envdata = parser.loadDataFull(fn, appendfiles) + return envdata + def shutdown(self): self.cooker.shutdown(force=True) self.cooker.post_serve() diff --git a/import-layers/yocto-poky/bitbake/lib/bb/ui/buildinfohelper.py b/import-layers/yocto-poky/bitbake/lib/bb/ui/buildinfohelper.py index 93979054d..5b69660a3 100644 --- a/import-layers/yocto-poky/bitbake/lib/bb/ui/buildinfohelper.py +++ b/import-layers/yocto-poky/bitbake/lib/bb/ui/buildinfohelper.py @@ -21,28 +21,29 @@ import bb import re import os -os.environ["DJANGO_SETTINGS_MODULE"] = "toaster.toastermain.settings" - - import django from django.utils import timezone +import toaster +# Add toaster module to the search path to help django.setup() find the right +# modules +sys.path.insert(0, os.path.dirname(toaster.__file__)) -def _configure_toaster(): - """ Add toaster to sys path for importing modules - """ - sys.path.append(os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'toaster')) -_configure_toaster() - +#Set the DJANGO_SETTINGS_MODULE if it's not already set +os.environ["DJANGO_SETTINGS_MODULE"] =\ + os.environ.get("DJANGO_SETTINGS_MODULE", + "toaster.toastermain.settings") +# Setup django framework (needs to be done before importing modules) django.setup() from orm.models import Build, Task, Recipe, Layer_Version, Layer, Target, LogMessage, HelpText -from orm.models import Target_Image_File, BuildArtifact +from orm.models import Target_Image_File, TargetKernelFile, TargetSDKFile from orm.models import Variable, VariableHistory from orm.models import Package, Package_File, Target_Installed_Package, Target_File from orm.models import Task_Dependency, Package_Dependency from orm.models import Recipe_Dependency, Provides from orm.models import Project, CustomImagePackage, CustomImageRecipe +from orm.models import signal_runbuilds from bldcontrol.models import BuildEnvironment, BuildRequest @@ -54,11 +55,11 @@ from datetime import datetime, timedelta from django.db import transaction, connection + # pylint: disable=invalid-name # the logger name is standard throughout BitBake logger = logging.getLogger("ToasterLogger") - class NotExisting(Exception): pass @@ -121,11 +122,27 @@ class ORMWrapper(object): return vars(self)[dictname][key] + def get_similar_target_with_image_files(self, target): + """ + Get a Target object "similar" to target; i.e. with the same target + name ('core-image-minimal' etc.) and machine. + """ + return target.get_similar_target_with_image_files() + + def get_similar_target_with_sdk_files(self, target): + return target.get_similar_target_with_sdk_files() + + def clone_image_artifacts(self, target_from, target_to): + target_to.clone_image_artifacts_from(target_from) + + def clone_sdk_artifacts(self, target_from, target_to): + target_to.clone_sdk_artifacts_from(target_from) + def _timestamp_to_datetime(self, secs): """ Convert timestamp in seconds to Python datetime """ - return datetime(1970, 1, 1) + timedelta(seconds=secs) + return timezone.make_aware(datetime(1970, 1, 1) + timedelta(seconds=secs)) # pylint: disable=no-self-use # we disable detection of no self use in functions because the methods actually work on the object @@ -134,55 +151,33 @@ class ORMWrapper(object): # pylint: disable=bad-continuation # we do not follow the python conventions for continuation indentation due to long lines here - def create_build_object(self, build_info, brbe, project_id): - assert 'machine' in build_info - assert 'distro' in build_info - assert 'distro_version' in build_info - assert 'started_on' in build_info - assert 'cooker_log_path' in build_info - assert 'build_name' in build_info - assert 'bitbake_version' in build_info - + def get_or_create_build_object(self, brbe): prj = None buildrequest = None - if brbe is not None: # this build was triggered by a request from a user + if brbe is not None: + # Toaster-triggered build logger.debug(1, "buildinfohelper: brbe is %s" % brbe) br, _ = brbe.split(":") - buildrequest = BuildRequest.objects.get(pk = br) + buildrequest = BuildRequest.objects.get(pk=br) prj = buildrequest.project - - elif project_id is not None: # this build was triggered by an external system for a specific project - logger.debug(1, "buildinfohelper: project is %s" % prj) - prj = Project.objects.get(pk = project_id) - - else: # this build was triggered by a legacy system, or command line interactive mode + else: + # CLI build prj = Project.objects.get_or_create_default_project() logger.debug(1, "buildinfohelper: project is not specified, defaulting to %s" % prj) - if buildrequest is not None: + # reuse existing Build object build = buildrequest.build - logger.info("Updating existing build, with %s", build_info) build.project = prj - build.machine=build_info['machine'] - build.distro=build_info['distro'] - build.distro_version=build_info['distro_version'] - build.cooker_log_path=build_info['cooker_log_path'] - build.build_name=build_info['build_name'] - build.bitbake_version=build_info['bitbake_version'] build.save() - else: + # create new Build object + now = timezone.now() build = Build.objects.create( - project = prj, - machine=build_info['machine'], - distro=build_info['distro'], - distro_version=build_info['distro_version'], - started_on=build_info['started_on'], - completed_on=build_info['started_on'], - cooker_log_path=build_info['cooker_log_path'], - build_name=build_info['build_name'], - bitbake_version=build_info['bitbake_version']) + project=prj, + started_on=now, + completed_on=now, + build_name='') logger.debug(1, "buildinfohelper: build is created %s" % build) @@ -192,8 +187,17 @@ class ORMWrapper(object): return build + def update_build(self, build, data_dict): + for key in data_dict: + setattr(build, key, data_dict[key]) + build.save() + @staticmethod def get_or_create_targets(target_info): + """ + NB get_or_create() is used here because for Toaster-triggered builds, + we already created the targets when the build was triggered. + """ result = [] for target in target_info['targets']: task = '' @@ -203,17 +207,14 @@ class ORMWrapper(object): task = task[3:] if task == 'build': task = '' - obj, created = Target.objects.get_or_create(build=target_info['build'], - target=target) - if created: - obj.is_image = False - if task: - obj.task = task - obj.save() + + obj, _ = Target.objects.get_or_create(build=target_info['build'], + target=target, + task=task) result.append(obj) return result - def update_build_object(self, build, errors, warnings, taskfailures): + def update_build_stats_and_outcome(self, build, errors, warnings, taskfailures): assert isinstance(build,Build) assert isinstance(errors, int) assert isinstance(warnings, int) @@ -234,11 +235,16 @@ class ORMWrapper(object): build.completed_on = timezone.now() build.outcome = outcome build.save() + signal_runbuilds() def update_target_set_license_manifest(self, target, license_manifest_path): target.license_manifest_path = license_manifest_path target.save() + def update_target_set_package_manifest(self, target, package_manifest_path): + target.package_manifest_path = package_manifest_path + target.save() + def update_task_object(self, build, task_name, recipe_name, task_stats): """ Find the task for build which matches the recipe and task name @@ -372,7 +378,7 @@ class ORMWrapper(object): layer_copy, c = Layer_Version.objects.get_or_create( build=build_obj, layer=layer_obj.layer, - up_branch=layer_obj.up_branch, + release=layer_obj.release, branch=layer_version_information['branch'], commit=layer_version_information['commit'], local_path=layer_version_information['local_path'], @@ -415,13 +421,24 @@ class ORMWrapper(object): assert 'name' in layer_information assert 'layer_index_url' in layer_information + # From command line builds we have no brbe as the request is directly + # from bitbake if brbe is None: - layer_object, _ = Layer.objects.get_or_create( - name=layer_information['name'], - layer_index_url=layer_information['layer_index_url']) + # If we don't have git commit sha then we're using a non-git + # layer so set the layer_source_dir to identify it as such + if not layer_information['version']['commit']: + local_source_dir = layer_information["local_path"] + else: + local_source_dir = None + + layer_object, _ = \ + Layer.objects.get_or_create( + name=layer_information['name'], + local_source_dir=local_source_dir, + layer_index_url=layer_information['layer_index_url']) + return layer_object else: - # we are under managed mode; we must match the layer used in the Project Layer br_id, be_id = brbe.split(":") # find layer by checkout path; @@ -434,7 +451,11 @@ class ORMWrapper(object): # note that this is different buildrequest = BuildRequest.objects.get(pk = br_id) for brl in buildrequest.brlayer_set.all(): - localdirname = os.path.join(bc.getGitCloneDirectory(brl.giturl, brl.commit), brl.dirpath) + if brl.local_source_dir: + localdirname = os.path.join(brl.local_source_dir, + brl.dirpath) + else: + localdirname = os.path.join(bc.getGitCloneDirectory(brl.giturl, brl.commit), brl.dirpath) # we get a relative path, unless running in HEAD mode where the path is absolute if not localdirname.startswith("/"): localdirname = os.path.join(bc.be.sourcedir, localdirname) @@ -446,6 +467,11 @@ class ORMWrapper(object): if brl.layer_version: return brl.layer_version + # This might be a local layer (i.e. no git info) so try + # matching local_source_dir + if brl.local_source_dir and brl.local_source_dir == layer_information["local_path"]: + return brl.layer_version + # we matched the BRLayer, but we need the layer_version that generated this BR; reverse of the Project.schedule_build() #logger.debug(1, "Matched %s to BRlayer %s" % (pformat(layer_information["local_path"]), localdirname)) @@ -494,7 +520,7 @@ class ORMWrapper(object): parent_obj = self._cached_get(Target_File, target = target_obj, path = parent_path, inodetype = Target_File.ITYPE_DIRECTORY) tf_obj = Target_File.objects.create( target = target_obj, - path = unicode(path, 'utf-8'), + path = path, size = size, inodetype = Target_File.ITYPE_DIRECTORY, permission = permission, @@ -519,7 +545,7 @@ class ORMWrapper(object): tf_obj = Target_File.objects.create( target = target_obj, - path = unicode(path, 'utf-8'), + path = path, size = size, inodetype = inodetype, permission = permission, @@ -550,9 +576,7 @@ class ORMWrapper(object): filetarget_path = "/".join(fcpl) try: - filetarget_obj = Target_File.objects.get( - target = target_obj, - path = unicode(filetarget_path, 'utf-8')) + filetarget_obj = Target_File.objects.get(target = target_obj, path = filetarget_path) except Target_File.DoesNotExist: # we might have an invalid link; no way to detect this. just set it to None filetarget_obj = None @@ -561,7 +585,7 @@ class ORMWrapper(object): tf_obj = Target_File.objects.create( target = target_obj, - path = unicode(path, 'utf-8'), + path = path, size = size, inodetype = Target_File.ITYPE_SYMLINK, permission = permission, @@ -606,8 +630,8 @@ class ORMWrapper(object): Recipe, name=built_recipe.name, layer_version__build=None, - layer_version__up_branch= - built_recipe.layer_version.up_branch, + layer_version__release= + built_recipe.layer_version.release, file_path=built_recipe.file_path, version=built_recipe.version ) @@ -664,8 +688,8 @@ class ORMWrapper(object): dep_type = tdeptype, target = target_obj)) except KeyError as e: - logger.warn("Could not add dependency to the package %s " - "because %s is an unknown package", p, px) + logger.warning("Could not add dependency to the package %s " + "because %s is an unknown package", p, px) if len(packagedeps_objs) > 0: Package_Dependency.objects.bulk_create(packagedeps_objs) @@ -673,23 +697,26 @@ class ORMWrapper(object): logger.info("No package dependencies created") if len(errormsg) > 0: - logger.warn("buildinfohelper: target_package_info could not identify recipes: \n%s", errormsg) + logger.warning("buildinfohelper: target_package_info could not identify recipes: \n%s", errormsg) def save_target_image_file_information(self, target_obj, file_name, file_size): - Target_Image_File.objects.create( target = target_obj, - file_name = file_name, - file_size = file_size) - - def save_artifact_information(self, build_obj, file_name, file_size): - # we skip the image files from other builds - if Target_Image_File.objects.filter(file_name = file_name).count() > 0: - return + Target_Image_File.objects.create(target=target_obj, + file_name=file_name, file_size=file_size) - # do not update artifacts found in other builds - if BuildArtifact.objects.filter(file_name = file_name).count() > 0: - return + def save_target_kernel_file(self, target_obj, file_name, file_size): + """ + Save kernel file (bzImage, modules*) information for a Target target_obj. + """ + TargetKernelFile.objects.create(target=target_obj, + file_name=file_name, file_size=file_size) - BuildArtifact.objects.create(build = build_obj, file_name = file_name, file_size = file_size) + def save_target_sdk_file(self, target_obj, file_name, file_size): + """ + Save SDK artifacts to the database, associating them with a + Target object. + """ + TargetSDKFile.objects.create(target=target_obj, file_name=file_name, + file_size=file_size) def create_logmessage(self, log_information): assert 'build' in log_information @@ -857,6 +884,11 @@ class BuildInfoHelper(object): Keeps in memory all data that needs matching before writing it to the database """ + # tasks which produce image files; note we include '', as we set + # the task for a target to '' (i.e. 'build') if no target is + # explicitly defined + IMAGE_GENERATING_TASKS = ['', 'build', 'image', 'populate_sdk_ext'] + # pylint: disable=protected-access # the code will look into the protected variables of the event; no easy way around this # pylint: disable=bad-continuation @@ -888,22 +920,55 @@ class BuildInfoHelper(object): ################### ## methods to convert event/external info into objects that the ORM layer uses + def _ensure_build(self): + """ + Ensure the current build object exists and is up to date with + data on the bitbake server + """ + if not 'build' in self.internal_state or not self.internal_state['build']: + # create the Build object + self.internal_state['build'] = \ + self.orm_wrapper.get_or_create_build_object(self.brbe) + + build = self.internal_state['build'] - def _get_build_information(self, build_log_path): + # update missing fields on the Build object with found data build_info = {} - build_info['machine'] = self.server.runCommand(["getVariable", "MACHINE"])[0] - build_info['distro'] = self.server.runCommand(["getVariable", "DISTRO"])[0] - build_info['distro_version'] = self.server.runCommand(["getVariable", "DISTRO_VERSION"])[0] - build_info['started_on'] = timezone.now() - build_info['completed_on'] = timezone.now() - build_info['cooker_log_path'] = build_log_path - build_info['build_name'] = self.server.runCommand(["getVariable", "BUILDNAME"])[0] - build_info['bitbake_version'] = self.server.runCommand(["getVariable", "BB_VERSION"])[0] - build_info['project'] = self.project = self.server.runCommand(["getVariable", "TOASTER_PROJECT"])[0] - return build_info + + # set to True if at least one field is going to be set + changed = False + + if not build.build_name: + build_name = self.server.runCommand(["getVariable", "BUILDNAME"])[0] + + # only reset the build name if the one on the server is actually + # a valid value for the build_name field + if build_name != None: + build_info['build_name'] = build_name + changed = True + + if not build.machine: + build_info['machine'] = self.server.runCommand(["getVariable", "MACHINE"])[0] + changed = True + + if not build.distro: + build_info['distro'] = self.server.runCommand(["getVariable", "DISTRO"])[0] + changed = True + + if not build.distro_version: + build_info['distro_version'] = self.server.runCommand(["getVariable", "DISTRO_VERSION"])[0] + changed = True + + if not build.bitbake_version: + build_info['bitbake_version'] = self.server.runCommand(["getVariable", "BB_VERSION"])[0] + changed = True + + if changed: + self.orm_wrapper.update_build(self.internal_state['build'], build_info) def _get_task_information(self, event, recipe): assert 'taskname' in vars(event) + self._ensure_build() task_information = {} task_information['build'] = self.internal_state['build'] @@ -918,8 +983,7 @@ class BuildInfoHelper(object): return task_information def _get_layer_version_for_path(self, path): - assert path.startswith("/") - assert 'build' in self.internal_state + self._ensure_build() def _slkey_interactive(layer_version): assert isinstance(layer_version, Layer_Version) @@ -930,9 +994,12 @@ class BuildInfoHelper(object): # we can match to the recipe file path if path.startswith(lvo.local_path): return lvo + if lvo.layer.local_source_dir and \ + path.startswith(lvo.layer.local_source_dir): + return lvo #if we get here, we didn't read layers correctly; dump whatever information we have on the error log - logger.warn("Could not match layer version for recipe path %s : %s", path, self.orm_wrapper.layer_version_objects) + logger.warning("Could not match layer version for recipe path %s : %s", path, self.orm_wrapper.layer_version_objects) #mockup the new layer unknown_layer, _ = Layer.objects.get_or_create(name="Unidentified layer", layer_index_url="") @@ -963,6 +1030,8 @@ class BuildInfoHelper(object): return recipe_info def _get_path_information(self, task_object): + self._ensure_build() + assert isinstance(task_object, Task) build_stats_format = "{tmpdir}/buildstats/{buildname}/{package}/" build_stats_path = [] @@ -1003,19 +1072,33 @@ class BuildInfoHelper(object): self.internal_state['lvs'][self.orm_wrapper.get_update_layer_object(layerinfos[layer], self.brbe)] = layerinfos[layer]['version'] self.internal_state['lvs'][self.orm_wrapper.get_update_layer_object(layerinfos[layer], self.brbe)]['local_path'] = layerinfos[layer]['local_path'] except NotExisting as nee: - logger.warn("buildinfohelper: cannot identify layer exception:%s ", nee) + logger.warning("buildinfohelper: cannot identify layer exception:%s ", nee) + + def store_started_build(self): + self._ensure_build() + def save_build_log_file_path(self, build_log_path): + self._ensure_build() - def store_started_build(self, event, build_log_path): + if not self.internal_state['build'].cooker_log_path: + data_dict = {'cooker_log_path': build_log_path} + self.orm_wrapper.update_build(self.internal_state['build'], data_dict) + + def save_build_targets(self, event): + self._ensure_build() + + # create target information assert '_pkgs' in vars(event) - build_information = self._get_build_information(build_log_path) + target_information = {} + target_information['targets'] = event._pkgs + target_information['build'] = self.internal_state['build'] - # Update brbe and project as they can be changed for every build - self.project = build_information['project'] + self.internal_state['targets'] = self.orm_wrapper.get_or_create_targets(target_information) - build_obj = self.orm_wrapper.create_build_object(build_information, self.brbe, self.project) + def save_build_layers_and_variables(self): + self._ensure_build() - self.internal_state['build'] = build_obj + build_obj = self.internal_state['build'] # save layer version information for this build if not 'lvs' in self.internal_state: @@ -1026,13 +1109,6 @@ class BuildInfoHelper(object): del self.internal_state['lvs'] - # create target information - target_information = {} - target_information['targets'] = event._pkgs - target_information['build'] = build_obj - - self.internal_state['targets'] = self.orm_wrapper.get_or_create_targets(target_information) - # Save build configuration data = self.server.runCommand(["getAllKeysWithFlags", ["doc", "func"]])[0] @@ -1055,7 +1131,8 @@ class BuildInfoHelper(object): abs_file_name = vh['file'] for pp in path_prefixes: if abs_file_name.startswith(pp + "/"): - vh['file']=abs_file_name[len(pp + "/"):] + # preserve layer name in relative path + vh['file']=abs_file_name[pp.rfind("/")+1:] break # save the variables @@ -1063,35 +1140,48 @@ class BuildInfoHelper(object): return self.brbe + def set_recipes_to_parse(self, num_recipes): + """ + Set the number of recipes which need to be parsed for this build. + This is set the first time ParseStarted is received by toasterui. + """ + self._ensure_build() + self.internal_state['build'].recipes_to_parse = num_recipes + self.internal_state['build'].save() + + def set_recipes_parsed(self, num_recipes): + """ + Set the number of recipes parsed so far for this build; this is updated + each time a ParseProgress or ParseCompleted event is received by + toasterui. + """ + self._ensure_build() + if num_recipes <= self.internal_state['build'].recipes_to_parse: + self.internal_state['build'].recipes_parsed = num_recipes + self.internal_state['build'].save() def update_target_image_file(self, event): evdata = BuildInfoHelper._get_data_from_event(event) for t in self.internal_state['targets']: if t.is_image == True: - output_files = list(evdata.viewkeys()) + output_files = list(evdata.keys()) for output in output_files: if t.target in output and 'rootfs' in output and not output.endswith(".manifest"): self.orm_wrapper.save_target_image_file_information(t, output, evdata[output]) def update_artifact_image_file(self, event): + self._ensure_build() evdata = BuildInfoHelper._get_data_from_event(event) for artifact_path in evdata.keys(): - self.orm_wrapper.save_artifact_information(self.internal_state['build'], artifact_path, evdata[artifact_path]) + self.orm_wrapper.save_artifact_information( + self.internal_state['build'], artifact_path, + evdata[artifact_path]) def update_build_information(self, event, errors, warnings, taskfailures): - if 'build' in self.internal_state: - self.orm_wrapper.update_build_object(self.internal_state['build'], errors, warnings, taskfailures) - - - def store_license_manifest_path(self, event): - deploy_dir = BuildInfoHelper._get_data_from_event(event)['deploy_dir'] - image_name = BuildInfoHelper._get_data_from_event(event)['image_name'] - path = deploy_dir + "/licenses/" + image_name + "/license.manifest" - for target in self.internal_state['targets']: - if target.target in image_name: - self.orm_wrapper.update_target_set_license_manifest(target, path) - + self._ensure_build() + self.orm_wrapper.update_build_stats_and_outcome( + self.internal_state['build'], errors, warnings, taskfailures) def store_started_task(self, event): assert isinstance(event, (bb.runqueue.sceneQueueTaskStarted, bb.runqueue.runQueueTaskStarted, bb.runqueue.runQueueTaskSkipped)) @@ -1134,6 +1224,7 @@ class BuildInfoHelper(object): def store_tasks_stats(self, event): + self._ensure_build() task_data = BuildInfoHelper._get_data_from_event(event) for (task_file, task_name, task_stats, recipe_name) in task_data: @@ -1229,6 +1320,8 @@ class BuildInfoHelper(object): def store_target_package_data(self, event): + self._ensure_build() + # for all image targets for target in self.internal_state['targets']: if target.is_image: @@ -1240,17 +1333,32 @@ class BuildInfoHelper(object): self.orm_wrapper.save_target_package_information(self.internal_state['build'], target, imgdata, pkgdata, self.internal_state['recipes'], built_package=True) self.orm_wrapper.save_target_package_information(self.internal_state['build'], target, imgdata.copy(), pkgdata, self.internal_state['recipes'], built_package=False) except KeyError as e: - logger.warn("KeyError in save_target_package_information" - "%s ", e) + logger.warning("KeyError in save_target_package_information" + "%s ", e) - try: - self.orm_wrapper.save_target_file_information(self.internal_state['build'], target, filedata) - except KeyError as e: - logger.warn("KeyError in save_target_file_information" - "%s ", e) + # only try to find files in the image if the task for this + # target is one which produces image files; otherwise, the old + # list of files in the files-in-image.txt file will be + # appended to the target even if it didn't produce any images + if target.task in BuildInfoHelper.IMAGE_GENERATING_TASKS: + try: + self.orm_wrapper.save_target_file_information(self.internal_state['build'], target, filedata) + except KeyError as e: + logger.warning("KeyError in save_target_file_information" + "%s ", e) + def cancel_cli_build(self): + """ + If a build is currently underway, set its state to CANCELLED; + note that this only gets called for command line builds which are + interrupted, so it doesn't touch any BuildRequest objects + """ + self._ensure_build() + self.internal_state['build'].outcome = Build.CANCELLED + self.internal_state['build'].save() + signal_runbuilds() def store_dependency_information(self, event): assert '_depgraph' in vars(event) @@ -1392,10 +1500,12 @@ class BuildInfoHelper(object): Task_Dependency.objects.bulk_create(taskdeps_objects) if len(errormsg) > 0: - logger.warn("buildinfohelper: dependency info not identify recipes: \n%s", errormsg) + logger.warning("buildinfohelper: dependency info not identify recipes: \n%s", errormsg) def store_build_package_information(self, event): + self._ensure_build() + package_info = BuildInfoHelper._get_data_from_event(event) self.orm_wrapper.save_build_package_information( self.internal_state['build'], @@ -1411,10 +1521,12 @@ class BuildInfoHelper(object): def _store_build_done(self, errorcode): logger.info("Build exited with errorcode %d", errorcode) + + if not self.brbe: + return + br_id, be_id = self.brbe.split(":") - be = BuildEnvironment.objects.get(pk = be_id) - be.lock = BuildEnvironment.LOCK_LOCK - be.save() + br = BuildRequest.objects.get(pk = br_id) # if we're 'done' because we got cancelled update the build outcome @@ -1432,6 +1544,10 @@ class BuildInfoHelper(object): br.state = BuildRequest.REQ_FAILED br.save() + be = BuildEnvironment.objects.get(pk = be_id) + be.lock = BuildEnvironment.LOCK_FREE + be.save() + signal_runbuilds() def store_log_error(self, text): mockevent = MockEvent() @@ -1449,30 +1565,25 @@ class BuildInfoHelper(object): mockevent.lineno = -1 self.store_log_event(mockevent) - def store_log_event(self, event): + self._ensure_build() + if event.levelno < formatter.WARNING: return - if 'args' in vars(event): - event.msg = event.msg % event.args - - if not 'build' in self.internal_state: - if self.brbe is None: - if not 'backlog' in self.internal_state: - self.internal_state['backlog'] = [] - self.internal_state['backlog'].append(event) - return - else: # we're under Toaster control, the build is already created - br, _ = self.brbe.split(":") - buildrequest = BuildRequest.objects.get(pk = br) - self.internal_state['build'] = buildrequest.build + # early return for CLI builds + if self.brbe is None: + if not 'backlog' in self.internal_state: + self.internal_state['backlog'] = [] + self.internal_state['backlog'].append(event) + return - if 'build' in self.internal_state and 'backlog' in self.internal_state: + if 'backlog' in self.internal_state: # if we have a backlog of events, do our best to save them here if len(self.internal_state['backlog']): tempevent = self.internal_state['backlog'].pop() - logger.debug(1, "buildinfohelper: Saving stored event %s " % tempevent) + logger.debug(1, "buildinfohelper: Saving stored event %s " + % tempevent) self.store_log_event(tempevent) else: logger.info("buildinfohelper: All events saved") @@ -1491,26 +1602,324 @@ class BuildInfoHelper(object): else: log_information['level'] = LogMessage.INFO - log_information['message'] = event.msg + log_information['message'] = event.getMessage() log_information['pathname'] = event.pathname log_information['lineno'] = event.lineno logger.info("Logging error 2: %s", log_information) self.orm_wrapper.create_logmessage(log_information) + def _get_filenames_from_image_license(self, image_license_manifest_path): + """ + Find the FILES line in the image_license.manifest file, + which has the basenames of the bzImage and modules files + in this format: + FILES: bzImage--4.4.11+git0+3a5f494784_53e84104c5-r0-qemux86-20160603165040.bin modules--4.4.11+git0+3a5f494784_53e84104c5-r0-qemux86-20160603165040.tgz + """ + files = [] + with open(image_license_manifest_path) as image_license: + for line in image_license: + if line.startswith('FILES'): + files_str = line.split(':')[1].strip() + files_str = re.sub(r' {2,}', ' ', files_str) + + # ignore lines like "FILES:" with no filenames + if files_str: + files += files_str.split(' ') + return files + + def _endswith(self, str_to_test, endings): + """ + Returns True if str ends with one of the strings in the list + endings, False otherwise + """ + endswith = False + for ending in endings: + if str_to_test.endswith(ending): + endswith = True + break + return endswith + + def _get_image_files(self, deploy_dir_image, image_name, image_file_extensions): + """ + Find files in deploy_dir_image whose basename starts with the + string image_name and ends with one of the strings in + image_file_extensions. + + Returns a list of file dictionaries like + + [ + { + 'path': '/path/to/image/file', + 'size': <file size in bytes> + } + ] + """ + image_files = [] + + for dirpath, _, filenames in os.walk(deploy_dir_image): + for filename in filenames: + if filename.startswith(image_name) and \ + self._endswith(filename, image_file_extensions): + image_file_path = os.path.join(dirpath, filename) + image_file_size = os.stat(image_file_path).st_size + + image_files.append({ + 'path': image_file_path, + 'size': image_file_size + }) + + return image_files + + def scan_image_artifacts(self): + """ + Scan for built image artifacts in DEPLOY_DIR_IMAGE and associate them + with a Target object in self.internal_state['targets']. + + We have two situations to handle: + + 1. This is the first time a target + machine has been built, so + add files from the DEPLOY_DIR_IMAGE to the target. + + OR + + 2. There are no new files for the target (they were already produced by + a previous build), so copy them from the most recent previous build with + the same target, task and machine. + """ + deploy_dir_image = \ + self.server.runCommand(['getVariable', 'DEPLOY_DIR_IMAGE'])[0] + + # if there's no DEPLOY_DIR_IMAGE, there aren't going to be + # any image artifacts, so we can return immediately + if not deploy_dir_image: + return + + buildname = self.server.runCommand(['getVariable', 'BUILDNAME'])[0] + machine = self.server.runCommand(['getVariable', 'MACHINE'])[0] + image_name = self.server.runCommand(['getVariable', 'IMAGE_NAME'])[0] + + # location of the manifest files for this build; + # note that this file is only produced if an image is produced + license_directory = \ + self.server.runCommand(['getVariable', 'LICENSE_DIRECTORY'])[0] + + # file name extensions for image files + image_file_extensions_unique = {} + image_fstypes = self.server.runCommand( + ['getVariable', 'IMAGE_FSTYPES'])[0] + if image_fstypes != None: + image_types_str = image_fstypes.strip() + image_file_extensions = re.sub(r' {2,}', ' ', image_types_str) + image_file_extensions_unique = set(image_file_extensions.split(' ')) + + targets = self.internal_state['targets'] + + # filter out anything which isn't an image target + image_targets = [target for target in targets if target.is_image] + + for image_target in image_targets: + # this is set to True if we find at least one file relating to + # this target; if this remains False after the scan, we copy the + # files from the most-recent Target with the same target + machine + # onto this Target instead + has_files = False + + # we construct this because by the time we reach + # BuildCompleted, this has reset to + # 'defaultpkgname-<MACHINE>-<BUILDNAME>'; + # we need to change it to + # <TARGET>-<MACHINE>-<BUILDNAME> + real_image_name = re.sub(r'^defaultpkgname', image_target.target, + image_name) + + image_license_manifest_path = os.path.join( + license_directory, + real_image_name, + 'image_license.manifest') + + image_package_manifest_path = os.path.join( + license_directory, + real_image_name, + 'image_license.manifest') + + # if image_license.manifest exists, we can read the names of + # bzImage, modules etc. files for this build from it, then look for + # them in the DEPLOY_DIR_IMAGE; note that this file is only produced + # if an image file was produced + if os.path.isfile(image_license_manifest_path): + has_files = True + + basenames = self._get_filenames_from_image_license( + image_license_manifest_path) + + for basename in basenames: + artifact_path = os.path.join(deploy_dir_image, basename) + if not os.path.exists(artifact_path): + logger.warning("artifact %s doesn't exist, skipping" % artifact_path) + continue + artifact_size = os.stat(artifact_path).st_size + + # note that the artifact will only be saved against this + # build if it hasn't been already + self.orm_wrapper.save_target_kernel_file(image_target, + artifact_path, artifact_size) + + # store the license manifest path on the target + # (this file is also created any time an image file is created) + license_manifest_path = os.path.join(license_directory, + real_image_name, 'license.manifest') + + self.orm_wrapper.update_target_set_license_manifest( + image_target, license_manifest_path) + + # store the package manifest path on the target (this file + # is created any time an image file is created) + package_manifest_path = os.path.join(deploy_dir_image, + real_image_name + '.rootfs.manifest') + + if os.path.exists(package_manifest_path): + self.orm_wrapper.update_target_set_package_manifest( + image_target, package_manifest_path) + + # scan the directory for image files relating to this build + # (via real_image_name); note that we don't have to set + # has_files = True, as searching for the license manifest file + # will already have set it to true if at least one image file was + # produced; note that the real_image_name includes BUILDNAME, which + # in turn includes a timestamp; so if no files were produced for + # this timestamp (i.e. the build reused existing image files already + # in the directory), no files will be recorded against this target + image_files = self._get_image_files(deploy_dir_image, + real_image_name, image_file_extensions_unique) + + for image_file in image_files: + self.orm_wrapper.save_target_image_file_information( + image_target, image_file['path'], image_file['size']) + + if not has_files: + # copy image files and build artifacts from the + # most-recently-built Target with the + # same target + machine as this Target; also copy the license + # manifest path, as that is not treated as an artifact and needs + # to be set separately + similar_target = \ + self.orm_wrapper.get_similar_target_with_image_files( + image_target) + + if similar_target: + logger.info('image artifacts for target %s cloned from ' \ + 'target %s' % (image_target.pk, similar_target.pk)) + self.orm_wrapper.clone_image_artifacts(similar_target, + image_target) + + def _get_sdk_targets(self): + """ + Return targets which could generate SDK artifacts, i.e. + "do_populate_sdk" and "do_populate_sdk_ext". + """ + return [target for target in self.internal_state['targets'] \ + if target.task in ['populate_sdk', 'populate_sdk_ext']] + + def scan_sdk_artifacts(self, event): + """ + Note that we have to intercept an SDKArtifactInfo event from + toaster.bbclass (via toasterui) to get hold of the SDK variables we + need to be able to scan for files accurately: this is because + variables like TOOLCHAIN_OUTPUTNAME have reset to None by the time + BuildCompleted is fired by bitbake, so we have to get those values + while the build is still in progress. + + For populate_sdk_ext, this runs twice, with two different + TOOLCHAIN_OUTPUTNAME settings, each of which will capture some of the + files in the SDK output directory. + """ + sdk_vars = BuildInfoHelper._get_data_from_event(event) + toolchain_outputname = sdk_vars['TOOLCHAIN_OUTPUTNAME'] + + # targets which might have created SDK artifacts + sdk_targets = self._get_sdk_targets() + + # location of SDK artifacts + tmpdir = self.server.runCommand(['getVariable', 'TMPDIR'])[0] + sdk_dir = os.path.join(tmpdir, 'deploy', 'sdk') + + # all files in the SDK directory + artifacts = [] + for dir_path, _, filenames in os.walk(sdk_dir): + for filename in filenames: + full_path = os.path.join(dir_path, filename) + if not os.path.islink(full_path): + artifacts.append(full_path) + + for sdk_target in sdk_targets: + # find files in the SDK directory which haven't already been + # recorded against a Target and whose basename matches + # TOOLCHAIN_OUTPUTNAME + for artifact_path in artifacts: + basename = os.path.basename(artifact_path) + + toolchain_match = basename.startswith(toolchain_outputname) + + # files which match the name of the target which produced them; + # for example, + # poky-glibc-x86_64-core-image-sato-i586-toolchain-ext-2.1+snapshot.sh + target_match = re.search(sdk_target.target, basename) + + # targets which produce "*-nativesdk-*" files + is_ext_sdk_target = sdk_target.task in \ + ['do_populate_sdk_ext', 'populate_sdk_ext'] + + # SDK files which don't match the target name, i.e. + # x86_64-nativesdk-libc.* + # poky-glibc-x86_64-buildtools-tarball-i586-buildtools-nativesdk-standalone-2.1+snapshot* + is_ext_sdk_file = re.search('-nativesdk-', basename) + + file_from_target = (toolchain_match and target_match) or \ + (is_ext_sdk_target and is_ext_sdk_file) + + if file_from_target: + # don't record the file if it's already been added to this + # target + matching_files = TargetSDKFile.objects.filter( + target=sdk_target, file_name=artifact_path) + + if matching_files.count() == 0: + artifact_size = os.stat(artifact_path).st_size + + self.orm_wrapper.save_target_sdk_file( + sdk_target, artifact_path, artifact_size) + + def clone_required_sdk_artifacts(self): + """ + If an SDK target doesn't have any SDK artifacts, this means that + the postfuncs of populate_sdk or populate_sdk_ext didn't fire, which + in turn means that the targets of this build didn't generate any new + artifacts. + + In this case, clone SDK artifacts for targets in the current build + from existing targets for this build. + """ + sdk_targets = self._get_sdk_targets() + for sdk_target in sdk_targets: + # only clone for SDK targets which have no TargetSDKFiles yet + if sdk_target.targetsdkfile_set.all().count() == 0: + similar_target = \ + self.orm_wrapper.get_similar_target_with_sdk_files( + sdk_target) + if similar_target: + logger.info('SDK artifacts for target %s cloned from ' \ + 'target %s' % (sdk_target.pk, similar_target.pk)) + self.orm_wrapper.clone_sdk_artifacts(similar_target, + sdk_target) + def close(self, errorcode): - if self.brbe is not None: - self._store_build_done(errorcode) + self._store_build_done(errorcode) if 'backlog' in self.internal_state: - if 'build' in self.internal_state: - # we save missed events in the database for the current build - tempevent = self.internal_state['backlog'].pop() - self.store_log_event(tempevent) - else: - # we have no build, and we still have events; something amazingly wrong happend - for event in self.internal_state['backlog']: - logger.error("UNSAVED log: %s", event.msg) + # we save missed events in the database for the current build + tempevent = self.internal_state['backlog'].pop() + self.store_log_event(tempevent) if not connection.features.autocommits_when_autocommit_is_off: transaction.set_autocommit(True) @@ -1519,3 +1928,7 @@ class BuildInfoHelper(object): # being incorrectly attached to the previous Toaster-triggered build; # see https://bugzilla.yoctoproject.org/show_bug.cgi?id=9021 self.brbe = None + + # unset the internal Build object to prevent it being reused for the + # next build + self.internal_state['build'] = None diff --git a/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/__init__.py b/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/__init__.py deleted file mode 100644 index b7cbe1a4f..000000000 --- a/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/__init__.py +++ /dev/null @@ -1,17 +0,0 @@ -# -# Gtk+ UI pieces for BitBake -# -# Copyright (C) 2006-2007 Richard Purdie -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 as -# published by the Free Software Foundation. -# -# 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, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. diff --git a/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/hig/crumbsdialog.py b/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/hig/crumbsdialog.py deleted file mode 100644 index c679f9a07..000000000 --- a/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/hig/crumbsdialog.py +++ /dev/null @@ -1,44 +0,0 @@ -# -# BitBake Graphical GTK User Interface -# -# Copyright (C) 2011-2012 Intel Corporation -# -# Authored by Joshua Lock <josh@linux.intel.com> -# Authored by Dongxiao Xu <dongxiao.xu@intel.com> -# Authored by Shane Wang <shane.wang@intel.com> -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 as -# published by the Free Software Foundation. -# -# 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, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -import gtk - -""" -The following are convenience classes for implementing GNOME HIG compliant -BitBake GUI's -In summary: spacing = 12px, border-width = 6px -""" - -class CrumbsDialog(gtk.Dialog): - """ - A GNOME HIG compliant dialog widget. - Add buttons with gtk.Dialog.add_button or gtk.Dialog.add_buttons - """ - def __init__(self, title="", parent=None, flags=0, buttons=None): - super(CrumbsDialog, self).__init__(title, parent, flags, buttons) - - self.set_property("has-separator", False) # note: deprecated in 2.22 - - self.set_border_width(6) - self.vbox.set_property("spacing", 12) - self.action_area.set_property("spacing", 12) - self.action_area.set_property("border-width", 6) diff --git a/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/hig/crumbsmessagedialog.py b/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/hig/crumbsmessagedialog.py deleted file mode 100644 index 3b998e463..000000000 --- a/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/hig/crumbsmessagedialog.py +++ /dev/null @@ -1,70 +0,0 @@ -# -# BitBake Graphical GTK User Interface -# -# Copyright (C) 2011-2012 Intel Corporation -# -# Authored by Joshua Lock <josh@linux.intel.com> -# Authored by Dongxiao Xu <dongxiao.xu@intel.com> -# Authored by Shane Wang <shane.wang@intel.com> -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 as -# published by the Free Software Foundation. -# -# 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, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -import glib -import gtk -from bb.ui.crumbs.hobwidget import HobIconChecker -from bb.ui.crumbs.hig.crumbsdialog import CrumbsDialog - -""" -The following are convenience classes for implementing GNOME HIG compliant -BitBake GUI's -In summary: spacing = 12px, border-width = 6px -""" - -class CrumbsMessageDialog(gtk.MessageDialog): - """ - A GNOME HIG compliant dialog widget. - Add buttons with gtk.Dialog.add_button or gtk.Dialog.add_buttons - """ - def __init__(self, parent = None, label="", dialog_type = gtk.MESSAGE_QUESTION, msg=""): - super(CrumbsMessageDialog, self).__init__(None, - gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, - dialog_type, - gtk.BUTTONS_NONE, - None) - - self.set_skip_taskbar_hint(False) - - self.set_markup(label) - - if 0 <= len(msg) < 300: - self.format_secondary_markup(msg) - else: - vbox = self.get_message_area() - vbox.set_border_width(1) - vbox.set_property("spacing", 12) - self.textWindow = gtk.ScrolledWindow() - self.textWindow.set_shadow_type(gtk.SHADOW_IN) - self.textWindow.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) - self.msgView = gtk.TextView() - self.msgView.set_editable(False) - self.msgView.set_wrap_mode(gtk.WRAP_WORD) - self.msgView.set_cursor_visible(False) - self.msgView.set_size_request(300, 300) - self.buf = gtk.TextBuffer() - self.buf.set_text(msg) - self.msgView.set_buffer(self.buf) - self.textWindow.add(self.msgView) - self.msgView.show() - vbox.add(self.textWindow) - self.textWindow.show() diff --git a/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/hig/deployimagedialog.py b/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/hig/deployimagedialog.py deleted file mode 100644 index a13fff906..000000000 --- a/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/hig/deployimagedialog.py +++ /dev/null @@ -1,219 +0,0 @@ -# -# BitBake Graphical GTK User Interface -# -# Copyright (C) 2011-2012 Intel Corporation -# -# Authored by Joshua Lock <josh@linux.intel.com> -# Authored by Dongxiao Xu <dongxiao.xu@intel.com> -# Authored by Shane Wang <shane.wang@intel.com> -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 as -# published by the Free Software Foundation. -# -# 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, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -import glob -import gtk -import gobject -import os -import re -import shlex -import subprocess -import tempfile -from bb.ui.crumbs.hobwidget import hic, HobButton -from bb.ui.crumbs.progressbar import HobProgressBar -import bb.ui.crumbs.utils -import bb.process -from bb.ui.crumbs.hig.crumbsdialog import CrumbsDialog -from bb.ui.crumbs.hig.crumbsmessagedialog import CrumbsMessageDialog - -""" -The following are convenience classes for implementing GNOME HIG compliant -BitBake GUI's -In summary: spacing = 12px, border-width = 6px -""" - -class DeployImageDialog (CrumbsDialog): - - __dummy_usb__ = "--select a usb drive--" - - def __init__(self, title, image_path, parent, flags, buttons=None, standalone=False): - super(DeployImageDialog, self).__init__(title, parent, flags, buttons) - - self.image_path = image_path - self.standalone = standalone - - self.create_visual_elements() - self.connect("response", self.response_cb) - - def create_visual_elements(self): - self.set_size_request(600, 400) - label = gtk.Label() - label.set_alignment(0.0, 0.5) - markup = "<span font_desc='12'>The image to be written into usb drive:</span>" - label.set_markup(markup) - self.vbox.pack_start(label, expand=False, fill=False, padding=2) - - table = gtk.Table(2, 10, False) - table.set_col_spacings(5) - table.set_row_spacings(5) - self.vbox.pack_start(table, expand=True, fill=True) - - scroll = gtk.ScrolledWindow() - scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) - scroll.set_shadow_type(gtk.SHADOW_IN) - tv = gtk.TextView() - tv.set_editable(False) - tv.set_wrap_mode(gtk.WRAP_WORD) - tv.set_cursor_visible(False) - self.buf = gtk.TextBuffer() - self.buf.set_text(self.image_path) - tv.set_buffer(self.buf) - scroll.add(tv) - table.attach(scroll, 0, 10, 0, 1) - - # There are 2 ways to use DeployImageDialog - # One way is that called by HOB when the 'Deploy Image' button is clicked - # The other way is that called by a standalone script. - # Following block of codes handles the latter way. It adds a 'Select Image' button and - # emit a signal when the button is clicked. - if self.standalone: - gobject.signal_new("select_image_clicked", self, gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, ()) - icon = gtk.Image() - pix_buffer = gtk.gdk.pixbuf_new_from_file(hic.ICON_IMAGES_DISPLAY_FILE) - icon.set_from_pixbuf(pix_buffer) - button = gtk.Button("Select Image") - button.set_image(icon) - #button.set_size_request(140, 50) - table.attach(button, 9, 10, 1, 2, gtk.FILL, 0, 0, 0) - button.connect("clicked", self.select_image_button_clicked_cb) - - separator = gtk.HSeparator() - self.vbox.pack_start(separator, expand=False, fill=False, padding=10) - - self.usb_desc = gtk.Label() - self.usb_desc.set_alignment(0.0, 0.5) - markup = "<span font_desc='12'>You haven't chosen any USB drive.</span>" - self.usb_desc.set_markup(markup) - - self.usb_combo = gtk.combo_box_new_text() - self.usb_combo.connect("changed", self.usb_combo_changed_cb) - model = self.usb_combo.get_model() - model.clear() - self.usb_combo.append_text(self.__dummy_usb__) - for usb in self.find_all_usb_devices(): - self.usb_combo.append_text("/dev/" + usb) - self.usb_combo.set_active(0) - self.vbox.pack_start(self.usb_combo, expand=False, fill=False) - self.vbox.pack_start(self.usb_desc, expand=False, fill=False, padding=2) - - self.progress_bar = HobProgressBar() - self.vbox.pack_start(self.progress_bar, expand=False, fill=False) - separator = gtk.HSeparator() - self.vbox.pack_start(separator, expand=False, fill=True, padding=10) - - self.vbox.show_all() - self.progress_bar.hide() - - def set_image_text_buffer(self, image_path): - self.buf.set_text(image_path) - - def set_image_path(self, image_path): - self.image_path = image_path - - def popen_read(self, cmd): - tmpout, errors = bb.process.run("%s" % cmd) - return tmpout.strip() - - def find_all_usb_devices(self): - usb_devs = [ os.readlink(u) - for u in glob.glob('/dev/disk/by-id/usb*') - if not re.search(r'part\d+', u) ] - return [ '%s' % u[u.rfind('/')+1:] for u in usb_devs ] - - def get_usb_info(self, dev): - return "%s %s" % \ - (self.popen_read('cat /sys/class/block/%s/device/vendor' % dev), - self.popen_read('cat /sys/class/block/%s/device/model' % dev)) - - def select_image_button_clicked_cb(self, button): - self.emit('select_image_clicked') - - def usb_combo_changed_cb(self, usb_combo): - combo_item = self.usb_combo.get_active_text() - if not combo_item or combo_item == self.__dummy_usb__: - markup = "<span font_desc='12'>You haven't chosen any USB drive.</span>" - self.usb_desc.set_markup(markup) - else: - markup = "<span font_desc='12'>" + self.get_usb_info(combo_item.lstrip("/dev/")) + "</span>" - self.usb_desc.set_markup(markup) - - def response_cb(self, dialog, response_id): - if response_id == gtk.RESPONSE_YES: - lbl = '' - msg = '' - combo_item = self.usb_combo.get_active_text() - if combo_item and combo_item != self.__dummy_usb__ and self.image_path: - cmdline = bb.ui.crumbs.utils.which_terminal() - if cmdline: - tmpfile = tempfile.NamedTemporaryFile() - cmdline += "\"sudo dd if=" + self.image_path + \ - " of=" + combo_item + " && sync; echo $? > " + tmpfile.name + "\"" - subprocess.call(shlex.split(cmdline)) - - if int(tmpfile.readline().strip()) == 0: - lbl = "<b>Deploy image successfully.</b>" - else: - lbl = "<b>Failed to deploy image.</b>" - msg = "Please check image <b>%s</b> exists and USB device <b>%s</b> is writable." % (self.image_path, combo_item) - tmpfile.close() - else: - if not self.image_path: - lbl = "<b>No selection made.</b>" - msg = "You have not selected an image to deploy." - else: - lbl = "<b>No selection made.</b>" - msg = "You have not selected a USB device." - if len(lbl): - crumbs_dialog = CrumbsMessageDialog(self, lbl, gtk.MESSAGE_INFO, msg) - button = crumbs_dialog.add_button("Close", gtk.RESPONSE_OK) - HobButton.style_button(button) - crumbs_dialog.run() - crumbs_dialog.destroy() - - def update_progress_bar(self, title, fraction, status=None): - self.progress_bar.update(fraction) - self.progress_bar.set_title(title) - self.progress_bar.set_rcstyle(status) - - def write_file(self, ifile, ofile): - self.progress_bar.reset() - self.progress_bar.show() - - f_from = os.open(ifile, os.O_RDONLY) - f_to = os.open(ofile, os.O_WRONLY) - - total_size = os.stat(ifile).st_size - written_size = 0 - - while True: - buf = os.read(f_from, 1024*1024) - if not buf: - break - os.write(f_to, buf) - written_size += 1024*1024 - self.update_progress_bar("Writing to usb:", written_size * 1.0/total_size) - - self.update_progress_bar("Writing completed:", 1.0) - os.close(f_from) - os.close(f_to) - self.progress_bar.hide() diff --git a/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/hig/imageselectiondialog.py b/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/hig/imageselectiondialog.py deleted file mode 100644 index 21216adc9..000000000 --- a/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/hig/imageselectiondialog.py +++ /dev/null @@ -1,172 +0,0 @@ -# -# BitBake Graphical GTK User Interface -# -# Copyright (C) 2011-2012 Intel Corporation -# -# Authored by Joshua Lock <josh@linux.intel.com> -# Authored by Dongxiao Xu <dongxiao.xu@intel.com> -# Authored by Shane Wang <shane.wang@intel.com> -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 as -# published by the Free Software Foundation. -# -# 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, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -import gtk -import gobject -import os -from bb.ui.crumbs.hobwidget import HobViewTable, HobInfoButton, HobButton, HobAltButton -from bb.ui.crumbs.hig.crumbsdialog import CrumbsDialog -from bb.ui.crumbs.hig.layerselectiondialog import LayerSelectionDialog - -""" -The following are convenience classes for implementing GNOME HIG compliant -BitBake GUI's -In summary: spacing = 12px, border-width = 6px -""" - -class ImageSelectionDialog (CrumbsDialog): - - __columns__ = [{ - 'col_name' : 'Image name', - 'col_id' : 0, - 'col_style': 'text', - 'col_min' : 400, - 'col_max' : 400 - }, { - 'col_name' : 'Select', - 'col_id' : 1, - 'col_style': 'radio toggle', - 'col_min' : 160, - 'col_max' : 160 - }] - - - def __init__(self, image_folder, image_types, title, parent, flags, buttons=None, image_extension = {}): - super(ImageSelectionDialog, self).__init__(title, parent, flags, buttons) - self.connect("response", self.response_cb) - - self.image_folder = image_folder - self.image_types = image_types - self.image_list = [] - self.image_names = [] - self.image_extension = image_extension - - # create visual elements on the dialog - self.create_visual_elements() - - self.image_store = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_BOOLEAN) - self.fill_image_store() - - def create_visual_elements(self): - hbox = gtk.HBox(False, 6) - - self.vbox.pack_start(hbox, expand=False, fill=False) - - entry = gtk.Entry() - entry.set_text(self.image_folder) - table = gtk.Table(1, 10, True) - table.set_size_request(560, -1) - hbox.pack_start(table, expand=False, fill=False) - table.attach(entry, 0, 9, 0, 1) - image = gtk.Image() - image.set_from_stock(gtk.STOCK_OPEN, gtk.ICON_SIZE_BUTTON) - open_button = gtk.Button() - open_button.set_image(image) - open_button.connect("clicked", self.select_path_cb, self, entry) - table.attach(open_button, 9, 10, 0, 1) - - self.image_table = HobViewTable(self.__columns__, "Images") - self.image_table.set_size_request(-1, 300) - self.image_table.connect("toggled", self.toggled_cb) - self.image_table.connect_group_selection(self.table_selected_cb) - self.image_table.connect("row-activated", self.row_actived_cb) - self.vbox.pack_start(self.image_table, expand=True, fill=True) - - self.show_all() - - def change_image_cb(self, model, path, columnid): - if not model: - return - iter = model.get_iter_first() - while iter: - rowpath = model.get_path(iter) - model[rowpath][columnid] = False - iter = model.iter_next(iter) - - model[path][columnid] = True - - def toggled_cb(self, table, cell, path, columnid, tree): - model = tree.get_model() - self.change_image_cb(model, path, columnid) - - def table_selected_cb(self, selection): - model, paths = selection.get_selected_rows() - if paths: - self.change_image_cb(model, paths[0], 1) - - def row_actived_cb(self, tab, model, path): - self.change_image_cb(model, path, 1) - self.emit('response', gtk.RESPONSE_YES) - - def select_path_cb(self, action, parent, entry): - dialog = gtk.FileChooserDialog("", parent, - gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER) - text = entry.get_text() - dialog.set_current_folder(text if len(text) > 0 else os.getcwd()) - button = dialog.add_button("Cancel", gtk.RESPONSE_NO) - HobAltButton.style_button(button) - button = dialog.add_button("Open", gtk.RESPONSE_YES) - HobButton.style_button(button) - response = dialog.run() - if response == gtk.RESPONSE_YES: - path = dialog.get_filename() - entry.set_text(path) - self.image_folder = path - self.fill_image_store() - - dialog.destroy() - - def fill_image_store(self): - self.image_list = [] - self.image_store.clear() - imageset = set() - for root, dirs, files in os.walk(self.image_folder): - # ignore the sub directories - dirs[:] = [] - for f in files: - for image_type in self.image_types: - if image_type in self.image_extension: - real_types = self.image_extension[image_type] - else: - real_types = [image_type] - for real_image_type in real_types: - if f.endswith('.' + real_image_type): - imageset.add(f.rsplit('.' + real_image_type)[0].rsplit('.rootfs')[0]) - self.image_list.append(f) - - for image in imageset: - self.image_store.set(self.image_store.append(), 0, image, 1, False) - - self.image_table.set_model(self.image_store) - - def response_cb(self, dialog, response_id): - self.image_names = [] - if response_id == gtk.RESPONSE_YES: - iter = self.image_store.get_iter_first() - while iter: - path = self.image_store.get_path(iter) - if self.image_store[path][1]: - for f in self.image_list: - if f.startswith(self.image_store[path][0] + '.'): - self.image_names.append(f) - break - iter = self.image_store.iter_next(iter) diff --git a/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/hig/layerselectiondialog.py b/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/hig/layerselectiondialog.py deleted file mode 100644 index 52d57b673..000000000 --- a/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/hig/layerselectiondialog.py +++ /dev/null @@ -1,298 +0,0 @@ -# -# BitBake Graphical GTK User Interface -# -# Copyright (C) 2011-2012 Intel Corporation -# -# Authored by Joshua Lock <josh@linux.intel.com> -# Authored by Dongxiao Xu <dongxiao.xu@intel.com> -# Authored by Shane Wang <shane.wang@intel.com> -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 as -# published by the Free Software Foundation. -# -# 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, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -import gtk -import gobject -import os -import tempfile -from bb.ui.crumbs.hobwidget import hic, HobButton, HobAltButton -from bb.ui.crumbs.hig.crumbsdialog import CrumbsDialog -from bb.ui.crumbs.hig.crumbsmessagedialog import CrumbsMessageDialog - -""" -The following are convenience classes for implementing GNOME HIG compliant -BitBake GUI's -In summary: spacing = 12px, border-width = 6px -""" - -class CellRendererPixbufActivatable(gtk.CellRendererPixbuf): - """ - A custom CellRenderer implementation which is activatable - so that we can handle user clicks - """ - __gsignals__ = { 'clicked' : (gobject.SIGNAL_RUN_LAST, - gobject.TYPE_NONE, - (gobject.TYPE_STRING,)), } - - def __init__(self): - gtk.CellRendererPixbuf.__init__(self) - self.set_property('mode', gtk.CELL_RENDERER_MODE_ACTIVATABLE) - self.set_property('follow-state', True) - - """ - Respond to a user click on a cell - """ - def do_activate(self, even, widget, path, background_area, cell_area, flags): - self.emit('clicked', path) - -# -# LayerSelectionDialog -# -class LayerSelectionDialog (CrumbsDialog): - - TARGETS = [ - ("MY_TREE_MODEL_ROW", gtk.TARGET_SAME_WIDGET, 0), - ("text/plain", 0, 1), - ("TEXT", 0, 2), - ("STRING", 0, 3), - ] - - def gen_label_widget(self, content): - label = gtk.Label() - label.set_alignment(0, 0) - label.set_markup(content) - label.show() - return label - - def layer_widget_toggled_cb(self, cell, path, layer_store): - name = layer_store[path][0] - toggle = not layer_store[path][1] - layer_store[path][1] = toggle - - def layer_widget_add_clicked_cb(self, action, layer_store, parent): - dialog = gtk.FileChooserDialog("Add new layer", parent, - gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER) - button = dialog.add_button("Cancel", gtk.RESPONSE_NO) - HobAltButton.style_button(button) - button = dialog.add_button("Open", gtk.RESPONSE_YES) - HobButton.style_button(button) - label = gtk.Label("Select the layer you wish to add") - label.show() - dialog.set_extra_widget(label) - response = dialog.run() - path = dialog.get_filename() - dialog.destroy() - - lbl = "<b>Error</b>" - msg = "Unable to load layer <i>%s</i> because " % path - if response == gtk.RESPONSE_YES: - import os - import os.path - layers = [] - it = layer_store.get_iter_first() - while it: - layers.append(layer_store.get_value(it, 0)) - it = layer_store.iter_next(it) - - if not path: - msg += "it is an invalid path." - elif not os.path.exists(path+"/conf/layer.conf"): - msg += "there is no layer.conf inside the directory." - elif path in layers: - msg += "it is already in loaded layers." - else: - layer_store.append([path]) - return - dialog = CrumbsMessageDialog(parent, lbl, gtk.MESSAGE_ERROR, msg) - dialog.add_button(gtk.STOCK_CLOSE, gtk.RESPONSE_OK) - response = dialog.run() - dialog.destroy() - - def layer_widget_del_clicked_cb(self, action, tree_selection, layer_store): - model, iter = tree_selection.get_selected() - if iter: - layer_store.remove(iter) - - - def gen_layer_widget(self, layers, layers_avail, window, tooltip=""): - hbox = gtk.HBox(False, 6) - - layer_tv = gtk.TreeView() - layer_tv.set_rules_hint(True) - layer_tv.set_headers_visible(False) - tree_selection = layer_tv.get_selection() - tree_selection.set_mode(gtk.SELECTION_SINGLE) - - # Allow enable drag and drop of rows including row move - dnd_internal_target = '' - dnd_targets = [(dnd_internal_target, gtk.TARGET_SAME_WIDGET, 0)] - layer_tv.enable_model_drag_source( gtk.gdk.BUTTON1_MASK, - dnd_targets, - gtk.gdk.ACTION_MOVE) - layer_tv.enable_model_drag_dest(dnd_targets, - gtk.gdk.ACTION_MOVE) - layer_tv.connect("drag_data_get", self.drag_data_get_cb) - layer_tv.connect("drag_data_received", self.drag_data_received_cb) - - col0= gtk.TreeViewColumn('Path') - cell0 = gtk.CellRendererText() - cell0.set_padding(5,2) - col0.pack_start(cell0, True) - col0.set_cell_data_func(cell0, self.draw_layer_path_cb) - layer_tv.append_column(col0) - - scroll = gtk.ScrolledWindow() - scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) - scroll.set_shadow_type(gtk.SHADOW_IN) - scroll.add(layer_tv) - - table_layer = gtk.Table(2, 10, False) - hbox.pack_start(table_layer, expand=True, fill=True) - - table_layer.attach(scroll, 0, 10, 0, 1) - - layer_store = gtk.ListStore(gobject.TYPE_STRING) - for layer in layers: - layer_store.append([layer]) - - col1 = gtk.TreeViewColumn('Enabled') - layer_tv.append_column(col1) - - cell1 = CellRendererPixbufActivatable() - cell1.set_fixed_size(-1,35) - cell1.connect("clicked", self.del_cell_clicked_cb, layer_store) - col1.pack_start(cell1, True) - col1.set_cell_data_func(cell1, self.draw_delete_button_cb, layer_tv) - - add_button = gtk.Button() - add_button.set_relief(gtk.RELIEF_NONE) - box = gtk.HBox(False, 6) - box.show() - add_button.add(box) - add_button.connect("enter-notify-event", self.add_hover_cb) - add_button.connect("leave-notify-event", self.add_leave_cb) - self.im = gtk.Image() - self.im.set_from_file(hic.ICON_INDI_ADD_FILE) - self.im.show() - box.pack_start(self.im, expand=False, fill=False, padding=6) - lbl = gtk.Label("Add layer") - lbl.set_alignment(0.0, 0.5) - lbl.show() - box.pack_start(lbl, expand=True, fill=True, padding=6) - add_button.connect("clicked", self.layer_widget_add_clicked_cb, layer_store, window) - table_layer.attach(add_button, 0, 10, 1, 2, gtk.EXPAND | gtk.FILL, 0, 0, 6) - layer_tv.set_model(layer_store) - - hbox.show_all() - - return hbox, layer_store - - def drag_data_get_cb(self, treeview, context, selection, target_id, etime): - treeselection = treeview.get_selection() - model, iter = treeselection.get_selected() - data = model.get_value(iter, 0) - selection.set(selection.target, 8, data) - - def drag_data_received_cb(self, treeview, context, x, y, selection, info, etime): - model = treeview.get_model() - data = selection.data - drop_info = treeview.get_dest_row_at_pos(x, y) - if drop_info: - path, position = drop_info - iter = model.get_iter(path) - if (position == gtk.TREE_VIEW_DROP_BEFORE or position == gtk.TREE_VIEW_DROP_INTO_OR_BEFORE): - model.insert_before(iter, [data]) - else: - model.insert_after(iter, [data]) - else: - model.append([data]) - if context.action == gtk.gdk.ACTION_MOVE: - context.finish(True, True, etime) - return - - def add_hover_cb(self, button, event): - self.im.set_from_file(hic.ICON_INDI_ADD_HOVER_FILE) - - def add_leave_cb(self, button, event): - self.im.set_from_file(hic.ICON_INDI_ADD_FILE) - - def __init__(self, title, layers, layers_non_removable, all_layers, parent, flags, buttons=None): - super(LayerSelectionDialog, self).__init__(title, parent, flags, buttons) - - # class members from other objects - self.layers = layers - self.layers_non_removable = layers_non_removable - self.all_layers = all_layers - self.layers_changed = False - - # icon for remove button in TreeView - im = gtk.Image() - im.set_from_file(hic.ICON_INDI_REMOVE_FILE) - self.rem_icon = im.get_pixbuf() - - # class members for internal use - self.layer_store = None - - # create visual elements on the dialog - self.create_visual_elements() - self.connect("response", self.response_cb) - - def create_visual_elements(self): - layer_widget, self.layer_store = self.gen_layer_widget(self.layers, self.all_layers, self, None) - layer_widget.set_size_request(450, 250) - self.vbox.pack_start(layer_widget, expand=True, fill=True) - self.show_all() - - def response_cb(self, dialog, response_id): - model = self.layer_store - it = model.get_iter_first() - layers = [] - while it: - layers.append(model.get_value(it, 0)) - it = model.iter_next(it) - - self.layers_changed = (self.layers != layers) - self.layers = layers - - """ - A custom cell_data_func to draw a delete 'button' in the TreeView for layers - other than the meta layer. The deletion of which is prevented so that the - user can't shoot themselves in the foot too badly. - """ - def draw_delete_button_cb(self, col, cell, model, it, tv): - path = model.get_value(it, 0) - if path in self.layers_non_removable: - cell.set_sensitive(False) - cell.set_property('pixbuf', None) - cell.set_property('mode', gtk.CELL_RENDERER_MODE_INERT) - else: - cell.set_property('pixbuf', self.rem_icon) - cell.set_sensitive(True) - cell.set_property('mode', gtk.CELL_RENDERER_MODE_ACTIVATABLE) - - return True - - """ - A custom cell_data_func to write an extra message into the layer path cell - for the meta layer. We should inform the user that they can't remove it for - their own safety. - """ - def draw_layer_path_cb(self, col, cell, model, it): - path = model.get_value(it, 0) - if path in self.layers_non_removable: - cell.set_property('markup', "<b>It cannot be removed</b>\n%s" % path) - else: - cell.set_property('text', path) - - def del_cell_clicked_cb(self, cell, path, model): - it = model.get_iter_from_string(path) - model.remove(it) diff --git a/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/hig/propertydialog.py b/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/hig/propertydialog.py deleted file mode 100644 index 09b9ce6de..000000000 --- a/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/hig/propertydialog.py +++ /dev/null @@ -1,437 +0,0 @@ -# -# BitBake Graphical GTK User Interface -# -# Copyright (C) 2011-2013 Intel Corporation -# -# Authored by Andrei Dinu <andrei.adrianx.dinu@intel.com> -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 as -# published by the Free Software Foundation. -# -# 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, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -import string -import gtk -import gobject -import os -import tempfile -import glib -from bb.ui.crumbs.hig.crumbsdialog import CrumbsDialog -from bb.ui.crumbs.hig.settingsuihelper import SettingsUIHelper -from bb.ui.crumbs.hig.crumbsmessagedialog import CrumbsMessageDialog -from bb.ui.crumbs.hig.layerselectiondialog import LayerSelectionDialog - -""" -The following are convenience classes for implementing GNOME HIG compliant -BitBake GUI's -In summary: spacing = 12px, border-width = 6px -""" - -class PropertyDialog(CrumbsDialog): - - def __init__(self, title, parent, information, flags, buttons=None): - - super(PropertyDialog, self).__init__(title, parent, flags, buttons) - - self.properties = information - - if len(self.properties) == 10: - self.create_recipe_visual_elements() - elif len(self.properties) == 5: - self.create_package_visual_elements() - else: - self.create_information_visual_elements() - - - def create_information_visual_elements(self): - - HOB_ICON_BASE_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), ("icons/")) - ICON_PACKAGES_DISPLAY_FILE = os.path.join(HOB_ICON_BASE_DIR, ('info/info_display.png')) - - self.set_resizable(False) - - self.table = gtk.Table(1,1,False) - self.table.set_row_spacings(0) - self.table.set_col_spacings(0) - - self.image = gtk.Image() - self.image.set_from_file(ICON_PACKAGES_DISPLAY_FILE) - self.image.set_property("xalign",0) - #self.vbox.add(self.image) - - image_info = self.properties.split("*")[0] - info = self.properties.split("*")[1] - - vbox = gtk.VBox(True, spacing=30) - - self.label_short = gtk.Label() - self.label_short.set_line_wrap(False) - self.label_short.set_markup(image_info) - self.label_short.set_property("xalign", 0) - - self.info_label = gtk.Label() - self.info_label.set_line_wrap(True) - self.info_label.set_markup(info) - self.info_label.set_property("yalign", 0.5) - - self.table.attach(self.image, 0,1,0,1, xoptions=gtk.FILL|gtk.EXPAND, yoptions=gtk.FILL,xpadding=5,ypadding=5) - self.table.attach(self.label_short, 0,1,0,1, xoptions=gtk.FILL|gtk.EXPAND, yoptions=gtk.FILL,xpadding=40,ypadding=5) - self.table.attach(self.info_label, 0,1,1,2, xoptions=gtk.FILL|gtk.EXPAND, yoptions=gtk.FILL,xpadding=40,ypadding=10) - - self.vbox.add(self.table) - self.connect('delete-event', lambda w, e: self.destroy() or True) - - def treeViewTooltip( self, widget, e, tooltips, cell, emptyText="" ): - try: - (path,col,x,y) = widget.get_path_at_pos( int(e.x), int(e.y) ) - it = widget.get_model().get_iter(path) - value = widget.get_model().get_value(it,cell) - if value in self.tooltip_items: - tooltips.set_tip(widget, self.tooltip_items[value]) - tooltips.enable() - else: - tooltips.set_tip(widget, emptyText) - except: - tooltips.set_tip(widget, emptyText) - - - def create_package_visual_elements(self): - - import json - - name = self.properties['name'] - binb = self.properties['binb'] - size = self.properties['size'] - recipe = self.properties['recipe'] - file_list = json.loads(self.properties['files_list']) - - files_temp = '' - paths_temp = '' - files_binb = [] - paths_binb = [] - - self.tooltip_items = {} - - self.set_resizable(False) - - #cleaning out the recipe variable - recipe = recipe.split("+")[0] - - vbox = gtk.VBox(True,spacing = 0) - - ###################################### NAME ROW + COL ################################# - - self.label_short = gtk.Label() - self.label_short.set_size_request(300,-1) - self.label_short.set_selectable(True) - self.label_short.set_line_wrap(True) - self.label_short.set_markup("<span weight=\"bold\">Name: </span>" + name) - self.label_short.set_property("xalign", 0) - - self.vbox.add(self.label_short) - - ###################################### SIZE ROW + COL ###################################### - - self.label_short = gtk.Label() - self.label_short.set_size_request(300,-1) - self.label_short.set_selectable(True) - self.label_short.set_line_wrap(True) - self.label_short.set_markup("<span weight=\"bold\">Size: </span>" + size) - self.label_short.set_property("xalign", 0) - - self.vbox.add(self.label_short) - - ##################################### RECIPE ROW + COL ######################################### - - self.label_short = gtk.Label() - self.label_short.set_size_request(300,-1) - self.label_short.set_selectable(True) - self.label_short.set_line_wrap(True) - self.label_short.set_markup("<span weight=\"bold\">Recipe: </span>" + recipe) - self.label_short.set_property("xalign", 0) - - self.vbox.add(self.label_short) - - ##################################### BINB ROW + COL ####################################### - - if binb != '': - self.label_short = gtk.Label() - self.label_short.set_selectable(True) - self.label_short.set_line_wrap(True) - self.label_short.set_markup("<span weight=\"bold\">Brought in by: </span>") - self.label_short.set_property("xalign", 0) - - self.label_info = gtk.Label() - self.label_info.set_size_request(300,-1) - self.label_info.set_selectable(True) - self.label_info.set_line_wrap(True) - self.label_info.set_markup(binb) - self.label_info.set_property("xalign", 0) - - self.vbox.add(self.label_short) - self.vbox.add(self.label_info) - - #################################### FILES BROUGHT BY PACKAGES ################################### - - if file_list: - - self.textWindow = gtk.ScrolledWindow() - self.textWindow.set_shadow_type(gtk.SHADOW_IN) - self.textWindow.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) - self.textWindow.set_size_request(100, 170) - - packagefiles_store = gtk.ListStore(str) - - self.packagefiles_tv = gtk.TreeView() - self.packagefiles_tv.set_rules_hint(True) - self.packagefiles_tv.set_headers_visible(True) - self.textWindow.add(self.packagefiles_tv) - - self.cell1 = gtk.CellRendererText() - col1 = gtk.TreeViewColumn('Package files', self.cell1) - col1.set_cell_data_func(self.cell1, self.regex_field) - self.packagefiles_tv.append_column(col1) - - items = file_list.keys() - items.sort() - for item in items: - fullpath = item - while len(item) > 35: - item = item[:len(item)/2] + "" + item[len(item)/2+1:] - if len(item) == 35: - item = item[:len(item)/2] + "..." + item[len(item)/2+3:] - self.tooltip_items[item] = fullpath - - packagefiles_store.append([str(item)]) - - self.packagefiles_tv.set_model(packagefiles_store) - - tips = gtk.Tooltips() - tips.set_tip(self.packagefiles_tv, "") - self.packagefiles_tv.connect("motion-notify-event", self.treeViewTooltip, tips, 0) - self.packagefiles_tv.set_events(gtk.gdk.POINTER_MOTION_MASK) - - self.vbox.add(self.textWindow) - - self.vbox.show_all() - - - def regex_field(self, column, cell, model, iter): - cell.set_property('text', model.get_value(iter, 0)) - return - - - def create_recipe_visual_elements(self): - - summary = self.properties['summary'] - name = self.properties['name'] - version = self.properties['version'] - revision = self.properties['revision'] - binb = self.properties['binb'] - group = self.properties['group'] - license = self.properties['license'] - homepage = self.properties['homepage'] - bugtracker = self.properties['bugtracker'] - description = self.properties['description'] - - self.set_resizable(False) - - #cleaning out the version variable and also the summary - version = version.split(":")[1] - if len(version) > 30: - version = version.split("+")[0] - else: - version = version.split("-")[0] - license = license.replace("&" , "and") - if (homepage == ''): - homepage = 'unknown' - if (bugtracker == ''): - bugtracker = 'unknown' - summary = summary.split("+")[0] - - #calculating the rows needed for the table - binb_items_count = len(binb.split(',')) - binb_items = binb.split(',') - - vbox = gtk.VBox(False,spacing = 0) - - ######################################## SUMMARY LABEL ######################################### - - if summary != '': - self.label_short = gtk.Label() - self.label_short.set_width_chars(37) - self.label_short.set_selectable(True) - self.label_short.set_line_wrap(True) - self.label_short.set_markup("<b>" + summary + "</b>") - self.label_short.set_property("xalign", 0) - - self.vbox.add(self.label_short) - - ########################################## NAME ROW + COL ####################################### - - self.label_short = gtk.Label() - self.label_short.set_selectable(True) - self.label_short.set_line_wrap(True) - self.label_short.set_markup("<span weight=\"bold\">Name: </span>" + name) - self.label_short.set_property("xalign", 0) - - self.vbox.add(self.label_short) - - ####################################### VERSION ROW + COL #################################### - - self.label_short = gtk.Label() - self.label_short.set_selectable(True) - self.label_short.set_line_wrap(True) - self.label_short.set_markup("<span weight=\"bold\">Version: </span>" + version) - self.label_short.set_property("xalign", 0) - - self.vbox.add(self.label_short) - - ##################################### REVISION ROW + COL ##################################### - - self.label_short = gtk.Label() - self.label_short.set_line_wrap(True) - self.label_short.set_selectable(True) - self.label_short.set_markup("<span weight=\"bold\">Revision: </span>" + revision) - self.label_short.set_property("xalign", 0) - - self.vbox.add(self.label_short) - - ################################## GROUP ROW + COL ############################################ - - self.label_short = gtk.Label() - self.label_short.set_selectable(True) - self.label_short.set_line_wrap(True) - self.label_short.set_markup("<span weight=\"bold\">Group: </span>" + group) - self.label_short.set_property("xalign", 0) - - self.vbox.add(self.label_short) - - ################################# HOMEPAGE ROW + COL ############################################ - - if homepage != 'unknown': - self.label_info = gtk.Label() - self.label_info.set_selectable(True) - self.label_info.set_line_wrap(True) - if len(homepage) > 35: - self.label_info.set_markup("<a href=\"" + homepage + "\">" + homepage[0:35] + "..." + "</a>") - else: - self.label_info.set_markup("<a href=\"" + homepage + "\">" + homepage[0:60] + "</a>") - - self.label_info.set_property("xalign", 0) - - self.label_short = gtk.Label() - self.label_short.set_selectable(True) - self.label_short.set_line_wrap(True) - self.label_short.set_markup("<b>Homepage: </b>") - self.label_short.set_property("xalign", 0) - - self.vbox.add(self.label_short) - self.vbox.add(self.label_info) - - ################################# BUGTRACKER ROW + COL ########################################### - - if bugtracker != 'unknown': - self.label_info = gtk.Label() - self.label_info.set_selectable(True) - self.label_info.set_line_wrap(True) - if len(bugtracker) > 35: - self.label_info.set_markup("<a href=\"" + bugtracker + "\">" + bugtracker[0:35] + "..." + "</a>") - else: - self.label_info.set_markup("<a href=\"" + bugtracker + "\">" + bugtracker[0:60] + "</a>") - self.label_info.set_property("xalign", 0) - - self.label_short = gtk.Label() - self.label_short.set_selectable(True) - self.label_short.set_line_wrap(True) - self.label_short.set_markup("<b>Bugtracker: </b>") - self.label_short.set_property("xalign", 0) - - self.vbox.add(self.label_short) - self.vbox.add(self.label_info) - - ################################# LICENSE ROW + COL ############################################ - - self.label_info = gtk.Label() - self.label_info.set_selectable(True) - self.label_info.set_line_wrap(True) - self.label_info.set_markup(license) - self.label_info.set_property("xalign", 0) - - self.label_short = gtk.Label() - self.label_short.set_selectable(True) - self.label_short.set_line_wrap(True) - self.label_short.set_markup("<span weight=\"bold\">License: </span>") - self.label_short.set_property("xalign", 0) - - self.vbox.add(self.label_short) - self.vbox.add(self.label_info) - - ################################### BINB ROW+COL ############################################# - - if binb != '': - self.label_short = gtk.Label() - self.label_short.set_selectable(True) - self.label_short.set_line_wrap(True) - self.label_short.set_markup("<span weight=\"bold\">Brought in by: </span>") - self.label_short.set_property("xalign", 0) - self.vbox.add(self.label_short) - self.label_info = gtk.Label() - self.label_info.set_selectable(True) - self.label_info.set_width_chars(36) - if len(binb) > 200: - scrolled_window = gtk.ScrolledWindow() - scrolled_window.set_policy(gtk.POLICY_NEVER,gtk.POLICY_ALWAYS) - scrolled_window.set_size_request(100,100) - self.label_info.set_markup(binb) - self.label_info.set_padding(6,6) - self.label_info.set_alignment(0,0) - self.label_info.set_line_wrap(True) - scrolled_window.add_with_viewport(self.label_info) - self.vbox.add(scrolled_window) - else: - self.label_info.set_markup(binb) - self.label_info.set_property("xalign", 0) - self.label_info.set_line_wrap(True) - self.vbox.add(self.label_info) - - ################################ DESCRIPTION TAG ROW ################################################# - - self.label_short = gtk.Label() - self.label_short.set_line_wrap(True) - self.label_short.set_markup("<span weight=\"bold\">Description </span>") - self.label_short.set_property("xalign", 0) - self.vbox.add(self.label_short) - - ################################ DESCRIPTION INFORMATION ROW ########################################## - - hbox = gtk.HBox(True,spacing = 0) - - self.label_short = gtk.Label() - self.label_short.set_selectable(True) - self.label_short.set_width_chars(36) - if len(description) > 200: - scrolled_window = gtk.ScrolledWindow() - scrolled_window.set_policy(gtk.POLICY_NEVER,gtk.POLICY_ALWAYS) - scrolled_window.set_size_request(100,100) - self.label_short.set_markup(description) - self.label_short.set_padding(6,6) - self.label_short.set_alignment(0,0) - self.label_short.set_line_wrap(True) - scrolled_window.add_with_viewport(self.label_short) - self.vbox.add(scrolled_window) - else: - self.label_short.set_markup(description) - self.label_short.set_property("xalign", 0) - self.label_short.set_line_wrap(True) - self.vbox.add(self.label_short) - - self.vbox.show_all() diff --git a/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/hig/settingsuihelper.py b/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/hig/settingsuihelper.py deleted file mode 100644 index e0285c93c..000000000 --- a/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/hig/settingsuihelper.py +++ /dev/null @@ -1,122 +0,0 @@ -# -# BitBake Graphical GTK User Interface -# -# Copyright (C) 2011-2012 Intel Corporation -# -# Authored by Joshua Lock <josh@linux.intel.com> -# Authored by Dongxiao Xu <dongxiao.xu@intel.com> -# Authored by Shane Wang <shane.wang@intel.com> -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 as -# published by the Free Software Foundation. -# -# 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, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -import gtk -import os -from bb.ui.crumbs.hobwidget import HobInfoButton, HobButton, HobAltButton - -""" -The following are convenience classes for implementing GNOME HIG compliant -BitBake GUI's -In summary: spacing = 12px, border-width = 6px -""" - -class SettingsUIHelper(): - - def gen_label_widget(self, content): - label = gtk.Label() - label.set_alignment(0, 0) - label.set_markup(content) - label.show() - return label - - def gen_label_info_widget(self, content, tooltip): - table = gtk.Table(1, 10, False) - label = self.gen_label_widget(content) - info = HobInfoButton(tooltip, self) - table.attach(label, 0, 1, 0, 1, xoptions=gtk.FILL) - table.attach(info, 1, 2, 0, 1, xoptions=gtk.FILL, xpadding=10) - return table - - def gen_spinner_widget(self, content, lower, upper, tooltip=""): - hbox = gtk.HBox(False, 12) - adjust = gtk.Adjustment(value=content, lower=lower, upper=upper, step_incr=1) - spinner = gtk.SpinButton(adjustment=adjust, climb_rate=1, digits=0) - - spinner.set_value(content) - hbox.pack_start(spinner, expand=False, fill=False) - - info = HobInfoButton(tooltip, self) - hbox.pack_start(info, expand=False, fill=False) - - hbox.show_all() - return hbox, spinner - - def gen_combo_widget(self, curr_item, all_item, tooltip=""): - hbox = gtk.HBox(False, 12) - combo = gtk.combo_box_new_text() - hbox.pack_start(combo, expand=False, fill=False) - - index = 0 - for item in all_item or []: - combo.append_text(item) - if item == curr_item: - combo.set_active(index) - index += 1 - - info = HobInfoButton(tooltip, self) - hbox.pack_start(info, expand=False, fill=False) - - hbox.show_all() - return hbox, combo - - def entry_widget_select_path_cb(self, action, parent, entry): - dialog = gtk.FileChooserDialog("", parent, - gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER) - text = entry.get_text() - dialog.set_current_folder(text if len(text) > 0 else os.getcwd()) - button = dialog.add_button("Cancel", gtk.RESPONSE_NO) - HobAltButton.style_button(button) - button = dialog.add_button("Open", gtk.RESPONSE_YES) - HobButton.style_button(button) - response = dialog.run() - if response == gtk.RESPONSE_YES: - path = dialog.get_filename() - entry.set_text(path) - - dialog.destroy() - - def gen_entry_widget(self, content, parent, tooltip="", need_button=True): - hbox = gtk.HBox(False, 12) - entry = gtk.Entry() - entry.set_text(content) - entry.set_size_request(350,30) - - if need_button: - table = gtk.Table(1, 10, False) - hbox.pack_start(table, expand=True, fill=True) - table.attach(entry, 0, 9, 0, 1, xoptions=gtk.SHRINK) - image = gtk.Image() - image.set_from_stock(gtk.STOCK_OPEN,gtk.ICON_SIZE_BUTTON) - open_button = gtk.Button() - open_button.set_image(image) - open_button.connect("clicked", self.entry_widget_select_path_cb, parent, entry) - table.attach(open_button, 9, 10, 0, 1, xoptions=gtk.SHRINK) - else: - hbox.pack_start(entry, expand=True, fill=True) - - if tooltip != "": - info = HobInfoButton(tooltip, self) - hbox.pack_start(info, expand=False, fill=False) - - hbox.show_all() - return hbox, entry diff --git a/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/hobwidget.py b/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/hobwidget.py deleted file mode 100644 index 2b969c146..000000000 --- a/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/hobwidget.py +++ /dev/null @@ -1,904 +0,0 @@ -# BitBake Graphical GTK User Interface -# -# Copyright (C) 2011-2012 Intel Corporation -# -# Authored by Dongxiao Xu <dongxiao.xu@intel.com> -# Authored by Shane Wang <shane.wang@intel.com> -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 as -# published by the Free Software Foundation. -# -# 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, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -import gtk -import gobject -import os -import os.path -import sys -import pango, pangocairo -import cairo -import math - -from bb.ui.crumbs.hobcolor import HobColors -from bb.ui.crumbs.persistenttooltip import PersistentTooltip - -class hwc: - - MAIN_WIN_WIDTH = 1024 - MAIN_WIN_HEIGHT = 700 - -class hic: - - HOB_ICON_BASE_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), ("ui/icons/")) - - ICON_RCIPE_DISPLAY_FILE = os.path.join(HOB_ICON_BASE_DIR, ('recipe/recipe_display.png')) - ICON_RCIPE_HOVER_FILE = os.path.join(HOB_ICON_BASE_DIR, ('recipe/recipe_hover.png')) - ICON_PACKAGES_DISPLAY_FILE = os.path.join(HOB_ICON_BASE_DIR, ('packages/packages_display.png')) - ICON_PACKAGES_HOVER_FILE = os.path.join(HOB_ICON_BASE_DIR, ('packages/packages_hover.png')) - ICON_LAYERS_DISPLAY_FILE = os.path.join(HOB_ICON_BASE_DIR, ('layers/layers_display.png')) - ICON_LAYERS_HOVER_FILE = os.path.join(HOB_ICON_BASE_DIR, ('layers/layers_hover.png')) - ICON_IMAGES_DISPLAY_FILE = os.path.join(HOB_ICON_BASE_DIR, ('images/images_display.png')) - ICON_IMAGES_HOVER_FILE = os.path.join(HOB_ICON_BASE_DIR, ('images/images_hover.png')) - ICON_SETTINGS_DISPLAY_FILE = os.path.join(HOB_ICON_BASE_DIR, ('settings/settings_display.png')) - ICON_SETTINGS_HOVER_FILE = os.path.join(HOB_ICON_BASE_DIR, ('settings/settings_hover.png')) - ICON_INFO_DISPLAY_FILE = os.path.join(HOB_ICON_BASE_DIR, ('info/info_display.png')) - ICON_INFO_HOVER_FILE = os.path.join(HOB_ICON_BASE_DIR, ('info/info_hover.png')) - ICON_INDI_CONFIRM_FILE = os.path.join(HOB_ICON_BASE_DIR, ('indicators/confirmation.png')) - ICON_INDI_ERROR_FILE = os.path.join(HOB_ICON_BASE_DIR, ('indicators/denied.png')) - ICON_INDI_REMOVE_FILE = os.path.join(HOB_ICON_BASE_DIR, ('indicators/remove.png')) - ICON_INDI_REMOVE_HOVER_FILE = os.path.join(HOB_ICON_BASE_DIR, ('indicators/remove-hover.png')) - ICON_INDI_ADD_FILE = os.path.join(HOB_ICON_BASE_DIR, ('indicators/add.png')) - ICON_INDI_ADD_HOVER_FILE = os.path.join(HOB_ICON_BASE_DIR, ('indicators/add-hover.png')) - ICON_INDI_REFRESH_FILE = os.path.join(HOB_ICON_BASE_DIR, ('indicators/refresh.png')) - ICON_INDI_ALERT_FILE = os.path.join(HOB_ICON_BASE_DIR, ('indicators/alert.png')) - ICON_INDI_TICK_FILE = os.path.join(HOB_ICON_BASE_DIR, ('indicators/tick.png')) - ICON_INDI_INFO_FILE = os.path.join(HOB_ICON_BASE_DIR, ('indicators/info.png')) - -class HobViewTable (gtk.VBox): - """ - A VBox to contain the table for different recipe views and package view - """ - __gsignals__ = { - "toggled" : (gobject.SIGNAL_RUN_LAST, - gobject.TYPE_NONE, - (gobject.TYPE_PYOBJECT, - gobject.TYPE_STRING, - gobject.TYPE_INT, - gobject.TYPE_PYOBJECT,)), - "row-activated" : (gobject.SIGNAL_RUN_LAST, - gobject.TYPE_NONE, - (gobject.TYPE_PYOBJECT, - gobject.TYPE_PYOBJECT,)), - "cell-fadeinout-stopped" : (gobject.SIGNAL_RUN_LAST, - gobject.TYPE_NONE, - (gobject.TYPE_PYOBJECT, - gobject.TYPE_PYOBJECT, - gobject.TYPE_PYOBJECT,)), - } - - def __init__(self, columns, name): - gtk.VBox.__init__(self, False, 6) - self.table_tree = gtk.TreeView() - self.table_tree.set_headers_visible(True) - self.table_tree.set_headers_clickable(True) - self.table_tree.set_rules_hint(True) - self.table_tree.set_enable_tree_lines(True) - self.table_tree.get_selection().set_mode(gtk.SELECTION_SINGLE) - self.toggle_columns = [] - self.table_tree.connect("row-activated", self.row_activated_cb) - self.top_bar = None - self.tab_name = name - - for i, column in enumerate(columns): - col_name = column['col_name'] - col = gtk.TreeViewColumn(col_name) - col.set_clickable(True) - col.set_resizable(True) - if self.tab_name.startswith('Included'): - if col_name!='Included': - col.set_sort_column_id(column['col_id']) - else: - col.set_sort_column_id(column['col_id']) - if 'col_min' in column.keys(): - col.set_min_width(column['col_min']) - if 'col_max' in column.keys(): - col.set_max_width(column['col_max']) - if 'expand' in column.keys(): - col.set_expand(True) - self.table_tree.append_column(col) - - if (not 'col_style' in column.keys()) or column['col_style'] == 'text': - cell = gtk.CellRendererText() - col.pack_start(cell, True) - col.set_attributes(cell, text=column['col_id']) - if 'col_t_id' in column.keys(): - col.add_attribute(cell, 'font', column['col_t_id']) - elif column['col_style'] == 'check toggle': - cell = HobCellRendererToggle() - cell.set_property('activatable', True) - cell.connect("toggled", self.toggled_cb, i, self.table_tree) - cell.connect_render_state_changed(self.stop_cell_fadeinout_cb, self.table_tree) - self.toggle_id = i - col.pack_end(cell, True) - col.set_attributes(cell, active=column['col_id']) - self.toggle_columns.append(col_name) - if 'col_group' in column.keys(): - col.set_cell_data_func(cell, self.set_group_number_cb) - elif column['col_style'] == 'radio toggle': - cell = gtk.CellRendererToggle() - cell.set_property('activatable', True) - cell.set_radio(True) - cell.connect("toggled", self.toggled_cb, i, self.table_tree) - self.toggle_id = i - col.pack_end(cell, True) - col.set_attributes(cell, active=column['col_id']) - self.toggle_columns.append(col_name) - elif column['col_style'] == 'binb': - cell = gtk.CellRendererText() - col.pack_start(cell, True) - col.set_cell_data_func(cell, self.display_binb_cb, column['col_id']) - if 'col_t_id' in column.keys(): - col.add_attribute(cell, 'font', column['col_t_id']) - - self.scroll = gtk.ScrolledWindow() - self.scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) - self.scroll.add(self.table_tree) - - self.pack_end(self.scroll, True, True, 0) - - def add_no_result_bar(self, entry): - color = HobColors.KHAKI - self.top_bar = gtk.EventBox() - self.top_bar.set_size_request(-1, 70) - self.top_bar.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse(color)) - self.top_bar.set_flags(gtk.CAN_DEFAULT) - self.top_bar.grab_default() - - no_result_tab = gtk.Table(5, 20, True) - self.top_bar.add(no_result_tab) - - label = gtk.Label() - label.set_alignment(0.0, 0.5) - title = "No results matching your search" - label.set_markup("<span size='x-large'><b>%s</b></span>" % title) - no_result_tab.attach(label, 1, 14, 1, 4) - - clear_button = HobButton("Clear search") - clear_button.set_tooltip_text("Clear search query") - clear_button.connect('clicked', self.set_search_entry_clear_cb, entry) - no_result_tab.attach(clear_button, 16, 19, 1, 4) - - self.pack_start(self.top_bar, False, True, 12) - self.top_bar.show_all() - - def set_search_entry_clear_cb(self, button, search): - if search.get_editable() == True: - search.set_text("") - search.set_icon_sensitive(gtk.ENTRY_ICON_SECONDARY, False) - search.grab_focus() - - def display_binb_cb(self, col, cell, model, it, col_id): - binb = model.get_value(it, col_id) - # Just display the first item - if binb: - bin = binb.split(', ') - total_no = len(bin) - if total_no > 1 and bin[0] == "User Selected": - if total_no > 2: - present_binb = bin[1] + ' (+' + str(total_no - 1) + ')' - else: - present_binb = bin[1] - else: - if total_no > 1: - present_binb = bin[0] + ' (+' + str(total_no - 1) + ')' - else: - present_binb = bin[0] - cell.set_property('text', present_binb) - else: - cell.set_property('text', "") - return True - - def set_model(self, tree_model): - self.table_tree.set_model(tree_model) - - def toggle_default(self): - model = self.table_tree.get_model() - if not model: - return - iter = model.get_iter_first() - if iter: - rowpath = model.get_path(iter) - model[rowpath][self.toggle_id] = True - - def toggled_cb(self, cell, path, columnid, tree): - self.emit("toggled", cell, path, columnid, tree) - - def row_activated_cb(self, tree, path, view_column): - if not view_column.get_title() in self.toggle_columns: - self.emit("row-activated", tree.get_model(), path) - - def stop_cell_fadeinout_cb(self, ctrl, cell, tree): - self.emit("cell-fadeinout-stopped", ctrl, cell, tree) - - def set_group_number_cb(self, col, cell, model, iter): - if model and (model.iter_parent(iter) == None): - cell.cell_attr["number_of_children"] = model.iter_n_children(iter) - else: - cell.cell_attr["number_of_children"] = 0 - - def connect_group_selection(self, cb_func): - self.table_tree.get_selection().connect("changed", cb_func) - -""" -A method to calculate a softened value for the colour of widget when in the -provided state. - -widget: the widget whose style to use -state: the state of the widget to use the style for - -Returns a string value representing the softened colour -""" -def soften_color(widget, state=gtk.STATE_NORMAL): - # this colour munging routine is heavily inspired bu gdu_util_get_mix_color() - # from gnome-disk-utility: - # http://git.gnome.org/browse/gnome-disk-utility/tree/src/gdu-gtk/gdu-gtk.c?h=gnome-3-0 - blend = 0.7 - style = widget.get_style() - color = style.text[state] - color.red = color.red * blend + style.base[state].red * (1.0 - blend) - color.green = color.green * blend + style.base[state].green * (1.0 - blend) - color.blue = color.blue * blend + style.base[state].blue * (1.0 - blend) - return color.to_string() - -class BaseHobButton(gtk.Button): - """ - A gtk.Button subclass which follows the visual design of Hob for primary - action buttons - - label: the text to display as the button's label - """ - def __init__(self, label): - gtk.Button.__init__(self, label) - HobButton.style_button(self) - - @staticmethod - def style_button(button): - style = button.get_style() - style = gtk.rc_get_style_by_paths(gtk.settings_get_default(), 'gtk-button', 'gtk-button', gobject.TYPE_NONE) - - button.set_flags(gtk.CAN_DEFAULT) - button.grab_default() - -# label = "<span size='x-large'><b>%s</b></span>" % gobject.markup_escape_text(button.get_label()) - label = button.get_label() - button.set_label(label) - button.child.set_use_markup(True) - -class HobButton(BaseHobButton): - """ - A gtk.Button subclass which follows the visual design of Hob for primary - action buttons - - label: the text to display as the button's label - """ - def __init__(self, label): - BaseHobButton.__init__(self, label) - HobButton.style_button(self) - -class HobAltButton(BaseHobButton): - """ - A gtk.Button subclass which has no relief, and so is more discrete - """ - def __init__(self, label): - BaseHobButton.__init__(self, label) - HobAltButton.style_button(self) - - """ - A callback for the state-changed event to ensure the text is displayed - differently when the widget is not sensitive - """ - @staticmethod - def desensitise_on_state_change_cb(button, state): - if not button.get_property("sensitive"): - HobAltButton.set_text(button, False) - else: - HobAltButton.set_text(button, True) - - """ - Set the button label with an appropriate colour for the current widget state - """ - @staticmethod - def set_text(button, sensitive=True): - if sensitive: - colour = HobColors.PALE_BLUE - else: - colour = HobColors.LIGHT_GRAY - button.set_label("<span size='large' color='%s'><b>%s</b></span>" % (colour, gobject.markup_escape_text(button.text))) - button.child.set_use_markup(True) - -class HobImageButton(gtk.Button): - """ - A gtk.Button with an icon and two rows of text, the second of which is - displayed in a blended colour. - - primary_text: the main button label - secondary_text: optional second line of text - icon_path: path to the icon file to display on the button - """ - def __init__(self, primary_text, secondary_text="", icon_path="", hover_icon_path=""): - gtk.Button.__init__(self) - self.set_relief(gtk.RELIEF_NONE) - - self.icon_path = icon_path - self.hover_icon_path = hover_icon_path - - hbox = gtk.HBox(False, 10) - hbox.show() - self.add(hbox) - self.icon = gtk.Image() - self.icon.set_from_file(self.icon_path) - self.icon.set_alignment(0.5, 0.0) - self.icon.show() - if self.hover_icon_path and len(self.hover_icon_path): - self.connect("enter-notify-event", self.set_hover_icon_cb) - self.connect("leave-notify-event", self.set_icon_cb) - hbox.pack_start(self.icon, False, False, 0) - label = gtk.Label() - label.set_alignment(0.0, 0.5) - colour = soften_color(label) - mark = "<span size='x-large'>%s</span>\n<span size='medium' fgcolor='%s' weight='ultralight'>%s</span>" % (primary_text, colour, secondary_text) - label.set_markup(mark) - label.show() - hbox.pack_start(label, True, True, 0) - - def set_hover_icon_cb(self, widget, event): - self.icon.set_from_file(self.hover_icon_path) - - def set_icon_cb(self, widget, event): - self.icon.set_from_file(self.icon_path) - -class HobInfoButton(gtk.EventBox): - """ - This class implements a button-like widget per the Hob visual and UX designs - which will display a persistent tooltip, with the contents of tip_markup, when - clicked. - - tip_markup: the Pango Markup to be displayed in the persistent tooltip - """ - def __init__(self, tip_markup, parent=None): - gtk.EventBox.__init__(self) - self.image = gtk.Image() - self.image.set_from_file( - hic.ICON_INFO_DISPLAY_FILE) - self.image.show() - self.add(self.image) - self.tip_markup = tip_markup - self.my_parent = parent - - self.set_events(gtk.gdk.BUTTON_RELEASE | - gtk.gdk.ENTER_NOTIFY_MASK | - gtk.gdk.LEAVE_NOTIFY_MASK) - - self.connect("button-release-event", self.button_release_cb) - self.connect("enter-notify-event", self.mouse_in_cb) - self.connect("leave-notify-event", self.mouse_out_cb) - - """ - When the mouse click is released emulate a button-click and show the associated - PersistentTooltip - """ - def button_release_cb(self, widget, event): - from bb.ui.crumbs.hig.propertydialog import PropertyDialog - self.dialog = PropertyDialog(title = '', - parent = self.my_parent, - information = self.tip_markup, - flags = gtk.DIALOG_DESTROY_WITH_PARENT - | gtk.DIALOG_NO_SEPARATOR) - - button = self.dialog.add_button("Close", gtk.RESPONSE_CANCEL) - HobAltButton.style_button(button) - button.connect("clicked", lambda w: self.dialog.destroy()) - self.dialog.show_all() - self.dialog.run() - - """ - Change to the prelight image when the mouse enters the widget - """ - def mouse_in_cb(self, widget, event): - self.image.set_from_file(hic.ICON_INFO_HOVER_FILE) - - """ - Change to the stock image when the mouse enters the widget - """ - def mouse_out_cb(self, widget, event): - self.image.set_from_file(hic.ICON_INFO_DISPLAY_FILE) - -class HobIndicator(gtk.DrawingArea): - def __init__(self, count): - gtk.DrawingArea.__init__(self) - # Set no window for transparent background - self.set_has_window(False) - self.set_size_request(38,38) - # We need to pass through button clicks - self.add_events(gtk.gdk.BUTTON_PRESS_MASK | gtk.gdk.BUTTON_RELEASE_MASK) - - self.connect('expose-event', self.expose) - - self.count = count - self.color = HobColors.GRAY - - def expose(self, widget, event): - if self.count and self.count > 0: - ctx = widget.window.cairo_create() - - x, y, w, h = self.allocation - - ctx.set_operator(cairo.OPERATOR_OVER) - ctx.set_source_color(gtk.gdk.color_parse(self.color)) - ctx.translate(w/2, h/2) - ctx.arc(x, y, min(w,h)/2 - 2, 0, 2*math.pi) - ctx.fill_preserve() - - layout = self.create_pango_layout(str(self.count)) - textw, texth = layout.get_pixel_size() - x = (w/2)-(textw/2) + x - y = (h/2) - (texth/2) + y - ctx.move_to(x, y) - self.window.draw_layout(self.style.light_gc[gtk.STATE_NORMAL], int(x), int(y), layout) - - def set_count(self, count): - self.count = count - - def set_active(self, active): - if active: - self.color = HobColors.DEEP_RED - else: - self.color = HobColors.GRAY - -class HobTabLabel(gtk.HBox): - def __init__(self, text, count=0): - gtk.HBox.__init__(self, False, 0) - self.indicator = HobIndicator(count) - self.indicator.show() - self.pack_end(self.indicator, False, False) - self.lbl = gtk.Label(text) - self.lbl.set_alignment(0.0, 0.5) - self.lbl.show() - self.pack_end(self.lbl, True, True, 6) - - def set_count(self, count): - self.indicator.set_count(count) - - def set_active(self, active=True): - self.indicator.set_active(active) - -class HobNotebook(gtk.Notebook): - def __init__(self): - gtk.Notebook.__init__(self) - self.set_property('homogeneous', True) - - self.pages = [] - - self.search = None - self.search_focus = False - self.page_changed = False - - self.connect("switch-page", self.page_changed_cb) - - self.show_all() - - def page_changed_cb(self, nb, page, page_num): - for p, lbl in enumerate(self.pages): - if p == page_num: - lbl.set_active() - else: - lbl.set_active(False) - - if self.search: - self.page_changed = True - self.reset_entry(self.search, page_num) - - def append_page(self, child, tab_label, tab_tooltip=None): - label = HobTabLabel(tab_label) - if tab_tooltip: - label.set_tooltip_text(tab_tooltip) - label.set_active(False) - self.pages.append(label) - gtk.Notebook.append_page(self, child, label) - - def set_entry(self, names, tips): - self.search = gtk.Entry() - self.search_names = names - self.search_tips = tips - style = self.search.get_style() - style.text[gtk.STATE_NORMAL] = self.get_colormap().alloc_color(HobColors.GRAY, False, False) - self.search.set_style(style) - self.search.set_text(names[0]) - self.search.set_tooltip_text(self.search_tips[0]) - self.search.props.has_tooltip = True - - self.search.set_editable(False) - self.search.set_icon_from_stock(gtk.ENTRY_ICON_SECONDARY, gtk.STOCK_CLEAR) - self.search.set_icon_sensitive(gtk.ENTRY_ICON_SECONDARY, False) - self.search.connect("icon-release", self.set_search_entry_clear_cb) - self.search.set_width_chars(30) - self.search.show() - - self.search.connect("focus-in-event", self.set_search_entry_editable_cb) - self.search.connect("focus-out-event", self.set_search_entry_reset_cb) - self.set_action_widget(self.search, gtk.PACK_END) - - def show_indicator_icon(self, title, number): - for child in self.pages: - if child.lbl.get_label() == title: - child.set_count(number) - - def hide_indicator_icon(self, title): - for child in self.pages: - if child.lbl.get_label() == title: - child.set_count(0) - - def set_search_entry_editable_cb(self, search, event): - self.search_focus = True - search.set_editable(True) - text = search.get_text() - if text in self.search_names: - search.set_text("") - style = self.search.get_style() - style.text[gtk.STATE_NORMAL] = self.get_colormap().alloc_color(HobColors.BLACK, False, False) - search.set_style(style) - - def set_search_entry_reset_cb(self, search, event): - page_num = self.get_current_page() - text = search.get_text() - if not text: - self.reset_entry(search, page_num) - - def reset_entry(self, entry, page_num): - style = entry.get_style() - style.text[gtk.STATE_NORMAL] = self.get_colormap().alloc_color(HobColors.GRAY, False, False) - entry.set_style(style) - entry.set_text(self.search_names[page_num]) - entry.set_tooltip_text(self.search_tips[page_num]) - entry.set_editable(False) - entry.set_icon_sensitive(gtk.ENTRY_ICON_SECONDARY, False) - - def set_search_entry_clear_cb(self, search, icon_pos, event): - if search.get_editable() == True: - search.set_text("") - search.set_icon_sensitive(gtk.ENTRY_ICON_SECONDARY, False) - search.grab_focus() - - def set_page(self, title): - for child in self.pages: - if child.lbl.get_label() == title: - child.grab_focus() - self.set_current_page(self.pages.index(child)) - return - -class HobWarpCellRendererText(gtk.CellRendererText): - def __init__(self, col_number): - gtk.CellRendererText.__init__(self) - self.set_property("wrap-mode", pango.WRAP_WORD_CHAR) - self.set_property("wrap-width", 300) # default value wrap width is 300 - self.col_n = col_number - - def do_render(self, window, widget, background_area, cell_area, expose_area, flags): - if widget: - self.props.wrap_width = self.get_resized_wrap_width(widget, widget.get_column(self.col_n)) - return gtk.CellRendererText.do_render(self, window, widget, background_area, cell_area, expose_area, flags) - - def get_resized_wrap_width(self, treeview, column): - otherCols = [] - for col in treeview.get_columns(): - if col != column: - otherCols.append(col) - adjwidth = treeview.allocation.width - sum(c.get_width() for c in otherCols) - adjwidth -= treeview.style_get_property("horizontal-separator") * 4 - if self.props.wrap_width == adjwidth or adjwidth <= 0: - adjwidth = self.props.wrap_width - return adjwidth - -gobject.type_register(HobWarpCellRendererText) - -class HobIconChecker(hic): - def set_hob_icon_to_stock_icon(self, file_path, stock_id=""): - try: - pixbuf = gtk.gdk.pixbuf_new_from_file(file_path) - except Exception, e: - return None - - if stock_id and (gtk.icon_factory_lookup_default(stock_id) == None): - icon_factory = gtk.IconFactory() - icon_factory.add_default() - icon_factory.add(stock_id, gtk.IconSet(pixbuf)) - gtk.stock_add([(stock_id, '_label', 0, 0, '')]) - - return icon_factory.lookup(stock_id) - - return None - - """ - For make hob icon consistently by request, and avoid icon view diff by system or gtk version, we use some 'hob icon' to replace the 'gtk icon'. - this function check the stock_id and make hob_id to replaced the gtk_id then return it or "" - """ - def check_stock_icon(self, stock_name=""): - HOB_CHECK_STOCK_NAME = { - ('hic-dialog-info', 'gtk-dialog-info', 'dialog-info') : self.ICON_INDI_INFO_FILE, - ('hic-ok', 'gtk-ok', 'ok') : self.ICON_INDI_TICK_FILE, - ('hic-dialog-error', 'gtk-dialog-error', 'dialog-error') : self.ICON_INDI_ERROR_FILE, - ('hic-dialog-warning', 'gtk-dialog-warning', 'dialog-warning') : self.ICON_INDI_ALERT_FILE, - ('hic-task-refresh', 'gtk-execute', 'execute') : self.ICON_INDI_REFRESH_FILE, - } - valid_stock_id = stock_name - if stock_name: - for names, path in HOB_CHECK_STOCK_NAME.iteritems(): - if stock_name in names: - valid_stock_id = names[0] - if not gtk.icon_factory_lookup_default(valid_stock_id): - self.set_hob_icon_to_stock_icon(path, valid_stock_id) - - return valid_stock_id - -class HobCellRendererController(gobject.GObject): - (MODE_CYCLE_RUNNING, MODE_ONE_SHORT) = range(2) - __gsignals__ = { - "run-timer-stopped" : (gobject.SIGNAL_RUN_LAST, - gobject.TYPE_NONE, - ()), - } - def __init__(self, runningmode=MODE_CYCLE_RUNNING, is_draw_row=False): - gobject.GObject.__init__(self) - self.timeout_id = None - self.current_angle_pos = 0.0 - self.step_angle = 0.0 - self.tree_headers_height = 0 - self.running_cell_areas = [] - self.running_mode = runningmode - self.is_queue_draw_row_area = is_draw_row - self.force_stop_enable = False - - def is_active(self): - if self.timeout_id: - return True - else: - return False - - def reset_run(self): - self.force_stop() - self.running_cell_areas = [] - self.current_angle_pos = 0.0 - self.step_angle = 0.0 - - ''' time_iterval: (1~1000)ms, which will be as the basic interval count for timer - init_usrdata: the current data which related the progress-bar will be at - min_usrdata: the range of min of user data - max_usrdata: the range of max of user data - step: each step which you want to progress - Note: the init_usrdata should in the range of from min to max, and max should > min - step should < (max - min) - ''' - def start_run(self, time_iterval, init_usrdata, min_usrdata, max_usrdata, step, tree): - if (not time_iterval) or (not max_usrdata): - return - usr_range = (max_usrdata - min_usrdata) * 1.0 - self.current_angle_pos = (init_usrdata * 1.0) / usr_range - self.step_angle = (step * 1) / usr_range - self.timeout_id = gobject.timeout_add(int(time_iterval), - self.make_image_on_progressing_cb, tree) - self.tree_headers_height = self.get_treeview_headers_height(tree) - self.force_stop_enable = False - - def force_stop(self): - self.emit("run-timer-stopped") - self.force_stop_enable = True - if self.timeout_id: - if gobject.source_remove(self.timeout_id): - self.timeout_id = None - - def on_draw_pixbuf_cb(self, pixbuf, cr, x, y, img_width, img_height, do_refresh=True): - if pixbuf: - r = max(img_width/2, img_height/2) - cr.translate(x + r, y + r) - if do_refresh: - cr.rotate(2 * math.pi * self.current_angle_pos) - - cr.set_source_pixbuf(pixbuf, -img_width/2, -img_height/2) - cr.paint() - - def on_draw_fadeinout_cb(self, cr, color, x, y, width, height, do_fadeout=True): - if do_fadeout: - alpha = self.current_angle_pos * 0.8 - else: - alpha = (1.0 - self.current_angle_pos) * 0.8 - - cr.set_source_rgba(color.red, color.green, color.blue, alpha) - cr.rectangle(x, y, width, height) - cr.fill() - - def get_treeview_headers_height(self, tree): - if tree and (tree.get_property("headers-visible") == True): - height = tree.get_allocation().height - tree.get_bin_window().get_size()[1] - return height - - return 0 - - def make_image_on_progressing_cb(self, tree): - self.current_angle_pos += self.step_angle - if self.running_mode == self.MODE_CYCLE_RUNNING: - if (self.current_angle_pos >= 1): - self.current_angle_pos = 0 - else: - if self.current_angle_pos > 1: - self.force_stop() - return False - - if self.is_queue_draw_row_area: - for path in self.running_cell_areas: - rect = tree.get_cell_area(path, tree.get_column(0)) - row_x, _, row_width, _ = tree.get_visible_rect() - tree.queue_draw_area(row_x, rect.y + self.tree_headers_height, row_width, rect.height) - else: - for rect in self.running_cell_areas: - tree.queue_draw_area(rect.x, rect.y + self.tree_headers_height, rect.width, rect.height) - - return (not self.force_stop_enable) - - def append_running_cell_area(self, cell_area): - if cell_area and (cell_area not in self.running_cell_areas): - self.running_cell_areas.append(cell_area) - - def remove_running_cell_area(self, cell_area): - if cell_area in self.running_cell_areas: - self.running_cell_areas.remove(cell_area) - if not self.running_cell_areas: - self.reset_run() - -gobject.type_register(HobCellRendererController) - -class HobCellRendererPixbuf(gtk.CellRendererPixbuf): - def __init__(self): - gtk.CellRendererPixbuf.__init__(self) - self.control = HobCellRendererController() - # add icon checker for make the gtk-icon transfer to hob-icon - self.checker = HobIconChecker() - self.set_property("stock-size", gtk.ICON_SIZE_DND) - - def get_pixbuf_from_stock_icon(self, widget, stock_id="", size=gtk.ICON_SIZE_DIALOG): - if widget and stock_id and gtk.icon_factory_lookup_default(stock_id): - return widget.render_icon(stock_id, size) - - return None - - def set_icon_name_to_id(self, new_name): - if new_name and type(new_name) == str: - # check the name is need to transfer to hob icon or not - name = self.checker.check_stock_icon(new_name) - if name.startswith("hic") or name.startswith("gtk"): - stock_id = name - else: - stock_id = 'gtk-' + name - - return stock_id - - ''' render cell exactly, "icon-name" is priority - if use the 'hic-task-refresh' will make the pix animation - if 'pix' will change the pixbuf for it from the pixbuf or image. - ''' - def do_render(self, window, tree, background_area,cell_area, expose_area, flags): - if (not self.control) or (not tree): - return - - x, y, w, h = self.on_get_size(tree, cell_area) - x += cell_area.x - y += cell_area.y - w -= 2 * self.get_property("xpad") - h -= 2 * self.get_property("ypad") - - stock_id = "" - if self.props.icon_name: - stock_id = self.set_icon_name_to_id(self.props.icon_name) - elif self.props.stock_id: - stock_id = self.props.stock_id - elif self.props.pixbuf: - pix = self.props.pixbuf - else: - return - - if stock_id: - pix = self.get_pixbuf_from_stock_icon(tree, stock_id, self.props.stock_size) - if stock_id == 'hic-task-refresh': - self.control.append_running_cell_area(cell_area) - if self.control.is_active(): - self.control.on_draw_pixbuf_cb(pix, window.cairo_create(), x, y, w, h, True) - else: - self.control.start_run(200, 0, 0, 1000, 150, tree) - else: - self.control.remove_running_cell_area(cell_area) - self.control.on_draw_pixbuf_cb(pix, window.cairo_create(), x, y, w, h, False) - - def on_get_size(self, widget, cell_area): - if self.props.icon_name or self.props.pixbuf or self.props.stock_id: - w, h = gtk.icon_size_lookup(self.props.stock_size) - calc_width = self.get_property("xpad") * 2 + w - calc_height = self.get_property("ypad") * 2 + h - x_offset = 0 - y_offset = 0 - if cell_area and w > 0 and h > 0: - x_offset = self.get_property("xalign") * (cell_area.width - calc_width - self.get_property("xpad")) - y_offset = self.get_property("yalign") * (cell_area.height - calc_height - self.get_property("ypad")) - - return x_offset, y_offset, w, h - - return 0, 0, 0, 0 - -gobject.type_register(HobCellRendererPixbuf) - -class HobCellRendererToggle(gtk.CellRendererToggle): - def __init__(self): - gtk.CellRendererToggle.__init__(self) - self.ctrl = HobCellRendererController(is_draw_row=True) - self.ctrl.running_mode = self.ctrl.MODE_ONE_SHORT - self.cell_attr = {"fadeout": False, "number_of_children": 0} - - def do_render(self, window, widget, background_area, cell_area, expose_area, flags): - if (not self.ctrl) or (not widget): - return - - if flags & gtk.CELL_RENDERER_SELECTED: - state = gtk.STATE_SELECTED - else: - state = gtk.STATE_NORMAL - - if self.ctrl.is_active(): - path = widget.get_path_at_pos(cell_area.x + cell_area.width/2, cell_area.y + cell_area.height/2) - # sometimes the parameters of cell_area will be a negative number,such as pull up down the scroll bar - # it's over the tree container range, so the path will be bad - if not path: return - path = path[0] - if path in self.ctrl.running_cell_areas: - cr = window.cairo_create() - color = widget.get_style().base[state] - - row_x, _, row_width, _ = widget.get_visible_rect() - border_y = self.get_property("ypad") - self.ctrl.on_draw_fadeinout_cb(cr, color, row_x, cell_area.y - border_y, row_width, \ - cell_area.height + border_y * 2, self.cell_attr["fadeout"]) - # draw number of a group - if self.cell_attr["number_of_children"]: - text = "%d pkg" % self.cell_attr["number_of_children"] - pangolayout = widget.create_pango_layout(text) - textw, texth = pangolayout.get_pixel_size() - x = cell_area.x + (cell_area.width/2) - (textw/2) - y = cell_area.y + (cell_area.height/2) - (texth/2) - - widget.style.paint_layout(window, state, True, cell_area, widget, "checkbox", x, y, pangolayout) - else: - return gtk.CellRendererToggle.do_render(self, window, widget, background_area, cell_area, expose_area, flags) - - '''delay: normally delay time is 1000ms - cell_list: whilch cells need to be render - ''' - def fadeout(self, tree, delay, cell_list=None): - if (delay < 200) or (not tree): - return - self.cell_attr["fadeout"] = True - self.ctrl.running_cell_areas = cell_list - self.ctrl.start_run(200, 0, 0, delay, (delay * 200 / 1000), tree) - - def connect_render_state_changed(self, func, usrdata=None): - if not func: - return - if usrdata: - self.ctrl.connect("run-timer-stopped", func, self, usrdata) - else: - self.ctrl.connect("run-timer-stopped", func, self) - -gobject.type_register(HobCellRendererToggle) diff --git a/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/persistenttooltip.py b/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/persistenttooltip.py deleted file mode 100644 index 927c19429..000000000 --- a/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/persistenttooltip.py +++ /dev/null @@ -1,186 +0,0 @@ -# -# BitBake Graphical GTK User Interface -# -# Copyright (C) 2012 Intel Corporation -# -# Authored by Joshua Lock <josh@linux.intel.com> -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 as -# published by the Free Software Foundation. -# -# 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, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -import gobject -import gtk -try: - import gconf -except: - pass - -class PersistentTooltip(gtk.Window): - """ - A tooltip which persists once shown until the user dismisses it with the Esc - key or by clicking the close button. - - # FIXME: the PersistentTooltip should be disabled when the user clicks anywhere off - # it. We can't do this with focus-out-event becuase modal ensures we have focus? - - markup: some Pango text markup to display in the tooltip - """ - def __init__(self, markup, parent_win=None): - gtk.Window.__init__(self, gtk.WINDOW_POPUP) - - # Inherit the system theme for a tooltip - style = gtk.rc_get_style_by_paths(gtk.settings_get_default(), - 'gtk-tooltip', 'gtk-tooltip', gobject.TYPE_NONE) - self.set_style(style) - - # The placement of the close button on the tip should reflect how the - # window manager of the users system places close buttons. Try to read - # the metacity gconf key to determine whether the close button is on the - # left or the right. - # In the case that we can't determine the users configuration we default - # to close buttons being on the right. - __button_right = True - try: - client = gconf.client_get_default() - order = client.get_string("/apps/metacity/general/button_layout") - if order and order.endswith(":"): - __button_right = False - except NameError: - pass - - # We need to ensure we're only shown once - self.shown = False - - # We don't want any WM decorations - self.set_decorated(False) - # We don't want to show in the taskbar or window switcher - self.set_skip_pager_hint(True) - self.set_skip_taskbar_hint(True) - # We must be modal to ensure we grab focus when presented from a gtk.Dialog - self.set_modal(True) - - self.set_border_width(0) - self.set_position(gtk.WIN_POS_MOUSE) - self.set_opacity(0.95) - - # Ensure a reasonable minimum size - self.set_geometry_hints(self, 100, 50) - - # Set this window as a transient window for parent(main window) - if parent_win: - self.set_transient_for(parent_win) - self.set_destroy_with_parent(True) - # Draw our label and close buttons - hbox = gtk.HBox(False, 0) - hbox.show() - self.add(hbox) - - img = gtk.Image() - img.set_from_stock(gtk.STOCK_CLOSE, gtk.ICON_SIZE_BUTTON) - - self.button = gtk.Button() - self.button.set_image(img) - self.button.connect("clicked", self._dismiss_cb) - self.button.set_flags(gtk.CAN_DEFAULT) - self.button.grab_focus() - self.button.show() - vbox = gtk.VBox(False, 0) - vbox.show() - vbox.pack_start(self.button, False, False, 0) - if __button_right: - hbox.pack_end(vbox, True, True, 0) - else: - hbox.pack_start(vbox, True, True, 0) - - self.set_default(self.button) - - bin = gtk.HBox(True, 6) - bin.set_border_width(6) - bin.show() - self.label = gtk.Label() - self.label.set_line_wrap(True) - # We want to match the colours of the normal tooltips, as dictated by - # the users gtk+-2.0 theme, wherever possible - on some systems this - # requires explicitly setting a fg_color for the label which matches the - # tooltip_fg_color - settings = gtk.settings_get_default() - colours = settings.get_property('gtk-color-scheme').split('\n') - # remove any empty lines, there's likely to be a trailing one after - # calling split on a dictionary-like string - colours = filter(None, colours) - for col in colours: - item, val = col.split(': ') - if item == 'tooltip_fg_color': - style = self.label.get_style() - style.fg[gtk.STATE_NORMAL] = gtk.gdk.color_parse(val) - self.label.set_style(style) - break # we only care for the tooltip_fg_color - - self.label.set_markup(markup) - self.label.show() - bin.add(self.label) - hbox.pack_end(bin, True, True, 6) - - # add the original URL display for user reference - if 'a href' in markup: - hbox.set_tooltip_text(self.get_markup_url(markup)) - hbox.show() - - self.connect("key-press-event", self._catch_esc_cb) - - """ - Callback when the PersistentTooltip's close button is clicked. - Hides the PersistentTooltip. - """ - def _dismiss_cb(self, button): - self.hide() - return True - - """ - Callback when the Esc key is detected. Hides the PersistentTooltip. - """ - def _catch_esc_cb(self, widget, event): - keyname = gtk.gdk.keyval_name(event.keyval) - if keyname == "Escape": - self.hide() - return True - - """ - Called to present the PersistentTooltip. - Overrides the superclasses show() method to include state tracking. - """ - def show(self): - if not self.shown: - self.shown = True - gtk.Window.show(self) - - """ - Called to hide the PersistentTooltip. - Overrides the superclasses hide() method to include state tracking. - """ - def hide(self): - self.shown = False - gtk.Window.hide(self) - - """ - Called to get the hyperlink URL from markup text. - """ - def get_markup_url(self, markup): - url = "http:" - if markup and type(markup) == str: - s = markup - if 'http:' in s: - import re - url = re.search('(http:[^,\\ "]+)', s).group(0) - - return url diff --git a/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/progress.py b/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/progress.py deleted file mode 100644 index 1d28a111b..000000000 --- a/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/progress.py +++ /dev/null @@ -1,23 +0,0 @@ -import gtk - -class ProgressBar(gtk.Dialog): - def __init__(self, parent): - - gtk.Dialog.__init__(self, flags=(gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT)) - self.set_title("Parsing metadata, please wait...") - self.set_default_size(500, 0) - self.set_transient_for(parent) - self.progress = gtk.ProgressBar() - self.vbox.pack_start(self.progress) - self.show_all() - - def set_text(self, msg): - self.progress.set_text(msg) - - def update(self, x, y): - self.progress.set_fraction(float(x)/float(y)) - self.progress.set_text("%2d %%" % (x*100/y)) - - def pulse(self): - self.progress.set_text("Loading...") - self.progress.pulse() diff --git a/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/progressbar.py b/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/progressbar.py deleted file mode 100644 index 3e2c660e4..000000000 --- a/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/progressbar.py +++ /dev/null @@ -1,59 +0,0 @@ -# BitBake Graphical GTK User Interface -# -# Copyright (C) 2011 Intel Corporation -# -# Authored by Shane Wang <shane.wang@intel.com> -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 as -# published by the Free Software Foundation. -# -# 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, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -import gtk -from bb.ui.crumbs.hobcolor import HobColors - -class HobProgressBar (gtk.ProgressBar): - def __init__(self): - gtk.ProgressBar.__init__(self) - self.set_rcstyle(True) - self.percentage = 0 - - def set_rcstyle(self, status): - rcstyle = gtk.RcStyle() - rcstyle.fg[2] = gtk.gdk.Color(HobColors.BLACK) - if status == "stop": - rcstyle.bg[3] = gtk.gdk.Color(HobColors.WARNING) - elif status == "fail": - rcstyle.bg[3] = gtk.gdk.Color(HobColors.ERROR) - else: - rcstyle.bg[3] = gtk.gdk.Color(HobColors.RUNNING) - self.modify_style(rcstyle) - - def set_title(self, text=None): - if not text: - text = "" - text += " %.0f%%" % self.percentage - self.set_text(text) - - def set_stop_title(self, text=None): - if not text: - text = "" - self.set_text(text) - - def reset(self): - self.set_fraction(0) - self.set_text("") - self.set_rcstyle(True) - self.percentage = 0 - - def update(self, fraction): - self.percentage = int(fraction * 100) - self.set_fraction(fraction) diff --git a/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/puccho.glade b/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/puccho.glade deleted file mode 100644 index d7553a6e1..000000000 --- a/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/puccho.glade +++ /dev/null @@ -1,606 +0,0 @@ -<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd"> -<!--Generated with glade3 3.4.5 on Mon Nov 10 12:24:12 2008 --> -<glade-interface> - <widget class="GtkDialog" id="build_dialog"> - <property name="title" translatable="yes">Start a build</property> - <property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property> - <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property> - <property name="has_separator">False</property> - <child internal-child="vbox"> - <widget class="GtkVBox" id="dialog-vbox1"> - <property name="visible">True</property> - <property name="spacing">2</property> - <child> - <widget class="GtkTable" id="build_table"> - <property name="visible">True</property> - <property name="border_width">6</property> - <property name="n_rows">7</property> - <property name="n_columns">3</property> - <property name="column_spacing">5</property> - <property name="row_spacing">6</property> - <child> - <widget class="GtkAlignment" id="status_alignment"> - <property name="visible">True</property> - <property name="left_padding">12</property> - <child> - <widget class="GtkHBox" id="status_hbox"> - <property name="spacing">6</property> - <child> - <widget class="GtkImage" id="status_image"> - <property name="visible">True</property> - <property name="no_show_all">True</property> - <property name="xalign">0</property> - <property name="stock">gtk-dialog-error</property> - </widget> - <packing> - <property name="expand">False</property> - <property name="fill">False</property> - </packing> - </child> - <child> - <widget class="GtkLabel" id="status_label"> - <property name="visible">True</property> - <property name="xalign">0</property> - <property name="label" translatable="yes">If you see this text something is wrong...</property> - <property name="use_markup">True</property> - <property name="use_underline">True</property> - </widget> - <packing> - <property name="position">1</property> - </packing> - </child> - </widget> - </child> - </widget> - <packing> - <property name="right_attach">3</property> - <property name="top_attach">2</property> - <property name="bottom_attach">3</property> - </packing> - </child> - <child> - <widget class="GtkLabel" id="label2"> - <property name="visible">True</property> - <property name="xalign">0</property> - <property name="label" translatable="yes"><b>Build configuration</b></property> - <property name="use_markup">True</property> - </widget> - <packing> - <property name="right_attach">3</property> - <property name="top_attach">3</property> - <property name="bottom_attach">4</property> - <property name="y_options"></property> - </packing> - </child> - <child> - <widget class="GtkComboBox" id="image_combo"> - <property name="visible">True</property> - <property name="sensitive">False</property> - </widget> - <packing> - <property name="left_attach">1</property> - <property name="right_attach">2</property> - <property name="top_attach">6</property> - <property name="bottom_attach">7</property> - <property name="y_options"></property> - </packing> - </child> - <child> - <widget class="GtkLabel" id="image_label"> - <property name="visible">True</property> - <property name="sensitive">False</property> - <property name="xalign">0</property> - <property name="xpad">12</property> - <property name="label" translatable="yes">Image:</property> - </widget> - <packing> - <property name="top_attach">6</property> - <property name="bottom_attach">7</property> - <property name="y_options"></property> - </packing> - </child> - <child> - <widget class="GtkComboBox" id="distribution_combo"> - <property name="visible">True</property> - <property name="sensitive">False</property> - </widget> - <packing> - <property name="left_attach">1</property> - <property name="right_attach">2</property> - <property name="top_attach">5</property> - <property name="bottom_attach">6</property> - <property name="y_options"></property> - </packing> - </child> - <child> - <widget class="GtkLabel" id="distribution_label"> - <property name="visible">True</property> - <property name="sensitive">False</property> - <property name="xalign">0</property> - <property name="xpad">12</property> - <property name="label" translatable="yes">Distribution:</property> - </widget> - <packing> - <property name="top_attach">5</property> - <property name="bottom_attach">6</property> - <property name="y_options"></property> - </packing> - </child> - <child> - <widget class="GtkComboBox" id="machine_combo"> - <property name="visible">True</property> - <property name="sensitive">False</property> - </widget> - <packing> - <property name="left_attach">1</property> - <property name="right_attach">2</property> - <property name="top_attach">4</property> - <property name="bottom_attach">5</property> - <property name="y_options"></property> - </packing> - </child> - <child> - <widget class="GtkLabel" id="machine_label"> - <property name="visible">True</property> - <property name="sensitive">False</property> - <property name="xalign">0</property> - <property name="xpad">12</property> - <property name="label" translatable="yes">Machine:</property> - </widget> - <packing> - <property name="top_attach">4</property> - <property name="bottom_attach">5</property> - <property name="y_options"></property> - </packing> - </child> - <child> - <widget class="GtkButton" id="refresh_button"> - <property name="visible">True</property> - <property name="sensitive">False</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> - <property name="label" translatable="yes">gtk-refresh</property> - <property name="use_stock">True</property> - <property name="response_id">0</property> - </widget> - <packing> - <property name="left_attach">2</property> - <property name="right_attach">3</property> - <property name="top_attach">1</property> - <property name="bottom_attach">2</property> - <property name="y_options"></property> - </packing> - </child> - <child> - <widget class="GtkEntry" id="location_entry"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="width_chars">32</property> - </widget> - <packing> - <property name="left_attach">1</property> - <property name="right_attach">2</property> - <property name="top_attach">1</property> - <property name="bottom_attach">2</property> - <property name="y_options"></property> - </packing> - </child> - <child> - <widget class="GtkLabel" id="label3"> - <property name="visible">True</property> - <property name="xalign">0</property> - <property name="xpad">12</property> - <property name="label" translatable="yes">Location:</property> - </widget> - <packing> - <property name="top_attach">1</property> - <property name="bottom_attach">2</property> - <property name="y_options"></property> - </packing> - </child> - <child> - <widget class="GtkLabel" id="label1"> - <property name="visible">True</property> - <property name="xalign">0</property> - <property name="label" translatable="yes"><b>Repository</b></property> - <property name="use_markup">True</property> - </widget> - <packing> - <property name="right_attach">3</property> - <property name="y_options"></property> - </packing> - </child> - <child> - <widget class="GtkAlignment" id="alignment1"> - <property name="visible">True</property> - <child> - <placeholder/> - </child> - </widget> - <packing> - <property name="left_attach">2</property> - <property name="right_attach">3</property> - <property name="top_attach">4</property> - <property name="bottom_attach">5</property> - <property name="y_options"></property> - </packing> - </child> - <child> - <widget class="GtkAlignment" id="alignment2"> - <property name="visible">True</property> - <child> - <placeholder/> - </child> - </widget> - <packing> - <property name="left_attach">2</property> - <property name="right_attach">3</property> - <property name="top_attach">5</property> - <property name="bottom_attach">6</property> - <property name="y_options"></property> - </packing> - </child> - <child> - <widget class="GtkAlignment" id="alignment3"> - <property name="visible">True</property> - <child> - <placeholder/> - </child> - </widget> - <packing> - <property name="left_attach">2</property> - <property name="right_attach">3</property> - <property name="top_attach">6</property> - <property name="bottom_attach">7</property> - <property name="y_options"></property> - </packing> - </child> - </widget> - <packing> - <property name="position">1</property> - </packing> - </child> - <child internal-child="action_area"> - <widget class="GtkHButtonBox" id="dialog-action_area1"> - <property name="visible">True</property> - <property name="layout_style">GTK_BUTTONBOX_END</property> - <child> - <placeholder/> - </child> - <child> - <placeholder/> - </child> - <child> - <placeholder/> - </child> - </widget> - <packing> - <property name="expand">False</property> - <property name="pack_type">GTK_PACK_END</property> - </packing> - </child> - </widget> - </child> - </widget> - <widget class="GtkDialog" id="dialog2"> - <property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property> - <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property> - <property name="has_separator">False</property> - <child internal-child="vbox"> - <widget class="GtkVBox" id="dialog-vbox2"> - <property name="visible">True</property> - <property name="spacing">2</property> - <child> - <widget class="GtkTable" id="table2"> - <property name="visible">True</property> - <property name="border_width">6</property> - <property name="n_rows">7</property> - <property name="n_columns">3</property> - <property name="column_spacing">6</property> - <property name="row_spacing">6</property> - <child> - <widget class="GtkLabel" id="label7"> - <property name="visible">True</property> - <property name="xalign">0</property> - <property name="label" translatable="yes"><b>Repositories</b></property> - <property name="use_markup">True</property> - </widget> - <packing> - <property name="right_attach">3</property> - <property name="y_options"></property> - </packing> - </child> - <child> - <widget class="GtkAlignment" id="alignment4"> - <property name="visible">True</property> - <property name="xalign">0</property> - <property name="left_padding">12</property> - <child> - <widget class="GtkScrolledWindow" id="scrolledwindow1"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property> - <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property> - <child> - <widget class="GtkTreeView" id="treeview1"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="headers_clickable">True</property> - </widget> - </child> - </widget> - </child> - </widget> - <packing> - <property name="right_attach">3</property> - <property name="top_attach">2</property> - <property name="bottom_attach">3</property> - <property name="y_options"></property> - </packing> - </child> - <child> - <widget class="GtkEntry" id="entry1"> - <property name="visible">True</property> - <property name="can_focus">True</property> - </widget> - <packing> - <property name="left_attach">1</property> - <property name="right_attach">3</property> - <property name="top_attach">1</property> - <property name="bottom_attach">2</property> - <property name="y_options"></property> - </packing> - </child> - <child> - <widget class="GtkLabel" id="label9"> - <property name="visible">True</property> - <property name="xalign">0</property> - <property name="label" translatable="yes"><b>Additional packages</b></property> - <property name="use_markup">True</property> - </widget> - <packing> - <property name="right_attach">3</property> - <property name="top_attach">4</property> - <property name="bottom_attach">5</property> - <property name="y_options"></property> - </packing> - </child> - <child> - <widget class="GtkAlignment" id="alignment6"> - <property name="visible">True</property> - <property name="xalign">0</property> - <property name="xscale">0</property> - <child> - <widget class="GtkLabel" id="label8"> - <property name="visible">True</property> - <property name="xalign">0</property> - <property name="yalign">0</property> - <property name="xpad">12</property> - <property name="label" translatable="yes">Location: </property> - </widget> - </child> - </widget> - <packing> - <property name="top_attach">1</property> - <property name="bottom_attach">2</property> - <property name="y_options"></property> - </packing> - </child> - <child> - <widget class="GtkAlignment" id="alignment7"> - <property name="visible">True</property> - <property name="xalign">1</property> - <property name="xscale">0</property> - <child> - <widget class="GtkHButtonBox" id="hbuttonbox1"> - <property name="visible">True</property> - <property name="spacing">5</property> - <child> - <widget class="GtkButton" id="button7"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> - <property name="label" translatable="yes">gtk-remove</property> - <property name="use_stock">True</property> - <property name="response_id">0</property> - </widget> - </child> - <child> - <widget class="GtkButton" id="button6"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> - <property name="label" translatable="yes">gtk-edit</property> - <property name="use_stock">True</property> - <property name="response_id">0</property> - </widget> - <packing> - <property name="position">1</property> - </packing> - </child> - <child> - <widget class="GtkButton" id="button5"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> - <property name="label" translatable="yes">gtk-add</property> - <property name="use_stock">True</property> - <property name="response_id">0</property> - </widget> - <packing> - <property name="position">2</property> - </packing> - </child> - </widget> - </child> - </widget> - <packing> - <property name="left_attach">1</property> - <property name="right_attach">3</property> - <property name="top_attach">3</property> - <property name="bottom_attach">4</property> - <property name="y_options"></property> - </packing> - </child> - <child> - <widget class="GtkAlignment" id="alignment5"> - <property name="visible">True</property> - <child> - <placeholder/> - </child> - </widget> - <packing> - <property name="top_attach">3</property> - <property name="bottom_attach">4</property> - <property name="y_options"></property> - </packing> - </child> - <child> - <widget class="GtkLabel" id="label10"> - <property name="visible">True</property> - <property name="xalign">0</property> - <property name="yalign">0</property> - <property name="xpad">12</property> - <property name="label" translatable="yes">Search:</property> - </widget> - <packing> - <property name="top_attach">5</property> - <property name="bottom_attach">6</property> - <property name="y_options"></property> - </packing> - </child> - <child> - <widget class="GtkEntry" id="entry2"> - <property name="visible">True</property> - <property name="can_focus">True</property> - </widget> - <packing> - <property name="left_attach">1</property> - <property name="right_attach">3</property> - <property name="top_attach">5</property> - <property name="bottom_attach">6</property> - <property name="y_options"></property> - </packing> - </child> - <child> - <widget class="GtkAlignment" id="alignment8"> - <property name="visible">True</property> - <property name="xalign">0</property> - <property name="left_padding">12</property> - <child> - <widget class="GtkScrolledWindow" id="scrolledwindow2"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property> - <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property> - <child> - <widget class="GtkTreeView" id="treeview2"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="headers_clickable">True</property> - </widget> - </child> - </widget> - </child> - </widget> - <packing> - <property name="right_attach">3</property> - <property name="top_attach">6</property> - <property name="bottom_attach">7</property> - <property name="y_options"></property> - </packing> - </child> - </widget> - <packing> - <property name="position">1</property> - </packing> - </child> - <child internal-child="action_area"> - <widget class="GtkHButtonBox" id="dialog-action_area2"> - <property name="visible">True</property> - <property name="layout_style">GTK_BUTTONBOX_END</property> - <child> - <widget class="GtkButton" id="button4"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> - <property name="label" translatable="yes">gtk-close</property> - <property name="use_stock">True</property> - <property name="response_id">0</property> - </widget> - </child> - </widget> - <packing> - <property name="expand">False</property> - <property name="pack_type">GTK_PACK_END</property> - </packing> - </child> - </widget> - </child> - </widget> - <widget class="GtkWindow" id="main_window"> - <child> - <widget class="GtkVBox" id="main_window_vbox"> - <property name="visible">True</property> - <child> - <widget class="GtkToolbar" id="main_toolbar"> - <property name="visible">True</property> - <child> - <widget class="GtkToolButton" id="main_toolbutton_build"> - <property name="visible">True</property> - <property name="label" translatable="yes">Build</property> - <property name="stock_id">gtk-execute</property> - </widget> - <packing> - <property name="expand">False</property> - </packing> - </child> - </widget> - <packing> - <property name="expand">False</property> - </packing> - </child> - <child> - <widget class="GtkVPaned" id="vpaned1"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <child> - <widget class="GtkScrolledWindow" id="results_scrolledwindow"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property> - <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property> - <child> - <placeholder/> - </child> - </widget> - <packing> - <property name="resize">False</property> - <property name="shrink">True</property> - </packing> - </child> - <child> - <widget class="GtkScrolledWindow" id="progress_scrolledwindow"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property> - <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property> - <child> - <placeholder/> - </child> - </widget> - <packing> - <property name="resize">True</property> - <property name="shrink">True</property> - </packing> - </child> - </widget> - <packing> - <property name="position">1</property> - </packing> - </child> - </widget> - </child> - </widget> -</glade-interface> diff --git a/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/runningbuild.py b/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/runningbuild.py deleted file mode 100644 index 16a955d2b..000000000 --- a/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/runningbuild.py +++ /dev/null @@ -1,551 +0,0 @@ - -# -# BitBake Graphical GTK User Interface -# -# Copyright (C) 2008 Intel Corporation -# -# Authored by Rob Bradford <rob@linux.intel.com> -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 as -# published by the Free Software Foundation. -# -# 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, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -import gtk -import gobject -import logging -import time -import urllib -import urllib2 -import pango -from bb.ui.crumbs.hobcolor import HobColors -from bb.ui.crumbs.hobwidget import HobWarpCellRendererText, HobCellRendererPixbuf - -class RunningBuildModel (gtk.TreeStore): - (COL_LOG, COL_PACKAGE, COL_TASK, COL_MESSAGE, COL_ICON, COL_COLOR, COL_NUM_ACTIVE) = range(7) - - def __init__ (self): - gtk.TreeStore.__init__ (self, - gobject.TYPE_STRING, - gobject.TYPE_STRING, - gobject.TYPE_STRING, - gobject.TYPE_STRING, - gobject.TYPE_STRING, - gobject.TYPE_STRING, - gobject.TYPE_INT) - - def failure_model_filter(self, model, it): - color = model.get(it, self.COL_COLOR)[0] - if not color: - return False - if color == HobColors.ERROR or color == HobColors.WARNING: - return True - return False - - def failure_model(self): - model = self.filter_new() - model.set_visible_func(self.failure_model_filter) - return model - - def foreach_cell_func(self, model, path, iter, usr_data=None): - if model.get_value(iter, self.COL_ICON) == "gtk-execute": - model.set(iter, self.COL_ICON, "") - - def close_task_refresh(self): - self.foreach(self.foreach_cell_func, None) - -class RunningBuild (gobject.GObject): - __gsignals__ = { - 'build-started' : (gobject.SIGNAL_RUN_LAST, - gobject.TYPE_NONE, - ()), - 'build-succeeded' : (gobject.SIGNAL_RUN_LAST, - gobject.TYPE_NONE, - ()), - 'build-failed' : (gobject.SIGNAL_RUN_LAST, - gobject.TYPE_NONE, - ()), - 'build-complete' : (gobject.SIGNAL_RUN_LAST, - gobject.TYPE_NONE, - ()), - 'build-aborted' : (gobject.SIGNAL_RUN_LAST, - gobject.TYPE_NONE, - ()), - 'task-started' : (gobject.SIGNAL_RUN_LAST, - gobject.TYPE_NONE, - (gobject.TYPE_PYOBJECT,)), - 'log-error' : (gobject.SIGNAL_RUN_LAST, - gobject.TYPE_NONE, - ()), - 'log-warning' : (gobject.SIGNAL_RUN_LAST, - gobject.TYPE_NONE, - ()), - 'disk-full' : (gobject.SIGNAL_RUN_LAST, - gobject.TYPE_NONE, - ()), - 'no-provider' : (gobject.SIGNAL_RUN_LAST, - gobject.TYPE_NONE, - (gobject.TYPE_PYOBJECT,)), - 'log' : (gobject.SIGNAL_RUN_LAST, - gobject.TYPE_NONE, - (gobject.TYPE_STRING, gobject.TYPE_PYOBJECT,)), - } - pids_to_task = {} - tasks_to_iter = {} - - def __init__ (self, sequential=False): - gobject.GObject.__init__ (self) - self.model = RunningBuildModel() - self.sequential = sequential - self.buildaborted = False - - def reset (self): - self.pids_to_task.clear() - self.tasks_to_iter.clear() - self.model.clear() - - def handle_event (self, event, pbar=None): - # Handle an event from the event queue, this may result in updating - # the model and thus the UI. Or it may be to tell us that the build - # has finished successfully (or not, as the case may be.) - - parent = None - pid = 0 - package = None - task = None - - # If we have a pid attached to this message/event try and get the - # (package, task) pair for it. If we get that then get the parent iter - # for the message. - if hasattr(event, 'pid'): - pid = event.pid - if hasattr(event, 'process'): - pid = event.process - - if pid and pid in self.pids_to_task: - (package, task) = self.pids_to_task[pid] - parent = self.tasks_to_iter[(package, task)] - - if(isinstance(event, logging.LogRecord)): - if event.taskpid == 0 or event.levelno > logging.INFO: - self.emit("log", "handle", event) - # FIXME: this is a hack! More info in Yocto #1433 - # http://bugzilla.pokylinux.org/show_bug.cgi?id=1433, temporarily - # mask the error message as it's not informative for the user. - if event.msg.startswith("Execution of event handler 'run_buildstats' failed"): - return - - if (event.levelno < logging.INFO or - event.msg.startswith("Running task")): - return # don't add these to the list - - if event.levelno >= logging.ERROR: - icon = "dialog-error" - color = HobColors.ERROR - self.emit("log-error") - elif event.levelno >= logging.WARNING: - icon = "dialog-warning" - color = HobColors.WARNING - self.emit("log-warning") - else: - icon = None - color = HobColors.OK - - # if we know which package we belong to, we'll append onto its list. - # otherwise, we'll jump to the top of the master list - if self.sequential or not parent: - tree_add = self.model.append - else: - tree_add = self.model.prepend - tree_add(parent, - (None, - package, - task, - event.getMessage(), - icon, - color, - 0)) - - # if there are warnings while processing a package - # (parent), mark the task with warning color; - # in case there are errors, the updates will be - # handled on TaskFailed. - if color == HobColors.WARNING and parent: - self.model.set(parent, self.model.COL_COLOR, color) - if task: #then we have a parent (package), and update it's color - self.model.set(self.tasks_to_iter[(package, None)], self.model.COL_COLOR, color) - - elif isinstance(event, bb.build.TaskStarted): - (package, task) = (event._package, event._task) - - # Save out this PID. - self.pids_to_task[pid] = (package, task) - - # Check if we already have this package in our model. If so then - # that can be the parent for the task. Otherwise we create a new - # top level for the package. - if ((package, None) in self.tasks_to_iter): - parent = self.tasks_to_iter[(package, None)] - else: - if self.sequential: - add = self.model.append - else: - add = self.model.prepend - parent = add(None, (None, - package, - None, - "Package: %s" % (package), - None, - HobColors.OK, - 0)) - self.tasks_to_iter[(package, None)] = parent - - # Because this parent package now has an active child mark it as - # such. - self.model.set(parent, self.model.COL_ICON, "gtk-execute") - parent_color = self.model.get(parent, self.model.COL_COLOR)[0] - if parent_color != HobColors.ERROR and parent_color != HobColors.WARNING: - self.model.set(parent, self.model.COL_COLOR, HobColors.RUNNING) - - # Add an entry in the model for this task - i = self.model.append (parent, (None, - package, - task, - "Task: %s" % (task), - "gtk-execute", - HobColors.RUNNING, - 0)) - - # update the parent's active task count - num_active = self.model.get(parent, self.model.COL_NUM_ACTIVE)[0] + 1 - self.model.set(parent, self.model.COL_NUM_ACTIVE, num_active) - - # Save out the iter so that we can find it when we have a message - # that we need to attach to a task. - self.tasks_to_iter[(package, task)] = i - - elif isinstance(event, bb.build.TaskBase): - self.emit("log", "info", event._message) - current = self.tasks_to_iter[(package, task)] - parent = self.tasks_to_iter[(package, None)] - - # remove this task from the parent's active count - num_active = self.model.get(parent, self.model.COL_NUM_ACTIVE)[0] - 1 - self.model.set(parent, self.model.COL_NUM_ACTIVE, num_active) - - if isinstance(event, bb.build.TaskFailed): - # Mark the task and parent as failed - icon = "dialog-error" - color = HobColors.ERROR - - logfile = event.logfile - if logfile and os.path.exists(logfile): - with open(logfile) as f: - logdata = f.read() - self.model.append(current, ('pastebin', None, None, logdata, 'gtk-error', HobColors.OK, 0)) - - for i in (current, parent): - self.model.set(i, self.model.COL_ICON, icon, - self.model.COL_COLOR, color) - else: - # Mark the parent package and the task as inactive, - # but make sure to preserve error, warnings and active - # states - parent_color = self.model.get(parent, self.model.COL_COLOR)[0] - task_color = self.model.get(current, self.model.COL_COLOR)[0] - - # Mark the task as inactive - self.model.set(current, self.model.COL_ICON, None) - if task_color != HobColors.ERROR: - if task_color == HobColors.WARNING: - self.model.set(current, self.model.COL_ICON, 'dialog-warning') - else: - self.model.set(current, self.model.COL_COLOR, HobColors.OK) - - # Mark the parent as inactive - if parent_color != HobColors.ERROR: - if parent_color == HobColors.WARNING: - self.model.set(parent, self.model.COL_ICON, "dialog-warning") - else: - self.model.set(parent, self.model.COL_ICON, None) - if num_active == 0: - self.model.set(parent, self.model.COL_COLOR, HobColors.OK) - - # Clear the iters and the pids since when the task goes away the - # pid will no longer be used for messages - del self.tasks_to_iter[(package, task)] - del self.pids_to_task[pid] - - elif isinstance(event, bb.event.BuildStarted): - - self.emit("build-started") - self.model.prepend(None, (None, - None, - None, - "Build Started (%s)" % time.strftime('%m/%d/%Y %H:%M:%S'), - None, - HobColors.OK, - 0)) - if pbar: - pbar.update(0, self.progress_total) - pbar.set_title(bb.event.getName(event)) - - elif isinstance(event, bb.event.BuildCompleted): - failures = int (event._failures) - self.model.prepend(None, (None, - None, - None, - "Build Completed (%s)" % time.strftime('%m/%d/%Y %H:%M:%S'), - None, - HobColors.OK, - 0)) - - # Emit the appropriate signal depending on the number of failures - if self.buildaborted: - self.emit ("build-aborted") - self.buildaborted = False - elif (failures >= 1): - self.emit ("build-failed") - else: - self.emit ("build-succeeded") - # Emit a generic "build-complete" signal for things wishing to - # handle when the build is finished - self.emit("build-complete") - # reset the all cell's icon indicator - self.model.close_task_refresh() - if pbar: - pbar.set_text(event.msg) - - elif isinstance(event, bb.event.DiskFull): - self.buildaborted = True - self.emit("disk-full") - - elif isinstance(event, bb.command.CommandFailed): - self.emit("log", "error", "Command execution failed: %s" % (event.error)) - if event.error.startswith("Exited with"): - # If the command fails with an exit code we're done, emit the - # generic signal for the UI to notify the user - self.emit("build-complete") - # reset the all cell's icon indicator - self.model.close_task_refresh() - - elif isinstance(event, bb.event.CacheLoadStarted) and pbar: - pbar.set_title("Loading cache") - self.progress_total = event.total - pbar.update(0, self.progress_total) - elif isinstance(event, bb.event.CacheLoadProgress) and pbar: - pbar.update(event.current, self.progress_total) - elif isinstance(event, bb.event.CacheLoadCompleted) and pbar: - pbar.update(self.progress_total, self.progress_total) - pbar.hide() - elif isinstance(event, bb.event.ParseStarted) and pbar: - if event.total == 0: - return - pbar.set_title("Processing recipes") - self.progress_total = event.total - pbar.update(0, self.progress_total) - elif isinstance(event, bb.event.ParseProgress) and pbar: - pbar.update(event.current, self.progress_total) - elif isinstance(event, bb.event.ParseCompleted) and pbar: - pbar.hide() - #using runqueue events as many as possible to update the progress bar - elif isinstance(event, bb.runqueue.runQueueTaskFailed): - self.emit("log", "error", "Task %s (%s) failed with exit code '%s'" % (event.taskid, event.taskstring, event.exitcode)) - elif isinstance(event, bb.runqueue.sceneQueueTaskFailed): - self.emit("log", "warn", "Setscene task %s (%s) failed with exit code '%s' - real task will be run instead" \ - % (event.taskid, event.taskstring, event.exitcode)) - elif isinstance(event, (bb.runqueue.runQueueTaskStarted, bb.runqueue.sceneQueueTaskStarted)): - if isinstance(event, bb.runqueue.sceneQueueTaskStarted): - self.emit("log", "info", "Running setscene task %d of %d (%s)" % \ - (event.stats.completed + event.stats.active + event.stats.failed + 1, - event.stats.total, event.taskstring)) - else: - if event.noexec: - tasktype = 'noexec task' - else: - tasktype = 'task' - self.emit("log", "info", "Running %s %s of %s (ID: %s, %s)" % \ - (tasktype, event.stats.completed + event.stats.active + event.stats.failed + 1, - event.stats.total, event.taskid, event.taskstring)) - message = {} - message["eventname"] = bb.event.getName(event) - num_of_completed = event.stats.completed + event.stats.failed - message["current"] = num_of_completed - message["total"] = event.stats.total - message["title"] = "" - message["task"] = event.taskstring - self.emit("task-started", message) - elif isinstance(event, bb.event.MultipleProviders): - self.emit("log", "info", "multiple providers are available for %s%s (%s)" \ - % (event._is_runtime and "runtime " or "", event._item, ", ".join(event._candidates))) - self.emit("log", "info", "consider defining a PREFERRED_PROVIDER entry to match %s" % (event._item)) - elif isinstance(event, bb.event.NoProvider): - msg = "" - if event._runtime: - r = "R" - else: - r = "" - - extra = '' - if not event._reasons: - if event._close_matches: - extra = ". Close matches:\n %s" % '\n '.join(event._close_matches) - - if event._dependees: - msg = "Nothing %sPROVIDES '%s' (but %s %sDEPENDS on or otherwise requires it)%s\n" % (r, event._item, ", ".join(event._dependees), r, extra) - else: - msg = "Nothing %sPROVIDES '%s'%s\n" % (r, event._item, extra) - if event._reasons: - for reason in event._reasons: - msg += ("%s\n" % reason) - self.emit("no-provider", msg) - self.emit("log", "error", msg) - elif isinstance(event, bb.event.LogExecTTY): - icon = "dialog-warning" - color = HobColors.WARNING - if self.sequential or not parent: - tree_add = self.model.append - else: - tree_add = self.model.prepend - tree_add(parent, - (None, - package, - task, - event.msg, - icon, - color, - 0)) - else: - if not isinstance(event, (bb.event.BuildBase, - bb.event.StampUpdate, - bb.event.ConfigParsed, - bb.event.RecipeParsed, - bb.event.RecipePreFinalise, - bb.runqueue.runQueueEvent, - bb.runqueue.runQueueExitWait, - bb.event.OperationStarted, - bb.event.OperationCompleted, - bb.event.OperationProgress)): - self.emit("log", "error", "Unknown event: %s" % (event.error if hasattr(event, 'error') else 'error')) - - return - - -def do_pastebin(text): - url = 'http://pastebin.com/api_public.php' - params = {'paste_code': text, 'paste_format': 'text'} - - req = urllib2.Request(url, urllib.urlencode(params)) - response = urllib2.urlopen(req) - paste_url = response.read() - - return paste_url - - -class RunningBuildTreeView (gtk.TreeView): - __gsignals__ = { - "button_press_event" : "override" - } - def __init__ (self, readonly=False, hob=False): - gtk.TreeView.__init__ (self) - self.readonly = readonly - - # The icon that indicates whether we're building or failed. - # add 'hob' flag because there has not only hob to share this code - if hob: - renderer = HobCellRendererPixbuf () - else: - renderer = gtk.CellRendererPixbuf() - col = gtk.TreeViewColumn ("Status", renderer) - col.add_attribute (renderer, "icon-name", 4) - self.append_column (col) - - # The message of the build. - # add 'hob' flag because there has not only hob to share this code - if hob: - self.message_renderer = HobWarpCellRendererText (col_number=1) - else: - self.message_renderer = gtk.CellRendererText () - self.message_column = gtk.TreeViewColumn ("Message", self.message_renderer, text=3) - self.message_column.add_attribute(self.message_renderer, 'background', 5) - self.message_renderer.set_property('editable', (not self.readonly)) - self.append_column (self.message_column) - - def do_button_press_event(self, event): - gtk.TreeView.do_button_press_event(self, event) - - if event.button == 3: - selection = super(RunningBuildTreeView, self).get_selection() - (model, it) = selection.get_selected() - if it is not None: - can_paste = model.get(it, model.COL_LOG)[0] - if can_paste == 'pastebin': - # build a simple menu with a pastebin option - menu = gtk.Menu() - menuitem = gtk.MenuItem("Copy") - menu.append(menuitem) - menuitem.connect("activate", self.clipboard_handler, (model, it)) - menuitem.show() - menuitem = gtk.MenuItem("Send log to pastebin") - menu.append(menuitem) - menuitem.connect("activate", self.pastebin_handler, (model, it)) - menuitem.show() - menu.show() - menu.popup(None, None, None, event.button, event.time) - - def _add_to_clipboard(self, clipping): - """ - Add the contents of clipping to the system clipboard. - """ - clipboard = gtk.clipboard_get() - clipboard.set_text(clipping) - clipboard.store() - - def pastebin_handler(self, widget, data): - """ - Send the log data to pastebin, then add the new paste url to the - clipboard. - """ - (model, it) = data - paste_url = do_pastebin(model.get(it, model.COL_MESSAGE)[0]) - - # @todo Provide visual feedback to the user that it is done and that - # it worked. - print paste_url - - self._add_to_clipboard(paste_url) - - def clipboard_handler(self, widget, data): - """ - """ - (model, it) = data - message = model.get(it, model.COL_MESSAGE)[0] - - self._add_to_clipboard(message) - -class BuildFailureTreeView(gtk.TreeView): - - def __init__ (self): - gtk.TreeView.__init__(self) - self.set_rules_hint(False) - self.set_headers_visible(False) - self.get_selection().set_mode(gtk.SELECTION_SINGLE) - - # The icon that indicates whether we're building or failed. - renderer = HobCellRendererPixbuf () - col = gtk.TreeViewColumn ("Status", renderer) - col.add_attribute (renderer, "icon-name", RunningBuildModel.COL_ICON) - self.append_column (col) - - # The message of the build. - self.message_renderer = HobWarpCellRendererText (col_number=1) - self.message_column = gtk.TreeViewColumn ("Message", self.message_renderer, text=RunningBuildModel.COL_MESSAGE, background=RunningBuildModel.COL_COLOR) - self.append_column (self.message_column) diff --git a/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/utils.py b/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/utils.py deleted file mode 100644 index 939864fa6..000000000 --- a/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/utils.py +++ /dev/null @@ -1,34 +0,0 @@ -# -# BitBake UI Utils -# -# Copyright (C) 2012 Intel Corporation -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 as -# published by the Free Software Foundation. -# -# 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, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -# This utility method looks for xterm or vte and return the -# frist to exist, currently we are keeping this simple, but -# we will likely move the oe.terminal implementation into -# bitbake which will allow more flexibility. - -import os -import bb - -def which_terminal(): - term = bb.utils.which(os.environ["PATH"], "xterm") - if term: - return term + " -e " - term = bb.utils.which(os.environ["PATH"], "vte") - if term: - return term + " -c " - return None diff --git a/import-layers/yocto-poky/bitbake/lib/bb/ui/depexp.py b/import-layers/yocto-poky/bitbake/lib/bb/ui/depexp.py index 240aafc3e..d879e04c0 100644 --- a/import-layers/yocto-poky/bitbake/lib/bb/ui/depexp.py +++ b/import-layers/yocto-poky/bitbake/lib/bb/ui/depexp.py @@ -18,14 +18,15 @@ # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. import sys -import gobject -import gtk -import Queue +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk, Gdk, GObject +from multiprocessing import Queue import threading -import xmlrpclib +from xmlrpc import client +import time import bb import bb.event -from bb.ui.crumbs.progressbar import HobProgressBar # Package Model (COL_PKG_NAME) = (0) @@ -35,19 +36,19 @@ from bb.ui.crumbs.progressbar import HobProgressBar (COL_DEP_TYPE, COL_DEP_PARENT, COL_DEP_PACKAGE) = (0, 1, 2) -class PackageDepView(gtk.TreeView): +class PackageDepView(Gtk.TreeView): def __init__(self, model, dep_type, label): - gtk.TreeView.__init__(self) + Gtk.TreeView.__init__(self) self.current = None self.dep_type = dep_type self.filter_model = model.filter_new() - self.filter_model.set_visible_func(self._filter) + self.filter_model.set_visible_func(self._filter, data=None) self.set_model(self.filter_model) - #self.connect("row-activated", self.on_package_activated, COL_DEP_PACKAGE) - self.append_column(gtk.TreeViewColumn(label, gtk.CellRendererText(), text=COL_DEP_PACKAGE)) + self.append_column(Gtk.TreeViewColumn(label, Gtk.CellRendererText(), text=COL_DEP_PACKAGE)) - def _filter(self, model, iter): - (this_type, package) = model.get(iter, COL_DEP_TYPE, COL_DEP_PARENT) + def _filter(self, model, iter, data): + this_type = model[iter][COL_DEP_TYPE] + package = model[iter][COL_DEP_PARENT] if this_type != self.dep_type: return False return package == self.current @@ -56,17 +57,17 @@ class PackageDepView(gtk.TreeView): self.filter_model.refilter() -class PackageReverseDepView(gtk.TreeView): +class PackageReverseDepView(Gtk.TreeView): def __init__(self, model, label): - gtk.TreeView.__init__(self) + Gtk.TreeView.__init__(self) self.current = None self.filter_model = model.filter_new() self.filter_model.set_visible_func(self._filter) self.set_model(self.filter_model) - self.append_column(gtk.TreeViewColumn(label, gtk.CellRendererText(), text=COL_DEP_PARENT)) + self.append_column(Gtk.TreeViewColumn(label, Gtk.CellRendererText(), text=COL_DEP_PARENT)) - def _filter(self, model, iter): - package = model.get_value(iter, COL_DEP_PACKAGE) + def _filter(self, model, iter, data): + package = model[iter][COL_DEP_PACKAGE] return package == self.current def set_current_package(self, package): @@ -74,50 +75,50 @@ class PackageReverseDepView(gtk.TreeView): self.filter_model.refilter() -class DepExplorer(gtk.Window): +class DepExplorer(Gtk.Window): def __init__(self): - gtk.Window.__init__(self) + Gtk.Window.__init__(self) self.set_title("Dependency Explorer") self.set_default_size(500, 500) - self.connect("delete-event", gtk.main_quit) + self.connect("delete-event", Gtk.main_quit) # Create the data models - self.pkg_model = gtk.ListStore(gobject.TYPE_STRING) - self.pkg_model.set_sort_column_id(COL_PKG_NAME, gtk.SORT_ASCENDING) - self.depends_model = gtk.ListStore(gobject.TYPE_INT, gobject.TYPE_STRING, gobject.TYPE_STRING) - self.depends_model.set_sort_column_id(COL_DEP_PACKAGE, gtk.SORT_ASCENDING) + self.pkg_model = Gtk.ListStore(GObject.TYPE_STRING) + self.pkg_model.set_sort_column_id(COL_PKG_NAME, Gtk.SortType.ASCENDING) + self.depends_model = Gtk.ListStore(GObject.TYPE_INT, GObject.TYPE_STRING, GObject.TYPE_STRING) + self.depends_model.set_sort_column_id(COL_DEP_PACKAGE, Gtk.SortType.ASCENDING) - pane = gtk.HPaned() + pane = Gtk.HPaned() pane.set_position(250) self.add(pane) # The master list of packages - scrolled = gtk.ScrolledWindow() - scrolled.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) - scrolled.set_shadow_type(gtk.SHADOW_IN) + scrolled = Gtk.ScrolledWindow() + scrolled.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) + scrolled.set_shadow_type(Gtk.ShadowType.IN) - self.pkg_treeview = gtk.TreeView(self.pkg_model) + self.pkg_treeview = Gtk.TreeView(self.pkg_model) self.pkg_treeview.get_selection().connect("changed", self.on_cursor_changed) - column = gtk.TreeViewColumn("Package", gtk.CellRendererText(), text=COL_PKG_NAME) + column = Gtk.TreeViewColumn("Package", Gtk.CellRendererText(), text=COL_PKG_NAME) self.pkg_treeview.append_column(column) pane.add1(scrolled) scrolled.add(self.pkg_treeview) - box = gtk.VBox(homogeneous=True, spacing=4) + box = Gtk.VBox(homogeneous=True, spacing=4) # Runtime Depends - scrolled = gtk.ScrolledWindow() - scrolled.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) - scrolled.set_shadow_type(gtk.SHADOW_IN) + scrolled = Gtk.ScrolledWindow() + scrolled.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) + scrolled.set_shadow_type(Gtk.ShadowType.IN) self.rdep_treeview = PackageDepView(self.depends_model, TYPE_RDEP, "Runtime Depends") self.rdep_treeview.connect("row-activated", self.on_package_activated, COL_DEP_PACKAGE) scrolled.add(self.rdep_treeview) box.add(scrolled) # Build Depends - scrolled = gtk.ScrolledWindow() - scrolled.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) - scrolled.set_shadow_type(gtk.SHADOW_IN) + scrolled = Gtk.ScrolledWindow() + scrolled.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) + scrolled.set_shadow_type(Gtk.ShadowType.IN) self.dep_treeview = PackageDepView(self.depends_model, TYPE_DEP, "Build Depends") self.dep_treeview.connect("row-activated", self.on_package_activated, COL_DEP_PACKAGE) scrolled.add(self.dep_treeview) @@ -125,9 +126,9 @@ class DepExplorer(gtk.Window): pane.add2(box) # Reverse Depends - scrolled = gtk.ScrolledWindow() - scrolled.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) - scrolled.set_shadow_type(gtk.SHADOW_IN) + scrolled = Gtk.ScrolledWindow() + scrolled.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) + scrolled.set_shadow_type(Gtk.ShadowType.IN) self.revdep_treeview = PackageReverseDepView(self.depends_model, "Reverse Depends") self.revdep_treeview.connect("row-activated", self.on_package_activated, COL_DEP_PARENT) scrolled.add(self.revdep_treeview) @@ -183,15 +184,23 @@ class gtkthread(threading.Thread): threading.Thread.__init__(self) self.setDaemon(True) self.shutdown = shutdown + if not Gtk.init_check()[0]: + sys.stderr.write("Gtk+ init failed. Make sure DISPLAY variable is set.\n") + gtkthread.quit.set() def run(self): - gobject.threads_init() - gtk.gdk.threads_init() - gtk.main() + GObject.threads_init() + Gdk.threads_init() + Gtk.main() gtkthread.quit.set() def main(server, eventHandler, params): + shutdown = 0 + + gtkgui = gtkthread(shutdown) + gtkgui.start() + try: params.updateFromServer(server) cmdline = params.parseActions() @@ -212,31 +221,24 @@ def main(server, eventHandler, params): elif ret != True: print("Error running command '%s': returned %s" % (cmdline, ret)) return 1 - except xmlrpclib.Fault as x: + except client.Fault as x: print("XMLRPC Fault getting commandline:\n %s" % x) return - try: - gtk.init_check() - except RuntimeError: - sys.stderr.write("Please set DISPLAY variable before running this command \n") + if gtkthread.quit.isSet(): return - shutdown = 0 - - gtkgui = gtkthread(shutdown) - gtkgui.start() - - gtk.gdk.threads_enter() + Gdk.threads_enter() dep = DepExplorer() - bardialog = gtk.Dialog(parent=dep, - flags=gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT) + bardialog = Gtk.Dialog(parent=dep, + flags=Gtk.DialogFlags.MODAL|Gtk.DialogFlags.DESTROY_WITH_PARENT) bardialog.set_default_size(400, 50) - pbar = HobProgressBar() - bardialog.vbox.pack_start(pbar) + box = bardialog.get_content_area() + pbar = Gtk.ProgressBar() + box.pack_start(pbar, True, True, 0) bardialog.show_all() - bardialog.connect("delete-event", gtk.main_quit) - gtk.gdk.threads_leave() + bardialog.connect("delete-event", Gtk.main_quit) + Gdk.threads_leave() progress_total = 0 while True: @@ -253,53 +255,76 @@ def main(server, eventHandler, params): if isinstance(event, bb.event.CacheLoadStarted): progress_total = event.total - gtk.gdk.threads_enter() + Gdk.threads_enter() bardialog.set_title("Loading Cache") - pbar.update(0) - gtk.gdk.threads_leave() + pbar.set_fraction(0.0) + Gdk.threads_leave() if isinstance(event, bb.event.CacheLoadProgress): x = event.current - gtk.gdk.threads_enter() - pbar.update(x * 1.0 / progress_total) - pbar.set_title('') - gtk.gdk.threads_leave() + Gdk.threads_enter() + pbar.set_fraction(x * 1.0 / progress_total) + Gdk.threads_leave() continue if isinstance(event, bb.event.CacheLoadCompleted): - bardialog.hide() continue if isinstance(event, bb.event.ParseStarted): progress_total = event.total if progress_total == 0: continue - gtk.gdk.threads_enter() - pbar.update(0) + Gdk.threads_enter() + pbar.set_fraction(0.0) bardialog.set_title("Processing recipes") - - gtk.gdk.threads_leave() + Gdk.threads_leave() if isinstance(event, bb.event.ParseProgress): x = event.current - gtk.gdk.threads_enter() - pbar.update(x * 1.0 / progress_total) - pbar.set_title('') - gtk.gdk.threads_leave() + Gdk.threads_enter() + pbar.set_fraction(x * 1.0 / progress_total) + Gdk.threads_leave() continue if isinstance(event, bb.event.ParseCompleted): - bardialog.hide() + Gdk.threads_enter() + bardialog.set_title("Generating dependency tree") + Gdk.threads_leave() continue if isinstance(event, bb.event.DepTreeGenerated): - gtk.gdk.threads_enter() + Gdk.threads_enter() + bardialog.hide() dep.parse(event._depgraph) - gtk.gdk.threads_leave() + Gdk.threads_leave() if isinstance(event, bb.command.CommandCompleted): continue + if isinstance(event, bb.event.NoProvider): + if event._runtime: + r = "R" + else: + r = "" + + extra = '' + if not event._reasons: + if event._close_matches: + extra = ". Close matches:\n %s" % '\n '.join(event._close_matches) + + if event._dependees: + print("Nothing %sPROVIDES '%s' (but %s %sDEPENDS on or otherwise requires it)%s" % r, event._item, ", ".join(event._dependees), r, extra) + else: + print("Nothing %sPROVIDES '%s'%s" % (r, event._item, extra)) + if event._reasons: + for reason in event._reasons: + print(reason) + + _, error = server.runCommand(["stateShutdown"]) + if error: + print('Unable to cleanly shutdown: %s' % error) + break + if isinstance(event, bb.command.CommandFailed): print("Command execution failed: %s" % event.error) return event.exitcode diff --git a/import-layers/yocto-poky/bitbake/lib/bb/ui/goggle.py b/import-layers/yocto-poky/bitbake/lib/bb/ui/goggle.py deleted file mode 100644 index f4ee7b41a..000000000 --- a/import-layers/yocto-poky/bitbake/lib/bb/ui/goggle.py +++ /dev/null @@ -1,121 +0,0 @@ -# -# BitBake Graphical GTK User Interface -# -# Copyright (C) 2008 Intel Corporation -# -# Authored by Rob Bradford <rob@linux.intel.com> -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 as -# published by the Free Software Foundation. -# -# 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, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -import gobject -import gtk -import xmlrpclib -from bb.ui.crumbs.runningbuild import RunningBuildTreeView, RunningBuild -from bb.ui.crumbs.progress import ProgressBar - -import Queue - - -def event_handle_idle_func (eventHandler, build, pbar): - - # Consume as many messages as we can in the time available to us - event = eventHandler.getEvent() - while event: - build.handle_event (event, pbar) - event = eventHandler.getEvent() - - return True - -def scroll_tv_cb (model, path, iter, view): - view.scroll_to_cell (path) - - -# @todo hook these into the GUI so the user has feedback... -def running_build_failed_cb (running_build): - pass - - -def running_build_succeeded_cb (running_build): - pass - - -class MainWindow (gtk.Window): - def __init__ (self): - gtk.Window.__init__ (self, gtk.WINDOW_TOPLEVEL) - - # Setup tree view and the scrolled window - scrolled_window = gtk.ScrolledWindow () - self.add (scrolled_window) - self.cur_build_tv = RunningBuildTreeView() - self.connect("delete-event", gtk.main_quit) - self.set_default_size(640, 480) - scrolled_window.add (self.cur_build_tv) - - -def main (server, eventHandler, params): - gobject.threads_init() - gtk.gdk.threads_init() - - window = MainWindow () - window.show_all () - pbar = ProgressBar(window) - pbar.connect("delete-event", gtk.main_quit) - - # Create the object for the current build - running_build = RunningBuild () - window.cur_build_tv.set_model (running_build.model) - running_build.model.connect("row-inserted", scroll_tv_cb, window.cur_build_tv) - running_build.connect ("build-succeeded", running_build_succeeded_cb) - running_build.connect ("build-failed", running_build_failed_cb) - - try: - params.updateFromServer(server) - cmdline = params.parseActions() - if not cmdline: - print("Nothing to do. Use 'bitbake world' to build everything, or run 'bitbake --help' for usage information.") - return 1 - if 'msg' in cmdline and cmdline['msg']: - logger.error(cmdline['msg']) - return 1 - cmdline = cmdline['action'] - ret, error = server.runCommand(cmdline) - if error: - print("Error running command '%s': %s" % (cmdline, error)) - return 1 - elif ret != True: - print("Error running command '%s': returned %s" % (cmdline, ret)) - return 1 - except xmlrpclib.Fault as x: - print("XMLRPC Fault getting commandline:\n %s" % x) - return 1 - - # Use a timeout function for probing the event queue to find out if we - # have a message waiting for us. - gobject.timeout_add (100, - event_handle_idle_func, - eventHandler, - running_build, - pbar) - - try: - gtk.main() - except EnvironmentError as ioerror: - # ignore interrupted io - if ioerror.args[0] == 4: - pass - except KeyboardInterrupt: - pass - finally: - server.runCommand(["stateForceShutdown"]) - diff --git a/import-layers/yocto-poky/bitbake/lib/bb/ui/knotty.py b/import-layers/yocto-poky/bitbake/lib/bb/ui/knotty.py index 268562770..948f52769 100644 --- a/import-layers/yocto-poky/bitbake/lib/bb/ui/knotty.py +++ b/import-layers/yocto-poky/bitbake/lib/bb/ui/knotty.py @@ -22,7 +22,7 @@ from __future__ import division import os import sys -import xmlrpclib +import xmlrpc.client as xmlrpclib import logging import progressbar import signal @@ -32,6 +32,7 @@ import fcntl import struct import copy import atexit + from bb.ui import uihelper featureSet = [bb.cooker.CookerFeatures.SEND_SANITYEVENTS] @@ -40,34 +41,59 @@ logger = logging.getLogger("BitBake") interactive = sys.stdout.isatty() class BBProgress(progressbar.ProgressBar): - def __init__(self, msg, maxval): + def __init__(self, msg, maxval, widgets=None, extrapos=-1, resize_handler=None): self.msg = msg - widgets = [progressbar.Percentage(), ' ', progressbar.Bar(), ' ', - progressbar.ETA()] - - try: + self.extrapos = extrapos + if not widgets: + widgets = [progressbar.Percentage(), ' ', progressbar.Bar(), ' ', + progressbar.ETA()] + self.extrapos = 4 + + if resize_handler: + self._resize_default = resize_handler + else: self._resize_default = signal.getsignal(signal.SIGWINCH) - except: - self._resize_default = None progressbar.ProgressBar.__init__(self, maxval, [self.msg + ": "] + widgets, fd=sys.stdout) - def _handle_resize(self, signum, frame): + def _handle_resize(self, signum=None, frame=None): progressbar.ProgressBar._handle_resize(self, signum, frame) if self._resize_default: self._resize_default(signum, frame) + def finish(self): progressbar.ProgressBar.finish(self) if self._resize_default: signal.signal(signal.SIGWINCH, self._resize_default) + def setmessage(self, msg): + self.msg = msg + self.widgets[0] = msg + + def setextra(self, extra): + if self.extrapos > -1: + if extra: + extrastr = str(extra) + if extrastr[0] != ' ': + extrastr = ' ' + extrastr + if extrastr[-1] != ' ': + extrastr += ' ' + else: + extrastr = ' ' + self.widgets[self.extrapos] = extrastr + + def _need_update(self): + # We always want the bar to print when update() is called + return True + class NonInteractiveProgress(object): fobj = sys.stdout def __init__(self, msg, maxval): self.msg = msg self.maxval = maxval + self.finished = False - def start(self): + def start(self, update=True): self.fobj.write("%s..." % self.msg) self.fobj.flush() return self @@ -76,8 +102,11 @@ class NonInteractiveProgress(object): pass def finish(self): + if self.finished: + return self.fobj.write("done.\n") self.fobj.flush() + self.finished = True def new_progress(msg, maxval): if interactive: @@ -134,7 +163,7 @@ class TerminalFilter(object): cr = (25, 80) return cr - def __init__(self, main, helper, console, errconsole, format): + def __init__(self, main, helper, console, errconsole, format, quiet): self.main = main self.helper = helper self.cuu = None @@ -142,6 +171,8 @@ class TerminalFilter(object): self.interactive = sys.stdout.isatty() self.footer_present = False self.lastpids = [] + self.lasttime = None + self.quiet = quiet if not self.interactive: return @@ -181,11 +212,14 @@ class TerminalFilter(object): console.addFilter(InteractConsoleLogFilter(self, format)) errconsole.addFilter(InteractConsoleLogFilter(self, format)) + self.main_progress = None + def clearFooter(self): if self.footer_present: lines = self.footer_present - sys.stdout.write(self.curses.tparm(self.cuu, lines)) - sys.stdout.write(self.curses.tparm(self.ed)) + sys.stdout.buffer.write(self.curses.tparm(self.cuu, lines)) + sys.stdout.buffer.write(self.curses.tparm(self.ed)) + sys.stdout.flush() self.footer_present = False def updateFooter(self): @@ -194,28 +228,81 @@ class TerminalFilter(object): activetasks = self.helper.running_tasks failedtasks = self.helper.failed_tasks runningpids = self.helper.running_pids - if self.footer_present and (self.lastcount == self.helper.tasknumber_current) and (self.lastpids == runningpids): + currenttime = time.time() + if not self.lasttime or (currenttime - self.lasttime > 5): + self.helper.needUpdate = True + self.lasttime = currenttime + if self.footer_present and not self.helper.needUpdate: return + self.helper.needUpdate = False if self.footer_present: self.clearFooter() if (not self.helper.tasknumber_total or self.helper.tasknumber_current == self.helper.tasknumber_total) and not len(activetasks): return tasks = [] for t in runningpids: - tasks.append("%s (pid %s)" % (activetasks[t]["title"], t)) + progress = activetasks[t].get("progress", None) + if progress is not None: + pbar = activetasks[t].get("progressbar", None) + rate = activetasks[t].get("rate", None) + start_time = activetasks[t].get("starttime", None) + if not pbar or pbar.bouncing != (progress < 0): + if progress < 0: + pbar = BBProgress("0: %s (pid %s) " % (activetasks[t]["title"], t), 100, widgets=[progressbar.BouncingSlider(), ''], extrapos=2, resize_handler=self.sigwinch_handle) + pbar.bouncing = True + else: + pbar = BBProgress("0: %s (pid %s) " % (activetasks[t]["title"], t), 100, widgets=[progressbar.Percentage(), ' ', progressbar.Bar(), ''], extrapos=4, resize_handler=self.sigwinch_handle) + pbar.bouncing = False + activetasks[t]["progressbar"] = pbar + tasks.append((pbar, progress, rate, start_time)) + else: + start_time = activetasks[t].get("starttime", None) + if start_time: + tasks.append("%s - %ds (pid %s)" % (activetasks[t]["title"], currenttime - start_time, t)) + else: + tasks.append("%s (pid %s)" % (activetasks[t]["title"], t)) if self.main.shutdown: content = "Waiting for %s running tasks to finish:" % len(activetasks) - elif not len(activetasks): - content = "No currently running tasks (%s of %s)" % (self.helper.tasknumber_current, self.helper.tasknumber_total) + print(content) else: - content = "Currently %s running tasks (%s of %s):" % (len(activetasks), self.helper.tasknumber_current, self.helper.tasknumber_total) - print(content) + if self.quiet: + content = "Running tasks (%s of %s)" % (self.helper.tasknumber_current, self.helper.tasknumber_total) + elif not len(activetasks): + content = "No currently running tasks (%s of %s)" % (self.helper.tasknumber_current, self.helper.tasknumber_total) + else: + content = "Currently %2s running tasks (%s of %s)" % (len(activetasks), self.helper.tasknumber_current, self.helper.tasknumber_total) + maxtask = self.helper.tasknumber_total + if not self.main_progress or self.main_progress.maxval != maxtask: + widgets = [' ', progressbar.Percentage(), ' ', progressbar.Bar()] + self.main_progress = BBProgress("Running tasks", maxtask, widgets=widgets, resize_handler=self.sigwinch_handle) + self.main_progress.start(False) + self.main_progress.setmessage(content) + progress = self.helper.tasknumber_current - 1 + if progress < 0: + progress = 0 + content = self.main_progress.update(progress) + print('') lines = 1 + int(len(content) / (self.columns + 1)) - for tasknum, task in enumerate(tasks[:(self.rows - 2)]): - content = "%s: %s" % (tasknum, task) - print(content) - lines = lines + 1 + int(len(content) / (self.columns + 1)) + if not self.quiet: + for tasknum, task in enumerate(tasks[:(self.rows - 2)]): + if isinstance(task, tuple): + pbar, progress, rate, start_time = task + if not pbar.start_time: + pbar.start(False) + if start_time: + pbar.start_time = start_time + pbar.setmessage('%s:%s' % (tasknum, pbar.msg.split(':', 1)[1])) + if progress > -1: + pbar.setextra(rate) + content = pbar.update(progress) + else: + content = pbar.update(1) + print('') + else: + content = "%s: %s" % (tasknum, task) + print(content) + lines = lines + 1 + int(len(content) / (self.columns + 1)) self.footer_present = lines self.lastpids = runningpids[:] self.lastcount = self.helper.tasknumber_current @@ -248,7 +335,8 @@ _evt_list = [ "bb.runqueue.runQueueExitWait", "bb.event.LogExecTTY", "logging.Lo "bb.command.CommandExit", "bb.command.CommandCompleted", "bb.cooker.CookerExit", "bb.event.MultipleProviders", "bb.event.NoProvider", "bb.runqueue.sceneQueueTaskStarted", "bb.runqueue.runQueueTaskStarted", "bb.runqueue.runQueueTaskFailed", "bb.runqueue.sceneQueueTaskFailed", - "bb.event.BuildBase", "bb.build.TaskStarted", "bb.build.TaskSucceeded", "bb.build.TaskFailedSilent"] + "bb.event.BuildBase", "bb.build.TaskStarted", "bb.build.TaskSucceeded", "bb.build.TaskFailedSilent", + "bb.build.TaskProgress", "bb.event.ProcessStarted", "bb.event.ProcessProgress", "bb.event.ProcessFinished"] def main(server, eventHandler, params, tf = TerminalFilter): @@ -265,7 +353,10 @@ def main(server, eventHandler, params, tf = TerminalFilter): errconsole = logging.StreamHandler(sys.stderr) format_str = "%(levelname)s: %(message)s" format = bb.msg.BBLogFormatter(format_str) - bb.msg.addDefaultlogFilter(console, bb.msg.BBLogFilterStdOut) + if params.options.quiet: + bb.msg.addDefaultlogFilter(console, bb.msg.BBLogFilterStdOut, bb.msg.BBLogFormatter.WARNING) + else: + bb.msg.addDefaultlogFilter(console, bb.msg.BBLogFilterStdOut) bb.msg.addDefaultlogFilter(errconsole, bb.msg.BBLogFilterStdErr) console.setFormatter(format) errconsole.setFormatter(format) @@ -278,6 +369,7 @@ def main(server, eventHandler, params, tf = TerminalFilter): server.terminateServer() return + consolelog = None if consolelogfile and not params.options.show_environment and not params.options.show_versions: bb.utils.mkdirhier(os.path.dirname(consolelogfile)) conlogformat = bb.msg.BBLogFormatter(format_str) @@ -285,6 +377,12 @@ def main(server, eventHandler, params, tf = TerminalFilter): bb.msg.addDefaultlogFilter(consolelog) consolelog.setFormatter(conlogformat) logger.addHandler(consolelog) + loglink = os.path.join(os.path.dirname(consolelogfile), 'console-latest.log') + bb.utils.remove(loglink) + try: + os.symlink(os.path.basename(consolelogfile), loglink) + except OSError: + pass llevel, debug_domains = bb.msg.constructLogOptions() server.runCommand(["setEventMask", server.getEventHandle(), llevel, debug_domains, _evt_list]) @@ -321,7 +419,7 @@ def main(server, eventHandler, params, tf = TerminalFilter): warnings = 0 taskfailures = [] - termfilter = tf(main, helper, console, errconsole, format) + termfilter = tf(main, helper, console, errconsole, format, params.options.quiet) atexit.register(termfilter.finish) while True: @@ -350,7 +448,7 @@ def main(server, eventHandler, params, tf = TerminalFilter): tries -= 1 if tries: continue - logger.warn(event.msg) + logger.warning(event.msg) continue if isinstance(event, logging.LogRecord): @@ -377,7 +475,7 @@ def main(server, eventHandler, params, tf = TerminalFilter): continue if isinstance(event, bb.build.TaskFailedSilent): - logger.warn("Logfile for failed setscene task is %s" % event.logfile) + logger.warning("Logfile for failed setscene task is %s" % event.logfile) continue if isinstance(event, bb.build.TaskFailed): return_value = 1 @@ -413,15 +511,19 @@ def main(server, eventHandler, params, tf = TerminalFilter): parseprogress = new_progress("Parsing recipes", event.total).start() continue if isinstance(event, bb.event.ParseProgress): - parseprogress.update(event.current) + if parseprogress: + parseprogress.update(event.current) + else: + bb.warn("Got ParseProgress event for parsing that never started?") continue if isinstance(event, bb.event.ParseCompleted): if not parseprogress: continue - parseprogress.finish() - print(("Parsing of %d .bb files complete (%d cached, %d parsed). %d targets, %d skipped, %d masked, %d errors." - % ( event.total, event.cached, event.parsed, event.virtuals, event.skipped, event.masked, event.errors))) + pasreprogress = None + if not params.options.quiet: + print(("Parsing of %d .bb files complete (%d cached, %d parsed). %d targets, %d skipped, %d masked, %d errors." + % ( event.total, event.cached, event.parsed, event.virtuals, event.skipped, event.masked, event.errors))) continue if isinstance(event, bb.event.CacheLoadStarted): @@ -432,7 +534,8 @@ def main(server, eventHandler, params, tf = TerminalFilter): continue if isinstance(event, bb.event.CacheLoadCompleted): cacheprogress.finish() - print("Loaded %d entries from dependency cache." % event.num_entries) + if not params.options.quiet: + print("Loaded %d entries from dependency cache." % event.num_entries) continue if isinstance(event, bb.command.CommandFailed): @@ -494,28 +597,44 @@ def main(server, eventHandler, params, tf = TerminalFilter): tasktype = 'noexec task' else: tasktype = 'task' - logger.info("Running %s %s of %s (ID: %s, %s)", + logger.info("Running %s %d of %d (%s)", tasktype, event.stats.completed + event.stats.active + event.stats.failed + 1, - event.stats.total, event.taskid, event.taskstring) + event.stats.total, event.taskstring) continue if isinstance(event, bb.runqueue.runQueueTaskFailed): return_value = 1 taskfailures.append(event.taskstring) - logger.error("Task %s (%s) failed with exit code '%s'", - event.taskid, event.taskstring, event.exitcode) + logger.error("Task (%s) failed with exit code '%s'", + event.taskstring, event.exitcode) continue if isinstance(event, bb.runqueue.sceneQueueTaskFailed): - logger.warn("Setscene task %s (%s) failed with exit code '%s' - real task will be run instead", - event.taskid, event.taskstring, event.exitcode) + logger.warning("Setscene task (%s) failed with exit code '%s' - real task will be run instead", + event.taskstring, event.exitcode) continue if isinstance(event, bb.event.DepTreeGenerated): continue + if isinstance(event, bb.event.ProcessStarted): + parseprogress = new_progress(event.processname, event.total) + parseprogress.start(False) + continue + if isinstance(event, bb.event.ProcessProgress): + if parseprogress: + parseprogress.update(event.progress) + else: + bb.warn("Got ProcessProgress event for someting that never started?") + continue + if isinstance(event, bb.event.ProcessFinished): + if parseprogress: + parseprogress.finish() + parseprogress = None + continue + # ignore if isinstance(event, (bb.event.BuildBase, bb.event.MetadataEvent, @@ -527,7 +646,8 @@ def main(server, eventHandler, params, tf = TerminalFilter): bb.event.OperationStarted, bb.event.OperationCompleted, bb.event.OperationProgress, - bb.event.DiskFull)): + bb.event.DiskFull, + bb.build.TaskProgress)): continue logger.error("Unknown event: %s", event) @@ -567,6 +687,7 @@ def main(server, eventHandler, params, tf = TerminalFilter): main.shutdown = 2 return_value = 1 try: + termfilter.clearFooter() summary = "" if taskfailures: summary += pluralise("\nSummary: %s task failed:", @@ -579,7 +700,7 @@ def main(server, eventHandler, params, tf = TerminalFilter): if return_value and errors: summary += pluralise("\nSummary: There was %s ERROR message shown, returning a non-zero exit code.", "\nSummary: There were %s ERROR messages shown, returning a non-zero exit code.", errors) - if summary: + if summary and not params.options.quiet: print(summary) if interrupted: @@ -591,4 +712,8 @@ def main(server, eventHandler, params, tf = TerminalFilter): if e.errno == errno.EPIPE: pass + if consolelog: + logger.removeHandler(consolelog) + consolelog.close() + return return_value diff --git a/import-layers/yocto-poky/bitbake/lib/bb/ui/ncurses.py b/import-layers/yocto-poky/bitbake/lib/bb/ui/ncurses.py index 9589a77d7..d81e4138b 100644 --- a/import-layers/yocto-poky/bitbake/lib/bb/ui/ncurses.py +++ b/import-layers/yocto-poky/bitbake/lib/bb/ui/ncurses.py @@ -45,7 +45,7 @@ """ -from __future__ import division + import logging import os, sys, itertools, time, subprocess @@ -55,7 +55,7 @@ except ImportError: sys.exit("FATAL: The ncurses ui could not load the required curses python module.") import bb -import xmlrpclib +import xmlrpc.client from bb import ui from bb.ui import uihelper @@ -252,7 +252,7 @@ class NCursesUI: elif ret != True: print("Couldn't get default commandlind! %s" % ret) return - except xmlrpclib.Fault as x: + except xmlrpc.client.Fault as x: print("XMLRPC Fault getting commandline:\n %s" % x) return @@ -331,7 +331,7 @@ class NCursesUI: taw.setText(0, 0, "") if activetasks: taw.appendText("Active Tasks:\n") - for task in activetasks.itervalues(): + for task in activetasks.values(): taw.appendText(task["title"] + '\n') if failedtasks: taw.appendText("Failed Tasks:\n") diff --git a/import-layers/yocto-poky/bitbake/lib/bb/ui/toasterui.py b/import-layers/yocto-poky/bitbake/lib/bb/ui/toasterui.py index 6bf4c1f03..9808f6bc8 100644 --- a/import-layers/yocto-poky/bitbake/lib/bb/ui/toasterui.py +++ b/import-layers/yocto-poky/bitbake/lib/bb/ui/toasterui.py @@ -39,7 +39,7 @@ import os # module properties for UI modules are read by bitbake and the contract should not be broken -featureSet = [bb.cooker.CookerFeatures.HOB_EXTRA_CACHES, bb.cooker.CookerFeatures.SEND_DEPENDS_TREE, bb.cooker.CookerFeatures.BASEDATASTORE_TRACKING, bb.cooker.CookerFeatures.SEND_SANITYEVENTS] +featureSet = [bb.cooker.CookerFeatures.HOB_EXTRA_CACHES, bb.cooker.CookerFeatures.BASEDATASTORE_TRACKING, bb.cooker.CookerFeatures.SEND_SANITYEVENTS] logger = logging.getLogger("ToasterLogger") interactive = sys.stdout.isatty() @@ -102,6 +102,7 @@ _evt_list = [ "bb.command.CommandExit", "bb.command.CommandFailed", "bb.cooker.CookerExit", + "bb.event.BuildInit", "bb.event.BuildCompleted", "bb.event.BuildStarted", "bb.event.CacheLoadCompleted", @@ -115,6 +116,7 @@ _evt_list = [ "bb.event.NoProvider", "bb.event.ParseCompleted", "bb.event.ParseProgress", + "bb.event.ParseStarted", "bb.event.RecipeParsed", "bb.event.SanityCheck", "bb.event.SanityCheckPassed", @@ -163,7 +165,7 @@ def main(server, eventHandler, params): inheritlist, _ = server.runCommand(["getVariable", "INHERIT"]) if not "buildhistory" in inheritlist.split(" "): - logger.warn("buildhistory is not enabled. Please enable INHERIT += \"buildhistory\" to see image details.") + logger.warning("buildhistory is not enabled. Please enable INHERIT += \"buildhistory\" to see image details.") build_history_enabled = False if not params.observe_only: @@ -231,19 +233,35 @@ def main(server, eventHandler, params): # pylint: disable=protected-access # the code will look into the protected variables of the event; no easy way around this - # we treat ParseStarted as the first event of toaster-triggered - # builds; that way we get the Build Configuration included in the log - # and any errors that occur before BuildStarted is fired if isinstance(event, bb.event.ParseStarted): if not (build_log and build_log_file_path): build_log, build_log_file_path = _open_build_log(log_dir) + + buildinfohelper.store_started_build() + buildinfohelper.save_build_log_file_path(build_log_file_path) + buildinfohelper.set_recipes_to_parse(event.total) continue - if isinstance(event, bb.event.BuildStarted): + # create a build object in buildinfohelper from either BuildInit + # (if available) or BuildStarted (for jethro and previous versions) + if isinstance(event, (bb.event.BuildStarted, bb.event.BuildInit)): if not (build_log and build_log_file_path): build_log, build_log_file_path = _open_build_log(log_dir) - buildinfohelper.store_started_build(event, build_log_file_path) + buildinfohelper.save_build_targets(event) + buildinfohelper.save_build_log_file_path(build_log_file_path) + + # get additional data from BuildStarted + if isinstance(event, bb.event.BuildStarted): + buildinfohelper.save_build_layers_and_variables() + continue + + if isinstance(event, bb.event.ParseProgress): + buildinfohelper.set_recipes_parsed(event.current) + continue + + if isinstance(event, bb.event.ParseCompleted): + buildinfohelper.set_recipes_parsed(event.total) continue if isinstance(event, (bb.build.TaskStarted, bb.build.TaskSucceeded, bb.build.TaskFailedSilent)): @@ -289,10 +307,6 @@ def main(server, eventHandler, params): # timing and error informations from the parsing phase in Toaster if isinstance(event, (bb.event.SanityCheckPassed, bb.event.SanityCheck)): continue - if isinstance(event, bb.event.ParseProgress): - continue - if isinstance(event, bb.event.ParseCompleted): - continue if isinstance(event, bb.event.CacheLoadStarted): continue if isinstance(event, bb.event.CacheLoadProgress): @@ -344,8 +358,8 @@ def main(server, eventHandler, params): if isinstance(event, bb.runqueue.runQueueTaskFailed): buildinfohelper.update_and_store_task(event) taskfailures.append(event.taskstring) - logger.error("Task %s (%s) failed with exit code '%s'", - event.taskid, event.taskstring, event.exitcode) + logger.error("Task (%s) failed with exit code '%s'", + event.taskstring, event.exitcode) continue if isinstance(event, (bb.runqueue.sceneQueueTaskCompleted, bb.runqueue.sceneQueueTaskFailed)): @@ -363,6 +377,9 @@ def main(server, eventHandler, params): errors += 1 errorcode = 1 logger.error("Command execution failed: %s", event.error) + elif isinstance(event, bb.event.BuildCompleted): + buildinfohelper.scan_image_artifacts() + buildinfohelper.clone_required_sdk_artifacts() # turn off logging to the current build log _close_build_log(build_log) @@ -410,18 +427,18 @@ def main(server, eventHandler, params): buildinfohelper.store_target_package_data(event) elif event.type == "MissedSstate": buildinfohelper.store_missed_state_tasks(event) - elif event.type == "ImageFileSize": - buildinfohelper.update_target_image_file(event) - elif event.type == "ArtifactFileSize": - buildinfohelper.update_artifact_image_file(event) - elif event.type == "LicenseManifestPath": - buildinfohelper.store_license_manifest_path(event) + elif event.type == "SDKArtifactInfo": + buildinfohelper.scan_sdk_artifacts(event) elif event.type == "SetBRBE": buildinfohelper.brbe = buildinfohelper._get_data_from_event(event) + elif event.type == "TaskArtifacts": + # not implemented yet + # see https://bugzilla.yoctoproject.org/show_bug.cgi?id=10283 for details + pass elif event.type == "OSErrorException": logger.error(event) else: - logger.error("Unprocessed MetadataEvent %s ", str(event)) + logger.error("Unprocessed MetadataEvent %s", event.type) continue if isinstance(event, bb.cooker.CookerExit): @@ -433,15 +450,33 @@ def main(server, eventHandler, params): buildinfohelper.store_dependency_information(event) continue - logger.warn("Unknown event: %s", event) + logger.warning("Unknown event: %s", event) return_value += 1 except EnvironmentError as ioerror: - # ignore interrupted io - if ioerror.args[0] == 4: - pass + logger.warning("EnvironmentError: %s" % ioerror) + # ignore interrupted io system calls + if ioerror.args[0] == 4: # errno 4 is EINTR + logger.warning("Skipped EINTR: %s" % ioerror) + else: + raise except KeyboardInterrupt: - main.shutdown = 1 + if params.observe_only: + print("\nKeyboard Interrupt, exiting observer...") + main.shutdown = 2 + if not params.observe_only and main.shutdown == 1: + print("\nSecond Keyboard Interrupt, stopping...\n") + _, error = server.runCommand(["stateForceShutdown"]) + if error: + logger.error("Unable to cleanly stop: %s" % error) + if not params.observe_only and main.shutdown == 0: + print("\nKeyboard Interrupt, closing down...\n") + interrupted = True + _, error = server.runCommand(["stateShutdown"]) + if error: + logger.error("Unable to cleanly shutdown: %s" % error) + buildinfohelper.cancel_cli_build() + main.shutdown = main.shutdown + 1 except Exception as e: # print errors to log import traceback @@ -461,5 +496,5 @@ def main(server, eventHandler, params): if interrupted and return_value == 0: return_value += 1 - logger.warn("Return value is %d", return_value) + logger.warning("Return value is %d", return_value) return return_value diff --git a/import-layers/yocto-poky/bitbake/lib/bb/ui/uievent.py b/import-layers/yocto-poky/bitbake/lib/bb/ui/uievent.py index df093c53c..9542b911c 100644 --- a/import-layers/yocto-poky/bitbake/lib/bb/ui/uievent.py +++ b/import-layers/yocto-poky/bitbake/lib/bb/ui/uievent.py @@ -25,7 +25,7 @@ client/server deadlocks. """ import socket, threading, pickle, collections -from SimpleXMLRPCServer import SimpleXMLRPCServer, SimpleXMLRPCRequestHandler +from xmlrpc.server import SimpleXMLRPCServer, SimpleXMLRPCRequestHandler class BBUIEventQueue: def __init__(self, BBServer, clientinfo=("localhost, 0")): @@ -116,7 +116,7 @@ class BBUIEventQueue: self.server.handle_request() except Exception as e: import traceback - logger.error("BBUIEventQueue.startCallbackHandler: Exception while trying to handle request: %s\n%s" % (e, traceback.format_exc(e))) + logger.error("BBUIEventQueue.startCallbackHandler: Exception while trying to handle request: %s\n%s" % (e, traceback.format_exc())) self.server.server_close() @@ -137,7 +137,7 @@ class UIXMLRPCServer (SimpleXMLRPCServer): SimpleXMLRPCServer.__init__( self, interface, requestHandler=SimpleXMLRPCRequestHandler, - logRequests=False, allow_none=True) + logRequests=False, allow_none=True, use_builtin_types=True) def get_request(self): while not self.quit: diff --git a/import-layers/yocto-poky/bitbake/lib/bb/ui/uihelper.py b/import-layers/yocto-poky/bitbake/lib/bb/ui/uihelper.py index db70b763f..fda7cc2c7 100644 --- a/import-layers/yocto-poky/bitbake/lib/bb/ui/uihelper.py +++ b/import-layers/yocto-poky/bitbake/lib/bb/ui/uihelper.py @@ -18,6 +18,7 @@ # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. import bb.build +import time class BBUIHelper: def __init__(self): @@ -31,29 +32,33 @@ class BBUIHelper: def eventHandler(self, event): if isinstance(event, bb.build.TaskStarted): - self.running_tasks[event.pid] = { 'title' : "%s %s" % (event._package, event._task) } + self.running_tasks[event.pid] = { 'title' : "%s %s" % (event._package, event._task), 'starttime' : time.time() } self.running_pids.append(event.pid) self.needUpdate = True - if isinstance(event, bb.build.TaskSucceeded): + elif isinstance(event, bb.build.TaskSucceeded): del self.running_tasks[event.pid] self.running_pids.remove(event.pid) self.needUpdate = True - if isinstance(event, bb.build.TaskFailedSilent): + elif isinstance(event, bb.build.TaskFailedSilent): del self.running_tasks[event.pid] self.running_pids.remove(event.pid) # Don't add to the failed tasks list since this is e.g. a setscene task failure self.needUpdate = True - if isinstance(event, bb.build.TaskFailed): + elif isinstance(event, bb.build.TaskFailed): del self.running_tasks[event.pid] self.running_pids.remove(event.pid) self.failed_tasks.append( { 'title' : "%s %s" % (event._package, event._task)}) self.needUpdate = True - if isinstance(event, bb.runqueue.runQueueTaskStarted) or isinstance(event, bb.runqueue.sceneQueueTaskStarted): + elif isinstance(event, bb.runqueue.runQueueTaskStarted) or isinstance(event, bb.runqueue.sceneQueueTaskStarted): self.tasknumber_current = event.stats.completed + event.stats.active + event.stats.failed + 1 self.tasknumber_total = event.stats.total self.needUpdate = True + elif isinstance(event, bb.build.TaskProgress): + if event.pid > 0: + self.running_tasks[event.pid]['progress'] = event.progress + self.running_tasks[event.pid]['rate'] = event.rate + self.needUpdate = True def getTasks(self): self.needUpdate = False return (self.running_tasks, self.failed_tasks) - diff --git a/import-layers/yocto-poky/bitbake/lib/bb/utils.py b/import-layers/yocto-poky/bitbake/lib/bb/utils.py index 3544bbe17..729848a1c 100644 --- a/import-layers/yocto-poky/bitbake/lib/bb/utils.py +++ b/import-layers/yocto-poky/bitbake/lib/bb/utils.py @@ -27,6 +27,8 @@ import bb import bb.msg import multiprocessing import fcntl +import imp +import itertools import subprocess import glob import fnmatch @@ -34,12 +36,15 @@ import traceback import errno import signal import ast -from commands import getstatusoutput +import collections +import copy +from subprocess import getstatusoutput from contextlib import contextmanager from ctypes import cdll - logger = logging.getLogger("BitBake.Util") +python_extensions = [e for e, _, _ in imp.get_suffixes()] + def clean_context(): return { @@ -71,7 +76,7 @@ def explode_version(s): r.append((0, int(m.group(1)))) s = m.group(2) continue - if s[0] in string.letters: + if s[0] in string.ascii_letters: m = alpha_regexp.match(s) r.append((1, m.group(1))) s = m.group(2) @@ -188,7 +193,7 @@ def explode_dep_versions2(s): "DEPEND1 (optional version) DEPEND2 (optional version) ..." and return a dictionary of dependencies and versions. """ - r = {} + r = collections.OrderedDict() l = s.replace(",", "").split() lastdep = None lastcmp = "" @@ -245,6 +250,7 @@ def explode_dep_versions2(s): if not (i in r and r[i]): r[lastdep] = [] + r = collections.OrderedDict(sorted(r.items(), key=lambda x: x[0])) return r def explode_dep_versions(s): @@ -369,6 +375,12 @@ def _print_exception(t, value, tb, realfile, text, context): level = level + 1 error.append("Exception: %s" % ''.join(exception)) + + # If the exception is from spwaning a task, let's be helpful and display + # the output (which hopefully includes stderr). + if isinstance(value, subprocess.CalledProcessError): + error.append("Subprocess output:") + error.append(value.output.decode("utf-8", errors="ignore")) finally: logger.error("\n".join(error)) @@ -403,8 +415,13 @@ def better_exec(code, context, text = None, realfile = "<code>", pythonexception def simple_exec(code, context): exec(code, get_context(), context) -def better_eval(source, locals): - return eval(source, get_context(), locals) +def better_eval(source, locals, extraglobals = None): + ctx = get_context() + if extraglobals: + ctx = copy.copy(ctx) + for g in extraglobals: + ctx[g] = extraglobals[g] + return eval(source, ctx, locals) @contextmanager def fileslocked(files): @@ -563,6 +580,8 @@ def preserved_envvars_exported(): 'SHELL', 'TERM', 'USER', + 'LC_ALL', + 'BBSERVER', ] def preserved_envvars(): @@ -582,14 +601,19 @@ def filter_environment(good_vars): """ removed_vars = {} - for key in os.environ.keys(): + for key in list(os.environ): if key in good_vars: continue removed_vars[key] = os.environ[key] - os.unsetenv(key) del os.environ[key] + # If we spawn a python process, we need to have a UTF-8 locale, else python's file + # access methods will use ascii. You can't change that mode once the interpreter is + # started so we have to ensure a locale is set. Ideally we'd use C.UTF-8 but not all + # distros support that and we need to set something. + os.environ["LC_ALL"] = "en_US.UTF-8" + if removed_vars: logger.debug(1, "Removed the following variables from the environment: %s", ", ".join(removed_vars.keys())) @@ -629,7 +653,7 @@ def empty_environment(): """ Remove all variables from the environment. """ - for s in os.environ.keys(): + for s in list(os.environ.keys()): os.unsetenv(s) del os.environ[s] @@ -818,7 +842,7 @@ def copyfile(src, dest, newmtime = None, sstat = None): if not sstat: sstat = os.lstat(src) except Exception as e: - logger.warn("copyfile: stat of %s failed (%s)" % (src, e)) + logger.warning("copyfile: stat of %s failed (%s)" % (src, e)) return False destexists = 1 @@ -845,7 +869,7 @@ def copyfile(src, dest, newmtime = None, sstat = None): #os.lchown(dest,sstat[stat.ST_UID],sstat[stat.ST_GID]) return os.lstat(dest) except Exception as e: - logger.warn("copyfile: failed to create symlink %s to %s (%s)" % (dest, target, e)) + logger.warning("copyfile: failed to create symlink %s to %s (%s)" % (dest, target, e)) return False if stat.S_ISREG(sstat[stat.ST_MODE]): @@ -860,7 +884,7 @@ def copyfile(src, dest, newmtime = None, sstat = None): shutil.copyfile(src, dest + "#new") os.rename(dest + "#new", dest) except Exception as e: - logger.warn("copyfile: copy %s to %s failed (%s)" % (src, dest, e)) + logger.warning("copyfile: copy %s to %s failed (%s)" % (src, dest, e)) return False finally: if srcchown: @@ -871,13 +895,13 @@ def copyfile(src, dest, newmtime = None, sstat = None): #we don't yet handle special, so we need to fall back to /bin/mv a = getstatusoutput("/bin/cp -f " + "'" + src + "' '" + dest + "'") if a[0] != 0: - logger.warn("copyfile: failed to copy special file %s to %s (%s)" % (src, dest, a)) + logger.warning("copyfile: failed to copy special file %s to %s (%s)" % (src, dest, a)) return False # failure try: os.lchown(dest, sstat[stat.ST_UID], sstat[stat.ST_GID]) os.chmod(dest, stat.S_IMODE(sstat[stat.ST_MODE])) # Sticky is reset on chown except Exception as e: - logger.warn("copyfile: failed to chown/chmod %s (%s)" % (dest, e)) + logger.warning("copyfile: failed to chown/chmod %s (%s)" % (dest, e)) return False if newmtime: @@ -946,7 +970,7 @@ def contains(variable, checkvalues, truevalue, falsevalue, d): if not val: return falsevalue val = set(val.split()) - if isinstance(checkvalues, basestring): + if isinstance(checkvalues, str): checkvalues = set(checkvalues.split()) else: checkvalues = set(checkvalues) @@ -959,7 +983,7 @@ def contains_any(variable, checkvalues, truevalue, falsevalue, d): if not val: return falsevalue val = set(val.split()) - if isinstance(checkvalues, basestring): + if isinstance(checkvalues, str): checkvalues = set(checkvalues.split()) else: checkvalues = set(checkvalues) @@ -1028,7 +1052,7 @@ def exec_flat_python_func(func, *args, **kwargs): aidx += 1 # Handle keyword arguments context.update(kwargs) - funcargs.extend(['%s=%s' % (arg, arg) for arg in kwargs.iterkeys()]) + funcargs.extend(['%s=%s' % (arg, arg) for arg in kwargs.keys()]) code = 'retval = %s(%s)' % (func, ', '.join(funcargs)) comp = bb.utils.better_compile(code, '<string>', '<string>') bb.utils.better_exec(comp, context, code, '<string>') @@ -1057,7 +1081,7 @@ def edit_metadata(meta_lines, variables, varfunc, match_overrides=False): newlines: list of lines up to this point. You can use this to prepend lines before this variable setting if you wish. - and should return a three-element tuple: + and should return a four-element tuple: newvalue: new value to substitute in, or None to drop the variable setting entirely. (If the removal results in two consecutive blank lines, one of the @@ -1071,6 +1095,8 @@ def edit_metadata(meta_lines, variables, varfunc, match_overrides=False): multi-line value to continue on the same line as the assignment, False to indent before the first element. + To clarify, if you wish not to change the value, then you + would return like this: return origvalue, None, 0, True match_overrides: True to match items with _overrides on the end, False otherwise Returns a tuple: @@ -1115,7 +1141,7 @@ def edit_metadata(meta_lines, variables, varfunc, match_overrides=False): else: varset_new = varset_start - if isinstance(indent, (int, long)): + if isinstance(indent, int): if indent == -1: indentspc = ' ' * (len(varset_new) + 2) else: @@ -1183,7 +1209,7 @@ def edit_metadata(meta_lines, variables, varfunc, match_overrides=False): in_var = None else: skip = False - for (varname, var_re) in var_res.iteritems(): + for (varname, var_re) in var_res.items(): res = var_re.match(line) if res: isfunc = varname.endswith('()') @@ -1361,7 +1387,7 @@ def get_file_layer(filename, d): # Use longest path so we handle nested layers matchlen = 0 match = None - for collection, regex in collection_res.iteritems(): + for collection, regex in collection_res.items(): if len(regex) > matchlen and re.match(regex, path): matchlen = len(regex) match = collection @@ -1427,9 +1453,8 @@ def set_process_name(name): # This is nice to have for debugging, not essential try: libc = cdll.LoadLibrary('libc.so.6') - buff = create_string_buffer(len(name)+1) - buff.value = name - libc.prctl(15, byref(buff), 0, 0, 0) + buf = create_string_buffer(bytes(name, 'utf-8')) + libc.prctl(15, byref(buf), 0, 0, 0) except: pass @@ -1438,7 +1463,8 @@ def export_proxies(d): import os variables = ['http_proxy', 'HTTP_PROXY', 'https_proxy', 'HTTPS_PROXY', - 'ftp_proxy', 'FTP_PROXY', 'no_proxy', 'NO_PROXY'] + 'ftp_proxy', 'FTP_PROXY', 'no_proxy', 'NO_PROXY', + 'GIT_PROXY_COMMAND'] exported = False for v in variables: @@ -1451,3 +1477,29 @@ def export_proxies(d): exported = True return exported + + +def load_plugins(logger, plugins, pluginpath): + def load_plugin(name): + logger.debug('Loading plugin %s' % name) + fp, pathname, description = imp.find_module(name, [pluginpath]) + try: + return imp.load_module(name, fp, pathname, description) + finally: + if fp: + fp.close() + + logger.debug('Loading plugins from %s...' % pluginpath) + + expanded = (glob.glob(os.path.join(pluginpath, '*' + ext)) + for ext in python_extensions) + files = itertools.chain.from_iterable(expanded) + names = set(os.path.splitext(os.path.basename(fn))[0] for fn in files) + for name in names: + if name != '__init__': + plugin = load_plugin(name) + if hasattr(plugin, 'plugin_init'): + obj = plugin.plugin_init(plugins) + plugins.append(obj or plugin) + else: + plugins.append(plugin) diff --git a/import-layers/yocto-poky/bitbake/lib/bblayers/__init__.py b/import-layers/yocto-poky/bitbake/lib/bblayers/__init__.py new file mode 100644 index 000000000..3ad9513f4 --- /dev/null +++ b/import-layers/yocto-poky/bitbake/lib/bblayers/__init__.py @@ -0,0 +1,2 @@ +from pkgutil import extend_path +__path__ = extend_path(__path__, __name__) diff --git a/import-layers/yocto-poky/bitbake/lib/bblayers/action.py b/import-layers/yocto-poky/bitbake/lib/bblayers/action.py new file mode 100644 index 000000000..739ae27b9 --- /dev/null +++ b/import-layers/yocto-poky/bitbake/lib/bblayers/action.py @@ -0,0 +1,233 @@ +import fnmatch +import logging +import os +import sys + +import bb.utils + +from bblayers.common import LayerPlugin + +logger = logging.getLogger('bitbake-layers') + + +def plugin_init(plugins): + return ActionPlugin() + + +class ActionPlugin(LayerPlugin): + def do_add_layer(self, args): + """Add a layer to bblayers.conf.""" + layerdir = os.path.abspath(args.layerdir) + if not os.path.exists(layerdir): + sys.stderr.write("Specified layer directory doesn't exist\n") + return 1 + + layer_conf = os.path.join(layerdir, 'conf', 'layer.conf') + if not os.path.exists(layer_conf): + sys.stderr.write("Specified layer directory doesn't contain a conf/layer.conf file\n") + return 1 + + bblayers_conf = os.path.join('conf', 'bblayers.conf') + if not os.path.exists(bblayers_conf): + sys.stderr.write("Unable to find bblayers.conf\n") + return 1 + + notadded, _ = bb.utils.edit_bblayers_conf(bblayers_conf, layerdir, None) + if notadded: + for item in notadded: + sys.stderr.write("Specified layer %s is already in BBLAYERS\n" % item) + + def do_remove_layer(self, args): + """Remove a layer from bblayers.conf.""" + bblayers_conf = os.path.join('conf', 'bblayers.conf') + if not os.path.exists(bblayers_conf): + sys.stderr.write("Unable to find bblayers.conf\n") + return 1 + + if args.layerdir.startswith('*'): + layerdir = args.layerdir + elif not '/' in args.layerdir: + layerdir = '*/%s' % args.layerdir + else: + layerdir = os.path.abspath(args.layerdir) + (_, notremoved) = bb.utils.edit_bblayers_conf(bblayers_conf, None, layerdir) + if notremoved: + for item in notremoved: + sys.stderr.write("No layers matching %s found in BBLAYERS\n" % item) + return 1 + + def do_flatten(self, args): + """flatten layer configuration into a separate output directory. + +Takes the specified layers (or all layers in the current layer +configuration if none are specified) and builds a "flattened" directory +containing the contents of all layers, with any overlayed recipes removed +and bbappends appended to the corresponding recipes. Note that some manual +cleanup may still be necessary afterwards, in particular: + +* where non-recipe files (such as patches) are overwritten (the flatten + command will show a warning for these) +* where anything beyond the normal layer setup has been added to + layer.conf (only the lowest priority number layer's layer.conf is used) +* overridden/appended items from bbappends will need to be tidied up +* when the flattened layers do not have the same directory structure (the + flatten command should show a warning when this will cause a problem) + +Warning: if you flatten several layers where another layer is intended to +be used "inbetween" them (in layer priority order) such that recipes / +bbappends in the layers interact, and then attempt to use the new output +layer together with that other layer, you may no longer get the same +build results (as the layer priority order has effectively changed). +""" + if len(args.layer) == 1: + logger.error('If you specify layers to flatten you must specify at least two') + return 1 + + outputdir = args.outputdir + if os.path.exists(outputdir) and os.listdir(outputdir): + logger.error('Directory %s exists and is non-empty, please clear it out first' % outputdir) + return 1 + + layers = self.bblayers + if len(args.layer) > 2: + layernames = args.layer + found_layernames = [] + found_layerdirs = [] + for layerdir in layers: + layername = self.get_layer_name(layerdir) + if layername in layernames: + found_layerdirs.append(layerdir) + found_layernames.append(layername) + + for layername in layernames: + if not layername in found_layernames: + logger.error('Unable to find layer %s in current configuration, please run "%s show-layers" to list configured layers' % (layername, os.path.basename(sys.argv[0]))) + return + layers = found_layerdirs + else: + layernames = [] + + # Ensure a specified path matches our list of layers + def layer_path_match(path): + for layerdir in layers: + if path.startswith(os.path.join(layerdir, '')): + return layerdir + return None + + applied_appends = [] + for layer in layers: + overlayed = [] + for f in self.tinfoil.cooker.collection.overlayed.keys(): + for of in self.tinfoil.cooker.collection.overlayed[f]: + if of.startswith(layer): + overlayed.append(of) + + logger.plain('Copying files from %s...' % layer ) + for root, dirs, files in os.walk(layer): + if '.git' in dirs: + dirs.remove('.git') + if '.hg' in dirs: + dirs.remove('.hg') + + for f1 in files: + f1full = os.sep.join([root, f1]) + if f1full in overlayed: + logger.plain(' Skipping overlayed file %s' % f1full ) + else: + ext = os.path.splitext(f1)[1] + if ext != '.bbappend': + fdest = f1full[len(layer):] + fdest = os.path.normpath(os.sep.join([outputdir,fdest])) + bb.utils.mkdirhier(os.path.dirname(fdest)) + if os.path.exists(fdest): + if f1 == 'layer.conf' and root.endswith('/conf'): + logger.plain(' Skipping layer config file %s' % f1full ) + continue + else: + logger.warning('Overwriting file %s', fdest) + bb.utils.copyfile(f1full, fdest) + if ext == '.bb': + for append in self.tinfoil.cooker.collection.get_file_appends(f1full): + if layer_path_match(append): + logger.plain(' Applying append %s to %s' % (append, fdest)) + self.apply_append(append, fdest) + applied_appends.append(append) + + # Take care of when some layers are excluded and yet we have included bbappends for those recipes + for b in self.tinfoil.cooker.collection.bbappends: + (recipename, appendname) = b + if appendname not in applied_appends: + first_append = None + layer = layer_path_match(appendname) + if layer: + if first_append: + self.apply_append(appendname, first_append) + else: + fdest = appendname[len(layer):] + fdest = os.path.normpath(os.sep.join([outputdir,fdest])) + bb.utils.mkdirhier(os.path.dirname(fdest)) + bb.utils.copyfile(appendname, fdest) + first_append = fdest + + # Get the regex for the first layer in our list (which is where the conf/layer.conf file will + # have come from) + first_regex = None + layerdir = layers[0] + for layername, pattern, regex, _ in self.tinfoil.cooker.bbfile_config_priorities: + if regex.match(os.path.join(layerdir, 'test')): + first_regex = regex + break + + if first_regex: + # Find the BBFILES entries that match (which will have come from this conf/layer.conf file) + bbfiles = str(self.tinfoil.config_data.getVar('BBFILES', True)).split() + bbfiles_layer = [] + for item in bbfiles: + if first_regex.match(item): + newpath = os.path.join(outputdir, item[len(layerdir)+1:]) + bbfiles_layer.append(newpath) + + if bbfiles_layer: + # Check that all important layer files match BBFILES + for root, dirs, files in os.walk(outputdir): + for f1 in files: + ext = os.path.splitext(f1)[1] + if ext in ['.bb', '.bbappend']: + f1full = os.sep.join([root, f1]) + entry_found = False + for item in bbfiles_layer: + if fnmatch.fnmatch(f1full, item): + entry_found = True + break + if not entry_found: + logger.warning("File %s does not match the flattened layer's BBFILES setting, you may need to edit conf/layer.conf or move the file elsewhere" % f1full) + + def get_file_layer(self, filename): + layerdir = self.get_file_layerdir(filename) + if layerdir: + return self.get_layer_name(layerdir) + else: + return '?' + + def get_file_layerdir(self, filename): + layer = bb.utils.get_file_layer(filename, self.tinfoil.config_data) + return self.bbfile_collections.get(layer, None) + + def apply_append(self, appendname, recipename): + with open(appendname, 'r') as appendfile: + with open(recipename, 'a') as recipefile: + recipefile.write('\n') + recipefile.write('##### bbappended from %s #####\n' % self.get_file_layer(appendname)) + recipefile.writelines(appendfile.readlines()) + + def register_commands(self, sp): + parser_add_layer = self.add_command(sp, 'add-layer', self.do_add_layer, parserecipes=False) + parser_add_layer.add_argument('layerdir', help='Layer directory to add') + + parser_remove_layer = self.add_command(sp, 'remove-layer', self.do_remove_layer, parserecipes=False) + parser_remove_layer.add_argument('layerdir', help='Layer directory to remove (wildcards allowed, enclose in quotes to avoid shell expansion)') + parser_remove_layer.set_defaults(func=self.do_remove_layer) + + parser_flatten = self.add_command(sp, 'flatten', self.do_flatten) + parser_flatten.add_argument('layer', nargs='*', help='Optional layer(s) to flatten (otherwise all are flattened)') + parser_flatten.add_argument('outputdir', help='Output directory') diff --git a/import-layers/yocto-poky/bitbake/lib/bblayers/common.py b/import-layers/yocto-poky/bitbake/lib/bblayers/common.py new file mode 100644 index 000000000..b10fb4cea --- /dev/null +++ b/import-layers/yocto-poky/bitbake/lib/bblayers/common.py @@ -0,0 +1,33 @@ +import argparse +import logging +import os + +logger = logging.getLogger('bitbake-layers') + + +class LayerPlugin(): + def __init__(self): + self.tinfoil = None + self.bblayers = [] + + def tinfoil_init(self, tinfoil): + self.tinfoil = tinfoil + self.bblayers = (self.tinfoil.config_data.getVar('BBLAYERS', True) or "").split() + layerconfs = self.tinfoil.config_data.varhistory.get_variable_items_files('BBFILE_COLLECTIONS', self.tinfoil.config_data) + self.bbfile_collections = {layer: os.path.dirname(os.path.dirname(path)) for layer, path in layerconfs.items()} + + @staticmethod + def add_command(subparsers, cmdname, function, parserecipes=True, *args, **kwargs): + """Convert docstring for function to help.""" + docsplit = function.__doc__.splitlines() + help = docsplit[0] + if len(docsplit) > 1: + desc = '\n'.join(docsplit[1:]) + else: + desc = help + subparser = subparsers.add_parser(cmdname, *args, help=help, description=desc, formatter_class=argparse.RawTextHelpFormatter, **kwargs) + subparser.set_defaults(func=function, parserecipes=parserecipes) + return subparser + + def get_layer_name(self, layerdir): + return os.path.basename(layerdir.rstrip(os.sep)) diff --git a/import-layers/yocto-poky/bitbake/lib/bblayers/layerindex.py b/import-layers/yocto-poky/bitbake/lib/bblayers/layerindex.py new file mode 100644 index 000000000..10ad718eb --- /dev/null +++ b/import-layers/yocto-poky/bitbake/lib/bblayers/layerindex.py @@ -0,0 +1,270 @@ +import argparse +import http.client +import json +import logging +import os +import subprocess +import urllib.parse + +from bblayers.action import ActionPlugin + +logger = logging.getLogger('bitbake-layers') + + +def plugin_init(plugins): + return LayerIndexPlugin() + + +class LayerIndexPlugin(ActionPlugin): + """Subcommands for interacting with the layer index. + + This class inherits ActionPlugin to get do_add_layer. + """ + + def get_json_data(self, apiurl): + proxy_settings = os.environ.get("http_proxy", None) + conn = None + _parsedurl = urllib.parse.urlparse(apiurl) + path = _parsedurl.path + query = _parsedurl.query + + def parse_url(url): + parsedurl = urllib.parse.urlparse(url) + if parsedurl.netloc[0] == '[': + host, port = parsedurl.netloc[1:].split(']', 1) + if ':' in port: + port = port.rsplit(':', 1)[1] + else: + port = None + else: + if parsedurl.netloc.count(':') == 1: + (host, port) = parsedurl.netloc.split(":") + else: + host = parsedurl.netloc + port = None + return (host, 80 if port is None else int(port)) + + if proxy_settings is None: + host, port = parse_url(apiurl) + conn = http.client.HTTPConnection(host, port) + conn.request("GET", path + "?" + query) + else: + host, port = parse_url(proxy_settings) + conn = http.client.HTTPConnection(host, port) + conn.request("GET", apiurl) + + r = conn.getresponse() + if r.status != 200: + raise Exception("Failed to read " + path + ": %d %s" % (r.status, r.reason)) + return json.loads(r.read()) + + def get_layer_deps(self, layername, layeritems, layerbranches, layerdependencies, branchnum, selfname=False): + def layeritems_info_id(items_name, layeritems): + litems_id = None + for li in layeritems: + if li['name'] == items_name: + litems_id = li['id'] + break + return litems_id + + def layerbranches_info(items_id, layerbranches): + lbranch = {} + for lb in layerbranches: + if lb['layer'] == items_id and lb['branch'] == branchnum: + lbranch['id'] = lb['id'] + lbranch['vcs_subdir'] = lb['vcs_subdir'] + break + return lbranch + + def layerdependencies_info(lb_id, layerdependencies): + ld_deps = [] + for ld in layerdependencies: + if ld['layerbranch'] == lb_id and not ld['dependency'] in ld_deps: + ld_deps.append(ld['dependency']) + if not ld_deps: + logger.error("The dependency of layerDependencies is not found.") + return ld_deps + + def layeritems_info_name_subdir(items_id, layeritems): + litems = {} + for li in layeritems: + if li['id'] == items_id: + litems['vcs_url'] = li['vcs_url'] + litems['name'] = li['name'] + break + return litems + + if selfname: + selfid = layeritems_info_id(layername, layeritems) + lbinfo = layerbranches_info(selfid, layerbranches) + if lbinfo: + selfsubdir = lbinfo['vcs_subdir'] + else: + logger.error("%s is not found in the specified branch" % layername) + return + selfurl = layeritems_info_name_subdir(selfid, layeritems)['vcs_url'] + if selfurl: + return selfurl, selfsubdir + else: + logger.error("Cannot get layer %s git repo and subdir" % layername) + return + ldict = {} + itemsid = layeritems_info_id(layername, layeritems) + if not itemsid: + return layername, None + lbid = layerbranches_info(itemsid, layerbranches) + if lbid: + lbid = layerbranches_info(itemsid, layerbranches)['id'] + else: + logger.error("%s is not found in the specified branch" % layername) + return None, None + for dependency in layerdependencies_info(lbid, layerdependencies): + lname = layeritems_info_name_subdir(dependency, layeritems)['name'] + lurl = layeritems_info_name_subdir(dependency, layeritems)['vcs_url'] + lsubdir = layerbranches_info(dependency, layerbranches)['vcs_subdir'] + ldict[lname] = lurl, lsubdir + return None, ldict + + def get_fetch_layer(self, fetchdir, url, subdir, fetch_layer): + layername = self.get_layer_name(url) + if os.path.splitext(layername)[1] == '.git': + layername = os.path.splitext(layername)[0] + repodir = os.path.join(fetchdir, layername) + layerdir = os.path.join(repodir, subdir) + if not os.path.exists(repodir): + if fetch_layer: + result = subprocess.call('git clone %s %s' % (url, repodir), shell = True) + if result: + logger.error("Failed to download %s" % url) + return None, None + else: + return layername, layerdir + else: + logger.plain("Repository %s needs to be fetched" % url) + return layername, layerdir + elif os.path.exists(layerdir): + return layername, layerdir + else: + logger.error("%s is not in %s" % (url, subdir)) + return None, None + + def do_layerindex_fetch(self, args): + """Fetches a layer from a layer index along with its dependent layers, and adds them to conf/bblayers.conf. +""" + apiurl = self.tinfoil.config_data.getVar('BBLAYERS_LAYERINDEX_URL', True) + if not apiurl: + logger.error("Cannot get BBLAYERS_LAYERINDEX_URL") + return 1 + else: + if apiurl[-1] != '/': + apiurl += '/' + apiurl += "api/" + apilinks = self.get_json_data(apiurl) + branches = self.get_json_data(apilinks['branches']) + + branchnum = 0 + for branch in branches: + if branch['name'] == args.branch: + branchnum = branch['id'] + break + if branchnum == 0: + validbranches = ', '.join([branch['name'] for branch in branches]) + logger.error('Invalid layer branch name "%s". Valid branches: %s' % (args.branch, validbranches)) + return 1 + + ignore_layers = [] + for collection in self.tinfoil.config_data.getVar('BBFILE_COLLECTIONS', True).split(): + lname = self.tinfoil.config_data.getVar('BBLAYERS_LAYERINDEX_NAME_%s' % collection, True) + if lname: + ignore_layers.append(lname) + + if args.ignore: + ignore_layers.extend(args.ignore.split(',')) + + layeritems = self.get_json_data(apilinks['layerItems']) + layerbranches = self.get_json_data(apilinks['layerBranches']) + layerdependencies = self.get_json_data(apilinks['layerDependencies']) + invaluenames = [] + repourls = {} + printlayers = [] + + def query_dependencies(layers, layeritems, layerbranches, layerdependencies, branchnum): + depslayer = [] + for layername in layers: + invaluename, layerdict = self.get_layer_deps(layername, layeritems, layerbranches, layerdependencies, branchnum) + if layerdict: + repourls[layername] = self.get_layer_deps(layername, layeritems, layerbranches, layerdependencies, branchnum, selfname=True) + for layer in layerdict: + if not layer in ignore_layers: + depslayer.append(layer) + printlayers.append((layername, layer, layerdict[layer][0], layerdict[layer][1])) + if not layer in ignore_layers and not layer in repourls: + repourls[layer] = (layerdict[layer][0], layerdict[layer][1]) + if invaluename and not invaluename in invaluenames: + invaluenames.append(invaluename) + return depslayer + + depslayers = query_dependencies(args.layername, layeritems, layerbranches, layerdependencies, branchnum) + while depslayers: + depslayer = query_dependencies(depslayers, layeritems, layerbranches, layerdependencies, branchnum) + depslayers = depslayer + if invaluenames: + for invaluename in invaluenames: + logger.error('Layer "%s" not found in layer index' % invaluename) + return 1 + logger.plain("%s %s %s %s" % ("Layer".ljust(19), "Required by".ljust(19), "Git repository".ljust(54), "Subdirectory")) + logger.plain('=' * 115) + for layername in args.layername: + layerurl = repourls[layername] + logger.plain("%s %s %s %s" % (layername.ljust(20), '-'.ljust(20), layerurl[0].ljust(55), layerurl[1])) + printedlayers = [] + for layer, dependency, gitrepo, subdirectory in printlayers: + if dependency in printedlayers: + continue + logger.plain("%s %s %s %s" % (dependency.ljust(20), layer.ljust(20), gitrepo.ljust(55), subdirectory)) + printedlayers.append(dependency) + + if repourls: + fetchdir = self.tinfoil.config_data.getVar('BBLAYERS_FETCH_DIR', True) + if not fetchdir: + logger.error("Cannot get BBLAYERS_FETCH_DIR") + return 1 + if not os.path.exists(fetchdir): + os.makedirs(fetchdir) + addlayers = [] + for repourl, subdir in repourls.values(): + name, layerdir = self.get_fetch_layer(fetchdir, repourl, subdir, not args.show_only) + if not name: + # Error already shown + return 1 + addlayers.append((subdir, name, layerdir)) + if not args.show_only: + for subdir, name, layerdir in set(addlayers): + if os.path.exists(layerdir): + if subdir: + logger.plain("Adding layer \"%s\" to conf/bblayers.conf" % subdir) + else: + logger.plain("Adding layer \"%s\" to conf/bblayers.conf" % name) + localargs = argparse.Namespace() + localargs.layerdir = layerdir + self.do_add_layer(localargs) + else: + break + + def do_layerindex_show_depends(self, args): + """Find layer dependencies from layer index. +""" + args.show_only = True + args.ignore = [] + self.do_layerindex_fetch(args) + + def register_commands(self, sp): + parser_layerindex_fetch = self.add_command(sp, 'layerindex-fetch', self.do_layerindex_fetch) + parser_layerindex_fetch.add_argument('-n', '--show-only', help='show dependencies and do nothing else', action='store_true') + parser_layerindex_fetch.add_argument('-b', '--branch', help='branch name to fetch (default %(default)s)', default='master') + parser_layerindex_fetch.add_argument('-i', '--ignore', help='assume the specified layers do not need to be fetched/added (separate multiple layers with commas, no spaces)', metavar='LAYER') + parser_layerindex_fetch.add_argument('layername', nargs='+', help='layer to fetch') + + parser_layerindex_show_depends = self.add_command(sp, 'layerindex-show-depends', self.do_layerindex_show_depends) + parser_layerindex_show_depends.add_argument('-b', '--branch', help='branch name to fetch (default %(default)s)', default='master') + parser_layerindex_show_depends.add_argument('layername', nargs='+', help='layer to query') diff --git a/import-layers/yocto-poky/bitbake/lib/bblayers/query.py b/import-layers/yocto-poky/bitbake/lib/bblayers/query.py new file mode 100644 index 000000000..ee1e7c8a1 --- /dev/null +++ b/import-layers/yocto-poky/bitbake/lib/bblayers/query.py @@ -0,0 +1,500 @@ +import collections +import fnmatch +import logging +import sys +import os +import re + +import bb.cache +import bb.providers +import bb.utils + +from bblayers.common import LayerPlugin + +logger = logging.getLogger('bitbake-layers') + + +def plugin_init(plugins): + return QueryPlugin() + + +class QueryPlugin(LayerPlugin): + def do_show_layers(self, args): + """show current configured layers.""" + logger.plain("%s %s %s" % ("layer".ljust(20), "path".ljust(40), "priority")) + logger.plain('=' * 74) + for layer, _, regex, pri in self.tinfoil.cooker.bbfile_config_priorities: + layerdir = self.bbfile_collections.get(layer, None) + layername = self.get_layer_name(layerdir) + logger.plain("%s %s %d" % (layername.ljust(20), layerdir.ljust(40), pri)) + + def version_str(self, pe, pv, pr = None): + verstr = "%s" % pv + if pr: + verstr = "%s-%s" % (verstr, pr) + if pe: + verstr = "%s:%s" % (pe, verstr) + return verstr + + def do_show_overlayed(self, args): + """list overlayed recipes (where the same recipe exists in another layer) + +Lists the names of overlayed recipes and the available versions in each +layer, with the preferred version first. Note that skipped recipes that +are overlayed will also be listed, with a " (skipped)" suffix. +""" + + items_listed = self.list_recipes('Overlayed recipes', None, True, args.same_version, args.filenames, True, None) + + # Check for overlayed .bbclass files + classes = collections.defaultdict(list) + for layerdir in self.bblayers: + classdir = os.path.join(layerdir, 'classes') + if os.path.exists(classdir): + for classfile in os.listdir(classdir): + if os.path.splitext(classfile)[1] == '.bbclass': + classes[classfile].append(classdir) + + # Locating classes and other files is a bit more complicated than recipes - + # layer priority is not a factor; instead BitBake uses the first matching + # file in BBPATH, which is manipulated directly by each layer's + # conf/layer.conf in turn, thus the order of layers in bblayers.conf is a + # factor - however, each layer.conf is free to either prepend or append to + # BBPATH (or indeed do crazy stuff with it). Thus the order in BBPATH might + # not be exactly the order present in bblayers.conf either. + bbpath = str(self.tinfoil.config_data.getVar('BBPATH', True)) + overlayed_class_found = False + for (classfile, classdirs) in classes.items(): + if len(classdirs) > 1: + if not overlayed_class_found: + logger.plain('=== Overlayed classes ===') + overlayed_class_found = True + + mainfile = bb.utils.which(bbpath, os.path.join('classes', classfile)) + if args.filenames: + logger.plain('%s' % mainfile) + else: + # We effectively have to guess the layer here + logger.plain('%s:' % classfile) + mainlayername = '?' + for layerdir in self.bblayers: + classdir = os.path.join(layerdir, 'classes') + if mainfile.startswith(classdir): + mainlayername = self.get_layer_name(layerdir) + logger.plain(' %s' % mainlayername) + for classdir in classdirs: + fullpath = os.path.join(classdir, classfile) + if fullpath != mainfile: + if args.filenames: + print(' %s' % fullpath) + else: + print(' %s' % self.get_layer_name(os.path.dirname(classdir))) + + if overlayed_class_found: + items_listed = True; + + if not items_listed: + logger.plain('No overlayed files found.') + + def do_show_recipes(self, args): + """list available recipes, showing the layer they are provided by + +Lists the names of recipes and the available versions in each +layer, with the preferred version first. Optionally you may specify +pnspec to match a specified recipe name (supports wildcards). Note that +skipped recipes will also be listed, with a " (skipped)" suffix. +""" + + inheritlist = args.inherits.split(',') if args.inherits else [] + if inheritlist or args.pnspec or args.multiple: + title = 'Matching recipes:' + else: + title = 'Available recipes:' + self.list_recipes(title, args.pnspec, False, False, args.filenames, args.multiple, inheritlist) + + def list_recipes(self, title, pnspec, show_overlayed_only, show_same_ver_only, show_filenames, show_multi_provider_only, inherits): + if inherits: + bbpath = str(self.tinfoil.config_data.getVar('BBPATH', True)) + for classname in inherits: + classfile = 'classes/%s.bbclass' % classname + if not bb.utils.which(bbpath, classfile, history=False): + logger.error('No class named %s found in BBPATH', classfile) + sys.exit(1) + + pkg_pn = self.tinfoil.cooker.recipecaches[''].pkg_pn + (latest_versions, preferred_versions) = bb.providers.findProviders(self.tinfoil.config_data, self.tinfoil.cooker.recipecaches[''], pkg_pn) + allproviders = bb.providers.allProviders(self.tinfoil.cooker.recipecaches['']) + + # Ensure we list skipped recipes + # We are largely guessing about PN, PV and the preferred version here, + # but we have no choice since skipped recipes are not fully parsed + skiplist = list(self.tinfoil.cooker.skiplist.keys()) + skiplist.sort( key=lambda fileitem: self.tinfoil.cooker.collection.calc_bbfile_priority(fileitem) ) + skiplist.reverse() + for fn in skiplist: + recipe_parts = os.path.splitext(os.path.basename(fn))[0].split('_') + p = recipe_parts[0] + if len(recipe_parts) > 1: + ver = (None, recipe_parts[1], None) + else: + ver = (None, 'unknown', None) + allproviders[p].append((ver, fn)) + if not p in pkg_pn: + pkg_pn[p] = 'dummy' + preferred_versions[p] = (ver, fn) + + def print_item(f, pn, ver, layer, ispref): + if f in skiplist: + skipped = ' (skipped)' + else: + skipped = '' + if show_filenames: + if ispref: + logger.plain("%s%s", f, skipped) + else: + logger.plain(" %s%s", f, skipped) + else: + if ispref: + logger.plain("%s:", pn) + logger.plain(" %s %s%s", layer.ljust(20), ver, skipped) + + global_inherit = (self.tinfoil.config_data.getVar('INHERIT', True) or "").split() + cls_re = re.compile('classes/') + + preffiles = [] + items_listed = False + for p in sorted(pkg_pn): + if pnspec: + if not fnmatch.fnmatch(p, pnspec): + continue + + if len(allproviders[p]) > 1 or not show_multi_provider_only: + pref = preferred_versions[p] + realfn = bb.cache.virtualfn2realfn(pref[1]) + preffile = realfn[0] + + # We only display once per recipe, we should prefer non extended versions of the + # recipe if present (so e.g. in OpenEmbedded, openssl rather than nativesdk-openssl + # which would otherwise sort first). + if realfn[1] and realfn[0] in self.tinfoil.cooker.recipecaches[''].pkg_fn: + continue + + if inherits: + matchcount = 0 + recipe_inherits = self.tinfoil.cooker_data.inherits.get(preffile, []) + for cls in recipe_inherits: + if cls_re.match(cls): + continue + classname = os.path.splitext(os.path.basename(cls))[0] + if classname in global_inherit: + continue + elif classname in inherits: + matchcount += 1 + if matchcount != len(inherits): + # No match - skip this recipe + continue + + if preffile not in preffiles: + preflayer = self.get_file_layer(preffile) + multilayer = False + same_ver = True + provs = [] + for prov in allproviders[p]: + provfile = bb.cache.virtualfn2realfn(prov[1])[0] + provlayer = self.get_file_layer(provfile) + provs.append((provfile, provlayer, prov[0])) + if provlayer != preflayer: + multilayer = True + if prov[0] != pref[0]: + same_ver = False + + if (multilayer or not show_overlayed_only) and (same_ver or not show_same_ver_only): + if not items_listed: + logger.plain('=== %s ===' % title) + items_listed = True + print_item(preffile, p, self.version_str(pref[0][0], pref[0][1]), preflayer, True) + for (provfile, provlayer, provver) in provs: + if provfile != preffile: + print_item(provfile, p, self.version_str(provver[0], provver[1]), provlayer, False) + # Ensure we don't show two entries for BBCLASSEXTENDed recipes + preffiles.append(preffile) + + return items_listed + + def get_file_layer(self, filename): + layerdir = self.get_file_layerdir(filename) + if layerdir: + return self.get_layer_name(layerdir) + else: + return '?' + + def get_file_layerdir(self, filename): + layer = bb.utils.get_file_layer(filename, self.tinfoil.config_data) + return self.bbfile_collections.get(layer, None) + + def remove_layer_prefix(self, f): + """Remove the layer_dir prefix, e.g., f = /path/to/layer_dir/foo/blah, the + return value will be: layer_dir/foo/blah""" + f_layerdir = self.get_file_layerdir(f) + if not f_layerdir: + return f + prefix = os.path.join(os.path.dirname(f_layerdir), '') + return f[len(prefix):] if f.startswith(prefix) else f + + def do_show_appends(self, args): + """list bbappend files and recipe files they apply to + +Lists recipes with the bbappends that apply to them as subitems. +""" + + logger.plain('=== Appended recipes ===') + + pnlist = list(self.tinfoil.cooker_data.pkg_pn.keys()) + pnlist.sort() + appends = False + for pn in pnlist: + if self.show_appends_for_pn(pn): + appends = True + + if self.show_appends_for_skipped(): + appends = True + + if not appends: + logger.plain('No append files found') + + def show_appends_for_pn(self, pn): + filenames = self.tinfoil.cooker_data.pkg_pn[pn] + + best = bb.providers.findBestProvider(pn, + self.tinfoil.config_data, + self.tinfoil.cooker_data, + self.tinfoil.cooker_data.pkg_pn) + best_filename = os.path.basename(best[3]) + + return self.show_appends_output(filenames, best_filename) + + def show_appends_for_skipped(self): + filenames = [os.path.basename(f) + for f in self.tinfoil.cooker.skiplist.keys()] + return self.show_appends_output(filenames, None, " (skipped)") + + def show_appends_output(self, filenames, best_filename, name_suffix = ''): + appended, missing = self.get_appends_for_files(filenames) + if appended: + for basename, appends in appended: + logger.plain('%s%s:', basename, name_suffix) + for append in appends: + logger.plain(' %s', append) + + if best_filename: + if best_filename in missing: + logger.warning('%s: missing append for preferred version', + best_filename) + return True + else: + return False + + def get_appends_for_files(self, filenames): + appended, notappended = [], [] + for filename in filenames: + _, cls, _ = bb.cache.virtualfn2realfn(filename) + if cls: + continue + + basename = os.path.basename(filename) + appends = self.tinfoil.cooker.collection.get_file_appends(basename) + if appends: + appended.append((basename, list(appends))) + else: + notappended.append(basename) + return appended, notappended + + def do_show_cross_depends(self, args): + """Show dependencies between recipes that cross layer boundaries. + +Figure out the dependencies between recipes that cross layer boundaries. + +NOTE: .bbappend files can impact the dependencies. +""" + ignore_layers = (args.ignore or '').split(',') + + pkg_fn = self.tinfoil.cooker_data.pkg_fn + bbpath = str(self.tinfoil.config_data.getVar('BBPATH', True)) + self.require_re = re.compile(r"require\s+(.+)") + self.include_re = re.compile(r"include\s+(.+)") + self.inherit_re = re.compile(r"inherit\s+(.+)") + + global_inherit = (self.tinfoil.config_data.getVar('INHERIT', True) or "").split() + + # The bb's DEPENDS and RDEPENDS + for f in pkg_fn: + f = bb.cache.virtualfn2realfn(f)[0] + # Get the layername that the file is in + layername = self.get_file_layer(f) + + # The DEPENDS + deps = self.tinfoil.cooker_data.deps[f] + for pn in deps: + if pn in self.tinfoil.cooker_data.pkg_pn: + best = bb.providers.findBestProvider(pn, + self.tinfoil.config_data, + self.tinfoil.cooker_data, + self.tinfoil.cooker_data.pkg_pn) + self.check_cross_depends("DEPENDS", layername, f, best[3], args.filenames, ignore_layers) + + # The RDPENDS + all_rdeps = self.tinfoil.cooker_data.rundeps[f].values() + # Remove the duplicated or null one. + sorted_rdeps = {} + # The all_rdeps is the list in list, so we need two for loops + for k1 in all_rdeps: + for k2 in k1: + sorted_rdeps[k2] = 1 + all_rdeps = sorted_rdeps.keys() + for rdep in all_rdeps: + all_p = bb.providers.getRuntimeProviders(self.tinfoil.cooker_data, rdep) + if all_p: + if f in all_p: + # The recipe provides this one itself, ignore + continue + best = bb.providers.filterProvidersRunTime(all_p, rdep, + self.tinfoil.config_data, + self.tinfoil.cooker_data)[0][0] + self.check_cross_depends("RDEPENDS", layername, f, best, args.filenames, ignore_layers) + + # The RRECOMMENDS + all_rrecs = self.tinfoil.cooker_data.runrecs[f].values() + # Remove the duplicated or null one. + sorted_rrecs = {} + # The all_rrecs is the list in list, so we need two for loops + for k1 in all_rrecs: + for k2 in k1: + sorted_rrecs[k2] = 1 + all_rrecs = sorted_rrecs.keys() + for rrec in all_rrecs: + all_p = bb.providers.getRuntimeProviders(self.tinfoil.cooker_data, rrec) + if all_p: + if f in all_p: + # The recipe provides this one itself, ignore + continue + best = bb.providers.filterProvidersRunTime(all_p, rrec, + self.tinfoil.config_data, + self.tinfoil.cooker_data)[0][0] + self.check_cross_depends("RRECOMMENDS", layername, f, best, args.filenames, ignore_layers) + + # The inherit class + cls_re = re.compile('classes/') + if f in self.tinfoil.cooker_data.inherits: + inherits = self.tinfoil.cooker_data.inherits[f] + for cls in inherits: + # The inherits' format is [classes/cls, /path/to/classes/cls] + # ignore the classes/cls. + if not cls_re.match(cls): + classname = os.path.splitext(os.path.basename(cls))[0] + if classname in global_inherit: + continue + inherit_layername = self.get_file_layer(cls) + if inherit_layername != layername and not inherit_layername in ignore_layers: + if not args.filenames: + f_short = self.remove_layer_prefix(f) + cls = self.remove_layer_prefix(cls) + else: + f_short = f + logger.plain("%s inherits %s" % (f_short, cls)) + + # The 'require/include xxx' in the bb file + pv_re = re.compile(r"\${PV}") + with open(f, 'r') as fnfile: + line = fnfile.readline() + while line: + m, keyword = self.match_require_include(line) + # Found the 'require/include xxxx' + if m: + needed_file = m.group(1) + # Replace the ${PV} with the real PV + if pv_re.search(needed_file) and f in self.tinfoil.cooker_data.pkg_pepvpr: + pv = self.tinfoil.cooker_data.pkg_pepvpr[f][1] + needed_file = re.sub(r"\${PV}", pv, needed_file) + self.print_cross_files(bbpath, keyword, layername, f, needed_file, args.filenames, ignore_layers) + line = fnfile.readline() + + # The "require/include xxx" in conf/machine/*.conf, .inc and .bbclass + conf_re = re.compile(".*/conf/machine/[^\/]*\.conf$") + inc_re = re.compile(".*\.inc$") + # The "inherit xxx" in .bbclass + bbclass_re = re.compile(".*\.bbclass$") + for layerdir in self.bblayers: + layername = self.get_layer_name(layerdir) + for dirpath, dirnames, filenames in os.walk(layerdir): + for name in filenames: + f = os.path.join(dirpath, name) + s = conf_re.match(f) or inc_re.match(f) or bbclass_re.match(f) + if s: + with open(f, 'r') as ffile: + line = ffile.readline() + while line: + m, keyword = self.match_require_include(line) + # Only bbclass has the "inherit xxx" here. + bbclass="" + if not m and f.endswith(".bbclass"): + m, keyword = self.match_inherit(line) + bbclass=".bbclass" + # Find a 'require/include xxxx' + if m: + self.print_cross_files(bbpath, keyword, layername, f, m.group(1) + bbclass, args.filenames, ignore_layers) + line = ffile.readline() + + def print_cross_files(self, bbpath, keyword, layername, f, needed_filename, show_filenames, ignore_layers): + """Print the depends that crosses a layer boundary""" + needed_file = bb.utils.which(bbpath, needed_filename) + if needed_file: + # Which layer is this file from + needed_layername = self.get_file_layer(needed_file) + if needed_layername != layername and not needed_layername in ignore_layers: + if not show_filenames: + f = self.remove_layer_prefix(f) + needed_file = self.remove_layer_prefix(needed_file) + logger.plain("%s %s %s" %(f, keyword, needed_file)) + + def match_inherit(self, line): + """Match the inherit xxx line""" + return (self.inherit_re.match(line), "inherits") + + def match_require_include(self, line): + """Match the require/include xxx line""" + m = self.require_re.match(line) + keyword = "requires" + if not m: + m = self.include_re.match(line) + keyword = "includes" + return (m, keyword) + + def check_cross_depends(self, keyword, layername, f, needed_file, show_filenames, ignore_layers): + """Print the DEPENDS/RDEPENDS file that crosses a layer boundary""" + best_realfn = bb.cache.virtualfn2realfn(needed_file)[0] + needed_layername = self.get_file_layer(best_realfn) + if needed_layername != layername and not needed_layername in ignore_layers: + if not show_filenames: + f = self.remove_layer_prefix(f) + best_realfn = self.remove_layer_prefix(best_realfn) + + logger.plain("%s %s %s" % (f, keyword, best_realfn)) + + def register_commands(self, sp): + self.add_command(sp, 'show-layers', self.do_show_layers, parserecipes=False) + + parser_show_overlayed = self.add_command(sp, 'show-overlayed', self.do_show_overlayed) + parser_show_overlayed.add_argument('-f', '--filenames', help='instead of the default formatting, list filenames of higher priority recipes with the ones they overlay indented underneath', action='store_true') + parser_show_overlayed.add_argument('-s', '--same-version', help='only list overlayed recipes where the version is the same', action='store_true') + + parser_show_recipes = self.add_command(sp, 'show-recipes', self.do_show_recipes) + parser_show_recipes.add_argument('-f', '--filenames', help='instead of the default formatting, list filenames of higher priority recipes with the ones they overlay indented underneath', action='store_true') + parser_show_recipes.add_argument('-m', '--multiple', help='only list where multiple recipes (in the same layer or different layers) exist for the same recipe name', action='store_true') + parser_show_recipes.add_argument('-i', '--inherits', help='only list recipes that inherit the named class', metavar='CLASS', default='') + parser_show_recipes.add_argument('pnspec', nargs='?', help='optional recipe name specification (wildcards allowed, enclose in quotes to avoid shell expansion)') + + self.add_command(sp, 'show-appends', self.do_show_appends) + + parser_show_cross_depends = self.add_command(sp, 'show-cross-depends', self.do_show_cross_depends) + parser_show_cross_depends.add_argument('-f', '--filenames', help='show full file path', action='store_true') + parser_show_cross_depends.add_argument('-i', '--ignore', help='ignore dependencies on items in the specified layer(s) (split multiple layer names with commas, no spaces)', metavar='LAYERNAME') diff --git a/import-layers/yocto-poky/bitbake/lib/bs4/__init__.py b/import-layers/yocto-poky/bitbake/lib/bs4/__init__.py index 7ba34269a..f6fdfd50b 100644 --- a/import-layers/yocto-poky/bitbake/lib/bs4/__init__.py +++ b/import-layers/yocto-poky/bitbake/lib/bs4/__init__.py @@ -17,8 +17,8 @@ http://www.crummy.com/software/BeautifulSoup/bs4/doc/ """ __author__ = "Leonard Richardson (leonardr@segfault.org)" -__version__ = "4.3.2" -__copyright__ = "Copyright (c) 2004-2013 Leonard Richardson" +__version__ = "4.4.1" +__copyright__ = "Copyright (c) 2004-2015 Leonard Richardson" __license__ = "MIT" __all__ = ['BeautifulSoup'] @@ -45,7 +45,7 @@ from .element import ( # The very first thing we do is give a useful error if someone is # running this code under Python 3 without converting it. -syntax_error = u'You are trying to run the Python 2 version of Beautiful Soup under Python 3. This will not work. You need to convert the code, either by installing it (`python setup.py install`) or by running 2to3 (`2to3 -w bs4`).' +'You are trying to run the Python 2 version of Beautiful Soup under Python 3. This will not work.'!='You need to convert the code, either by installing it (`python setup.py install`) or by running 2to3 (`2to3 -w bs4`).' class BeautifulSoup(Tag): """ @@ -69,7 +69,7 @@ class BeautifulSoup(Tag): like HTML's <br> tag), call handle_starttag and then handle_endtag. """ - ROOT_TAG_NAME = u'[document]' + ROOT_TAG_NAME = '[document]' # If the end-user gives no indication which tree builder they # want, look for one with these features. @@ -77,8 +77,11 @@ class BeautifulSoup(Tag): ASCII_SPACES = '\x20\x0a\x09\x0c\x0d' + NO_PARSER_SPECIFIED_WARNING = "No parser was explicitly specified, so I'm using the best available %(markup_type)s parser for this system (\"%(parser)s\"). This usually isn't a problem, but if you run this code on another system, or in a different virtual environment, it may use a different parser and behave differently.\n\nTo get rid of this warning, change this:\n\n BeautifulSoup([your markup])\n\nto this:\n\n BeautifulSoup([your markup], \"%(parser)s\")\n" + def __init__(self, markup="", features=None, builder=None, - parse_only=None, from_encoding=None, **kwargs): + parse_only=None, from_encoding=None, exclude_encodings=None, + **kwargs): """The Soup object is initialized as the 'root tag', and the provided markup (which can be a string or a file-like object) is fed into the underlying parser.""" @@ -114,9 +117,9 @@ class BeautifulSoup(Tag): del kwargs['isHTML'] warnings.warn( "BS4 does not respect the isHTML argument to the " - "BeautifulSoup constructor. You can pass in features='html' " - "or features='xml' to get a builder capable of handling " - "one or the other.") + "BeautifulSoup constructor. Suggest you use " + "features='lxml' for HTML and features='lxml-xml' for " + "XML.") def deprecated_argument(old_name, new_name): if old_name in kwargs: @@ -135,12 +138,13 @@ class BeautifulSoup(Tag): "fromEncoding", "from_encoding") if len(kwargs) > 0: - arg = kwargs.keys().pop() + arg = list(kwargs.keys()).pop() raise TypeError( "__init__() got an unexpected keyword argument '%s'" % arg) if builder is None: - if isinstance(features, basestring): + original_features = features + if isinstance(features, str): features = [features] if features is None or len(features) == 0: features = self.DEFAULT_BUILDER_FEATURES @@ -151,6 +155,16 @@ class BeautifulSoup(Tag): "requested: %s. Do you need to install a parser library?" % ",".join(features)) builder = builder_class() + if not (original_features == builder.NAME or + original_features in builder.ALTERNATE_NAMES): + if builder.is_xml: + markup_type = "XML" + else: + markup_type = "HTML" + warnings.warn(self.NO_PARSER_SPECIFIED_WARNING % dict( + parser=builder.NAME, + markup_type=markup_type)) + self.builder = builder self.is_xml = builder.is_xml self.builder.soup = self @@ -164,7 +178,7 @@ class BeautifulSoup(Tag): # involving passing non-markup to Beautiful Soup. # Beautiful Soup will still parse the input as markup, # just in case that's what the user really wants. - if (isinstance(markup, unicode) + if (isinstance(markup, str) and not os.path.supports_unicode_filenames): possible_filename = markup.encode("utf8") else: @@ -172,25 +186,30 @@ class BeautifulSoup(Tag): is_file = False try: is_file = os.path.exists(possible_filename) - except Exception, e: + except Exception as e: # This is almost certainly a problem involving # characters not valid in filenames on this # system. Just let it go. pass if is_file: + if isinstance(markup, str): + markup = markup.encode("utf8") warnings.warn( '"%s" looks like a filename, not markup. You should probably open this file and pass the filehandle into Beautiful Soup.' % markup) if markup[:5] == "http:" or markup[:6] == "https:": # TODO: This is ugly but I couldn't get it to work in # Python 3 otherwise. if ((isinstance(markup, bytes) and not b' ' in markup) - or (isinstance(markup, unicode) and not u' ' in markup)): + or (isinstance(markup, str) and not ' ' in markup)): + if isinstance(markup, str): + markup = markup.encode("utf8") warnings.warn( '"%s" looks like a URL. Beautiful Soup is not an HTTP client. You should probably use an HTTP client to get the document behind the URL, and feed that document to Beautiful Soup.' % markup) for (self.markup, self.original_encoding, self.declared_html_encoding, self.contains_replacement_characters) in ( - self.builder.prepare_markup(markup, from_encoding)): + self.builder.prepare_markup( + markup, from_encoding, exclude_encodings=exclude_encodings)): self.reset() try: self._feed() @@ -203,6 +222,16 @@ class BeautifulSoup(Tag): self.markup = None self.builder.soup = None + def __copy__(self): + return type(self)(self.encode(), builder=self.builder) + + def __getstate__(self): + # Frequently a tree builder can't be pickled. + d = dict(self.__dict__) + if 'builder' in d and not self.builder.picklable: + del d['builder'] + return d + def _feed(self): # Convert the document to Unicode. self.builder.reset() @@ -229,9 +258,7 @@ class BeautifulSoup(Tag): def new_string(self, s, subclass=NavigableString): """Create a new NavigableString associated with this soup.""" - navigable = subclass(s) - navigable.setup() - return navigable + return subclass(s) def insert_before(self, successor): raise NotImplementedError("BeautifulSoup objects don't support insert_before().") @@ -259,7 +286,7 @@ class BeautifulSoup(Tag): def endData(self, containerClass=NavigableString): if self.current_data: - current_data = u''.join(self.current_data) + current_data = ''.join(self.current_data) # If whitespace is not preserved, and this string contains # nothing but ASCII spaces, replace it with a single space # or newline. @@ -290,14 +317,49 @@ class BeautifulSoup(Tag): def object_was_parsed(self, o, parent=None, most_recent_element=None): """Add an object to the parse tree.""" parent = parent or self.currentTag - most_recent_element = most_recent_element or self._most_recent_element - o.setup(parent, most_recent_element) + previous_element = most_recent_element or self._most_recent_element + + next_element = previous_sibling = next_sibling = None + if isinstance(o, Tag): + next_element = o.next_element + next_sibling = o.next_sibling + previous_sibling = o.previous_sibling + if not previous_element: + previous_element = o.previous_element + + o.setup(parent, previous_element, next_element, previous_sibling, next_sibling) - if most_recent_element is not None: - most_recent_element.next_element = o self._most_recent_element = o parent.contents.append(o) + if parent.next_sibling: + # This node is being inserted into an element that has + # already been parsed. Deal with any dangling references. + index = parent.contents.index(o) + if index == 0: + previous_element = parent + previous_sibling = None + else: + previous_element = previous_sibling = parent.contents[index-1] + if index == len(parent.contents)-1: + next_element = parent.next_sibling + next_sibling = None + else: + next_element = next_sibling = parent.contents[index+1] + + o.previous_element = previous_element + if previous_element: + previous_element.next_element = o + o.next_element = next_element + if next_element: + next_element.previous_element = o + o.next_sibling = next_sibling + if next_sibling: + next_sibling.previous_sibling = o + o.previous_sibling = previous_sibling + if previous_sibling: + previous_sibling.next_sibling = o + def _popToTag(self, name, nsprefix=None, inclusivePop=True): """Pops the tag stack up to and including the most recent instance of the given tag. If inclusivePop is false, pops the tag @@ -367,9 +429,9 @@ class BeautifulSoup(Tag): encoding_part = '' if eventual_encoding != None: encoding_part = ' encoding="%s"' % eventual_encoding - prefix = u'<?xml version="1.0"%s?>\n' % encoding_part + prefix = '<?xml version="1.0"%s?>\n' % encoding_part else: - prefix = u'' + prefix = '' if not pretty_print: indent_level = None else: @@ -403,4 +465,4 @@ class FeatureNotFound(ValueError): if __name__ == '__main__': import sys soup = BeautifulSoup(sys.stdin) - print soup.prettify() + print(soup.prettify()) diff --git a/import-layers/yocto-poky/bitbake/lib/bs4/builder/__init__.py b/import-layers/yocto-poky/bitbake/lib/bs4/builder/__init__.py index 740f5f29c..6ccd4d23d 100644 --- a/import-layers/yocto-poky/bitbake/lib/bs4/builder/__init__.py +++ b/import-layers/yocto-poky/bitbake/lib/bs4/builder/__init__.py @@ -80,9 +80,12 @@ builder_registry = TreeBuilderRegistry() class TreeBuilder(object): """Turn a document into a Beautiful Soup object tree.""" + NAME = "[Unknown tree builder]" + ALTERNATE_NAMES = [] features = [] is_xml = False + picklable = False preserve_whitespace_tags = set() empty_element_tags = None # A tag will be considered an empty-element # tag when and only when it has no contents. @@ -153,13 +156,13 @@ class TreeBuilder(object): universal = self.cdata_list_attributes.get('*', []) tag_specific = self.cdata_list_attributes.get( tag_name.lower(), None) - for attr in attrs.keys(): + for attr in list(attrs.keys()): if attr in universal or (tag_specific and attr in tag_specific): # We have a "class"-type attribute whose string # value is a whitespace-separated list of # values. Split it into a list. value = attrs[attr] - if isinstance(value, basestring): + if isinstance(value, str): values = whitespace_re.split(value) else: # html5lib sometimes calls setAttributes twice diff --git a/import-layers/yocto-poky/bitbake/lib/bs4/builder/_html5lib.py b/import-layers/yocto-poky/bitbake/lib/bs4/builder/_html5lib.py index 7de36ae75..f0e5924eb 100644 --- a/import-layers/yocto-poky/bitbake/lib/bs4/builder/_html5lib.py +++ b/import-layers/yocto-poky/bitbake/lib/bs4/builder/_html5lib.py @@ -2,6 +2,7 @@ __all__ = [ 'HTML5TreeBuilder', ] +from pdb import set_trace import warnings from bs4.builder import ( PERMISSIVE, @@ -9,7 +10,10 @@ from bs4.builder import ( HTML_5, HTMLTreeBuilder, ) -from bs4.element import NamespacedAttribute +from bs4.element import ( + NamespacedAttribute, + whitespace_re, +) import html5lib from html5lib.constants import namespaces from bs4.element import ( @@ -22,11 +26,20 @@ from bs4.element import ( class HTML5TreeBuilder(HTMLTreeBuilder): """Use html5lib to build a tree.""" - features = ['html5lib', PERMISSIVE, HTML_5, HTML] + NAME = "html5lib" + + features = [NAME, PERMISSIVE, HTML_5, HTML] - def prepare_markup(self, markup, user_specified_encoding): + def prepare_markup(self, markup, user_specified_encoding, + document_declared_encoding=None, exclude_encodings=None): # Store the user-specified encoding for use later on. self.user_specified_encoding = user_specified_encoding + + # document_declared_encoding and exclude_encodings aren't used + # ATM because the html5lib TreeBuilder doesn't use + # UnicodeDammit. + if exclude_encodings: + warnings.warn("You provided a value for exclude_encoding, but the html5lib tree builder doesn't support exclude_encoding.") yield (markup, None, None, False) # These methods are defined by Beautiful Soup. @@ -37,7 +50,7 @@ class HTML5TreeBuilder(HTMLTreeBuilder): doc = parser.parse(markup, encoding=self.user_specified_encoding) # Set the character encoding detected by the tokenizer. - if isinstance(markup, unicode): + if isinstance(markup, str): # We need to special-case this because html5lib sets # charEncoding to UTF-8 if it gets Unicode input. doc.original_encoding = None @@ -51,7 +64,7 @@ class HTML5TreeBuilder(HTMLTreeBuilder): def test_fragment_to_document(self, fragment): """See `TreeBuilder`.""" - return u'<html><head></head><body>%s</body></html>' % fragment + return '<html><head></head><body>%s</body></html>' % fragment class TreeBuilderForHtml5lib(html5lib.treebuilders._base.TreeBuilder): @@ -101,7 +114,16 @@ class AttrList(object): def __iter__(self): return list(self.attrs.items()).__iter__() def __setitem__(self, name, value): - "set attr", name, value + # If this attribute is a multi-valued attribute for this element, + # turn its value into a list. + list_attr = HTML5TreeBuilder.cdata_list_attributes + if (name in list_attr['*'] + or (self.element.name in list_attr + and name in list_attr[self.element.name])): + # A node that is being cloned may have already undergone + # this procedure. + if not isinstance(value, list): + value = whitespace_re.split(value) self.element[name] = value def items(self): return list(self.attrs.items()) @@ -124,7 +146,7 @@ class Element(html5lib.treebuilders._base.Node): def appendChild(self, node): string_child = child = None - if isinstance(node, basestring): + if isinstance(node, str): # Some other piece of code decided to pass in a string # instead of creating a TextElement object to contain the # string. @@ -139,7 +161,7 @@ class Element(html5lib.treebuilders._base.Node): else: child = node.element - if not isinstance(child, basestring) and child.parent is not None: + if not isinstance(child, str) and child.parent is not None: node.element.extract() if (string_child and self.element.contents @@ -152,7 +174,7 @@ class Element(html5lib.treebuilders._base.Node): old_element.replace_with(new_element) self.soup._most_recent_element = new_element else: - if isinstance(node, basestring): + if isinstance(node, str): # Create a brand new NavigableString from this string. child = self.soup.new_string(node) @@ -161,6 +183,12 @@ class Element(html5lib.treebuilders._base.Node): # immediately after the parent, if it has no children.) if self.element.contents: most_recent_element = self.element._last_descendant(False) + elif self.element.next_element is not None: + # Something from further ahead in the parse tree is + # being inserted into this earlier element. This is + # very annoying because it means an expensive search + # for the last element in the tree. + most_recent_element = self.soup._last_descendant() else: most_recent_element = self.element @@ -172,6 +200,7 @@ class Element(html5lib.treebuilders._base.Node): return AttrList(self.element) def setAttributes(self, attributes): + if attributes is not None and len(attributes) > 0: converted_attributes = [] @@ -183,7 +212,7 @@ class Element(html5lib.treebuilders._base.Node): self.soup.builder._replace_cdata_list_attribute_values( self.name, attributes) - for name, value in attributes.items(): + for name, value in list(attributes.items()): self.element[name] = value # The attributes may contain variables that need substitution. @@ -218,6 +247,9 @@ class Element(html5lib.treebuilders._base.Node): def reparentChildren(self, new_parent): """Move all of this tag's children into another tag.""" + # print "MOVE", self.element.contents + # print "FROM", self.element + # print "TO", new_parent.element element = self.element new_parent_element = new_parent.element # Determine what this tag's next_element will be once all the children @@ -236,17 +268,28 @@ class Element(html5lib.treebuilders._base.Node): new_parents_last_descendant_next_element = new_parent_element.next_element to_append = element.contents - append_after = new_parent.element.contents + append_after = new_parent_element.contents if len(to_append) > 0: # Set the first child's previous_element and previous_sibling # to elements within the new parent first_child = to_append[0] - first_child.previous_element = new_parents_last_descendant + if new_parents_last_descendant: + first_child.previous_element = new_parents_last_descendant + else: + first_child.previous_element = new_parent_element first_child.previous_sibling = new_parents_last_child + if new_parents_last_descendant: + new_parents_last_descendant.next_element = first_child + else: + new_parent_element.next_element = first_child + if new_parents_last_child: + new_parents_last_child.next_sibling = first_child # Fix the last child's next_element and next_sibling last_child = to_append[-1] last_child.next_element = new_parents_last_descendant_next_element + if new_parents_last_descendant_next_element: + new_parents_last_descendant_next_element.previous_element = last_child last_child.next_sibling = None for child in to_append: @@ -257,6 +300,10 @@ class Element(html5lib.treebuilders._base.Node): element.contents = [] element.next_element = final_next_element + # print "DONE WITH MOVE" + # print "FROM", self.element + # print "TO", new_parent_element + def cloneNode(self): tag = self.soup.new_tag(self.element.name, self.namespace) node = Element(tag, self.soup, self.namespace) diff --git a/import-layers/yocto-poky/bitbake/lib/bs4/builder/_htmlparser.py b/import-layers/yocto-poky/bitbake/lib/bs4/builder/_htmlparser.py index ca8d8b892..bb0a63f2f 100644 --- a/import-layers/yocto-poky/bitbake/lib/bs4/builder/_htmlparser.py +++ b/import-layers/yocto-poky/bitbake/lib/bs4/builder/_htmlparser.py @@ -4,10 +4,16 @@ __all__ = [ 'HTMLParserTreeBuilder', ] -from HTMLParser import ( - HTMLParser, - HTMLParseError, - ) +from html.parser import HTMLParser + +try: + from html.parser import HTMLParseError +except ImportError as e: + # HTMLParseError is removed in Python 3.5. Since it can never be + # thrown in 3.5, we can just define our own class as a placeholder. + class HTMLParseError(Exception): + pass + import sys import warnings @@ -19,10 +25,10 @@ import warnings # At the end of this file, we monkeypatch HTMLParser so that # strict=True works well on Python 3.2.2. major, minor, release = sys.version_info[:3] -CONSTRUCTOR_TAKES_STRICT = ( - major > 3 - or (major == 3 and minor > 2) - or (major == 3 and minor == 2 and release >= 3)) +CONSTRUCTOR_TAKES_STRICT = major == 3 and minor == 2 and release >= 3 +CONSTRUCTOR_STRICT_IS_DEPRECATED = major == 3 and minor == 3 +CONSTRUCTOR_TAKES_CONVERT_CHARREFS = major == 3 and minor >= 4 + from bs4.element import ( CData, @@ -63,7 +69,8 @@ class BeautifulSoupHTMLParser(HTMLParser): def handle_charref(self, name): # XXX workaround for a bug in HTMLParser. Remove this once - # it's fixed. + # it's fixed in all supported versions. + # http://bugs.python.org/issue13633 if name.startswith('x'): real_name = int(name.lstrip('x'), 16) elif name.startswith('X'): @@ -72,9 +79,9 @@ class BeautifulSoupHTMLParser(HTMLParser): real_name = int(name) try: - data = unichr(real_name) - except (ValueError, OverflowError), e: - data = u"\N{REPLACEMENT CHARACTER}" + data = chr(real_name) + except (ValueError, OverflowError) as e: + data = "\N{REPLACEMENT CHARACTER}" self.handle_data(data) @@ -113,14 +120,6 @@ class BeautifulSoupHTMLParser(HTMLParser): def handle_pi(self, data): self.soup.endData() - if data.endswith("?") and data.lower().startswith("xml"): - # "An XHTML processing instruction using the trailing '?' - # will cause the '?' to be included in data." - HTMLParser - # docs. - # - # Strip the question mark so we don't end up with two - # question marks. - data = data[:-1] self.soup.handle_data(data) self.soup.endData(ProcessingInstruction) @@ -128,26 +127,31 @@ class BeautifulSoupHTMLParser(HTMLParser): class HTMLParserTreeBuilder(HTMLTreeBuilder): is_xml = False - features = [HTML, STRICT, HTMLPARSER] + picklable = True + NAME = HTMLPARSER + features = [NAME, HTML, STRICT] def __init__(self, *args, **kwargs): - if CONSTRUCTOR_TAKES_STRICT: + if CONSTRUCTOR_TAKES_STRICT and not CONSTRUCTOR_STRICT_IS_DEPRECATED: kwargs['strict'] = False + if CONSTRUCTOR_TAKES_CONVERT_CHARREFS: + kwargs['convert_charrefs'] = False self.parser_args = (args, kwargs) def prepare_markup(self, markup, user_specified_encoding=None, - document_declared_encoding=None): + document_declared_encoding=None, exclude_encodings=None): """ :return: A 4-tuple (markup, original encoding, encoding declared within markup, whether any characters had to be replaced with REPLACEMENT CHARACTER). """ - if isinstance(markup, unicode): + if isinstance(markup, str): yield (markup, None, None, False) return try_encodings = [user_specified_encoding, document_declared_encoding] - dammit = UnicodeDammit(markup, try_encodings, is_html=True) + dammit = UnicodeDammit(markup, try_encodings, is_html=True, + exclude_encodings=exclude_encodings) yield (dammit.markup, dammit.original_encoding, dammit.declared_html_encoding, dammit.contains_replacement_characters) @@ -158,7 +162,7 @@ class HTMLParserTreeBuilder(HTMLTreeBuilder): parser.soup = self.soup try: parser.feed(markup) - except HTMLParseError, e: + except HTMLParseError as e: warnings.warn(RuntimeWarning( "Python's built-in HTMLParser cannot parse the given document. This is not a bug in Beautiful Soup. The best solution is to install an external parser (lxml or html5lib), and use Beautiful Soup with that parser. See http://www.crummy.com/software/BeautifulSoup/bs4/doc/#installing-a-parser for help.")) raise e diff --git a/import-layers/yocto-poky/bitbake/lib/bs4/builder/_lxml.py b/import-layers/yocto-poky/bitbake/lib/bs4/builder/_lxml.py index fa5d49875..9c6c14ee6 100644 --- a/import-layers/yocto-poky/bitbake/lib/bs4/builder/_lxml.py +++ b/import-layers/yocto-poky/bitbake/lib/bs4/builder/_lxml.py @@ -4,10 +4,15 @@ __all__ = [ ] from io import BytesIO -from StringIO import StringIO +from io import StringIO import collections from lxml import etree -from bs4.element import Comment, Doctype, NamespacedAttribute +from bs4.element import ( + Comment, + Doctype, + NamespacedAttribute, + ProcessingInstruction, +) from bs4.builder import ( FAST, HTML, @@ -25,8 +30,11 @@ class LXMLTreeBuilderForXML(TreeBuilder): is_xml = True + NAME = "lxml-xml" + ALTERNATE_NAMES = ["xml"] + # Well, it's permissive by XML parser standards. - features = [LXML, XML, FAST, PERMISSIVE] + features = [NAME, LXML, XML, FAST, PERMISSIVE] CHUNK_SIZE = 512 @@ -70,6 +78,7 @@ class LXMLTreeBuilderForXML(TreeBuilder): return (None, tag) def prepare_markup(self, markup, user_specified_encoding=None, + exclude_encodings=None, document_declared_encoding=None): """ :yield: A series of 4-tuples. @@ -78,12 +87,12 @@ class LXMLTreeBuilderForXML(TreeBuilder): Each 4-tuple represents a strategy for parsing the document. """ - if isinstance(markup, unicode): + if isinstance(markup, str): # We were given Unicode. Maybe lxml can parse Unicode on # this system? yield markup, None, document_declared_encoding, False - if isinstance(markup, unicode): + if isinstance(markup, str): # No, apparently not. Convert the Unicode to UTF-8 and # tell lxml to parse it as UTF-8. yield (markup.encode("utf8"), "utf8", @@ -95,14 +104,15 @@ class LXMLTreeBuilderForXML(TreeBuilder): # the document as each one in turn. is_html = not self.is_xml try_encodings = [user_specified_encoding, document_declared_encoding] - detector = EncodingDetector(markup, try_encodings, is_html) + detector = EncodingDetector( + markup, try_encodings, is_html, exclude_encodings) for encoding in detector.encodings: yield (detector.markup, encoding, document_declared_encoding, False) def feed(self, markup): if isinstance(markup, bytes): markup = BytesIO(markup) - elif isinstance(markup, unicode): + elif isinstance(markup, str): markup = StringIO(markup) # Call feed() at least once, even if the markup is empty, @@ -117,7 +127,7 @@ class LXMLTreeBuilderForXML(TreeBuilder): if len(data) != 0: self.parser.feed(data) self.parser.close() - except (UnicodeDecodeError, LookupError, etree.ParserError), e: + except (UnicodeDecodeError, LookupError, etree.ParserError) as e: raise ParserRejectedMarkup(str(e)) def close(self): @@ -135,12 +145,12 @@ class LXMLTreeBuilderForXML(TreeBuilder): self.nsmaps.append(None) elif len(nsmap) > 0: # A new namespace mapping has come into play. - inverted_nsmap = dict((value, key) for key, value in nsmap.items()) + inverted_nsmap = dict((value, key) for key, value in list(nsmap.items())) self.nsmaps.append(inverted_nsmap) # Also treat the namespace mapping as a set of attributes on the # tag, so we can recreate it later. attrs = attrs.copy() - for prefix, namespace in nsmap.items(): + for prefix, namespace in list(nsmap.items()): attribute = NamespacedAttribute( "xmlns", prefix, "http://www.w3.org/2000/xmlns/") attrs[attribute] = namespace @@ -149,7 +159,7 @@ class LXMLTreeBuilderForXML(TreeBuilder): # from lxml with namespaces attached to their names, and # turn then into NamespacedAttribute objects. new_attrs = {} - for attr, value in attrs.items(): + for attr, value in list(attrs.items()): namespace, attr = self._getNsTag(attr) if namespace is None: new_attrs[attr] = value @@ -189,7 +199,9 @@ class LXMLTreeBuilderForXML(TreeBuilder): self.nsmaps.pop() def pi(self, target, data): - pass + self.soup.endData() + self.soup.handle_data(target + ' ' + data) + self.soup.endData(ProcessingInstruction) def data(self, content): self.soup.handle_data(content) @@ -207,12 +219,15 @@ class LXMLTreeBuilderForXML(TreeBuilder): def test_fragment_to_document(self, fragment): """See `TreeBuilder`.""" - return u'<?xml version="1.0" encoding="utf-8"?>\n%s' % fragment + return '<?xml version="1.0" encoding="utf-8"?>\n%s' % fragment class LXMLTreeBuilder(HTMLTreeBuilder, LXMLTreeBuilderForXML): - features = [LXML, HTML, FAST, PERMISSIVE] + NAME = LXML + ALTERNATE_NAMES = ["lxml-html"] + + features = ALTERNATE_NAMES + [NAME, HTML, FAST, PERMISSIVE] is_xml = False def default_parser(self, encoding): @@ -224,10 +239,10 @@ class LXMLTreeBuilder(HTMLTreeBuilder, LXMLTreeBuilderForXML): self.parser = self.parser_for(encoding) self.parser.feed(markup) self.parser.close() - except (UnicodeDecodeError, LookupError, etree.ParserError), e: + except (UnicodeDecodeError, LookupError, etree.ParserError) as e: raise ParserRejectedMarkup(str(e)) def test_fragment_to_document(self, fragment): """See `TreeBuilder`.""" - return u'<html><body>%s</body></html>' % fragment + return '<html><body>%s</body></html>' % fragment diff --git a/import-layers/yocto-poky/bitbake/lib/bs4/dammit.py b/import-layers/yocto-poky/bitbake/lib/bs4/dammit.py index 59640b7ce..68d419feb 100644 --- a/import-layers/yocto-poky/bitbake/lib/bs4/dammit.py +++ b/import-layers/yocto-poky/bitbake/lib/bs4/dammit.py @@ -3,12 +3,14 @@ This library converts a bytestream to Unicode through any means necessary. It is heavily based on code from Mark Pilgrim's Universal -Feed Parser. It works best on XML and XML, but it does not rewrite the +Feed Parser. It works best on XML and HTML, but it does not rewrite the XML or HTML to reflect a new encoding; that's the tree builder's job. """ +__license__ = "MIT" +from pdb import set_trace import codecs -from htmlentitydefs import codepoint2name +from html.entities import codepoint2name import re import logging import string @@ -56,7 +58,7 @@ class EntitySubstitution(object): reverse_lookup = {} characters_for_re = [] for codepoint, name in list(codepoint2name.items()): - character = unichr(codepoint) + character = chr(codepoint) if codepoint != 34: # There's no point in turning the quotation mark into # ", unless it happens within an attribute value, which @@ -212,8 +214,11 @@ class EncodingDetector: 5. Windows-1252. """ - def __init__(self, markup, override_encodings=None, is_html=False): + def __init__(self, markup, override_encodings=None, is_html=False, + exclude_encodings=None): self.override_encodings = override_encodings or [] + exclude_encodings = exclude_encodings or [] + self.exclude_encodings = set([x.lower() for x in exclude_encodings]) self.chardet_encoding = None self.is_html = is_html self.declared_encoding = None @@ -224,6 +229,8 @@ class EncodingDetector: def _usable(self, encoding, tried): if encoding is not None: encoding = encoding.lower() + if encoding in self.exclude_encodings: + return False if encoding not in tried: tried.add(encoding) return True @@ -266,6 +273,9 @@ class EncodingDetector: def strip_byte_order_mark(cls, data): """If a byte-order mark is present, strip it and return the encoding it implies.""" encoding = None + if isinstance(data, str): + # Unicode data cannot have a byte-order mark. + return data, encoding if (len(data) >= 4) and (data[:2] == b'\xfe\xff') \ and (data[2:4] != '\x00\x00'): encoding = 'utf-16be' @@ -306,7 +316,7 @@ class EncodingDetector: declared_encoding_match = html_meta_re.search(markup, endpos=html_endpos) if declared_encoding_match is not None: declared_encoding = declared_encoding_match.groups()[0].decode( - 'ascii') + 'ascii', 'replace') if declared_encoding: return declared_encoding.lower() return None @@ -331,18 +341,19 @@ class UnicodeDammit: ] def __init__(self, markup, override_encodings=[], - smart_quotes_to=None, is_html=False): + smart_quotes_to=None, is_html=False, exclude_encodings=[]): self.smart_quotes_to = smart_quotes_to self.tried_encodings = [] self.contains_replacement_characters = False self.is_html = is_html - self.detector = EncodingDetector(markup, override_encodings, is_html) + self.detector = EncodingDetector( + markup, override_encodings, is_html, exclude_encodings) # Short-circuit if the data is in Unicode to begin with. - if isinstance(markup, unicode) or markup == '': + if isinstance(markup, str) or markup == '': self.markup = markup - self.unicode_markup = unicode(markup) + self.unicode_markup = str(markup) self.original_encoding = None return @@ -425,7 +436,7 @@ class UnicodeDammit: def _to_unicode(self, data, encoding, errors="strict"): '''Given a string and its encoding, decodes the string into Unicode. %encoding is a string recognized by encodings.aliases''' - return unicode(data, encoding, errors) + return str(data, encoding, errors) @property def declared_html_encoding(self): diff --git a/import-layers/yocto-poky/bitbake/lib/bs4/diagnose.py b/import-layers/yocto-poky/bitbake/lib/bs4/diagnose.py index 4d0b00afa..083395fb4 100644 --- a/import-layers/yocto-poky/bitbake/lib/bs4/diagnose.py +++ b/import-layers/yocto-poky/bitbake/lib/bs4/diagnose.py @@ -1,7 +1,10 @@ """Diagnostic functions, mainly for use when doing tech support.""" + +__license__ = "MIT" + import cProfile -from StringIO import StringIO -from HTMLParser import HTMLParser +from io import StringIO +from html.parser import HTMLParser import bs4 from bs4 import BeautifulSoup, __version__ from bs4.builder import builder_registry @@ -17,8 +20,8 @@ import cProfile def diagnose(data): """Diagnostic suite for isolating common problems.""" - print "Diagnostic running on Beautiful Soup %s" % __version__ - print "Python version %s" % sys.version + print("Diagnostic running on Beautiful Soup %s" % __version__) + print("Python version %s" % sys.version) basic_parsers = ["html.parser", "html5lib", "lxml"] for name in basic_parsers: @@ -27,44 +30,53 @@ def diagnose(data): break else: basic_parsers.remove(name) - print ( + print(( "I noticed that %s is not installed. Installing it may help." % - name) + name)) if 'lxml' in basic_parsers: basic_parsers.append(["lxml", "xml"]) - from lxml import etree - print "Found lxml version %s" % ".".join(map(str,etree.LXML_VERSION)) + try: + from lxml import etree + print("Found lxml version %s" % ".".join(map(str,etree.LXML_VERSION))) + except ImportError as e: + print ( + "lxml is not installed or couldn't be imported.") + if 'html5lib' in basic_parsers: - import html5lib - print "Found html5lib version %s" % html5lib.__version__ + try: + import html5lib + print("Found html5lib version %s" % html5lib.__version__) + except ImportError as e: + print ( + "html5lib is not installed or couldn't be imported.") if hasattr(data, 'read'): data = data.read() elif os.path.exists(data): - print '"%s" looks like a filename. Reading data from the file.' % data + print('"%s" looks like a filename. Reading data from the file.' % data) data = open(data).read() elif data.startswith("http:") or data.startswith("https:"): - print '"%s" looks like a URL. Beautiful Soup is not an HTTP client.' % data - print "You need to use some other library to get the document behind the URL, and feed that document to Beautiful Soup." + print('"%s" looks like a URL. Beautiful Soup is not an HTTP client.' % data) + print("You need to use some other library to get the document behind the URL, and feed that document to Beautiful Soup.") return - print + print() for parser in basic_parsers: - print "Trying to parse your markup with %s" % parser + print("Trying to parse your markup with %s" % parser) success = False try: soup = BeautifulSoup(data, parser) success = True - except Exception, e: - print "%s could not parse the markup." % parser + except Exception as e: + print("%s could not parse the markup." % parser) traceback.print_exc() if success: - print "Here's what %s did with the markup:" % parser - print soup.prettify() + print("Here's what %s did with the markup:" % parser) + print(soup.prettify()) - print "-" * 80 + print("-" * 80) def lxml_trace(data, html=True, **kwargs): """Print out the lxml events that occur during parsing. @@ -74,7 +86,7 @@ def lxml_trace(data, html=True, **kwargs): """ from lxml import etree for event, element in etree.iterparse(StringIO(data), html=html, **kwargs): - print("%s, %4s, %s" % (event, element.tag, element.text)) + print(("%s, %4s, %s" % (event, element.tag, element.text))) class AnnouncingParser(HTMLParser): """Announces HTMLParser parse events, without doing anything else.""" @@ -156,9 +168,9 @@ def rdoc(num_elements=1000): def benchmark_parsers(num_elements=100000): """Very basic head-to-head performance benchmark.""" - print "Comparative parser benchmark on Beautiful Soup %s" % __version__ + print("Comparative parser benchmark on Beautiful Soup %s" % __version__) data = rdoc(num_elements) - print "Generated a large invalid HTML document (%d bytes)." % len(data) + print("Generated a large invalid HTML document (%d bytes)." % len(data)) for parser in ["lxml", ["lxml", "html"], "html5lib", "html.parser"]: success = False @@ -167,24 +179,24 @@ def benchmark_parsers(num_elements=100000): soup = BeautifulSoup(data, parser) b = time.time() success = True - except Exception, e: - print "%s could not parse the markup." % parser + except Exception as e: + print("%s could not parse the markup." % parser) traceback.print_exc() if success: - print "BS4+%s parsed the markup in %.2fs." % (parser, b-a) + print("BS4+%s parsed the markup in %.2fs." % (parser, b-a)) from lxml import etree a = time.time() etree.HTML(data) b = time.time() - print "Raw lxml parsed the markup in %.2fs." % (b-a) + print("Raw lxml parsed the markup in %.2fs." % (b-a)) import html5lib parser = html5lib.HTMLParser() a = time.time() parser.parse(data) b = time.time() - print "Raw html5lib parsed the markup in %.2fs." % (b-a) + print("Raw html5lib parsed the markup in %.2fs." % (b-a)) def profile(num_elements=100000, parser="lxml"): diff --git a/import-layers/yocto-poky/bitbake/lib/bs4/element.py b/import-layers/yocto-poky/bitbake/lib/bs4/element.py index da9afdf48..0e62c2e10 100644 --- a/import-layers/yocto-poky/bitbake/lib/bs4/element.py +++ b/import-layers/yocto-poky/bitbake/lib/bs4/element.py @@ -1,3 +1,6 @@ +__license__ = "MIT" + +from pdb import set_trace import collections import re import sys @@ -21,22 +24,22 @@ def _alias(attr): return alias -class NamespacedAttribute(unicode): +class NamespacedAttribute(str): def __new__(cls, prefix, name, namespace=None): if name is None: - obj = unicode.__new__(cls, prefix) + obj = str.__new__(cls, prefix) elif prefix is None: # Not really namespaced. - obj = unicode.__new__(cls, name) + obj = str.__new__(cls, name) else: - obj = unicode.__new__(cls, prefix + ":" + name) + obj = str.__new__(cls, prefix + ":" + name) obj.prefix = prefix obj.name = name obj.namespace = namespace return obj -class AttributeValueWithCharsetSubstitution(unicode): +class AttributeValueWithCharsetSubstitution(str): """A stand-in object for a character encoding specified in HTML.""" class CharsetMetaAttributeValue(AttributeValueWithCharsetSubstitution): @@ -47,7 +50,7 @@ class CharsetMetaAttributeValue(AttributeValueWithCharsetSubstitution): """ def __new__(cls, original_value): - obj = unicode.__new__(cls, original_value) + obj = str.__new__(cls, original_value) obj.original_value = original_value return obj @@ -70,9 +73,9 @@ class ContentMetaAttributeValue(AttributeValueWithCharsetSubstitution): match = cls.CHARSET_RE.search(original_value) if match is None: # No substitution necessary. - return unicode.__new__(unicode, original_value) + return str.__new__(str, original_value) - obj = unicode.__new__(cls, original_value) + obj = str.__new__(cls, original_value) obj.original_value = original_value return obj @@ -152,7 +155,7 @@ class PageElement(object): def format_string(self, s, formatter='minimal'): """Format the given string using the given formatter.""" - if not callable(formatter): + if not isinstance(formatter, collections.Callable): formatter = self._formatter_for_name(formatter) if formatter is None: output = s @@ -185,24 +188,40 @@ class PageElement(object): return self.HTML_FORMATTERS.get( name, HTMLAwareEntitySubstitution.substitute_xml) - def setup(self, parent=None, previous_element=None): + def setup(self, parent=None, previous_element=None, next_element=None, + previous_sibling=None, next_sibling=None): """Sets up the initial relations between this element and other elements.""" self.parent = parent + self.previous_element = previous_element if previous_element is not None: self.previous_element.next_element = self - self.next_element = None - self.previous_sibling = None - self.next_sibling = None - if self.parent is not None and self.parent.contents: - self.previous_sibling = self.parent.contents[-1] + + self.next_element = next_element + if self.next_element: + self.next_element.previous_element = self + + self.next_sibling = next_sibling + if self.next_sibling: + self.next_sibling.previous_sibling = self + + if (not previous_sibling + and self.parent is not None and self.parent.contents): + previous_sibling = self.parent.contents[-1] + + self.previous_sibling = previous_sibling + if previous_sibling: self.previous_sibling.next_sibling = self nextSibling = _alias("next_sibling") # BS3 previousSibling = _alias("previous_sibling") # BS3 def replace_with(self, replace_with): + if not self.parent: + raise ValueError( + "Cannot replace one element with another when the" + "element to be replaced is not part of a tree.") if replace_with is self: return if replace_with is self.parent: @@ -216,6 +235,10 @@ class PageElement(object): def unwrap(self): my_parent = self.parent + if not self.parent: + raise ValueError( + "Cannot replace an element with its contents when that" + "element is not part of a tree.") my_index = self.parent.index(self) self.extract() for child in reversed(self.contents[:]): @@ -240,17 +263,20 @@ class PageElement(object): last_child = self._last_descendant() next_element = last_child.next_element - if self.previous_element is not None: + if (self.previous_element is not None and + self.previous_element is not next_element): self.previous_element.next_element = next_element - if next_element is not None: + if next_element is not None and next_element is not self.previous_element: next_element.previous_element = self.previous_element self.previous_element = None last_child.next_element = None self.parent = None - if self.previous_sibling is not None: + if (self.previous_sibling is not None + and self.previous_sibling is not self.next_sibling): self.previous_sibling.next_sibling = self.next_sibling - if self.next_sibling is not None: + if (self.next_sibling is not None + and self.next_sibling is not self.previous_sibling): self.next_sibling.previous_sibling = self.previous_sibling self.previous_sibling = self.next_sibling = None return self @@ -263,16 +289,18 @@ class PageElement(object): last_child = self while isinstance(last_child, Tag) and last_child.contents: last_child = last_child.contents[-1] - if not accept_self and last_child == self: + if not accept_self and last_child is self: last_child = None return last_child # BS3: Not part of the API! _lastRecursiveChild = _last_descendant def insert(self, position, new_child): + if new_child is None: + raise ValueError("Cannot insert None into a tag.") if new_child is self: raise ValueError("Cannot insert a tag into itself.") - if (isinstance(new_child, basestring) + if (isinstance(new_child, str) and not isinstance(new_child, NavigableString)): new_child = NavigableString(new_child) @@ -478,6 +506,10 @@ class PageElement(object): def _find_all(self, name, attrs, text, limit, generator, **kwargs): "Iterates over a generator looking for things that match." + if text is None and 'string' in kwargs: + text = kwargs['string'] + del kwargs['string'] + if isinstance(name, SoupStrainer): strainer = name else: @@ -489,7 +521,7 @@ class PageElement(object): result = (element for element in generator if isinstance(element, Tag)) return ResultSet(strainer, result) - elif isinstance(name, basestring): + elif isinstance(name, str): # Optimization to find all tags with a given name. result = (element for element in generator if isinstance(element, Tag) @@ -548,17 +580,17 @@ class PageElement(object): # Methods for supporting CSS selectors. - tag_name_re = re.compile('^[a-z0-9]+$') + tag_name_re = re.compile('^[a-zA-Z0-9][-.a-zA-Z0-9:_]*$') - # /^(\w+)\[(\w+)([=~\|\^\$\*]?)=?"?([^\]"]*)"?\]$/ - # \---/ \---/\-------------/ \-------/ - # | | | | - # | | | The value - # | | ~,|,^,$,* or = - # | Attribute + # /^([a-zA-Z0-9][-.a-zA-Z0-9:_]*)\[(\w+)([=~\|\^\$\*]?)=?"?([^\]"]*)"?\]$/ + # \---------------------------/ \---/\-------------/ \-------/ + # | | | | + # | | | The value + # | | ~,|,^,$,* or = + # | Attribute # Tag attribselect_re = re.compile( - r'^(?P<tag>\w+)?\[(?P<attribute>\w+)(?P<operator>[=~\|\^\$\*]?)' + + r'^(?P<tag>[a-zA-Z0-9][-.a-zA-Z0-9:_]*)?\[(?P<attribute>[\w-]+)(?P<operator>[=~\|\^\$\*]?)' + r'=?"?(?P<value>[^\]"]*)"?\]$' ) @@ -640,7 +672,7 @@ class PageElement(object): return self.parents -class NavigableString(unicode, PageElement): +class NavigableString(str, PageElement): PREFIX = '' SUFFIX = '' @@ -653,15 +685,21 @@ class NavigableString(unicode, PageElement): passed in to the superclass's __new__ or the superclass won't know how to handle non-ASCII characters. """ - if isinstance(value, unicode): - return unicode.__new__(cls, value) - return unicode.__new__(cls, value, DEFAULT_OUTPUT_ENCODING) + if isinstance(value, str): + u = str.__new__(cls, value) + else: + u = str.__new__(cls, value, DEFAULT_OUTPUT_ENCODING) + u.setup() + return u def __copy__(self): - return self + """A copy of a NavigableString has the same contents and class + as the original, but it is not connected to the parse tree. + """ + return type(self)(self) def __getnewargs__(self): - return (unicode(self),) + return (str(self),) def __getattr__(self, attr): """text.string gives you text. This is for backwards @@ -701,23 +739,23 @@ class PreformattedString(NavigableString): class CData(PreformattedString): - PREFIX = u'<![CDATA[' - SUFFIX = u']]>' + PREFIX = '<![CDATA[' + SUFFIX = ']]>' class ProcessingInstruction(PreformattedString): - PREFIX = u'<?' - SUFFIX = u'?>' + PREFIX = '<?' + SUFFIX = '>' class Comment(PreformattedString): - PREFIX = u'<!--' - SUFFIX = u'-->' + PREFIX = '<!--' + SUFFIX = '-->' class Declaration(PreformattedString): - PREFIX = u'<!' - SUFFIX = u'!>' + PREFIX = '<?' + SUFFIX = '?>' class Doctype(PreformattedString): @@ -734,8 +772,8 @@ class Doctype(PreformattedString): return Doctype(value) - PREFIX = u'<!DOCTYPE ' - SUFFIX = u'>\n' + PREFIX = '<!DOCTYPE ' + SUFFIX = '>\n' class Tag(PageElement): @@ -759,9 +797,12 @@ class Tag(PageElement): self.prefix = prefix if attrs is None: attrs = {} - elif attrs and builder.cdata_list_attributes: - attrs = builder._replace_cdata_list_attribute_values( - self.name, attrs) + elif attrs: + if builder is not None and builder.cdata_list_attributes: + attrs = builder._replace_cdata_list_attribute_values( + self.name, attrs) + else: + attrs = dict(attrs) else: attrs = dict(attrs) self.attrs = attrs @@ -778,6 +819,18 @@ class Tag(PageElement): parserClass = _alias("parser_class") # BS3 + def __copy__(self): + """A copy of a Tag is a new Tag, unconnected to the parse tree. + Its contents are a copy of the old Tag's contents. + """ + clone = type(self)(None, self.builder, self.name, self.namespace, + self.nsprefix, self.attrs) + for attr in ('can_be_empty_element', 'hidden'): + setattr(clone, attr, getattr(self, attr)) + for child in self.contents: + clone.append(child.__copy__()) + return clone + @property def is_empty_element(self): """Is this tag an empty-element tag? (aka a self-closing tag) @@ -843,7 +896,7 @@ class Tag(PageElement): for string in self._all_strings(True): yield string - def get_text(self, separator=u"", strip=False, + def get_text(self, separator="", strip=False, types=(NavigableString, CData)): """ Get all child strings, concatenated using the given separator. @@ -915,7 +968,7 @@ class Tag(PageElement): def __contains__(self, x): return x in self.contents - def __nonzero__(self): + def __bool__(self): "A tag is non-None even if it has no contents." return True @@ -971,15 +1024,25 @@ class Tag(PageElement): as defined in __eq__.""" return not self == other - def __repr__(self, encoding=DEFAULT_OUTPUT_ENCODING): + def __repr__(self, encoding="unicode-escape"): """Renders this tag as a string.""" - return self.encode(encoding) + if PY3K: + # "The return value must be a string object", i.e. Unicode + return self.decode() + else: + # "The return value must be a string object", i.e. a bytestring. + # By convention, the return value of __repr__ should also be + # an ASCII string. + return self.encode(encoding) def __unicode__(self): return self.decode() def __str__(self): - return self.encode() + if PY3K: + return self.decode() + else: + return self.encode() if PY3K: __str__ = __repr__ = __unicode__ @@ -1014,7 +1077,7 @@ class Tag(PageElement): # First off, turn a string formatter into a function. This # will stop the lookup from happening over and over again. - if not callable(formatter): + if not isinstance(formatter, collections.Callable): formatter = self._formatter_for_name(formatter) attrs = [] @@ -1025,8 +1088,8 @@ class Tag(PageElement): else: if isinstance(val, list) or isinstance(val, tuple): val = ' '.join(val) - elif not isinstance(val, basestring): - val = unicode(val) + elif not isinstance(val, str): + val = str(val) elif ( isinstance(val, AttributeValueWithCharsetSubstitution) and eventual_encoding is not None): @@ -1034,7 +1097,7 @@ class Tag(PageElement): text = self.format_string(val, formatter) decoded = ( - unicode(key) + '=' + str(key) + '=' + EntitySubstitution.quoted_attribute_value(text)) attrs.append(decoded) close = '' @@ -1103,16 +1166,22 @@ class Tag(PageElement): formatter="minimal"): """Renders the contents of this tag as a Unicode string. + :param indent_level: Each line of the rendering will be + indented this many spaces. + :param eventual_encoding: The tag is destined to be encoded into this encoding. This method is _not_ responsible for performing that encoding. This information is passed in so that it can be substituted in if the document contains a <META> tag that mentions the document's encoding. + + :param formatter: The output formatter responsible for converting + entities to Unicode characters. """ # First off, turn a string formatter into a function. This # will stop the lookup from happening over and over again. - if not callable(formatter): + if not isinstance(formatter, collections.Callable): formatter = self._formatter_for_name(formatter) pretty_print = (indent_level is not None) @@ -1137,7 +1206,17 @@ class Tag(PageElement): def encode_contents( self, indent_level=None, encoding=DEFAULT_OUTPUT_ENCODING, formatter="minimal"): - """Renders the contents of this tag as a bytestring.""" + """Renders the contents of this tag as a bytestring. + + :param indent_level: Each line of the rendering will be + indented this many spaces. + + :param eventual_encoding: The bytestring will be in this encoding. + + :param formatter: The output formatter responsible for converting + entities to Unicode characters. + """ + contents = self.decode_contents(indent_level, encoding, formatter) return contents.encode(encoding) @@ -1201,26 +1280,57 @@ class Tag(PageElement): _selector_combinators = ['>', '+', '~'] _select_debug = False - def select(self, selector, _candidate_generator=None): + def select_one(self, selector): + """Perform a CSS selection operation on the current element.""" + value = self.select(selector, limit=1) + if value: + return value[0] + return None + + def select(self, selector, _candidate_generator=None, limit=None): """Perform a CSS selection operation on the current element.""" + + # Handle grouping selectors if ',' exists, ie: p,a + if ',' in selector: + context = [] + for partial_selector in selector.split(','): + partial_selector = partial_selector.strip() + if partial_selector == '': + raise ValueError('Invalid group selection syntax: %s' % selector) + candidates = self.select(partial_selector, limit=limit) + for candidate in candidates: + if candidate not in context: + context.append(candidate) + + if limit and len(context) >= limit: + break + return context + tokens = selector.split() current_context = [self] if tokens[-1] in self._selector_combinators: raise ValueError( 'Final combinator "%s" is missing an argument.' % tokens[-1]) + if self._select_debug: - print 'Running CSS selector "%s"' % selector + print('Running CSS selector "%s"' % selector) + for index, token in enumerate(tokens): - if self._select_debug: - print ' Considering token "%s"' % token - recursive_candidate_generator = None - tag_name = None + new_context = [] + new_context_ids = set([]) + if tokens[index-1] in self._selector_combinators: # This token was consumed by the previous combinator. Skip it. if self._select_debug: - print ' Token was consumed by the previous combinator.' + print(' Token was consumed by the previous combinator.') continue + + if self._select_debug: + print(' Considering token "%s"' % token) + recursive_candidate_generator = None + tag_name = None + # Each operation corresponds to a checker function, a rule # for determining whether a candidate matches the # selector. Candidates are generated by the active @@ -1256,35 +1366,38 @@ class Tag(PageElement): "A pseudo-class must be prefixed with a tag name.") pseudo_attributes = re.match('([a-zA-Z\d-]+)\(([a-zA-Z\d]+)\)', pseudo) found = [] - if pseudo_attributes is not None: + if pseudo_attributes is None: + pseudo_type = pseudo + pseudo_value = None + else: pseudo_type, pseudo_value = pseudo_attributes.groups() - if pseudo_type == 'nth-of-type': - try: - pseudo_value = int(pseudo_value) - except: - raise NotImplementedError( - 'Only numeric values are currently supported for the nth-of-type pseudo-class.') - if pseudo_value < 1: - raise ValueError( - 'nth-of-type pseudo-class value must be at least 1.') - class Counter(object): - def __init__(self, destination): - self.count = 0 - self.destination = destination - - def nth_child_of_type(self, tag): - self.count += 1 - if self.count == self.destination: - return True - if self.count > self.destination: - # Stop the generator that's sending us - # these things. - raise StopIteration() - return False - checker = Counter(pseudo_value).nth_child_of_type - else: + if pseudo_type == 'nth-of-type': + try: + pseudo_value = int(pseudo_value) + except: raise NotImplementedError( - 'Only the following pseudo-classes are implemented: nth-of-type.') + 'Only numeric values are currently supported for the nth-of-type pseudo-class.') + if pseudo_value < 1: + raise ValueError( + 'nth-of-type pseudo-class value must be at least 1.') + class Counter(object): + def __init__(self, destination): + self.count = 0 + self.destination = destination + + def nth_child_of_type(self, tag): + self.count += 1 + if self.count == self.destination: + return True + if self.count > self.destination: + # Stop the generator that's sending us + # these things. + raise StopIteration() + return False + checker = Counter(pseudo_value).nth_child_of_type + else: + raise NotImplementedError( + 'Only the following pseudo-classes are implemented: nth-of-type.') elif token == '*': # Star selector -- matches everything @@ -1311,7 +1424,6 @@ class Tag(PageElement): else: raise ValueError( 'Unsupported or invalid CSS selector: "%s"' % token) - if recursive_candidate_generator: # This happens when the selector looks like "> foo". # @@ -1325,14 +1437,14 @@ class Tag(PageElement): next_token = tokens[index+1] def recursive_select(tag): if self._select_debug: - print ' Calling select("%s") recursively on %s %s' % (next_token, tag.name, tag.attrs) - print '-' * 40 + print(' Calling select("%s") recursively on %s %s' % (next_token, tag.name, tag.attrs)) + print('-' * 40) for i in tag.select(next_token, recursive_candidate_generator): if self._select_debug: - print '(Recursive select picked up candidate %s %s)' % (i.name, i.attrs) + print('(Recursive select picked up candidate %s %s)' % (i.name, i.attrs)) yield i if self._select_debug: - print '-' * 40 + print('-' * 40) _use_candidate_generator = recursive_select elif _candidate_generator is None: # By default, a tag's candidates are all of its @@ -1343,7 +1455,7 @@ class Tag(PageElement): check = "[any]" else: check = tag_name - print ' Default candidate generator, tag name="%s"' % check + print(' Default candidate generator, tag name="%s"' % check) if self._select_debug: # This is redundant with later code, but it stops # a bunch of bogus tags from cluttering up the @@ -1361,12 +1473,11 @@ class Tag(PageElement): else: _use_candidate_generator = _candidate_generator - new_context = [] - new_context_ids = set([]) + count = 0 for tag in current_context: if self._select_debug: - print " Running candidate generator on %s %s" % ( - tag.name, repr(tag.attrs)) + print(" Running candidate generator on %s %s" % ( + tag.name, repr(tag.attrs))) for candidate in _use_candidate_generator(tag): if not isinstance(candidate, Tag): continue @@ -1381,21 +1492,24 @@ class Tag(PageElement): break if checker is None or result: if self._select_debug: - print " SUCCESS %s %s" % (candidate.name, repr(candidate.attrs)) + print(" SUCCESS %s %s" % (candidate.name, repr(candidate.attrs))) if id(candidate) not in new_context_ids: # If a tag matches a selector more than once, # don't include it in the context more than once. new_context.append(candidate) new_context_ids.add(id(candidate)) + if limit and len(new_context) >= limit: + break elif self._select_debug: - print " FAILURE %s %s" % (candidate.name, repr(candidate.attrs)) + print(" FAILURE %s %s" % (candidate.name, repr(candidate.attrs))) + current_context = new_context if self._select_debug: - print "Final verdict:" + print("Final verdict:") for i in current_context: - print " %s %s" % (i.name, i.attrs) + print(" %s %s" % (i.name, i.attrs)) return current_context # Old names for backwards compatibility @@ -1439,7 +1553,7 @@ class SoupStrainer(object): else: attrs = kwargs normalized_attrs = {} - for key, value in attrs.items(): + for key, value in list(attrs.items()): normalized_attrs[key] = self._normalize_search_value(value) self.attrs = normalized_attrs @@ -1448,7 +1562,7 @@ class SoupStrainer(object): def _normalize_search_value(self, value): # Leave it alone if it's a Unicode string, a callable, a # regular expression, a boolean, or None. - if (isinstance(value, unicode) or callable(value) or hasattr(value, 'match') + if (isinstance(value, str) or isinstance(value, collections.Callable) or hasattr(value, 'match') or isinstance(value, bool) or value is None): return value @@ -1461,7 +1575,7 @@ class SoupStrainer(object): new_value = [] for v in value: if (hasattr(v, '__iter__') and not isinstance(v, bytes) - and not isinstance(v, unicode)): + and not isinstance(v, str)): # This is almost certainly the user's mistake. In the # interests of avoiding infinite loops, we'll let # it through as-is rather than doing a recursive call. @@ -1473,7 +1587,7 @@ class SoupStrainer(object): # Otherwise, convert it into a Unicode string. # The unicode(str()) thing is so this will do the same thing on Python 2 # and Python 3. - return unicode(str(value)) + return str(str(value)) def __str__(self): if self.text: @@ -1527,7 +1641,7 @@ class SoupStrainer(object): found = None # If given a list of items, scan it for a text element that # matches. - if hasattr(markup, '__iter__') and not isinstance(markup, (Tag, basestring)): + if hasattr(markup, '__iter__') and not isinstance(markup, (Tag, str)): for element in markup: if isinstance(element, NavigableString) \ and self.search(element): @@ -1540,7 +1654,7 @@ class SoupStrainer(object): found = self.search_tag(markup) # If it's text, make sure the text matches. elif isinstance(markup, NavigableString) or \ - isinstance(markup, basestring): + isinstance(markup, str): if not self.name and not self.attrs and self._matches(markup, self.text): found = markup else: @@ -1554,7 +1668,7 @@ class SoupStrainer(object): if isinstance(markup, list) or isinstance(markup, tuple): # This should only happen when searching a multi-valued attribute # like 'class'. - if (isinstance(match_against, unicode) + if (isinstance(match_against, str) and ' ' in match_against): # A bit of a special case. If they try to match "foo # bar" on a multivalue attribute's value, only accept @@ -1589,7 +1703,7 @@ class SoupStrainer(object): # None matches None, False, an empty string, an empty list, and so on. return not match_against - if isinstance(match_against, unicode): + if isinstance(match_against, str): # Exact string match return markup == match_against diff --git a/import-layers/yocto-poky/bitbake/lib/bs4/testing.py b/import-layers/yocto-poky/bitbake/lib/bs4/testing.py index fd4495ac5..3a2f260e2 100644 --- a/import-layers/yocto-poky/bitbake/lib/bs4/testing.py +++ b/import-layers/yocto-poky/bitbake/lib/bs4/testing.py @@ -1,5 +1,8 @@ """Helper classes for tests.""" +__license__ = "MIT" + +import pickle import copy import functools import unittest @@ -43,6 +46,16 @@ class SoupTest(unittest.TestCase): self.assertEqual(obj.decode(), self.document_for(compare_parsed_to)) + def assertConnectedness(self, element): + """Ensure that next_element and previous_element are properly + set for all descendants of the given element. + """ + earlier = None + for e in element.descendants: + if earlier: + self.assertEqual(e, earlier.next_element) + self.assertEqual(earlier, e.previous_element) + earlier = e class HTMLTreeBuilderSmokeTest(object): @@ -54,6 +67,15 @@ class HTMLTreeBuilderSmokeTest(object): markup in these tests, there's not much room for interpretation. """ + def test_pickle_and_unpickle_identity(self): + # Pickling a tree, then unpickling it, yields a tree identical + # to the original. + tree = self.soup("<a><b>foo</a>") + dumped = pickle.dumps(tree, 2) + loaded = pickle.loads(dumped) + self.assertEqual(loaded.__class__, BeautifulSoup) + self.assertEqual(loaded.decode(), tree.decode()) + def assertDoctypeHandled(self, doctype_fragment): """Assert that a given doctype string is handled correctly.""" doctype_str, soup = self._document_with_doctype(doctype_fragment) @@ -114,6 +136,11 @@ class HTMLTreeBuilderSmokeTest(object): soup.encode("utf-8").replace(b"\n", b""), markup.replace(b"\n", b"")) + def test_processing_instruction(self): + markup = b"""<?PITarget PIContent?>""" + soup = self.soup(markup) + self.assertEqual(markup, soup.encode("utf8")) + def test_deepcopy(self): """Make sure you can copy the tree builder. @@ -155,6 +182,23 @@ class HTMLTreeBuilderSmokeTest(object): def test_nested_formatting_elements(self): self.assertSoupEquals("<em><em></em></em>") + def test_double_head(self): + html = '''<!DOCTYPE html> +<html> +<head> +<title>Ordinary HEAD element test</title> +</head> +<script type="text/javascript"> +alert("Help!"); +</script> +<body> +Hello, world! +</body> +</html> +''' + soup = self.soup(html) + self.assertEqual("text/javascript", soup.find('script')['type']) + def test_comment(self): # Comments are represented as Comment objects. markup = "<p>foo<!--foobar-->baz</p>" @@ -221,18 +265,26 @@ class HTMLTreeBuilderSmokeTest(object): soup = self.soup(markup) self.assertEqual(["css"], soup.div.div['class']) + def test_multivalued_attribute_on_html(self): + # html5lib uses a different API to set the attributes ot the + # <html> tag. This has caused problems with multivalued + # attributes. + markup = '<html class="a b"></html>' + soup = self.soup(markup) + self.assertEqual(["a", "b"], soup.html['class']) + def test_angle_brackets_in_attribute_values_are_escaped(self): self.assertSoupEquals('<a b="<a>"></a>', '<a b="<a>"></a>') def test_entities_in_attributes_converted_to_unicode(self): - expect = u'<p id="pi\N{LATIN SMALL LETTER N WITH TILDE}ata"></p>' + expect = '<p id="pi\N{LATIN SMALL LETTER N WITH TILDE}ata"></p>' self.assertSoupEquals('<p id="piñata"></p>', expect) self.assertSoupEquals('<p id="piñata"></p>', expect) self.assertSoupEquals('<p id="piñata"></p>', expect) self.assertSoupEquals('<p id="piñata"></p>', expect) def test_entities_in_text_converted_to_unicode(self): - expect = u'<p>pi\N{LATIN SMALL LETTER N WITH TILDE}ata</p>' + expect = '<p>pi\N{LATIN SMALL LETTER N WITH TILDE}ata</p>' self.assertSoupEquals("<p>piñata</p>", expect) self.assertSoupEquals("<p>piñata</p>", expect) self.assertSoupEquals("<p>piñata</p>", expect) @@ -243,7 +295,7 @@ class HTMLTreeBuilderSmokeTest(object): '<p>I said "good day!"</p>') def test_out_of_range_entity(self): - expect = u"\N{REPLACEMENT CHARACTER}" + expect = "\N{REPLACEMENT CHARACTER}" self.assertSoupEquals("�", expect) self.assertSoupEquals("�", expect) self.assertSoupEquals("�", expect) @@ -253,6 +305,35 @@ class HTMLTreeBuilderSmokeTest(object): soup = self.soup("<html><h2>\nfoo</h2><p></p></html>") self.assertEqual("p", soup.h2.string.next_element.name) self.assertEqual("p", soup.p.name) + self.assertConnectedness(soup) + + def test_head_tag_between_head_and_body(self): + "Prevent recurrence of a bug in the html5lib treebuilder." + content = """<html><head></head> + <link></link> + <body>foo</body> +</html> +""" + soup = self.soup(content) + self.assertNotEqual(None, soup.html.body) + self.assertConnectedness(soup) + + def test_multiple_copies_of_a_tag(self): + "Prevent recurrence of a bug in the html5lib treebuilder." + content = """<!DOCTYPE html> +<html> + <body> + <article id="a" > + <div><a href="1"></div> + <footer> + <a href="2"></a> + </footer> + </article> + </body> +</html> +""" + soup = self.soup(content) + self.assertConnectedness(soup.article) def test_basic_namespaces(self): """Parsers don't need to *understand* namespaces, but at the @@ -285,9 +366,9 @@ class HTMLTreeBuilderSmokeTest(object): # A seemingly innocuous document... but it's in Unicode! And # it contains characters that can't be represented in the # encoding found in the declaration! The horror! - markup = u'<html><head><meta encoding="euc-jp"></head><body>Sacr\N{LATIN SMALL LETTER E WITH ACUTE} bleu!</body>' + markup = '<html><head><meta encoding="euc-jp"></head><body>Sacr\N{LATIN SMALL LETTER E WITH ACUTE} bleu!</body>' soup = self.soup(markup) - self.assertEqual(u'Sacr\xe9 bleu!', soup.body.string) + self.assertEqual('Sacr\xe9 bleu!', soup.body.string) def test_soupstrainer(self): """Parsers should be able to work with SoupStrainers.""" @@ -327,7 +408,7 @@ class HTMLTreeBuilderSmokeTest(object): # Both XML and HTML entities are converted to Unicode characters # during parsing. text = "<p><<sacré bleu!>></p>" - expected = u"<p><<sacr\N{LATIN SMALL LETTER E WITH ACUTE} bleu!>></p>" + expected = "<p><<sacr\N{LATIN SMALL LETTER E WITH ACUTE} bleu!>></p>" self.assertSoupEquals(text, expected) def test_smart_quotes_converted_on_the_way_in(self): @@ -337,15 +418,15 @@ class HTMLTreeBuilderSmokeTest(object): soup = self.soup(quote) self.assertEqual( soup.p.string, - u"\N{LEFT SINGLE QUOTATION MARK}Foo\N{RIGHT SINGLE QUOTATION MARK}") + "\N{LEFT SINGLE QUOTATION MARK}Foo\N{RIGHT SINGLE QUOTATION MARK}") def test_non_breaking_spaces_converted_on_the_way_in(self): soup = self.soup("<a> </a>") - self.assertEqual(soup.a.string, u"\N{NO-BREAK SPACE}" * 2) + self.assertEqual(soup.a.string, "\N{NO-BREAK SPACE}" * 2) def test_entities_converted_on_the_way_out(self): text = "<p><<sacré bleu!>></p>" - expected = u"<p><<sacr\N{LATIN SMALL LETTER E WITH ACUTE} bleu!>></p>".encode("utf-8") + expected = "<p><<sacr\N{LATIN SMALL LETTER E WITH ACUTE} bleu!>></p>".encode("utf-8") soup = self.soup(text) self.assertEqual(soup.p.encode("utf-8"), expected) @@ -354,7 +435,7 @@ class HTMLTreeBuilderSmokeTest(object): # easy-to-understand document. # Here it is in Unicode. Note that it claims to be in ISO-Latin-1. - unicode_html = u'<html><head><meta content="text/html; charset=ISO-Latin-1" http-equiv="Content-type"/></head><body><p>Sacr\N{LATIN SMALL LETTER E WITH ACUTE} bleu!</p></body></html>' + unicode_html = '<html><head><meta content="text/html; charset=ISO-Latin-1" http-equiv="Content-type"/></head><body><p>Sacr\N{LATIN SMALL LETTER E WITH ACUTE} bleu!</p></body></html>' # That's because we're going to encode it into ISO-Latin-1, and use # that to test. @@ -463,11 +544,25 @@ class HTMLTreeBuilderSmokeTest(object): class XMLTreeBuilderSmokeTest(object): + def test_pickle_and_unpickle_identity(self): + # Pickling a tree, then unpickling it, yields a tree identical + # to the original. + tree = self.soup("<a><b>foo</a>") + dumped = pickle.dumps(tree, 2) + loaded = pickle.loads(dumped) + self.assertEqual(loaded.__class__, BeautifulSoup) + self.assertEqual(loaded.decode(), tree.decode()) + def test_docstring_generated(self): soup = self.soup("<root/>") self.assertEqual( soup.encode(), b'<?xml version="1.0" encoding="utf-8"?>\n<root/>') + def test_xml_declaration(self): + markup = b"""<?xml version="1.0" encoding="utf8"?>\n<foo/>""" + soup = self.soup(markup) + self.assertEqual(markup, soup.encode("utf8")) + def test_real_xhtml_document(self): """A real XHTML document should come out *exactly* the same as it went in.""" markup = b"""<?xml version="1.0" encoding="utf-8"?> @@ -485,7 +580,7 @@ class XMLTreeBuilderSmokeTest(object): <script type="text/javascript"> </script> """ - soup = BeautifulSoup(doc, "xml") + soup = BeautifulSoup(doc, "lxml-xml") # lxml would have stripped this while parsing, but we can add # it later. soup.script.string = 'console.log("< < hey > > ");' @@ -493,15 +588,15 @@ class XMLTreeBuilderSmokeTest(object): self.assertTrue(b"< < hey > >" in encoded) def test_can_parse_unicode_document(self): - markup = u'<?xml version="1.0" encoding="euc-jp"><root>Sacr\N{LATIN SMALL LETTER E WITH ACUTE} bleu!</root>' + markup = '<?xml version="1.0" encoding="euc-jp"><root>Sacr\N{LATIN SMALL LETTER E WITH ACUTE} bleu!</root>' soup = self.soup(markup) - self.assertEqual(u'Sacr\xe9 bleu!', soup.root.string) + self.assertEqual('Sacr\xe9 bleu!', soup.root.string) def test_popping_namespaced_tag(self): markup = '<rss xmlns:dc="foo"><dc:creator>b</dc:creator><dc:date>2012-07-02T20:33:42Z</dc:date><dc:rights>c</dc:rights><image>d</image></rss>' soup = self.soup(markup) self.assertEqual( - unicode(soup.rss), markup) + str(soup.rss), markup) def test_docstring_includes_correct_encoding(self): soup = self.soup("<root/>") @@ -532,17 +627,17 @@ class XMLTreeBuilderSmokeTest(object): def test_closing_namespaced_tag(self): markup = '<p xmlns:dc="http://purl.org/dc/elements/1.1/"><dc:date>20010504</dc:date></p>' soup = self.soup(markup) - self.assertEqual(unicode(soup.p), markup) + self.assertEqual(str(soup.p), markup) def test_namespaced_attributes(self): markup = '<foo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><bar xsi:schemaLocation="http://www.example.com"/></foo>' soup = self.soup(markup) - self.assertEqual(unicode(soup.foo), markup) + self.assertEqual(str(soup.foo), markup) def test_namespaced_attributes_xml_namespace(self): markup = '<foo xml:lang="fr">bar</foo>' soup = self.soup(markup) - self.assertEqual(unicode(soup.foo), markup) + self.assertEqual(str(soup.foo), markup) class HTML5TreeBuilderSmokeTest(HTMLTreeBuilderSmokeTest): """Smoke test for a tree builder that supports HTML5.""" diff --git a/import-layers/yocto-poky/bitbake/lib/bs4/tests/test_builder_registry.py b/import-layers/yocto-poky/bitbake/lib/bs4/tests/test_builder_registry.py index 92ad10fb0..90cad8293 100644 --- a/import-layers/yocto-poky/bitbake/lib/bs4/tests/test_builder_registry.py +++ b/import-layers/yocto-poky/bitbake/lib/bs4/tests/test_builder_registry.py @@ -1,6 +1,7 @@ """Tests of the builder registry.""" import unittest +import warnings from bs4 import BeautifulSoup from bs4.builder import ( @@ -67,10 +68,15 @@ class BuiltInRegistryTest(unittest.TestCase): HTMLParserTreeBuilder) def test_beautifulsoup_constructor_does_lookup(self): - # You can pass in a string. - BeautifulSoup("", features="html") - # Or a list of strings. - BeautifulSoup("", features=["html", "fast"]) + + with warnings.catch_warnings(record=True) as w: + # This will create a warning about not explicitly + # specifying a parser, but we'll ignore it. + + # You can pass in a string. + BeautifulSoup("", features="html") + # Or a list of strings. + BeautifulSoup("", features=["html", "fast"]) # You'll get an exception if BS can't find an appropriate # builder. diff --git a/import-layers/yocto-poky/bitbake/lib/bs4/tests/test_html5lib.py b/import-layers/yocto-poky/bitbake/lib/bs4/tests/test_html5lib.py index 594c3e1f2..a7494ca5b 100644 --- a/import-layers/yocto-poky/bitbake/lib/bs4/tests/test_html5lib.py +++ b/import-layers/yocto-poky/bitbake/lib/bs4/tests/test_html5lib.py @@ -5,7 +5,7 @@ import warnings try: from bs4.builder import HTML5TreeBuilder HTML5LIB_PRESENT = True -except ImportError, e: +except ImportError as e: HTML5LIB_PRESENT = False from bs4.element import SoupStrainer from bs4.testing import ( @@ -74,12 +74,25 @@ class HTML5LibBuilderSmokeTest(SoupTest, HTML5TreeBuilderSmokeTest): def test_reparented_markup(self): markup = '<p><em>foo</p>\n<p>bar<a></a></em></p>' soup = self.soup(markup) - self.assertEqual(u"<body><p><em>foo</em></p><em>\n</em><p><em>bar<a></a></em></p></body>", soup.body.decode()) + self.assertEqual("<body><p><em>foo</em></p><em>\n</em><p><em>bar<a></a></em></p></body>", soup.body.decode()) self.assertEqual(2, len(soup.find_all('p'))) def test_reparented_markup_ends_with_whitespace(self): markup = '<p><em>foo</p>\n<p>bar<a></a></em></p>\n' soup = self.soup(markup) - self.assertEqual(u"<body><p><em>foo</em></p><em>\n</em><p><em>bar<a></a></em></p>\n</body>", soup.body.decode()) + self.assertEqual("<body><p><em>foo</em></p><em>\n</em><p><em>bar<a></a></em></p>\n</body>", soup.body.decode()) self.assertEqual(2, len(soup.find_all('p'))) + + def test_processing_instruction(self): + """Processing instructions become comments.""" + markup = b"""<?PITarget PIContent?>""" + soup = self.soup(markup) + assert str(soup).startswith("<!--?PITarget PIContent?-->") + + def test_cloned_multivalue_node(self): + markup = b"""<a class="my_class"><p></a>""" + soup = self.soup(markup) + a1, a2 = soup.find_all('a') + self.assertEqual(a1, a2) + assert a1 is not a2 diff --git a/import-layers/yocto-poky/bitbake/lib/bs4/tests/test_htmlparser.py b/import-layers/yocto-poky/bitbake/lib/bs4/tests/test_htmlparser.py index bcb5ed232..b45e35f99 100644 --- a/import-layers/yocto-poky/bitbake/lib/bs4/tests/test_htmlparser.py +++ b/import-layers/yocto-poky/bitbake/lib/bs4/tests/test_htmlparser.py @@ -1,6 +1,8 @@ """Tests to ensure that the html.parser tree builder generates good trees.""" +from pdb import set_trace +import pickle from bs4.testing import SoupTest, HTMLTreeBuilderSmokeTest from bs4.builder import HTMLParserTreeBuilder @@ -17,3 +19,14 @@ class HTMLParserTreeBuilderSmokeTest(SoupTest, HTMLTreeBuilderSmokeTest): def test_namespaced_public_doctype(self): # html.parser can't handle namespaced doctypes, so skip this one. pass + + def test_builder_is_pickled(self): + """Unlike most tree builders, HTMLParserTreeBuilder and will + be restored after pickling. + """ + tree = self.soup("<a><b>foo</a>") + dumped = pickle.dumps(tree, 2) + loaded = pickle.loads(dumped) + self.assertTrue(isinstance(loaded.builder, type(tree.builder))) + + diff --git a/import-layers/yocto-poky/bitbake/lib/bs4/tests/test_lxml.py b/import-layers/yocto-poky/bitbake/lib/bs4/tests/test_lxml.py index 2b2e9b7e7..6c2a1d73e 100644 --- a/import-layers/yocto-poky/bitbake/lib/bs4/tests/test_lxml.py +++ b/import-layers/yocto-poky/bitbake/lib/bs4/tests/test_lxml.py @@ -7,7 +7,7 @@ try: import lxml.etree LXML_PRESENT = True LXML_VERSION = lxml.etree.LXML_VERSION -except ImportError, e: +except ImportError as e: LXML_PRESENT = False LXML_VERSION = (0,) @@ -62,24 +62,9 @@ class LXMLTreeBuilderSmokeTest(SoupTest, HTMLTreeBuilderSmokeTest): # if one is installed. with warnings.catch_warnings(record=True) as w: soup = BeautifulStoneSoup("<b />") - self.assertEqual(u"<b/>", unicode(soup.b)) + self.assertEqual("<b/>", str(soup.b)) self.assertTrue("BeautifulStoneSoup class is deprecated" in str(w[0].message)) - def test_real_xhtml_document(self): - """lxml strips the XML definition from an XHTML doc, which is fine.""" - markup = b"""<?xml version="1.0" encoding="utf-8"?> -<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"> -<html xmlns="http://www.w3.org/1999/xhtml"> -<head><title>Hello.</title></head> -<body>Goodbye.</body> -</html>""" - soup = self.soup(markup) - self.assertEqual( - soup.encode("utf-8").replace(b"\n", b''), - markup.replace(b'\n', b'').replace( - b'<?xml version="1.0" encoding="utf-8"?>', b'')) - - @skipIf( not LXML_PRESENT, "lxml seems not to be present, not testing its XML tree builder.") diff --git a/import-layers/yocto-poky/bitbake/lib/bs4/tests/test_soup.py b/import-layers/yocto-poky/bitbake/lib/bs4/tests/test_soup.py index 47ac245f9..f87949e3d 100644 --- a/import-layers/yocto-poky/bitbake/lib/bs4/tests/test_soup.py +++ b/import-layers/yocto-poky/bitbake/lib/bs4/tests/test_soup.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- """Tests of Beautiful Soup as a whole.""" +from pdb import set_trace import logging import unittest import sys @@ -20,6 +21,7 @@ import bs4.dammit from bs4.dammit import ( EntitySubstitution, UnicodeDammit, + EncodingDetector, ) from bs4.testing import ( SoupTest, @@ -30,7 +32,7 @@ import warnings try: from bs4.builder import LXMLTreeBuilder, LXMLTreeBuilderForXML LXML_PRESENT = True -except ImportError, e: +except ImportError as e: LXML_PRESENT = False PYTHON_2_PRE_2_7 = (sys.version_info < (2,7)) @@ -39,17 +41,43 @@ PYTHON_3_PRE_3_2 = (sys.version_info[0] == 3 and sys.version_info < (3,2)) class TestConstructor(SoupTest): def test_short_unicode_input(self): - data = u"<h1>éé</h1>" + data = "<h1>éé</h1>" soup = self.soup(data) - self.assertEqual(u"éé", soup.h1.string) + self.assertEqual("éé", soup.h1.string) def test_embedded_null(self): - data = u"<h1>foo\0bar</h1>" + data = "<h1>foo\0bar</h1>" soup = self.soup(data) - self.assertEqual(u"foo\0bar", soup.h1.string) + self.assertEqual("foo\0bar", soup.h1.string) + def test_exclude_encodings(self): + utf8_data = "Räksmörgås".encode("utf-8") + soup = self.soup(utf8_data, exclude_encodings=["utf-8"]) + self.assertEqual("windows-1252", soup.original_encoding) -class TestDeprecatedConstructorArguments(SoupTest): + +class TestWarnings(SoupTest): + + def _no_parser_specified(self, s, is_there=True): + v = s.startswith(BeautifulSoup.NO_PARSER_SPECIFIED_WARNING[:80]) + self.assertTrue(v) + + def test_warning_if_no_parser_specified(self): + with warnings.catch_warnings(record=True) as w: + soup = self.soup("<a><b></b></a>") + msg = str(w[0].message) + self._assert_no_parser_specified(msg) + + def test_warning_if_parser_specified_too_vague(self): + with warnings.catch_warnings(record=True) as w: + soup = self.soup("<a><b></b></a>", "html") + msg = str(w[0].message) + self._assert_no_parser_specified(msg) + + def test_no_warning_if_explicit_parser_specified(self): + with warnings.catch_warnings(record=True) as w: + soup = self.soup("<a><b></b></a>", "html.parser") + self.assertEqual([], w) def test_parseOnlyThese_renamed_to_parse_only(self): with warnings.catch_warnings(record=True) as w: @@ -117,9 +145,9 @@ class TestEntitySubstitution(unittest.TestCase): def test_simple_html_substitution(self): # Unicode characters corresponding to named HTML entites # are substituted, and no others. - s = u"foo\u2200\N{SNOWMAN}\u00f5bar" + s = "foo\u2200\N{SNOWMAN}\u00f5bar" self.assertEqual(self.sub.substitute_html(s), - u"foo∀\N{SNOWMAN}õbar") + "foo∀\N{SNOWMAN}õbar") def test_smart_quote_substitution(self): # MS smart quotes are a common source of frustration, so we @@ -184,7 +212,7 @@ class TestEncodingConversion(SoupTest): def setUp(self): super(TestEncodingConversion, self).setUp() - self.unicode_data = u'<html><head><meta charset="utf-8"/></head><body><foo>Sacr\N{LATIN SMALL LETTER E WITH ACUTE} bleu!</foo></body></html>' + self.unicode_data = '<html><head><meta charset="utf-8"/></head><body><foo>Sacr\N{LATIN SMALL LETTER E WITH ACUTE} bleu!</foo></body></html>' self.utf8_data = self.unicode_data.encode("utf-8") # Just so you know what it looks like. self.assertEqual( @@ -204,7 +232,7 @@ class TestEncodingConversion(SoupTest): ascii = b"<foo>a</foo>" soup_from_ascii = self.soup(ascii) unicode_output = soup_from_ascii.decode() - self.assertTrue(isinstance(unicode_output, unicode)) + self.assertTrue(isinstance(unicode_output, str)) self.assertEqual(unicode_output, self.document_for(ascii.decode())) self.assertEqual(soup_from_ascii.original_encoding.lower(), "utf-8") finally: @@ -216,7 +244,7 @@ class TestEncodingConversion(SoupTest): # is not set. soup_from_unicode = self.soup(self.unicode_data) self.assertEqual(soup_from_unicode.decode(), self.unicode_data) - self.assertEqual(soup_from_unicode.foo.string, u'Sacr\xe9 bleu!') + self.assertEqual(soup_from_unicode.foo.string, 'Sacr\xe9 bleu!') self.assertEqual(soup_from_unicode.original_encoding, None) def test_utf8_in_unicode_out(self): @@ -224,7 +252,7 @@ class TestEncodingConversion(SoupTest): # attribute is set. soup_from_utf8 = self.soup(self.utf8_data) self.assertEqual(soup_from_utf8.decode(), self.unicode_data) - self.assertEqual(soup_from_utf8.foo.string, u'Sacr\xe9 bleu!') + self.assertEqual(soup_from_utf8.foo.string, 'Sacr\xe9 bleu!') def test_utf8_out(self): # The internal data structures can be encoded as UTF-8. @@ -235,14 +263,14 @@ class TestEncodingConversion(SoupTest): PYTHON_2_PRE_2_7 or PYTHON_3_PRE_3_2, "Bad HTMLParser detected; skipping test of non-ASCII characters in attribute name.") def test_attribute_name_containing_unicode_characters(self): - markup = u'<div><a \N{SNOWMAN}="snowman"></a></div>' + markup = '<div><a \N{SNOWMAN}="snowman"></a></div>' self.assertEqual(self.soup(markup).div.encode("utf8"), markup.encode("utf8")) class TestUnicodeDammit(unittest.TestCase): """Standalone tests of UnicodeDammit.""" def test_unicode_input(self): - markup = u"I'm already Unicode! \N{SNOWMAN}" + markup = "I'm already Unicode! \N{SNOWMAN}" dammit = UnicodeDammit(markup) self.assertEqual(dammit.unicode_markup, markup) @@ -250,7 +278,7 @@ class TestUnicodeDammit(unittest.TestCase): markup = b"<foo>\x91\x92\x93\x94</foo>" dammit = UnicodeDammit(markup) self.assertEqual( - dammit.unicode_markup, u"<foo>\u2018\u2019\u201c\u201d</foo>") + dammit.unicode_markup, "<foo>\u2018\u2019\u201c\u201d</foo>") def test_smart_quotes_to_xml_entities(self): markup = b"<foo>\x91\x92\x93\x94</foo>" @@ -271,16 +299,17 @@ class TestUnicodeDammit(unittest.TestCase): dammit.unicode_markup, """<foo>''""</foo>""") def test_detect_utf8(self): - utf8 = b"\xc3\xa9" + utf8 = b"Sacr\xc3\xa9 bleu! \xe2\x98\x83" dammit = UnicodeDammit(utf8) - self.assertEqual(dammit.unicode_markup, u'\xe9') self.assertEqual(dammit.original_encoding.lower(), 'utf-8') + self.assertEqual(dammit.unicode_markup, 'Sacr\xe9 bleu! \N{SNOWMAN}') + def test_convert_hebrew(self): hebrew = b"\xed\xe5\xec\xf9" dammit = UnicodeDammit(hebrew, ["iso-8859-8"]) self.assertEqual(dammit.original_encoding.lower(), 'iso-8859-8') - self.assertEqual(dammit.unicode_markup, u'\u05dd\u05d5\u05dc\u05e9') + self.assertEqual(dammit.unicode_markup, '\u05dd\u05d5\u05dc\u05e9') def test_dont_see_smart_quotes_where_there_are_none(self): utf_8 = b"\343\202\261\343\203\274\343\202\277\343\202\244 Watch" @@ -289,16 +318,36 @@ class TestUnicodeDammit(unittest.TestCase): self.assertEqual(dammit.unicode_markup.encode("utf-8"), utf_8) def test_ignore_inappropriate_codecs(self): - utf8_data = u"Räksmörgås".encode("utf-8") + utf8_data = "Räksmörgås".encode("utf-8") dammit = UnicodeDammit(utf8_data, ["iso-8859-8"]) self.assertEqual(dammit.original_encoding.lower(), 'utf-8') def test_ignore_invalid_codecs(self): - utf8_data = u"Räksmörgås".encode("utf-8") + utf8_data = "Räksmörgås".encode("utf-8") for bad_encoding in ['.utf8', '...', 'utF---16.!']: dammit = UnicodeDammit(utf8_data, [bad_encoding]) self.assertEqual(dammit.original_encoding.lower(), 'utf-8') + def test_exclude_encodings(self): + # This is UTF-8. + utf8_data = "Räksmörgås".encode("utf-8") + + # But if we exclude UTF-8 from consideration, the guess is + # Windows-1252. + dammit = UnicodeDammit(utf8_data, exclude_encodings=["utf-8"]) + self.assertEqual(dammit.original_encoding.lower(), 'windows-1252') + + # And if we exclude that, there is no valid guess at all. + dammit = UnicodeDammit( + utf8_data, exclude_encodings=["utf-8", "windows-1252"]) + self.assertEqual(dammit.original_encoding, None) + + def test_encoding_detector_replaces_junk_in_encoding_name_with_replacement_character(self): + detected = EncodingDetector( + b'<?xml version="1.0" encoding="UTF-\xdb" ?>') + encodings = list(detected.encodings) + assert 'utf-\N{REPLACEMENT CHARACTER}' in encodings + def test_detect_html5_style_meta_tag(self): for data in ( @@ -337,7 +386,7 @@ class TestUnicodeDammit(unittest.TestCase): bs4.dammit.chardet_dammit = noop dammit = UnicodeDammit(doc) self.assertEqual(True, dammit.contains_replacement_characters) - self.assertTrue(u"\ufffd" in dammit.unicode_markup) + self.assertTrue("\ufffd" in dammit.unicode_markup) soup = BeautifulSoup(doc, "html.parser") self.assertTrue(soup.contains_replacement_characters) @@ -349,17 +398,17 @@ class TestUnicodeDammit(unittest.TestCase): # A document written in UTF-16LE will have its byte order marker stripped. data = b'\xff\xfe<\x00a\x00>\x00\xe1\x00\xe9\x00<\x00/\x00a\x00>\x00' dammit = UnicodeDammit(data) - self.assertEqual(u"<a>áé</a>", dammit.unicode_markup) + self.assertEqual("<a>áé</a>", dammit.unicode_markup) self.assertEqual("utf-16le", dammit.original_encoding) def test_detwingle(self): # Here's a UTF8 document. - utf8 = (u"\N{SNOWMAN}" * 3).encode("utf8") + utf8 = ("\N{SNOWMAN}" * 3).encode("utf8") # Here's a Windows-1252 document. windows_1252 = ( - u"\N{LEFT DOUBLE QUOTATION MARK}Hi, I like Windows!" - u"\N{RIGHT DOUBLE QUOTATION MARK}").encode("windows_1252") + "\N{LEFT DOUBLE QUOTATION MARK}Hi, I like Windows!" + "\N{RIGHT DOUBLE QUOTATION MARK}").encode("windows_1252") # Through some unholy alchemy, they've been stuck together. doc = utf8 + windows_1252 + utf8 @@ -374,7 +423,7 @@ class TestUnicodeDammit(unittest.TestCase): fixed = UnicodeDammit.detwingle(doc) self.assertEqual( - u"☃☃☃“Hi, I like Windows!”☃☃☃", fixed.decode("utf8")) + "☃☃☃“Hi, I like Windows!”☃☃☃", fixed.decode("utf8")) def test_detwingle_ignores_multibyte_characters(self): # Each of these characters has a UTF-8 representation ending @@ -382,9 +431,9 @@ class TestUnicodeDammit(unittest.TestCase): # Windows-1252. But our code knows to skip over multibyte # UTF-8 characters, so they'll survive the process unscathed. for tricky_unicode_char in ( - u"\N{LATIN SMALL LIGATURE OE}", # 2-byte char '\xc5\x93' - u"\N{LATIN SUBSCRIPT SMALL LETTER X}", # 3-byte char '\xe2\x82\x93' - u"\xf0\x90\x90\x93", # This is a CJK character, not sure which one. + "\N{LATIN SMALL LIGATURE OE}", # 2-byte char '\xc5\x93' + "\N{LATIN SUBSCRIPT SMALL LETTER X}", # 3-byte char '\xe2\x82\x93' + "\xf0\x90\x90\x93", # This is a CJK character, not sure which one. ): input = tricky_unicode_char.encode("utf8") self.assertTrue(input.endswith(b'\x93')) diff --git a/import-layers/yocto-poky/bitbake/lib/bs4/tests/test_tree.py b/import-layers/yocto-poky/bitbake/lib/bs4/tests/test_tree.py index f8515c0ea..6d3e67f31 100644 --- a/import-layers/yocto-poky/bitbake/lib/bs4/tests/test_tree.py +++ b/import-layers/yocto-poky/bitbake/lib/bs4/tests/test_tree.py @@ -9,6 +9,7 @@ same markup, but all Beautiful Soup trees can be traversed with the methods tested here. """ +from pdb import set_trace import copy import pickle import re @@ -19,8 +20,10 @@ from bs4.builder import ( HTMLParserTreeBuilder, ) from bs4.element import ( + PY3K, CData, Comment, + Declaration, Doctype, NavigableString, SoupStrainer, @@ -67,8 +70,14 @@ class TestFind(TreeTest): self.assertEqual(soup.find("b").string, "2") def test_unicode_text_find(self): - soup = self.soup(u'<h1>Räksmörgås</h1>') - self.assertEqual(soup.find(text=u'Räksmörgås'), u'Räksmörgås') + soup = self.soup('<h1>Räksmörgås</h1>') + self.assertEqual(soup.find(string='Räksmörgås'), 'Räksmörgås') + + def test_unicode_attribute_find(self): + soup = self.soup('<h1 id="Räksmörgås">here it is</h1>') + str(soup) + self.assertEqual("here it is", soup.find(id='Räksmörgås').text) + def test_find_everything(self): """Test an optimization that finds all tags.""" @@ -87,16 +96,17 @@ class TestFindAll(TreeTest): """You can search the tree for text nodes.""" soup = self.soup("<html>Foo<b>bar</b>\xbb</html>") # Exact match. - self.assertEqual(soup.find_all(text="bar"), [u"bar"]) + self.assertEqual(soup.find_all(string="bar"), ["bar"]) + self.assertEqual(soup.find_all(text="bar"), ["bar"]) # Match any of a number of strings. self.assertEqual( - soup.find_all(text=["Foo", "bar"]), [u"Foo", u"bar"]) + soup.find_all(text=["Foo", "bar"]), ["Foo", "bar"]) # Match a regular expression. self.assertEqual(soup.find_all(text=re.compile('.*')), - [u"Foo", u"bar", u'\xbb']) + ["Foo", "bar", '\xbb']) # Match anything. self.assertEqual(soup.find_all(text=True), - [u"Foo", u"bar", u'\xbb']) + ["Foo", "bar", '\xbb']) def test_find_all_limit(self): """You can limit the number of items returned by find_all.""" @@ -227,8 +237,8 @@ class TestFindAllByAttribute(TreeTest): ["Matching a.", "Matching b."]) def test_find_all_by_utf8_attribute_value(self): - peace = u"םולש".encode("utf8") - data = u'<a title="םולש"></a>'.encode("utf8") + peace = "םולש".encode("utf8") + data = '<a title="םולש"></a>'.encode("utf8") soup = self.soup(data) self.assertEqual([soup.a], soup.find_all(title=peace)) self.assertEqual([soup.a], soup.find_all(title=peace.decode("utf8"))) @@ -688,7 +698,7 @@ class TestTagCreation(SoupTest): def test_tag_inherits_self_closing_rules_from_builder(self): if XML_BUILDER_PRESENT: - xml_soup = BeautifulSoup("", "xml") + xml_soup = BeautifulSoup("", "lxml-xml") xml_br = xml_soup.new_tag("br") xml_p = xml_soup.new_tag("p") @@ -697,7 +707,7 @@ class TestTagCreation(SoupTest): self.assertEqual(b"<br/>", xml_br.encode()) self.assertEqual(b"<p/>", xml_p.encode()) - html_soup = BeautifulSoup("", "html") + html_soup = BeautifulSoup("", "html.parser") html_br = html_soup.new_tag("br") html_p = html_soup.new_tag("p") @@ -773,6 +783,14 @@ class TestTreeModification(SoupTest): new_a = a.unwrap() self.assertEqual(a, new_a) + def test_replace_with_and_unwrap_give_useful_exception_when_tag_has_no_parent(self): + soup = self.soup("<a><b>Foo</b></a><c>Bar</c>") + a = soup.a + a.extract() + self.assertEqual(None, a.parent) + self.assertRaises(ValueError, a.unwrap) + self.assertRaises(ValueError, a.replace_with, soup.c) + def test_replace_tag_with_itself(self): text = "<a><b></b><c>Foo<d></d></c></a><a><e></e></a>" soup = self.soup(text) @@ -1067,6 +1085,31 @@ class TestTreeModification(SoupTest): self.assertEqual(foo_2, soup.a.string) self.assertEqual(bar_2, soup.b.string) + def test_extract_multiples_of_same_tag(self): + soup = self.soup(""" +<html> +<head> +<script>foo</script> +</head> +<body> + <script>bar</script> + <a></a> +</body> +<script>baz</script> +</html>""") + [soup.script.extract() for i in soup.find_all("script")] + self.assertEqual("<body>\n\n<a></a>\n</body>", str(soup.body)) + + + def test_extract_works_when_element_is_surrounded_by_identical_strings(self): + soup = self.soup( + '<html>\n' + '<body>hi</body>\n' + '</html>') + soup.find('body').extract() + self.assertEqual(None, soup.find('body')) + + def test_clear(self): """Tag.clear()""" soup = self.soup("<p><a>String <em>Italicized</em></a> and another</p>") @@ -1287,27 +1330,72 @@ class TestPersistence(SoupTest): def test_unicode_pickle(self): # A tree containing Unicode characters can be pickled. - html = u"<b>\N{SNOWMAN}</b>" + html = "<b>\N{SNOWMAN}</b>" soup = self.soup(html) dumped = pickle.dumps(soup, pickle.HIGHEST_PROTOCOL) loaded = pickle.loads(dumped) self.assertEqual(loaded.decode(), soup.decode()) + def test_copy_navigablestring_is_not_attached_to_tree(self): + html = "<b>Foo<a></a></b><b>Bar</b>" + soup = self.soup(html) + s1 = soup.find(string="Foo") + s2 = copy.copy(s1) + self.assertEqual(s1, s2) + self.assertEqual(None, s2.parent) + self.assertEqual(None, s2.next_element) + self.assertNotEqual(None, s1.next_sibling) + self.assertEqual(None, s2.next_sibling) + self.assertEqual(None, s2.previous_element) + + def test_copy_navigablestring_subclass_has_same_type(self): + html = "<b><!--Foo--></b>" + soup = self.soup(html) + s1 = soup.string + s2 = copy.copy(s1) + self.assertEqual(s1, s2) + self.assertTrue(isinstance(s2, Comment)) + + def test_copy_entire_soup(self): + html = "<div><b>Foo<a></a></b><b>Bar</b></div>end" + soup = self.soup(html) + soup_copy = copy.copy(soup) + self.assertEqual(soup, soup_copy) + + def test_copy_tag_copies_contents(self): + html = "<div><b>Foo<a></a></b><b>Bar</b></div>end" + soup = self.soup(html) + div = soup.div + div_copy = copy.copy(div) + + # The two tags look the same, and evaluate to equal. + self.assertEqual(str(div), str(div_copy)) + self.assertEqual(div, div_copy) + + # But they're not the same object. + self.assertFalse(div is div_copy) + + # And they don't have the same relation to the parse tree. The + # copy is not associated with a parse tree at all. + self.assertEqual(None, div_copy.parent) + self.assertEqual(None, div_copy.previous_element) + self.assertEqual(None, div_copy.find(string='Bar').next_element) + self.assertNotEqual(None, div.find(string='Bar').next_element) class TestSubstitutions(SoupTest): def test_default_formatter_is_minimal(self): - markup = u"<b><<Sacr\N{LATIN SMALL LETTER E WITH ACUTE} bleu!>></b>" + markup = "<b><<Sacr\N{LATIN SMALL LETTER E WITH ACUTE} bleu!>></b>" soup = self.soup(markup) decoded = soup.decode(formatter="minimal") # The < is converted back into < but the e-with-acute is left alone. self.assertEqual( decoded, self.document_for( - u"<b><<Sacr\N{LATIN SMALL LETTER E WITH ACUTE} bleu!>></b>")) + "<b><<Sacr\N{LATIN SMALL LETTER E WITH ACUTE} bleu!>></b>")) def test_formatter_html(self): - markup = u"<b><<Sacr\N{LATIN SMALL LETTER E WITH ACUTE} bleu!>></b>" + markup = "<b><<Sacr\N{LATIN SMALL LETTER E WITH ACUTE} bleu!>></b>" soup = self.soup(markup) decoded = soup.decode(formatter="html") self.assertEqual( @@ -1315,49 +1403,49 @@ class TestSubstitutions(SoupTest): self.document_for("<b><<Sacré bleu!>></b>")) def test_formatter_minimal(self): - markup = u"<b><<Sacr\N{LATIN SMALL LETTER E WITH ACUTE} bleu!>></b>" + markup = "<b><<Sacr\N{LATIN SMALL LETTER E WITH ACUTE} bleu!>></b>" soup = self.soup(markup) decoded = soup.decode(formatter="minimal") # The < is converted back into < but the e-with-acute is left alone. self.assertEqual( decoded, self.document_for( - u"<b><<Sacr\N{LATIN SMALL LETTER E WITH ACUTE} bleu!>></b>")) + "<b><<Sacr\N{LATIN SMALL LETTER E WITH ACUTE} bleu!>></b>")) def test_formatter_null(self): - markup = u"<b><<Sacr\N{LATIN SMALL LETTER E WITH ACUTE} bleu!>></b>" + markup = "<b><<Sacr\N{LATIN SMALL LETTER E WITH ACUTE} bleu!>></b>" soup = self.soup(markup) decoded = soup.decode(formatter=None) # Neither the angle brackets nor the e-with-acute are converted. # This is not valid HTML, but it's what the user wanted. self.assertEqual(decoded, - self.document_for(u"<b><<Sacr\N{LATIN SMALL LETTER E WITH ACUTE} bleu!>></b>")) + self.document_for("<b><<Sacr\N{LATIN SMALL LETTER E WITH ACUTE} bleu!>></b>")) def test_formatter_custom(self): - markup = u"<b><foo></b><b>bar</b>" + markup = "<b><foo></b><b>bar</b>" soup = self.soup(markup) decoded = soup.decode(formatter = lambda x: x.upper()) # Instead of normal entity conversion code, the custom # callable is called on every string. self.assertEqual( decoded, - self.document_for(u"<b><FOO></b><b>BAR</b>")) + self.document_for("<b><FOO></b><b>BAR</b>")) def test_formatter_is_run_on_attribute_values(self): - markup = u'<a href="http://a.com?a=b&c=é">e</a>' + markup = '<a href="http://a.com?a=b&c=é">e</a>' soup = self.soup(markup) a = soup.a - expect_minimal = u'<a href="http://a.com?a=b&c=é">e</a>' + expect_minimal = '<a href="http://a.com?a=b&c=é">e</a>' self.assertEqual(expect_minimal, a.decode()) self.assertEqual(expect_minimal, a.decode(formatter="minimal")) - expect_html = u'<a href="http://a.com?a=b&c=é">e</a>' + expect_html = '<a href="http://a.com?a=b&c=é">e</a>' self.assertEqual(expect_html, a.decode(formatter="html")) self.assertEqual(markup, a.decode(formatter=None)) - expect_upper = u'<a href="HTTP://A.COM?A=B&C=É">E</a>' + expect_upper = '<a href="HTTP://A.COM?A=B&C=É">E</a>' self.assertEqual(expect_upper, a.decode(formatter=lambda x: x.upper())) def test_formatter_skips_script_tag_for_html_documents(self): @@ -1366,7 +1454,7 @@ class TestSubstitutions(SoupTest): console.log("< < hey > > "); </script> """ - encoded = BeautifulSoup(doc).encode() + encoded = BeautifulSoup(doc, 'html.parser').encode() self.assertTrue(b"< < hey > >" in encoded) def test_formatter_skips_style_tag_for_html_documents(self): @@ -1375,7 +1463,7 @@ class TestSubstitutions(SoupTest): console.log("< < hey > > "); </style> """ - encoded = BeautifulSoup(doc).encode() + encoded = BeautifulSoup(doc, 'html.parser').encode() self.assertTrue(b"< < hey > >" in encoded) def test_prettify_leaves_preformatted_text_alone(self): @@ -1383,24 +1471,24 @@ class TestSubstitutions(SoupTest): # Everything outside the <pre> tag is reformatted, but everything # inside is left alone. self.assertEqual( - u'<div>\n foo\n <pre> \tbar\n \n </pre>\n baz\n</div>', + '<div>\n foo\n <pre> \tbar\n \n </pre>\n baz\n</div>', soup.div.prettify()) def test_prettify_accepts_formatter(self): - soup = BeautifulSoup("<html><body>foo</body></html>") + soup = BeautifulSoup("<html><body>foo</body></html>", 'html.parser') pretty = soup.prettify(formatter = lambda x: x.upper()) self.assertTrue("FOO" in pretty) def test_prettify_outputs_unicode_by_default(self): soup = self.soup("<a></a>") - self.assertEqual(unicode, type(soup.prettify())) + self.assertEqual(str, type(soup.prettify())) def test_prettify_can_encode_data(self): soup = self.soup("<a></a>") self.assertEqual(bytes, type(soup.prettify("utf-8"))) def test_html_entity_substitution_off_by_default(self): - markup = u"<b>Sacr\N{LATIN SMALL LETTER E WITH ACUTE} bleu!</b>" + markup = "<b>Sacr\N{LATIN SMALL LETTER E WITH ACUTE} bleu!</b>" soup = self.soup(markup) encoded = soup.b.encode("utf-8") self.assertEqual(encoded, markup.encode('utf-8')) @@ -1444,45 +1532,53 @@ class TestEncoding(SoupTest): """Test the ability to encode objects into strings.""" def test_unicode_string_can_be_encoded(self): - html = u"<b>\N{SNOWMAN}</b>" + html = "<b>\N{SNOWMAN}</b>" soup = self.soup(html) self.assertEqual(soup.b.string.encode("utf-8"), - u"\N{SNOWMAN}".encode("utf-8")) + "\N{SNOWMAN}".encode("utf-8")) def test_tag_containing_unicode_string_can_be_encoded(self): - html = u"<b>\N{SNOWMAN}</b>" + html = "<b>\N{SNOWMAN}</b>" soup = self.soup(html) self.assertEqual( soup.b.encode("utf-8"), html.encode("utf-8")) def test_encoding_substitutes_unrecognized_characters_by_default(self): - html = u"<b>\N{SNOWMAN}</b>" + html = "<b>\N{SNOWMAN}</b>" soup = self.soup(html) self.assertEqual(soup.b.encode("ascii"), b"<b>☃</b>") def test_encoding_can_be_made_strict(self): - html = u"<b>\N{SNOWMAN}</b>" + html = "<b>\N{SNOWMAN}</b>" soup = self.soup(html) self.assertRaises( UnicodeEncodeError, soup.encode, "ascii", errors="strict") def test_decode_contents(self): - html = u"<b>\N{SNOWMAN}</b>" + html = "<b>\N{SNOWMAN}</b>" soup = self.soup(html) - self.assertEqual(u"\N{SNOWMAN}", soup.b.decode_contents()) + self.assertEqual("\N{SNOWMAN}", soup.b.decode_contents()) def test_encode_contents(self): - html = u"<b>\N{SNOWMAN}</b>" + html = "<b>\N{SNOWMAN}</b>" soup = self.soup(html) self.assertEqual( - u"\N{SNOWMAN}".encode("utf8"), soup.b.encode_contents( + "\N{SNOWMAN}".encode("utf8"), soup.b.encode_contents( encoding="utf8")) def test_deprecated_renderContents(self): - html = u"<b>\N{SNOWMAN}</b>" + html = "<b>\N{SNOWMAN}</b>" soup = self.soup(html) self.assertEqual( - u"\N{SNOWMAN}".encode("utf8"), soup.b.renderContents()) + "\N{SNOWMAN}".encode("utf8"), soup.b.renderContents()) + + def test_repr(self): + html = "<b>\N{SNOWMAN}</b>" + soup = self.soup(html) + if PY3K: + self.assertEqual(html, repr(soup)) + else: + self.assertEqual(b'<b>\\u2603</b>', repr(soup)) class TestNavigableStringSubclasses(SoupTest): @@ -1522,6 +1618,9 @@ class TestNavigableStringSubclasses(SoupTest): soup.insert(1, doctype) self.assertEqual(soup.encode(), b"<!DOCTYPE foo>\n") + def test_declaration(self): + d = Declaration("foo") + self.assertEqual("<?foo?>", d.output_ready()) class TestSoupSelector(TreeTest): @@ -1534,7 +1633,7 @@ class TestSoupSelector(TreeTest): <link rel="stylesheet" href="blah.css" type="text/css" id="l1"> </head> <body> - +<custom-dashed-tag class="dashed" id="dash1">Hello there.</custom-dashed-tag> <div id="main" class="fancy"> <div id="inner"> <h1 id="header1">An H1</h1> @@ -1552,8 +1651,18 @@ class TestSoupSelector(TreeTest): <a href="#" id="s2a1">span2a1</a> </span> <span class="span3"></span> +<custom-dashed-tag class="dashed" id="dash2"/> +<div data-tag="dashedvalue" id="data1"/> </span> </div> +<x id="xid"> +<z id="zida"/> +<z id="zidab"/> +<z id="zidac"/> +</x> +<y id="yid"> +<z id="zidb"/> +</y> <p lang="en" id="lang-en">English</p> <p lang="en-gb" id="lang-en-gb">English UK</p> <p lang="en-us" id="lang-en-us">English US</p> @@ -1565,7 +1674,7 @@ class TestSoupSelector(TreeTest): """ def setUp(self): - self.soup = BeautifulSoup(self.HTML) + self.soup = BeautifulSoup(self.HTML, 'html.parser') def assertSelects(self, selector, expected_ids): el_ids = [el['id'] for el in self.soup.select(selector)] @@ -1587,21 +1696,29 @@ class TestSoupSelector(TreeTest): els = self.soup.select('title') self.assertEqual(len(els), 1) self.assertEqual(els[0].name, 'title') - self.assertEqual(els[0].contents, [u'The title']) + self.assertEqual(els[0].contents, ['The title']) def test_one_tag_many(self): els = self.soup.select('div') - self.assertEqual(len(els), 3) + self.assertEqual(len(els), 4) for div in els: self.assertEqual(div.name, 'div') + el = self.soup.select_one('div') + self.assertEqual('main', el['id']) + + def test_select_one_returns_none_if_no_match(self): + match = self.soup.select_one('nonexistenttag') + self.assertEqual(None, match) + + def test_tag_in_tag_one(self): els = self.soup.select('div div') - self.assertSelects('div div', ['inner']) + self.assertSelects('div div', ['inner', 'data1']) def test_tag_in_tag_many(self): for selector in ('html div', 'html body div', 'body div'): - self.assertSelects(selector, ['main', 'inner', 'footer']) + self.assertSelects(selector, ['data1', 'main', 'inner', 'footer']) def test_tag_no_match(self): self.assertEqual(len(self.soup.select('del')), 0) @@ -1609,6 +1726,20 @@ class TestSoupSelector(TreeTest): def test_invalid_tag(self): self.assertRaises(ValueError, self.soup.select, 'tag%t') + def test_select_dashed_tag_ids(self): + self.assertSelects('custom-dashed-tag', ['dash1', 'dash2']) + + def test_select_dashed_by_id(self): + dashed = self.soup.select('custom-dashed-tag[id=\"dash2\"]') + self.assertEqual(dashed[0].name, 'custom-dashed-tag') + self.assertEqual(dashed[0]['id'], 'dash2') + + def test_dashed_tag_text(self): + self.assertEqual(self.soup.select('body > custom-dashed-tag')[0].text, 'Hello there.') + + def test_select_dashed_matches_find_all(self): + self.assertEqual(self.soup.select('custom-dashed-tag'), self.soup.find_all('custom-dashed-tag')) + def test_header_tags(self): self.assertSelectMultiple( ('h1', ['header1']), @@ -1709,6 +1840,7 @@ class TestSoupSelector(TreeTest): ('[id^="m"]', ['me', 'main']), ('div[id^="m"]', ['main']), ('a[id^="m"]', ['me']), + ('div[data-tag^="dashed"]', ['data1']) ) def test_attribute_endswith(self): @@ -1716,8 +1848,8 @@ class TestSoupSelector(TreeTest): ('[href$=".css"]', ['l1']), ('link[href$=".css"]', ['l1']), ('link[id$="1"]', ['l1']), - ('[id$="1"]', ['l1', 'p1', 'header1', 's1a1', 's2a1', 's1a2s1']), - ('div[id$="1"]', []), + ('[id$="1"]', ['data1', 'l1', 'p1', 'header1', 's1a1', 's2a1', 's1a2s1', 'dash1']), + ('div[id$="1"]', ['data1']), ('[id$="noending"]', []), ) @@ -1730,7 +1862,6 @@ class TestSoupSelector(TreeTest): ('[rel*="notstyle"]', []), ('link[rel*="notstyle"]', []), ('link[href*="bla"]', ['l1']), - ('a[href*="http://"]', ['bob', 'me']), ('[href*="http://"]', ['bob', 'me']), ('[id*="p"]', ['pmulti', 'p1']), ('div[id*="m"]', ['main']), @@ -1739,8 +1870,8 @@ class TestSoupSelector(TreeTest): ('[href*=".css"]', ['l1']), ('link[href*=".css"]', ['l1']), ('link[id*="1"]', ['l1']), - ('[id*="1"]', ['l1', 'p1', 'header1', 's1a1', 's1a2', 's2a1', 's1a2s1']), - ('div[id*="1"]', []), + ('[id*="1"]', ['data1', 'l1', 'p1', 'header1', 's1a1', 's1a2', 's2a1', 's1a2s1', 'dash1']), + ('div[id*="1"]', ['data1']), ('[id*="noending"]', []), # New for this test ('[href*="."]', ['bob', 'me', 'l1']), @@ -1748,6 +1879,7 @@ class TestSoupSelector(TreeTest): ('link[href*="."]', ['l1']), ('div[id*="n"]', ['main', 'inner']), ('div[id*="nn"]', ['inner']), + ('div[data-tag*="edval"]', ['data1']) ) def test_attribute_exact_or_hypen(self): @@ -1767,18 +1899,27 @@ class TestSoupSelector(TreeTest): ('p[class]', ['p1', 'pmulti']), ('[blah]', []), ('p[blah]', []), + ('div[data-tag]', ['data1']) ) + def test_unsupported_pseudoclass(self): + self.assertRaises( + NotImplementedError, self.soup.select, "a:no-such-pseudoclass") + + self.assertRaises( + NotImplementedError, self.soup.select, "a:nth-of-type(a)") + + def test_nth_of_type(self): # Try to select first paragraph els = self.soup.select('div#inner p:nth-of-type(1)') self.assertEqual(len(els), 1) - self.assertEqual(els[0].string, u'Some text') + self.assertEqual(els[0].string, 'Some text') # Try to select third paragraph els = self.soup.select('div#inner p:nth-of-type(3)') self.assertEqual(len(els), 1) - self.assertEqual(els[0].string, u'Another') + self.assertEqual(els[0].string, 'Another') # Try to select (non-existent!) fourth paragraph els = self.soup.select('div#inner p:nth-of-type(4)') @@ -1791,7 +1932,7 @@ class TestSoupSelector(TreeTest): def test_nth_of_type_direct_descendant(self): els = self.soup.select('div#inner > p:nth-of-type(1)') self.assertEqual(len(els), 1) - self.assertEqual(els[0].string, u'Some text') + self.assertEqual(els[0].string, 'Some text') def test_id_child_selector_nth_of_type(self): self.assertSelects('#inner > p:nth-of-type(2)', ['p1']) @@ -1803,7 +1944,7 @@ class TestSoupSelector(TreeTest): selected = inner.select("div") # The <div id="inner"> tag was selected. The <div id="footer"> # tag was not. - self.assertSelectsIDs(selected, ['inner']) + self.assertSelectsIDs(selected, ['inner', 'data1']) def test_overspecified_child_id(self): self.assertSelects(".fancy #inner", ['inner']) @@ -1827,3 +1968,44 @@ class TestSoupSelector(TreeTest): def test_sibling_combinator_wont_select_same_tag_twice(self): self.assertSelects('p[lang] ~ p', ['lang-en-gb', 'lang-en-us', 'lang-fr']) + + # Test the selector grouping operator (the comma) + def test_multiple_select(self): + self.assertSelects('x, y', ['xid', 'yid']) + + def test_multiple_select_with_no_space(self): + self.assertSelects('x,y', ['xid', 'yid']) + + def test_multiple_select_with_more_space(self): + self.assertSelects('x, y', ['xid', 'yid']) + + def test_multiple_select_duplicated(self): + self.assertSelects('x, x', ['xid']) + + def test_multiple_select_sibling(self): + self.assertSelects('x, y ~ p[lang=fr]', ['xid', 'lang-fr']) + + def test_multiple_select_tag_and_direct_descendant(self): + self.assertSelects('x, y > z', ['xid', 'zidb']) + + def test_multiple_select_direct_descendant_and_tags(self): + self.assertSelects('div > x, y, z', ['xid', 'yid', 'zida', 'zidb', 'zidab', 'zidac']) + + def test_multiple_select_indirect_descendant(self): + self.assertSelects('div x,y, z', ['xid', 'yid', 'zida', 'zidb', 'zidab', 'zidac']) + + def test_invalid_multiple_select(self): + self.assertRaises(ValueError, self.soup.select, ',x, y') + self.assertRaises(ValueError, self.soup.select, 'x,,y') + + def test_multiple_select_attrs(self): + self.assertSelects('p[lang=en], p[lang=en-gb]', ['lang-en', 'lang-en-gb']) + + def test_multiple_select_ids(self): + self.assertSelects('x, y > z[id=zida], z[id=zidab], z[id=zidb]', ['xid', 'zidb', 'zidab']) + + def test_multiple_select_nested(self): + self.assertSelects('body > div > x, y > z', ['xid', 'zidb']) + + + diff --git a/import-layers/yocto-poky/bitbake/lib/codegen.py b/import-layers/yocto-poky/bitbake/lib/codegen.py index be772d510..62a6748c4 100644 --- a/import-layers/yocto-poky/bitbake/lib/codegen.py +++ b/import-layers/yocto-poky/bitbake/lib/codegen.py @@ -214,11 +214,11 @@ class SourceGenerator(NodeVisitor): paren_or_comma() self.write(keyword.arg + '=') self.visit(keyword.value) - if node.starargs is not None: + if hasattr(node, 'starargs') and node.starargs is not None: paren_or_comma() self.write('*') self.visit(node.starargs) - if node.kwargs is not None: + if hasattr(node, 'kwargs') and node.kwargs is not None: paren_or_comma() self.write('**') self.visit(node.kwargs) @@ -379,11 +379,11 @@ class SourceGenerator(NodeVisitor): write_comma() self.write(keyword.arg + '=') self.visit(keyword.value) - if node.starargs is not None: + if hasattr(node, 'starargs') and node.starargs is not None: write_comma() self.write('*') self.visit(node.starargs) - if node.kwargs is not None: + if hasattr(node, 'kwargs') and node.kwargs is not None: write_comma() self.write('**') self.visit(node.kwargs) diff --git a/import-layers/yocto-poky/bitbake/lib/ply/yacc.py b/import-layers/yocto-poky/bitbake/lib/ply/yacc.py index 6168fd9a0..d50886ed2 100644 --- a/import-layers/yocto-poky/bitbake/lib/ply/yacc.py +++ b/import-layers/yocto-poky/bitbake/lib/ply/yacc.py @@ -195,6 +195,8 @@ class YaccProduction: self.lexer = None self.parser= None def __getitem__(self,n): + if isinstance(n,slice): + return [self[i] for i in range(*(n.indices(len(self.slice))))] if n >= 0: return self.slice[n].value else: return self.stack[n].value diff --git a/import-layers/yocto-poky/bitbake/lib/progressbar.py b/import-layers/yocto-poky/bitbake/lib/progressbar.py deleted file mode 100644 index 114cdc16b..000000000 --- a/import-layers/yocto-poky/bitbake/lib/progressbar.py +++ /dev/null @@ -1,384 +0,0 @@ -#!/usr/bin/env python -# -*- coding: iso-8859-1 -*- -# -# progressbar - Text progressbar library for python. -# Copyright (c) 2005 Nilton Volpato -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library 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 -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - - -"""Text progressbar library for python. - -This library provides a text mode progressbar. This is typically used -to display the progress of a long running operation, providing a -visual clue that processing is underway. - -The ProgressBar class manages the progress, and the format of the line -is given by a number of widgets. A widget is an object that may -display diferently depending on the state of the progress. There are -three types of widget: -- a string, which always shows itself; -- a ProgressBarWidget, which may return a diferent value every time -it's update method is called; and -- a ProgressBarWidgetHFill, which is like ProgressBarWidget, except it -expands to fill the remaining width of the line. - -The progressbar module is very easy to use, yet very powerful. And -automatically supports features like auto-resizing when available. -""" - -from __future__ import division - -__author__ = "Nilton Volpato" -__author_email__ = "first-name dot last-name @ gmail.com" -__date__ = "2006-05-07" -__version__ = "2.3-dev" - -import sys, time, os -from array import array -try: - from fcntl import ioctl - import termios -except ImportError: - pass -import signal -try: - basestring -except NameError: - basestring = (str,) - -class ProgressBarWidget(object): - """This is an element of ProgressBar formatting. - - The ProgressBar object will call it's update value when an update - is needed. It's size may change between call, but the results will - not be good if the size changes drastically and repeatedly. - """ - def update(self, pbar): - """Returns the string representing the widget. - - The parameter pbar is a reference to the calling ProgressBar, - where one can access attributes of the class for knowing how - the update must be made. - - At least this function must be overriden.""" - pass - -class ProgressBarWidgetHFill(object): - """This is a variable width element of ProgressBar formatting. - - The ProgressBar object will call it's update value, informing the - width this object must the made. This is like TeX \\hfill, it will - expand to fill the line. You can use more than one in the same - line, and they will all have the same width, and together will - fill the line. - """ - def update(self, pbar, width): - """Returns the string representing the widget. - - The parameter pbar is a reference to the calling ProgressBar, - where one can access attributes of the class for knowing how - the update must be made. The parameter width is the total - horizontal width the widget must have. - - At least this function must be overriden.""" - pass - - -class ETA(ProgressBarWidget): - "Widget for the Estimated Time of Arrival" - def format_time(self, seconds): - return time.strftime('%H:%M:%S', time.gmtime(seconds)) - def update(self, pbar): - if pbar.currval == 0: - return 'ETA: --:--:--' - elif pbar.finished: - return 'Time: %s' % self.format_time(pbar.seconds_elapsed) - else: - elapsed = pbar.seconds_elapsed - eta = elapsed * pbar.maxval / pbar.currval - elapsed - return 'ETA: %s' % self.format_time(eta) - -class FileTransferSpeed(ProgressBarWidget): - "Widget for showing the transfer speed (useful for file transfers)." - def __init__(self, unit='B'): - self.unit = unit - self.fmt = '%6.2f %s' - self.prefixes = ['', 'K', 'M', 'G', 'T', 'P'] - def update(self, pbar): - if pbar.seconds_elapsed < 2e-6:#== 0: - bps = 0.0 - else: - bps = pbar.currval / pbar.seconds_elapsed - spd = bps - for u in self.prefixes: - if spd < 1000: - break - spd /= 1000 - return self.fmt % (spd, u + self.unit + '/s') - -class RotatingMarker(ProgressBarWidget): - "A rotating marker for filling the bar of progress." - def __init__(self, markers='|/-\\'): - self.markers = markers - self.curmark = -1 - def update(self, pbar): - if pbar.finished: - return self.markers[0] - self.curmark = (self.curmark + 1) % len(self.markers) - return self.markers[self.curmark] - -class Percentage(ProgressBarWidget): - "Just the percentage done." - def update(self, pbar): - return '%3d%%' % pbar.percentage() - -class SimpleProgress(ProgressBarWidget): - "Returns what is already done and the total, e.g.: '5 of 47'" - def __init__(self, sep=' of '): - self.sep = sep - def update(self, pbar): - return '%d%s%d' % (pbar.currval, self.sep, pbar.maxval) - -class Bar(ProgressBarWidgetHFill): - "The bar of progress. It will stretch to fill the line." - def __init__(self, marker='#', left='|', right='|'): - self.marker = marker - self.left = left - self.right = right - def _format_marker(self, pbar): - if isinstance(self.marker, basestring): - return self.marker - else: - return self.marker.update(pbar) - def update(self, pbar, width): - percent = pbar.percentage() - cwidth = width - len(self.left) - len(self.right) - marked_width = int(percent * cwidth // 100) - m = self._format_marker(pbar) - bar = (self.left + (m * marked_width).ljust(cwidth) + self.right) - return bar - -class ReverseBar(Bar): - "The reverse bar of progress, or bar of regress. :)" - def update(self, pbar, width): - percent = pbar.percentage() - cwidth = width - len(self.left) - len(self.right) - marked_width = int(percent * cwidth // 100) - m = self._format_marker(pbar) - bar = (self.left + (m*marked_width).rjust(cwidth) + self.right) - return bar - -default_widgets = [Percentage(), ' ', Bar()] -class ProgressBar(object): - """This is the ProgressBar class, it updates and prints the bar. - - A common way of using it is like: - >>> pbar = ProgressBar().start() - >>> for i in xrange(100): - ... # do something - ... pbar.update(i+1) - ... - >>> pbar.finish() - - You can also use a progressbar as an iterator: - >>> progress = ProgressBar() - >>> for i in progress(some_iterable): - ... # do something - ... - - But anything you want to do is possible (well, almost anything). - You can supply different widgets of any type in any order. And you - can even write your own widgets! There are many widgets already - shipped and you should experiment with them. - - The term_width parameter must be an integer or None. In the latter case - it will try to guess it, if it fails it will default to 80 columns. - - When implementing a widget update method you may access any - attribute or function of the ProgressBar object calling the - widget's update method. The most important attributes you would - like to access are: - - currval: current value of the progress, 0 <= currval <= maxval - - maxval: maximum (and final) value of the progress - - finished: True if the bar has finished (reached 100%), False o/w - - start_time: the time when start() method of ProgressBar was called - - seconds_elapsed: seconds elapsed since start_time - - percentage(): percentage of the progress [0..100]. This is a method. - - The attributes above are unlikely to change between different versions, - the other ones may change or cease to exist without notice, so try to rely - only on the ones documented above if you are extending the progress bar. - """ - - __slots__ = ('currval', 'fd', 'finished', 'last_update_time', 'maxval', - 'next_update', 'num_intervals', 'seconds_elapsed', - 'signal_set', 'start_time', 'term_width', 'update_interval', - 'widgets', '_iterable') - - _DEFAULT_MAXVAL = 100 - - def __init__(self, maxval=None, widgets=default_widgets, term_width=None, - fd=sys.stderr): - self.maxval = maxval - self.widgets = widgets - self.fd = fd - self.signal_set = False - if term_width is not None: - self.term_width = term_width - else: - try: - self._handle_resize(None, None) - signal.signal(signal.SIGWINCH, self._handle_resize) - self.signal_set = True - except (SystemExit, KeyboardInterrupt): - raise - except: - self.term_width = int(os.environ.get('COLUMNS', 80)) - 1 - - self.currval = 0 - self.finished = False - self.start_time = None - self.last_update_time = None - self.seconds_elapsed = 0 - self._iterable = None - - def __call__(self, iterable): - try: - self.maxval = len(iterable) - except TypeError: - # If the iterable has no length, then rely on the value provided - # by the user, otherwise fail. - if not (isinstance(self.maxval, (int, long)) and self.maxval > 0): - raise RuntimeError('Could not determine maxval from iterable. ' - 'You must explicitly provide a maxval.') - self._iterable = iter(iterable) - self.start() - return self - - def __iter__(self): - return self - - def next(self): - try: - next = self._iterable.next() - self.update(self.currval + 1) - return next - except StopIteration: - self.finish() - raise - - def _handle_resize(self, signum, frame): - h, w = array('h', ioctl(self.fd, termios.TIOCGWINSZ, '\0' * 8))[:2] - self.term_width = w - - def percentage(self): - "Returns the percentage of the progress." - return self.currval * 100.0 / self.maxval - - def _format_widgets(self): - r = [] - hfill_inds = [] - num_hfill = 0 - currwidth = 0 - for i, w in enumerate(self.widgets): - if isinstance(w, ProgressBarWidgetHFill): - r.append(w) - hfill_inds.append(i) - num_hfill += 1 - elif isinstance(w, basestring): - r.append(w) - currwidth += len(w) - else: - weval = w.update(self) - currwidth += len(weval) - r.append(weval) - for iw in hfill_inds: - widget_width = int((self.term_width - currwidth) // num_hfill) - r[iw] = r[iw].update(self, widget_width) - return r - - def _format_line(self): - return ''.join(self._format_widgets()).ljust(self.term_width) - - def _next_update(self): - return int((int(self.num_intervals * - (self.currval / self.maxval)) + 1) * - self.update_interval) - - def _need_update(self): - """Returns true when the progressbar should print an updated line. - - You can override this method if you want finer grained control over - updates. - - The current implementation is optimized to be as fast as possible and - as economical as possible in the number of updates. However, depending - on your usage you may want to do more updates. For instance, if your - progressbar stays in the same percentage for a long time, and you want - to update other widgets, like ETA, then you could return True after - some time has passed with no updates. - - Ideally you could call self._format_line() and see if it's different - from the previous _format_line() call, but calling _format_line() takes - around 20 times more time than calling this implementation of - _need_update(). - """ - return self.currval >= self.next_update - - def update(self, value): - "Updates the progress bar to a new value." - assert 0 <= value <= self.maxval, '0 <= %d <= %d' % (value, self.maxval) - self.currval = value - if not self._need_update(): - return - if self.start_time is None: - raise RuntimeError('You must call start() before calling update()') - now = time.time() - self.seconds_elapsed = now - self.start_time - self.next_update = self._next_update() - self.fd.write(self._format_line() + '\r') - self.last_update_time = now - - def start(self): - """Starts measuring time, and prints the bar at 0%. - - It returns self so you can use it like this: - >>> pbar = ProgressBar().start() - >>> for i in xrange(100): - ... # do something - ... pbar.update(i+1) - ... - >>> pbar.finish() - """ - if self.maxval is None: - self.maxval = self._DEFAULT_MAXVAL - assert self.maxval > 0 - - self.num_intervals = max(100, self.term_width) - self.update_interval = self.maxval / self.num_intervals - self.next_update = 0 - - self.start_time = self.last_update_time = time.time() - self.update(0) - return self - - def finish(self): - """Used to tell the progress is finished.""" - self.finished = True - self.update(self.maxval) - self.fd.write('\n') - if self.signal_set: - signal.signal(signal.SIGWINCH, signal.SIG_DFL) diff --git a/import-layers/yocto-poky/bitbake/lib/progressbar/LICENSE.txt b/import-layers/yocto-poky/bitbake/lib/progressbar/LICENSE.txt new file mode 100644 index 000000000..fc8ccdc1c --- /dev/null +++ b/import-layers/yocto-poky/bitbake/lib/progressbar/LICENSE.txt @@ -0,0 +1,52 @@ +You can redistribute and/or modify this library under the terms of the +GNU LGPL license or BSD license (or both). + +--- + +progressbar - Text progress bar library for python. +Copyright (C) 2005 Nilton Volpato + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library 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 +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +--- + +progressbar - Text progress bar library for python +Copyright (c) 2008 Nilton Volpato + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + a. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + b. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + c. Neither the name of the author nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +DAMAGE. diff --git a/import-layers/yocto-poky/bitbake/lib/progressbar/__init__.py b/import-layers/yocto-poky/bitbake/lib/progressbar/__init__.py new file mode 100644 index 000000000..fbab744ee --- /dev/null +++ b/import-layers/yocto-poky/bitbake/lib/progressbar/__init__.py @@ -0,0 +1,49 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# progressbar - Text progress bar library for Python. +# Copyright (c) 2005 Nilton Volpato +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +"""Text progress bar library for Python. + +A text progress bar is typically used to display the progress of a long +running operation, providing a visual cue that processing is underway. + +The ProgressBar class manages the current progress, and the format of the line +is given by a number of widgets. A widget is an object that may display +differently depending on the state of the progress bar. There are three types +of widgets: + - a string, which always shows itself + + - a ProgressBarWidget, which may return a different value every time its + update method is called + + - a ProgressBarWidgetHFill, which is like ProgressBarWidget, except it + expands to fill the remaining width of the line. + +The progressbar module is very easy to use, yet very powerful. It will also +automatically enable features like auto-resizing when the system supports it. +""" + +__author__ = 'Nilton Volpato' +__author_email__ = 'first-name dot last-name @ gmail.com' +__date__ = '2011-05-14' +__version__ = '2.3' + +from .compat import * +from .widgets import * +from .progressbar import * diff --git a/import-layers/yocto-poky/bitbake/lib/progressbar/compat.py b/import-layers/yocto-poky/bitbake/lib/progressbar/compat.py new file mode 100644 index 000000000..a39f4a1f4 --- /dev/null +++ b/import-layers/yocto-poky/bitbake/lib/progressbar/compat.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- +# +# progressbar - Text progress bar library for Python. +# Copyright (c) 2005 Nilton Volpato +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +"""Compatibility methods and classes for the progressbar module.""" + + +# Python 3.x (and backports) use a modified iterator syntax +# This will allow 2.x to behave with 3.x iterators +try: + next +except NameError: + def next(iter): + try: + # Try new style iterators + return iter.__next__() + except AttributeError: + # Fallback in case of a "native" iterator + return iter.next() + + +# Python < 2.5 does not have "any" +try: + any +except NameError: + def any(iterator): + for item in iterator: + if item: return True + return False diff --git a/import-layers/yocto-poky/bitbake/lib/progressbar/progressbar.py b/import-layers/yocto-poky/bitbake/lib/progressbar/progressbar.py new file mode 100644 index 000000000..2873ad6ca --- /dev/null +++ b/import-layers/yocto-poky/bitbake/lib/progressbar/progressbar.py @@ -0,0 +1,315 @@ +# -*- coding: utf-8 -*- +# +# progressbar - Text progress bar library for Python. +# Copyright (c) 2005 Nilton Volpato +# +# (With some small changes after importing into BitBake) +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +"""Main ProgressBar class.""" + +from __future__ import division + +import math +import os +import signal +import sys +import time + +try: + from fcntl import ioctl + from array import array + import termios +except ImportError: + pass + +from .compat import * # for: any, next +from . import widgets + + +class UnknownLength: pass + + +class ProgressBar(object): + """The ProgressBar class which updates and prints the bar. + + A common way of using it is like: + >>> pbar = ProgressBar().start() + >>> for i in range(100): + ... # do something + ... pbar.update(i+1) + ... + >>> pbar.finish() + + You can also use a ProgressBar as an iterator: + >>> progress = ProgressBar() + >>> for i in progress(some_iterable): + ... # do something + ... + + Since the progress bar is incredibly customizable you can specify + different widgets of any type in any order. You can even write your own + widgets! However, since there are already a good number of widgets you + should probably play around with them before moving on to create your own + widgets. + + The term_width parameter represents the current terminal width. If the + parameter is set to an integer then the progress bar will use that, + otherwise it will attempt to determine the terminal width falling back to + 80 columns if the width cannot be determined. + + When implementing a widget's update method you are passed a reference to + the current progress bar. As a result, you have access to the + ProgressBar's methods and attributes. Although there is nothing preventing + you from changing the ProgressBar you should treat it as read only. + + Useful methods and attributes include (Public API): + - currval: current progress (0 <= currval <= maxval) + - maxval: maximum (and final) value + - finished: True if the bar has finished (reached 100%) + - start_time: the time when start() method of ProgressBar was called + - seconds_elapsed: seconds elapsed since start_time and last call to + update + - percentage(): progress in percent [0..100] + """ + + __slots__ = ('currval', 'fd', 'finished', 'last_update_time', + 'left_justify', 'maxval', 'next_update', 'num_intervals', + 'poll', 'seconds_elapsed', 'signal_set', 'start_time', + 'term_width', 'update_interval', 'widgets', '_time_sensitive', + '__iterable') + + _DEFAULT_MAXVAL = 100 + _DEFAULT_TERMSIZE = 80 + _DEFAULT_WIDGETS = [widgets.Percentage(), ' ', widgets.Bar()] + + def __init__(self, maxval=None, widgets=None, term_width=None, poll=1, + left_justify=True, fd=sys.stderr): + """Initializes a progress bar with sane defaults.""" + + # Don't share a reference with any other progress bars + if widgets is None: + widgets = list(self._DEFAULT_WIDGETS) + + self.maxval = maxval + self.widgets = widgets + self.fd = fd + self.left_justify = left_justify + + self.signal_set = False + if term_width is not None: + self.term_width = term_width + else: + try: + self._handle_resize(None, None) + signal.signal(signal.SIGWINCH, self._handle_resize) + self.signal_set = True + except (SystemExit, KeyboardInterrupt): raise + except Exception as e: + print("DEBUG 5 %s" % e) + self.term_width = self._env_size() + + self.__iterable = None + self._update_widgets() + self.currval = 0 + self.finished = False + self.last_update_time = None + self.poll = poll + self.seconds_elapsed = 0 + self.start_time = None + self.update_interval = 1 + self.next_update = 0 + + + def __call__(self, iterable): + """Use a ProgressBar to iterate through an iterable.""" + + try: + self.maxval = len(iterable) + except: + if self.maxval is None: + self.maxval = UnknownLength + + self.__iterable = iter(iterable) + return self + + + def __iter__(self): + return self + + + def __next__(self): + try: + value = next(self.__iterable) + if self.start_time is None: + self.start() + else: + self.update(self.currval + 1) + return value + except StopIteration: + if self.start_time is None: + self.start() + self.finish() + raise + + + # Create an alias so that Python 2.x won't complain about not being + # an iterator. + next = __next__ + + + def _env_size(self): + """Tries to find the term_width from the environment.""" + + return int(os.environ.get('COLUMNS', self._DEFAULT_TERMSIZE)) - 1 + + + def _handle_resize(self, signum=None, frame=None): + """Tries to catch resize signals sent from the terminal.""" + + h, w = array('h', ioctl(self.fd, termios.TIOCGWINSZ, '\0' * 8))[:2] + self.term_width = w + + + def percentage(self): + """Returns the progress as a percentage.""" + if self.currval >= self.maxval: + return 100.0 + return (self.currval * 100.0 / self.maxval) if self.maxval else 100.00 + + percent = property(percentage) + + + def _format_widgets(self): + result = [] + expanding = [] + width = self.term_width + + for index, widget in enumerate(self.widgets): + if isinstance(widget, widgets.WidgetHFill): + result.append(widget) + expanding.insert(0, index) + else: + widget = widgets.format_updatable(widget, self) + result.append(widget) + width -= len(widget) + + count = len(expanding) + while count: + portion = max(int(math.ceil(width * 1. / count)), 0) + index = expanding.pop() + count -= 1 + + widget = result[index].update(self, portion) + width -= len(widget) + result[index] = widget + + return result + + + def _format_line(self): + """Joins the widgets and justifies the line.""" + + widgets = ''.join(self._format_widgets()) + + if self.left_justify: return widgets.ljust(self.term_width) + else: return widgets.rjust(self.term_width) + + + def _need_update(self): + """Returns whether the ProgressBar should redraw the line.""" + if self.currval >= self.next_update or self.finished: return True + + delta = time.time() - self.last_update_time + return self._time_sensitive and delta > self.poll + + + def _update_widgets(self): + """Checks all widgets for the time sensitive bit.""" + + self._time_sensitive = any(getattr(w, 'TIME_SENSITIVE', False) + for w in self.widgets) + + + def update(self, value=None): + """Updates the ProgressBar to a new value.""" + + if value is not None and value is not UnknownLength: + if (self.maxval is not UnknownLength + and not 0 <= value <= self.maxval): + + raise ValueError('Value out of range') + + self.currval = value + + + if not self._need_update(): return + if self.start_time is None: + raise RuntimeError('You must call "start" before calling "update"') + + now = time.time() + self.seconds_elapsed = now - self.start_time + self.next_update = self.currval + self.update_interval + output = self._format_line() + self.fd.write(output + '\r') + self.fd.flush() + self.last_update_time = now + return output + + + def start(self, update=True): + """Starts measuring time, and prints the bar at 0%. + + It returns self so you can use it like this: + >>> pbar = ProgressBar().start() + >>> for i in range(100): + ... # do something + ... pbar.update(i+1) + ... + >>> pbar.finish() + """ + + if self.maxval is None: + self.maxval = self._DEFAULT_MAXVAL + + self.num_intervals = max(100, self.term_width) + self.next_update = 0 + + if self.maxval is not UnknownLength: + if self.maxval < 0: raise ValueError('Value out of range') + self.update_interval = self.maxval / self.num_intervals + + + self.start_time = time.time() + if update: + self.last_update_time = self.start_time + self.update(0) + else: + self.last_update_time = 0 + + return self + + + def finish(self): + """Puts the ProgressBar bar in the finished state.""" + + if self.finished: + return + self.finished = True + self.update(self.maxval) + self.fd.write('\n') + if self.signal_set: + signal.signal(signal.SIGWINCH, signal.SIG_DFL) diff --git a/import-layers/yocto-poky/bitbake/lib/progressbar/widgets.py b/import-layers/yocto-poky/bitbake/lib/progressbar/widgets.py new file mode 100644 index 000000000..77285ca7a --- /dev/null +++ b/import-layers/yocto-poky/bitbake/lib/progressbar/widgets.py @@ -0,0 +1,391 @@ +# -*- coding: utf-8 -*- +# +# progressbar - Text progress bar library for Python. +# Copyright (c) 2005 Nilton Volpato +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +"""Default ProgressBar widgets.""" + +from __future__ import division + +import datetime +import math + +try: + from abc import ABCMeta, abstractmethod +except ImportError: + AbstractWidget = object + abstractmethod = lambda fn: fn +else: + AbstractWidget = ABCMeta('AbstractWidget', (object,), {}) + + +def format_updatable(updatable, pbar): + if hasattr(updatable, 'update'): return updatable.update(pbar) + else: return updatable + + +class Widget(AbstractWidget): + """The base class for all widgets. + + The ProgressBar will call the widget's update value when the widget should + be updated. The widget's size may change between calls, but the widget may + display incorrectly if the size changes drastically and repeatedly. + + The boolean TIME_SENSITIVE informs the ProgressBar that it should be + updated more often because it is time sensitive. + """ + + TIME_SENSITIVE = False + __slots__ = () + + @abstractmethod + def update(self, pbar): + """Updates the widget. + + pbar - a reference to the calling ProgressBar + """ + + +class WidgetHFill(Widget): + """The base class for all variable width widgets. + + This widget is much like the \\hfill command in TeX, it will expand to + fill the line. You can use more than one in the same line, and they will + all have the same width, and together will fill the line. + """ + + @abstractmethod + def update(self, pbar, width): + """Updates the widget providing the total width the widget must fill. + + pbar - a reference to the calling ProgressBar + width - The total width the widget must fill + """ + + +class Timer(Widget): + """Widget which displays the elapsed seconds.""" + + __slots__ = ('format_string',) + TIME_SENSITIVE = True + + def __init__(self, format='Elapsed Time: %s'): + self.format_string = format + + @staticmethod + def format_time(seconds): + """Formats time as the string "HH:MM:SS".""" + + return str(datetime.timedelta(seconds=int(seconds))) + + + def update(self, pbar): + """Updates the widget to show the elapsed time.""" + + return self.format_string % self.format_time(pbar.seconds_elapsed) + + +class ETA(Timer): + """Widget which attempts to estimate the time of arrival.""" + + TIME_SENSITIVE = True + + def update(self, pbar): + """Updates the widget to show the ETA or total time when finished.""" + + if pbar.currval == 0: + return 'ETA: --:--:--' + elif pbar.finished: + return 'Time: %s' % self.format_time(pbar.seconds_elapsed) + else: + elapsed = pbar.seconds_elapsed + eta = elapsed * pbar.maxval / pbar.currval - elapsed + return 'ETA: %s' % self.format_time(eta) + + +class AdaptiveETA(Timer): + """Widget which attempts to estimate the time of arrival. + + Uses a weighted average of two estimates: + 1) ETA based on the total progress and time elapsed so far + 2) ETA based on the progress as per the last 10 update reports + + The weight depends on the current progress so that to begin with the + total progress is used and at the end only the most recent progress is + used. + """ + + TIME_SENSITIVE = True + NUM_SAMPLES = 10 + + def _update_samples(self, currval, elapsed): + sample = (currval, elapsed) + if not hasattr(self, 'samples'): + self.samples = [sample] * (self.NUM_SAMPLES + 1) + else: + self.samples.append(sample) + return self.samples.pop(0) + + def _eta(self, maxval, currval, elapsed): + return elapsed * maxval / float(currval) - elapsed + + def update(self, pbar): + """Updates the widget to show the ETA or total time when finished.""" + if pbar.currval == 0: + return 'ETA: --:--:--' + elif pbar.finished: + return 'Time: %s' % self.format_time(pbar.seconds_elapsed) + else: + elapsed = pbar.seconds_elapsed + currval1, elapsed1 = self._update_samples(pbar.currval, elapsed) + eta = self._eta(pbar.maxval, pbar.currval, elapsed) + if pbar.currval > currval1: + etasamp = self._eta(pbar.maxval - currval1, + pbar.currval - currval1, + elapsed - elapsed1) + weight = (pbar.currval / float(pbar.maxval)) ** 0.5 + eta = (1 - weight) * eta + weight * etasamp + return 'ETA: %s' % self.format_time(eta) + + +class FileTransferSpeed(Widget): + """Widget for showing the transfer speed (useful for file transfers).""" + + FORMAT = '%6.2f %s%s/s' + PREFIXES = ' kMGTPEZY' + __slots__ = ('unit',) + + def __init__(self, unit='B'): + self.unit = unit + + def update(self, pbar): + """Updates the widget with the current SI prefixed speed.""" + + if pbar.seconds_elapsed < 2e-6 or pbar.currval < 2e-6: # =~ 0 + scaled = power = 0 + else: + speed = pbar.currval / pbar.seconds_elapsed + power = int(math.log(speed, 1000)) + scaled = speed / 1000.**power + + return self.FORMAT % (scaled, self.PREFIXES[power], self.unit) + + +class AnimatedMarker(Widget): + """An animated marker for the progress bar which defaults to appear as if + it were rotating. + """ + + __slots__ = ('markers', 'curmark') + + def __init__(self, markers='|/-\\'): + self.markers = markers + self.curmark = -1 + + def update(self, pbar): + """Updates the widget to show the next marker or the first marker when + finished""" + + if pbar.finished: return self.markers[0] + + self.curmark = (self.curmark + 1) % len(self.markers) + return self.markers[self.curmark] + +# Alias for backwards compatibility +RotatingMarker = AnimatedMarker + + +class Counter(Widget): + """Displays the current count.""" + + __slots__ = ('format_string',) + + def __init__(self, format='%d'): + self.format_string = format + + def update(self, pbar): + return self.format_string % pbar.currval + + +class Percentage(Widget): + """Displays the current percentage as a number with a percent sign.""" + + def update(self, pbar): + return '%3d%%' % pbar.percentage() + + +class FormatLabel(Timer): + """Displays a formatted label.""" + + mapping = { + 'elapsed': ('seconds_elapsed', Timer.format_time), + 'finished': ('finished', None), + 'last_update': ('last_update_time', None), + 'max': ('maxval', None), + 'seconds': ('seconds_elapsed', None), + 'start': ('start_time', None), + 'value': ('currval', None) + } + + __slots__ = ('format_string',) + def __init__(self, format): + self.format_string = format + + def update(self, pbar): + context = {} + for name, (key, transform) in self.mapping.items(): + try: + value = getattr(pbar, key) + + if transform is None: + context[name] = value + else: + context[name] = transform(value) + except: pass + + return self.format_string % context + + +class SimpleProgress(Widget): + """Returns progress as a count of the total (e.g.: "5 of 47").""" + + __slots__ = ('sep',) + + def __init__(self, sep=' of '): + self.sep = sep + + def update(self, pbar): + return '%d%s%d' % (pbar.currval, self.sep, pbar.maxval) + + +class Bar(WidgetHFill): + """A progress bar which stretches to fill the line.""" + + __slots__ = ('marker', 'left', 'right', 'fill', 'fill_left') + + def __init__(self, marker='#', left='|', right='|', fill=' ', + fill_left=True): + """Creates a customizable progress bar. + + marker - string or updatable object to use as a marker + left - string or updatable object to use as a left border + right - string or updatable object to use as a right border + fill - character to use for the empty part of the progress bar + fill_left - whether to fill from the left or the right + """ + self.marker = marker + self.left = left + self.right = right + self.fill = fill + self.fill_left = fill_left + + + def update(self, pbar, width): + """Updates the progress bar and its subcomponents.""" + + left, marked, right = (format_updatable(i, pbar) for i in + (self.left, self.marker, self.right)) + + width -= len(left) + len(right) + # Marked must *always* have length of 1 + if pbar.maxval: + marked *= int(pbar.currval / pbar.maxval * width) + else: + marked = '' + + if self.fill_left: + return '%s%s%s' % (left, marked.ljust(width, self.fill), right) + else: + return '%s%s%s' % (left, marked.rjust(width, self.fill), right) + + +class ReverseBar(Bar): + """A bar which has a marker which bounces from side to side.""" + + def __init__(self, marker='#', left='|', right='|', fill=' ', + fill_left=False): + """Creates a customizable progress bar. + + marker - string or updatable object to use as a marker + left - string or updatable object to use as a left border + right - string or updatable object to use as a right border + fill - character to use for the empty part of the progress bar + fill_left - whether to fill from the left or the right + """ + self.marker = marker + self.left = left + self.right = right + self.fill = fill + self.fill_left = fill_left + + +class BouncingBar(Bar): + def update(self, pbar, width): + """Updates the progress bar and its subcomponents.""" + + left, marker, right = (format_updatable(i, pbar) for i in + (self.left, self.marker, self.right)) + + width -= len(left) + len(right) + + if pbar.finished: return '%s%s%s' % (left, width * marker, right) + + position = int(pbar.currval % (width * 2 - 1)) + if position > width: position = width * 2 - position + lpad = self.fill * (position - 1) + rpad = self.fill * (width - len(marker) - len(lpad)) + + # Swap if we want to bounce the other way + if not self.fill_left: rpad, lpad = lpad, rpad + + return '%s%s%s%s%s' % (left, lpad, marker, rpad, right) + + +class BouncingSlider(Bar): + """ + A slider that bounces back and forth in response to update() calls + without reference to the actual value. Based on a combination of + BouncingBar from a newer version of this module and RotatingMarker. + """ + def __init__(self, marker='<=>'): + self.curmark = -1 + self.forward = True + Bar.__init__(self, marker=marker) + def update(self, pbar, width): + left, marker, right = (format_updatable(i, pbar) for i in + (self.left, self.marker, self.right)) + + width -= len(left) + len(right) + if width < 0: + return '' + + if pbar.finished: return '%s%s%s' % (left, width * '=', right) + + self.curmark = self.curmark + 1 + position = int(self.curmark % (width * 2 - 1)) + if position + len(marker) > width: + self.forward = not self.forward + self.curmark = 1 + position = 1 + lpad = ' ' * (position - 1) + rpad = ' ' * (width - len(marker) - len(lpad)) + + if not self.forward: + temp = lpad + lpad = rpad + rpad = temp + return '%s%s%s%s%s' % (left, lpad, marker, rpad, right) diff --git a/import-layers/yocto-poky/bitbake/lib/prserv/db.py b/import-layers/yocto-poky/bitbake/lib/prserv/db.py index 2a8618417..495d09f39 100644 --- a/import-layers/yocto-poky/bitbake/lib/prserv/db.py +++ b/import-layers/yocto-poky/bitbake/lib/prserv/db.py @@ -260,7 +260,7 @@ class PRData(object): self.connection.close() def __getitem__(self,tblname): - if not isinstance(tblname, basestring): + if not isinstance(tblname, str): raise TypeError("tblname argument must be a string, not '%s'" % type(tblname)) if tblname in self._tables: diff --git a/import-layers/yocto-poky/bitbake/lib/prserv/serv.py b/import-layers/yocto-poky/bitbake/lib/prserv/serv.py index affccd9b3..cafcc820c 100644 --- a/import-layers/yocto-poky/bitbake/lib/prserv/serv.py +++ b/import-layers/yocto-poky/bitbake/lib/prserv/serv.py @@ -1,10 +1,10 @@ import os,sys,logging import signal, time -from SimpleXMLRPCServer import SimpleXMLRPCServer, SimpleXMLRPCRequestHandler +from xmlrpc.server import SimpleXMLRPCServer, SimpleXMLRPCRequestHandler import threading -import Queue +import queue import socket -import StringIO +import io try: import sqlite3 @@ -64,7 +64,7 @@ class PRServer(SimpleXMLRPCServer): self.register_function(self.importone, "importone") self.register_introspection_functions() - self.requestqueue = Queue.Queue() + self.requestqueue = queue.Queue() self.handlerthread = threading.Thread(target = self.process_request_thread) self.handlerthread.daemon = False @@ -83,7 +83,7 @@ class PRServer(SimpleXMLRPCServer): while not self.quit: try: (request, client_address) = self.requestqueue.get(True, 30) - except Queue.Empty: + except queue.Empty: self.table.sync_if_dirty() continue try: @@ -126,7 +126,7 @@ class PRServer(SimpleXMLRPCServer): Returns None if the database engine does not support dumping to script or if some other error is encountered in processing. """ - buff = StringIO.StringIO() + buff = io.StringIO() try: self.table.sync() self.table.dump_db(buff) @@ -242,8 +242,8 @@ class PRServer(SimpleXMLRPCServer): sys.stdout.flush() sys.stderr.flush() - si = file('/dev/null', 'r') - so = file(self.logfile, 'a+') + si = open('/dev/null', 'r') + so = open(self.logfile, 'a+') se = so os.dup2(si.fileno(),sys.stdin.fileno()) os.dup2(so.fileno(),sys.stdout.fileno()) @@ -263,7 +263,7 @@ class PRServer(SimpleXMLRPCServer): # write pidfile pid = str(os.getpid()) - pf = file(self.pidfile, 'w') + pf = open(self.pidfile, 'w') pf.write("%s\n" % pid) pf.close() @@ -323,7 +323,7 @@ def start_daemon(dbfile, host, port, logfile): ip = socket.gethostbyname(host) pidfile = PIDPREFIX % (ip, port) try: - pf = file(pidfile,'r') + pf = open(pidfile,'r') pid = int(pf.readline().strip()) pf.close() except IOError: @@ -350,7 +350,7 @@ def stop_daemon(host, port): ip = socket.gethostbyname(host) pidfile = PIDPREFIX % (ip, port) try: - pf = file(pidfile,'r') + pf = open(pidfile,'r') pid = int(pf.readline().strip()) pf.close() except IOError: @@ -420,7 +420,7 @@ class PRServiceConfigError(Exception): def auto_start(d): global singleton - host_params = filter(None, (d.getVar('PRSERV_HOST', True) or '').split(':')) + host_params = list(filter(None, (d.getVar('PRSERV_HOST', True) or '').split(':'))) if not host_params: return None diff --git a/import-layers/yocto-poky/bitbake/lib/pyinotify.py b/import-layers/yocto-poky/bitbake/lib/pyinotify.py index 2dae00211..4eb03b092 100644 --- a/import-layers/yocto-poky/bitbake/lib/pyinotify.py +++ b/import-layers/yocto-poky/bitbake/lib/pyinotify.py @@ -42,13 +42,14 @@ class UnsupportedPythonVersionError(PyinotifyError): @param version: Current Python version @type version: string """ - err = 'Python %s is unsupported, requires at least Python 2.4' - PyinotifyError.__init__(self, err % version) + PyinotifyError.__init__(self, + ('Python %s is unsupported, requires ' + 'at least Python 3.0') % version) # Check Python version import sys -if sys.version_info < (2, 4): +if sys.version_info < (3, 0): raise UnsupportedPythonVersionError(sys.version) @@ -68,6 +69,8 @@ from datetime import datetime, timedelta import time import re import asyncore +import glob +import locale import subprocess try: @@ -76,12 +79,6 @@ except ImportError: pass # Will fail on Python 2.4 which has reduce() builtin anyway. try: - from glob import iglob as glob -except ImportError: - # Python 2.4 does not have glob.iglob(). - from glob import glob as glob - -try: import ctypes import ctypes.util except ImportError: @@ -95,9 +92,7 @@ except ImportError: __author__ = "seb@dbzteam.org (Sebastien Martini)" -__version__ = "0.9.5" - -__metaclass__ = type # Use new-style classes by default +__version__ = "0.9.6" # Compatibity mode: set to True to improve compatibility with @@ -122,6 +117,9 @@ class INotifyWrapper: """ @staticmethod def create(): + """ + Factory method instanciating and returning the right wrapper. + """ # First, try to use ctypes. if ctypes: inotify = _CtypesLibcINotifyWrapper() @@ -173,7 +171,7 @@ class _INotifySyscallsWrapper(INotifyWrapper): def _inotify_init(self): try: fd = inotify_syscalls.inotify_init() - except IOError, err: + except IOError as err: self._last_errno = err.errno return -1 return fd @@ -181,7 +179,7 @@ class _INotifySyscallsWrapper(INotifyWrapper): def _inotify_add_watch(self, fd, pathname, mask): try: wd = inotify_syscalls.inotify_add_watch(fd, pathname, mask) - except IOError, err: + except IOError as err: self._last_errno = err.errno return -1 return wd @@ -189,7 +187,7 @@ class _INotifySyscallsWrapper(INotifyWrapper): def _inotify_rm_watch(self, fd, wd): try: ret = inotify_syscalls.inotify_rm_watch(fd, wd) - except IOError, err: + except IOError as err: self._last_errno = err.errno return -1 return ret @@ -213,17 +211,8 @@ class _CtypesLibcINotifyWrapper(INotifyWrapper): except (OSError, IOError): pass # Will attemp to load it with None anyway. - if sys.version_info >= (2, 6): - self._libc = ctypes.CDLL(libc_name, use_errno=True) - self._get_errno_func = ctypes.get_errno - else: - self._libc = ctypes.CDLL(libc_name) - try: - location = self._libc.__errno_location - location.restype = ctypes.POINTER(ctypes.c_int) - self._get_errno_func = lambda: location().contents.value - except AttributeError: - pass + self._libc = ctypes.CDLL(libc_name, use_errno=True) + self._get_errno_func = ctypes.get_errno # Eventually check that libc has needed inotify bindings. if (not hasattr(self._libc, 'inotify_init') or @@ -241,9 +230,8 @@ class _CtypesLibcINotifyWrapper(INotifyWrapper): return True def _get_errno(self): - if self._get_errno_func is not None: - return self._get_errno_func() - return None + assert self._get_errno_func + return self._get_errno_func() def _inotify_init(self): assert self._libc is not None @@ -251,6 +239,11 @@ class _CtypesLibcINotifyWrapper(INotifyWrapper): def _inotify_add_watch(self, fd, pathname, mask): assert self._libc is not None + # Encodes path to a bytes string. This conversion seems required because + # ctypes.create_string_buffer seems to manipulate bytes internally. + # Moreover it seems that inotify_add_watch does not work very well when + # it receives an ctypes.create_unicode_buffer instance as argument. + pathname = pathname.encode(sys.getfilesystemencoding()) pathname = ctypes.create_string_buffer(pathname) return self._libc.inotify_add_watch(fd, pathname, mask) @@ -258,10 +251,6 @@ class _CtypesLibcINotifyWrapper(INotifyWrapper): assert self._libc is not None return self._libc.inotify_rm_watch(fd, wd) - def _sysctl(self, *args): - assert self._libc is not None - return self._libc.sysctl(*args) - # Logging def logger_init(): @@ -278,97 +267,58 @@ log = logger_init() # inotify's variables -class SysCtlINotify: +class ProcINotify: """ - Access (read, write) inotify's variables through sysctl. Usually it - requires administrator rights to update them. + Access (read, write) inotify's variables through /proc/sys/. Note that + usually it requires administrator rights to update them. Examples: - Read max_queued_events attribute: myvar = max_queued_events.value - Update max_queued_events attribute: max_queued_events.value = 42 """ - - inotify_attrs = {'max_user_instances': 1, - 'max_user_watches': 2, - 'max_queued_events': 3} - - def __init__(self, attrname, inotify_wrapper): - # FIXME: right now only supporting ctypes - assert ctypes - self._attrname = attrname - self._inotify_wrapper = inotify_wrapper - sino = ctypes.c_int * 3 - self._attr = sino(5, 20, SysCtlINotify.inotify_attrs[attrname]) - - @staticmethod - def create(attrname): - """ - Factory method instanciating and returning the right wrapper. - """ - # FIXME: right now only supporting ctypes - if ctypes is None: - return None - inotify_wrapper = _CtypesLibcINotifyWrapper() - if not inotify_wrapper.init(): - return None - return SysCtlINotify(attrname, inotify_wrapper) + def __init__(self, attr): + self._base = "/proc/sys/fs/inotify" + self._attr = attr def get_val(self): """ - Gets attribute's value. Raises OSError if the operation failed. + Gets attribute's value. @return: stored value. @rtype: int + @raise IOError: if corresponding file in /proc/sys cannot be read. """ - oldv = ctypes.c_int(0) - size = ctypes.c_int(ctypes.sizeof(oldv)) - sysctl = self._inotify_wrapper._sysctl - res = sysctl(self._attr, 3, - ctypes.c_voidp(ctypes.addressof(oldv)), - ctypes.addressof(size), - None, 0) - if res == -1: - raise OSError(self._inotify_wrapper.get_errno(), - self._inotify_wrapper.str_errno()) - return oldv.value + with open(os.path.join(self._base, self._attr), 'r') as file_obj: + return int(file_obj.readline()) def set_val(self, nval): """ - Sets new attribute's value. Raises OSError if the operation failed. + Sets new attribute's value. @param nval: replaces current value by nval. @type nval: int + @raise IOError: if corresponding file in /proc/sys cannot be written. """ - oldv = ctypes.c_int(0) - sizeo = ctypes.c_int(ctypes.sizeof(oldv)) - newv = ctypes.c_int(nval) - sizen = ctypes.c_int(ctypes.sizeof(newv)) - sysctl = self._inotify_wrapper._sysctl - res = sysctl(self._attr, 3, - ctypes.c_voidp(ctypes.addressof(oldv)), - ctypes.addressof(sizeo), - ctypes.c_voidp(ctypes.addressof(newv)), - sizen) - if res == -1: - raise OSError(self._inotify_wrapper.get_errno(), - self._inotify_wrapper.str_errno()) + with open(os.path.join(self._base, self._attr), 'w') as file_obj: + file_obj.write(str(nval) + '\n') value = property(get_val, set_val) def __repr__(self): - return '<%s=%d>' % (self._attrname, self.get_val()) + return '<%s=%d>' % (self._attr, self.get_val()) # Inotify's variables # -# FIXME: currently these variables are only accessible when ctypes is used, -# otherwise there are set to None. +# Note: may raise IOError if the corresponding value in /proc/sys +# cannot be accessed. # -# read: myvar = max_queued_events.value -# update: max_queued_events.value = 42 +# Examples: +# - read: myvar = max_queued_events.value +# - update: max_queued_events.value = 42 # for attrname in ('max_queued_events', 'max_user_instances', 'max_user_watches'): - globals()[attrname] = SysCtlINotify.create(attrname) + globals()[attrname] = ProcINotify(attrname) class EventsCodes: @@ -536,7 +486,7 @@ class _Event: continue if attr == 'mask': value = hex(getattr(self, attr)) - elif isinstance(value, basestring) and not value: + elif isinstance(value, str) and not value: value = "''" s += ' %s%s%s' % (output_format.field_name(attr), output_format.punctuation('='), @@ -628,7 +578,7 @@ class Event(_Event): self.name)) else: self.pathname = os.path.abspath(self.path) - except AttributeError, err: + except AttributeError as err: # Usually it is not an error some events are perfectly valids # despite the lack of these attributes. log.debug(err) @@ -718,8 +668,8 @@ class _SysProcessEvent(_ProcessEvent): and self._mv. """ date_cur_ = datetime.now() - for seq in [self._mv_cookie, self._mv]: - for k in seq.keys(): + for seq in (self._mv_cookie, self._mv): + for k in list(seq.keys()): if (date_cur_ - seq[k][1]) > timedelta(minutes=1): log.debug('Cleanup: deleting entry %s', seq[k][0]) del seq[k] @@ -767,9 +717,9 @@ class _SysProcessEvent(_ProcessEvent): continue rawevent = _RawEvent(created_dir_wd, flags, 0, name) self._notifier.append_event(rawevent) - except OSError, err: - msg = "process_IN_CREATE, invalid directory %s: %s" - log.debug(msg % (created_dir, str(err))) + except OSError as err: + msg = "process_IN_CREATE, invalid directory: %s" + log.debug(msg % str(err)) return self.process_default(raw_event) def process_IN_MOVED_FROM(self, raw_event): @@ -1097,8 +1047,8 @@ class Stats(ProcessEvent): @type filename: string """ flags = os.O_WRONLY|os.O_CREAT|os.O_NOFOLLOW|os.O_EXCL - fd = os.open(filename, flags, 0600) - os.write(fd, str(self)) + fd = os.open(filename, flags, 0o0600) + os.write(fd, bytes(self.__str__(), locale.getpreferredencoding())) os.close(fd) def __str__(self, scale=45): @@ -1107,7 +1057,7 @@ class Stats(ProcessEvent): return '' m = max(stats.values()) - unity = float(scale) / m + unity = scale / m fmt = '%%-26s%%-%ds%%s' % (len(output_format.field_value('@' * scale)) + 1) def func(x): @@ -1149,7 +1099,7 @@ class Notifier: @type default_proc_fun: instance of ProcessEvent @param read_freq: if read_freq == 0, events are read asap, if read_freq is > 0, this thread sleeps - max(0, read_freq - timeout) seconds. But if + max(0, read_freq - (timeout / 1000)) seconds. But if timeout is None it may be different because poll is blocking waiting for something to read. @type read_freq: int @@ -1161,8 +1111,9 @@ class Notifier: until the amount of events to read is >= threshold. At least with read_freq set you might sleep. @type threshold: int - @param timeout: - https://docs.python.org/3/library/select.html#polling-objects + @param timeout: see read_freq above. If provided, it must be set in + milliseconds. See + https://docs.python.org/3/library/select.html#select.poll.poll @type timeout: int """ # Watch Manager instance @@ -1228,7 +1179,8 @@ class Notifier: milliseconds. @param timeout: If specified it overrides the corresponding instance - attribute _timeout. + attribute _timeout. timeout must be sepcified in + milliseconds. @type timeout: int @return: New events to read. @@ -1240,8 +1192,8 @@ class Notifier: if timeout is None: timeout = self._timeout ret = self._pollobj.poll(timeout) - except select.error, err: - if err[0] == errno.EINTR: + except select.error as err: + if err.args[0] == errno.EINTR: continue # interrupted, retry else: raise @@ -1271,7 +1223,7 @@ class Notifier: try: # Read content from file r = os.read(self._fd, queue_size) - except Exception, msg: + except Exception as msg: raise NotifierError(msg) log.debug('Event queue size: %d', queue_size) rsum = 0 # counter @@ -1281,9 +1233,11 @@ class Notifier: wd, mask, cookie, fname_len = struct.unpack('iIII', r[rsum:rsum+s_size]) # Retrieve name - fname, = struct.unpack('%ds' % fname_len, + bname, = struct.unpack('%ds' % fname_len, r[rsum + s_size:rsum + s_size + fname_len]) - rawevent = _RawEvent(wd, mask, cookie, fname) + # FIXME: should we explictly call sys.getdefaultencoding() here ?? + uname = bname.decode() + rawevent = _RawEvent(wd, mask, cookie, uname) if self._coalesce: # Only enqueue new (unique) events. raweventstr = str(rawevent) @@ -1326,13 +1280,10 @@ class Notifier: def __daemonize(self, pid_file=None, stdin=os.devnull, stdout=os.devnull, stderr=os.devnull): """ - @param pid_file: file where the pid will be written. If pid_file=None - the pid is written to - /var/run/<sys.argv[0]|pyinotify>.pid, if pid_file=False - no pid_file is written. - @param stdin: - @param stdout: - @param stderr: files associated to common streams. + pid_file: file where the pid will be written. If pid_file=None the pid + is written to /var/run/<sys.argv[0]|pyinotify>.pid, if + pid_file=False no pid_file is written. + stdin, stdout, stderr: files associated to common streams. """ if pid_file is None: dirname = '/var/run/' @@ -1354,7 +1305,7 @@ class Notifier: if (pid == 0): # child os.chdir('/') - os.umask(022) + os.umask(0o022) else: # parent 2 os._exit(0) @@ -1364,9 +1315,9 @@ class Notifier: fd_inp = os.open(stdin, os.O_RDONLY) os.dup2(fd_inp, 0) - fd_out = os.open(stdout, os.O_WRONLY|os.O_CREAT, 0600) + fd_out = os.open(stdout, os.O_WRONLY|os.O_CREAT, 0o0600) os.dup2(fd_out, 1) - fd_err = os.open(stderr, os.O_WRONLY|os.O_CREAT, 0600) + fd_err = os.open(stderr, os.O_WRONLY|os.O_CREAT, 0o0600) os.dup2(fd_err, 2) # Detach task @@ -1375,8 +1326,9 @@ class Notifier: # Write pid if pid_file != False: flags = os.O_WRONLY|os.O_CREAT|os.O_NOFOLLOW|os.O_EXCL - fd_pid = os.open(pid_file, flags, 0600) - os.write(fd_pid, str(os.getpid()) + '\n') + fd_pid = os.open(pid_file, flags, 0o0600) + os.write(fd_pid, bytes(str(os.getpid()) + '\n', + locale.getpreferredencoding())) os.close(fd_pid) # Register unlink function atexit.register(lambda : os.unlink(pid_file)) @@ -1441,9 +1393,12 @@ class Notifier: Close inotify's instance (close its file descriptor). It destroys all existing watches, pending events,... This method is automatically called at the end of loop(). + Afterward it is invalid to access this instance. """ - self._pollobj.unregister(self._fd) - os.close(self._fd) + if self._fd is not None: + self._pollobj.unregister(self._fd) + os.close(self._fd) + self._fd = None self._sys_proc_fun = None @@ -1468,7 +1423,7 @@ class ThreadedNotifier(threading.Thread, Notifier): @type default_proc_fun: instance of ProcessEvent @param read_freq: if read_freq == 0, events are read asap, if read_freq is > 0, this thread sleeps - max(0, read_freq - timeout) seconds. + max(0, read_freq - (timeout / 1000)) seconds. @type read_freq: int @param threshold: File descriptor will be read only if the accumulated size to read becomes >= threshold. If != 0, you likely @@ -1478,8 +1433,9 @@ class ThreadedNotifier(threading.Thread, Notifier): until the amount of events to read is >= threshold. At least with read_freq you might sleep. @type threshold: int - @param timeout: - https://docs.python.org/3/library/select.html#polling-objects + @param timeout: see read_freq above. If provided, it must be set in + milliseconds. See + https://docs.python.org/3/library/select.html#select.poll.poll @type timeout: int """ # Init threading base class @@ -1498,7 +1454,7 @@ class ThreadedNotifier(threading.Thread, Notifier): Stop notifier's loop. Stop notification. Join the thread. """ self._stop_event.set() - os.write(self._pipe[1], 'stop') + os.write(self._pipe[1], b'stop') threading.Thread.join(self) Notifier.stop(self) self._pollobj.unregister(self._pipe[0]) @@ -1699,7 +1655,6 @@ class Watch: class ExcludeFilter: """ ExcludeFilter is an exclusion filter. - """ def __init__(self, arg_lst): """ @@ -1731,16 +1686,13 @@ class ExcludeFilter: def _load_patterns_from_file(self, filename): lst = [] - file_obj = file(filename, 'r') - try: + with open(filename, 'r') as file_obj: for line in file_obj.readlines(): # Trim leading an trailing whitespaces pattern = line.strip() if not pattern or pattern.startswith('#'): continue lst.append(pattern) - finally: - file_obj.close() return lst def _match(self, regex, path): @@ -1764,7 +1716,6 @@ class WatchManagerError(Exception): """ WatchManager Exception. Raised on error encountered on watches operations. - """ def __init__(self, msg, wmd): """ @@ -1851,7 +1802,7 @@ class WatchManager: """ try: del self._wmd[wd] - except KeyError, err: + except KeyError as err: log.error('Cannot delete unknown watch descriptor %s' % str(err)) @property @@ -1868,13 +1819,7 @@ class WatchManager: """ Format path to its internal (stored in watch manager) representation. """ - # Unicode strings are converted back to strings, because it seems - # that inotify_add_watch from ctypes does not work well when - # it receives an ctypes.create_unicode_buffer instance as argument. - # Therefore even wd are indexed with bytes string and not with - # unicode paths. - if isinstance(path, unicode): - path = path.encode(sys.getfilesystemencoding()) + # path must be a unicode string (str) and is just normalized. return os.path.normpath(path) def __add_watch(self, path, mask, proc_fun, auto_add, exclude_filter): @@ -1890,13 +1835,14 @@ class WatchManager: return wd watch = Watch(wd=wd, path=path, mask=mask, proc_fun=proc_fun, auto_add=auto_add, exclude_filter=exclude_filter) + # wd are _always_ indexed with their original unicode paths in wmd. self._wmd[wd] = watch log.debug('New %s', watch) return wd def __glob(self, path, do_glob): if do_glob: - return glob(path) + return glob.iglob(path) else: return [path] @@ -1907,11 +1853,8 @@ class WatchManager: Add watch(s) on the provided |path|(s) with associated |mask| flag value and optionally with a processing |proc_fun| function and recursive flag |rec| set to True. - Ideally |path| components should not be unicode objects. Note that - although unicode paths are accepted there are converted to byte - strings before a watch is put on that path. The encoding used for - converting the unicode object is given by sys.getfilesystemencoding(). - If |path| si already watched it is ignored, but if it is called with + All |path| components _must_ be str (i.e. unicode) objects. + If |path| is already watched it is ignored, but if it is called with option rec=True a watch is put on each one of its not-watched subdirectory. @@ -1945,10 +1888,9 @@ class WatchManager: the class' constructor. @type exclude_filter: callable object @return: dict of paths associated to watch descriptors. A wd value - is positive if the watch was added sucessfully, - otherwise the value is negative. If the path was invalid - or was already watched it is not included into this returned - dictionary. + is positive if the watch was added sucessfully, otherwise + the value is negative. If the path was invalid or was already + watched it is not included into this returned dictionary. @rtype: dict of {str: int} """ ret_ = {} # return {path: wd, ...} @@ -1958,6 +1900,11 @@ class WatchManager: # normalize args as list elements for npath in self.__format_param(path): + # Require that path be a unicode string + if not isinstance(npath, str): + ret_[path] = -3 + continue + # unix pathname pattern expansion for apath in self.__glob(npath, do_glob): # recursively list subdirs according to rec param @@ -2242,7 +2189,6 @@ class WatchManager: "Make watch manager ignoring new events.") - class RawOutputFormat: """ Format string representations. diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/bldcollector/admin.py b/import-layers/yocto-poky/bitbake/lib/toaster/bldcollector/admin.py index c1f85d73d..1f2e07f50 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/bldcollector/admin.py +++ b/import-layers/yocto-poky/bitbake/lib/toaster/bldcollector/admin.py @@ -1,33 +1,33 @@ from django.contrib import admin -from django.contrib.admin.filters import RelatedFieldListFilter -from orm.models import BitbakeVersion, Release, LayerSource, ToasterSetting -from django.forms.widgets import Textarea +from orm.models import BitbakeVersion, Release, ToasterSetting, Layer_Version from django import forms import django.db.models as models -from django.contrib.admin import widgets, helpers - -class LayerSourceAdmin(admin.ModelAdmin): - pass class BitbakeVersionAdmin(admin.ModelAdmin): - # we override the formfield for db URLField because of broken URL validation + # we override the formfield for db URLField + # because of broken URL validation def formfield_for_dbfield(self, db_field, **kwargs): if isinstance(db_field, models.fields.URLField): return forms.fields.CharField() - return super(BitbakeVersionAdmin, self).formfield_for_dbfield(db_field, **kwargs) - + return super(BitbakeVersionAdmin, self).formfield_for_dbfield( + db_field, **kwargs) class ReleaseAdmin(admin.ModelAdmin): pass + class ToasterSettingAdmin(admin.ModelAdmin): pass -admin.site.register(LayerSource, LayerSourceAdmin) + +class LayerVersionsAdmin(admin.ModelAdmin): + pass + +admin.site.register(Layer_Version, LayerVersionsAdmin) admin.site.register(BitbakeVersion, BitbakeVersionAdmin) admin.site.register(Release, ReleaseAdmin) admin.site.register(ToasterSetting, ToasterSettingAdmin) diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/bldcontrol/bbcontroller.py b/import-layers/yocto-poky/bitbake/lib/toaster/bldcontrol/bbcontroller.py index d09ac1787..912f67bf8 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/bldcontrol/bbcontroller.py +++ b/import-layers/yocto-poky/bitbake/lib/toaster/bldcontrol/bbcontroller.py @@ -30,7 +30,6 @@ from bldcontrol.models import BuildEnvironment, BRLayer, BRVariable, BRTarget, B # load Bitbake components path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) sys.path.insert(0, path) -import bb.server.xmlrpc class BitbakeController(object): """ This is the basic class that controlls a bitbake server. @@ -38,6 +37,7 @@ class BitbakeController(object): """ def __init__(self, be): + import bb.server.xmlrpc self.connection = bb.server.xmlrpc._create_server(be.bbaddress, int(be.bbport))[0] @@ -79,7 +79,7 @@ def getBuildEnvironmentController(**kwargs): The return object MUST always be a BuildEnvironmentController. """ - from localhostbecontroller import LocalhostBEController + from bldcontrol.localhostbecontroller import LocalhostBEController be = BuildEnvironment.objects.filter(Q(**kwargs))[0] if be.betype == BuildEnvironment.TYPE_LOCAL: diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/bldcontrol/localhostbecontroller.py b/import-layers/yocto-poky/bitbake/lib/toaster/bldcontrol/localhostbecontroller.py index 7def1f3a1..e5f7c988c 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/bldcontrol/localhostbecontroller.py +++ b/import-layers/yocto-poky/bitbake/lib/toaster/bldcontrol/localhostbecontroller.py @@ -32,7 +32,7 @@ import subprocess from toastermain import settings -from bbcontroller import BuildEnvironmentController, ShellCmdException, BuildSetupException, BitbakeController +from bldcontrol.bbcontroller import BuildEnvironmentController, ShellCmdException, BuildSetupException, BitbakeController import logging logger = logging.getLogger("toaster") @@ -66,16 +66,16 @@ class LocalhostBEController(BuildEnvironmentController): err = "command: %s \n%s" % (command, out) else: err = "command: %s \n%s" % (command, err) - logger.warn("localhostbecontroller: shellcmd error %s" % err) + logger.warning("localhostbecontroller: shellcmd error %s" % err) raise ShellCmdException(err) else: logger.debug("localhostbecontroller: shellcmd success") - return out + return out.decode('utf-8') def getGitCloneDirectory(self, url, branch): """Construct unique clone directory name out of url and branch.""" if branch != "HEAD": - return "_toaster_clones/_%s_%s" % (re.sub('[:/@%]', '_', url), branch) + return "_toaster_clones/_%s_%s" % (re.sub('[:/@+%]', '_', url), branch) # word of attention; this is a localhost-specific issue; only on the localhost we expect to have "HEAD" releases # which _ALWAYS_ means the current poky checkout @@ -89,19 +89,34 @@ class LocalhostBEController(BuildEnvironmentController): """ a word of attention: by convention, the first layer for any build will be poky! """ assert self.be.sourcedir is not None + + layerlist = [] + nongitlayerlist = [] + # set layers in the layersource # 1. get a list of repos with branches, and map dirpaths for each layer gitrepos = {} - gitrepos[(bitbake.giturl, bitbake.commit)] = [] - gitrepos[(bitbake.giturl, bitbake.commit)].append( ("bitbake", bitbake.dirpath) ) + # if we're using a remotely fetched version of bitbake add its git + # details to the list of repos to clone + if bitbake.giturl and bitbake.commit: + gitrepos[(bitbake.giturl, bitbake.commit)] = [] + gitrepos[(bitbake.giturl, bitbake.commit)].append( + ("bitbake", bitbake.dirpath)) for layer in layers: # We don't need to git clone the layer for the CustomImageRecipe # as it's generated by us layer on if needed if CustomImageRecipe.LAYER_NAME in layer.name: continue + + # If we have local layers then we don't need clone them + # For local layers giturl will be empty + if not layer.giturl: + nongitlayerlist.append(layer.layer_version.layer.local_source_dir) + continue + if not (layer.giturl, layer.commit) in gitrepos: gitrepos[(layer.giturl, layer.commit)] = [] gitrepos[(layer.giturl, layer.commit)].append( (layer.name, layer.dirpath) ) @@ -131,19 +146,22 @@ class LocalhostBEController(BuildEnvironmentController): logger.info("Using pre-checked out source for layer %s", cached_layers) - layerlist = [] - - # 3. checkout the repositories for giturl, commit in gitrepos.keys(): localdirname = os.path.join(self.be.sourcedir, self.getGitCloneDirectory(giturl, commit)) logger.debug("localhostbecontroller: giturl %s:%s checking out in current directory %s" % (giturl, commit, localdirname)) - # make sure our directory is a git repository + # see if our directory is a git repository if os.path.exists(localdirname): - localremotes = self._shellcmd("git remote -v", localdirname) - if not giturl in localremotes: - raise BuildSetupException("Existing git repository at %s, but with different remotes ('%s', expected '%s'). Toaster will not continue out of fear of damaging something." % (localdirname, ", ".join(localremotes.split("\n")), giturl)) + try: + localremotes = self._shellcmd("git remote -v", + localdirname) + if not giturl in localremotes and commit != 'HEAD': + raise BuildSetupException("Existing git repository at %s, but with different remotes ('%s', expected '%s'). Toaster will not continue out of fear of damaging something." % (localdirname, ", ".join(localremotes.split("\n")), giturl)) + except ShellCmdException: + # our localdirname might not be a git repository + #- that's fine + pass else: if giturl in cached_layers: logger.debug("localhostbecontroller git-copying %s to %s" % (cached_layers[giturl], localdirname)) @@ -245,6 +263,7 @@ class LocalhostBEController(BuildEnvironmentController): layerlist.append(layerpath) self.islayerset = True + layerlist.extend(nongitlayerlist) return layerlist def readServerLogFile(self): @@ -287,7 +306,7 @@ class LocalhostBEController(BuildEnvironmentController): # run bitbake server from the clone bitbake = os.path.join(self.pokydirname, 'bitbake', 'bin', 'bitbake') - self._shellcmd('bash -c \"source %s %s; BITBAKE_UI="" %s --read %s ' + self._shellcmd('bash -c \"source %s %s; BITBAKE_UI="knotty" %s --read %s ' '--server-only -t xmlrpc -B 0.0.0.0:0\"' % (oe_init, builddir, bitbake, confpath), self.be.sourcedir) @@ -324,7 +343,7 @@ class LocalhostBEController(BuildEnvironmentController): 'bitbake') self._shellcmd(['bash -c \"(TOASTER_BRBE="%s" BBSERVER="0.0.0.0:-1" ' '%s %s -u toasterui --token="" >>%s 2>&1;' - 'BITBAKE_UI="" BBSERVER=0.0.0.0:-1 %s -m)&\"' \ + 'BITBAKE_UI="knotty" BBSERVER=0.0.0.0:-1 %s -m)&\"' \ % (brbe, local_bitbake, bbtargets, log, bitbake)], builddir, nowait=True) diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/bldcontrol/management/commands/checksettings.py b/import-layers/yocto-poky/bitbake/lib/toaster/bldcontrol/management/commands/checksettings.py index 5e70437b2..2ed994f61 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/bldcontrol/management/commands/checksettings.py +++ b/import-layers/yocto-poky/bitbake/lib/toaster/bldcontrol/management/commands/checksettings.py @@ -1,10 +1,15 @@ from django.core.management.base import NoArgsCommand, CommandError from django.db import transaction + +from django.core.management import call_command from bldcontrol.bbcontroller import getBuildEnvironmentController, ShellCmdException from bldcontrol.models import BuildRequest, BuildEnvironment, BRError -from orm.models import ToasterSetting, Build +from orm.models import ToasterSetting, Build, Layer + import os import traceback +import warnings + def DN(path): if path is None: @@ -21,39 +26,6 @@ class Command(NoArgsCommand): super(Command, self).__init__(*args, **kwargs) self.guesspath = DN(DN(DN(DN(DN(DN(DN(__file__))))))) - def _find_first_path_for_file(self, startdirectory, filename, level=0): - if level < 0: - return None - dirs = [] - for i in os.listdir(startdirectory): - j = os.path.join(startdirectory, i) - if os.path.isfile(j): - if i == filename: - return startdirectory - elif os.path.isdir(j): - dirs.append(j) - for j in dirs: - ret = self._find_first_path_for_file(j, filename, level - 1) - if ret is not None: - return ret - return None - - def _recursive_list_directories(self, startdirectory, level=0): - if level < 0: - return [] - dirs = [] - try: - for i in os.listdir(startdirectory): - j = os.path.join(startdirectory, i) - if os.path.isdir(j): - dirs.append(j) - except OSError: - pass - for j in dirs: - dirs = dirs + self._recursive_list_directories(j, level - 1) - return dirs - - def _verify_build_environment(self): # provide a local build env. This will be extended later to include non local if BuildEnvironment.objects.count() == 0: @@ -70,11 +42,10 @@ class Command(NoArgsCommand): return True if len(be.sourcedir) == 0: - print "\n -- Validation: The layers checkout directory must be set." is_changed = _update_sourcedir() if not be.sourcedir.startswith("/"): - print "\n -- Validation: The layers checkout directory must be set to an absolute path." + print("\n -- Validation: The layers checkout directory must be set to an absolute path.") is_changed = _update_sourcedir() if is_changed: @@ -87,38 +58,67 @@ class Command(NoArgsCommand): return True if len(be.builddir) == 0: - print "\n -- Validation: The build directory must be set." is_changed = _update_builddir() if not be.builddir.startswith("/"): - print "\n -- Validation: The build directory must to be set to an absolute path." + print("\n -- Validation: The build directory must to be set to an absolute path.") is_changed = _update_builddir() - if is_changed: - print "\nBuild configuration saved" + print("\nBuild configuration saved") be.save() return True - if be.needs_import: try: - config_file = os.environ.get('TOASTER_CONF') - print "\nImporting file: %s" % config_file - from loadconf import Command as LoadConfigCommand + print("Loading default settings") + call_command("loaddata", "settings") + template_conf = os.environ.get("TEMPLATECONF", "") + + if "poky" in template_conf: + print("Loading poky configuration") + call_command("loaddata", "poky") + else: + print("Loading OE-Core configuration") + call_command("loaddata", "oe-core") + if template_conf: + oe_core_path = os.path.realpath( + template_conf + + "/../") + else: + print("TEMPLATECONF not found. You may have to" + " manually configure layer paths") + oe_core_path = input("Please enter the path of" + " your openembedded-core " + "layer: ") + # Update the layer instances of openemebedded-core + for layer in Layer.objects.filter( + name="openembedded-core", + local_source_dir="OE-CORE-LAYER-DIR"): + layer.local_path = oe_core_path + layer.save() + + # Import the custom fixture if it's present + with warnings.catch_warnings(): + warnings.filterwarnings( + action="ignore", + message="^.*No fixture named.*$") + print("Importing custom settings if present") + call_command("loaddata", "custom") - LoadConfigCommand()._import_layer_config(config_file) # we run lsupdates after config update - print "\nLayer configuration imported. Updating information from the layer sources, please wait.\nYou can re-update any time later by running bitbake/lib/toaster/manage.py lsupdates" - from django.core.management import call_command + print("\nFetching information from the layer index, " + "please wait.\nYou can re-update any time later " + "by running bitbake/lib/toaster/manage.py " + "lsupdates\n") call_command("lsupdates") # we don't look for any other config files return is_changed except Exception as e: - print "Failure while trying to import the toaster config file %s: %s" %\ - (config_file, e) - traceback.print_exc(e) + print("Failure while trying to setup toaster: %s" + % e) + traceback.print_exc() return is_changed diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/bldcontrol/management/commands/loadconf.py b/import-layers/yocto-poky/bitbake/lib/toaster/bldcontrol/management/commands/loadconf.py deleted file mode 100644 index 5022b5940..000000000 --- a/import-layers/yocto-poky/bitbake/lib/toaster/bldcontrol/management/commands/loadconf.py +++ /dev/null @@ -1,183 +0,0 @@ -from django.core.management.base import BaseCommand, CommandError -from orm.models import LayerSource, ToasterSetting, Branch, Layer, Layer_Version -from orm.models import BitbakeVersion, Release, ReleaseDefaultLayer, ReleaseLayerSourcePriority -from django.db import IntegrityError -import os - -from checksettings import DN - -import logging -logger = logging.getLogger("toaster") - -def _reduce_canon_path(path): - components = [] - for c in path.split("/"): - if c == "..": - del components[-1] - elif c == ".": - pass - else: - components.append(c) - if len(components) < 2: - components.append('') - return "/".join(components) - -def _get_id_for_sourcetype(s): - for i in LayerSource.SOURCE_TYPE: - if s == i[1]: - return i[0] - raise Exception("Could not find definition for sourcetype '%s'. Valid source types are %s" % (str(s), ', '.join(map(lambda x: "'%s'" % x[1], LayerSource.SOURCE_TYPE )))) - -class Command(BaseCommand): - help = "Loads a toasterconf.json file in the database" - args = "filepath" - - - - def _import_layer_config(self, filepath): - if not os.path.exists(filepath) or not os.path.isfile(filepath): - raise Exception("Failed to find toaster config file %s ." % filepath) - - import json - data = json.loads(open(filepath, "r").read()) - - # verify config file validity before updating settings - for i in ['bitbake', 'releases', 'defaultrelease', 'config', 'layersources']: - assert i in data - - def _read_git_url_from_local_repository(address): - url = None - # we detect the remote name at runtime - import subprocess - (remote, remote_name) = address.split(":", 1) - cmd = subprocess.Popen("git remote -v", shell=True, cwd = os.path.dirname(filepath), stdout=subprocess.PIPE, stderr = subprocess.PIPE) - (out,err) = cmd.communicate() - if cmd.returncode != 0: - logging.warning("Error while importing layer vcs_url: git error: %s" % err) - for line in out.split("\n"): - try: - (name, path) = line.split("\t", 1) - if name == remote_name: - url = path.split(" ")[0] - break - except ValueError: - pass - if url == None: - logging.warning("Error while looking for remote \"%s\" in \"%s\"" % (remote_name, out)) - return url - - - # import bitbake data - for bvi in data['bitbake']: - bvo, created = BitbakeVersion.objects.get_or_create(name=bvi['name']) - if bvi['giturl'].startswith("remote:"): - bvo.giturl = _read_git_url_from_local_repository(bvi['giturl']) - if bvo.giturl is None: - logger.error("The toaster config file references the local git repo, but Toaster cannot detect it.\nYour local configuration for bitbake version %s is invalid. Make sure that the toasterconf.json file is correct." % bvi['name']) - - if bvo.giturl is None: - bvo.giturl = bvi['giturl'] - bvo.branch = bvi['branch'] - bvo.dirpath = bvi['dirpath'] - bvo.save() - - # set the layer sources - for lsi in data['layersources']: - assert 'sourcetype' in lsi - assert 'apiurl' in lsi - assert 'name' in lsi - assert 'branches' in lsi - - - if _get_id_for_sourcetype(lsi['sourcetype']) == LayerSource.TYPE_LAYERINDEX or lsi['apiurl'].startswith("/"): - apiurl = lsi['apiurl'] - else: - apiurl = _reduce_canon_path(os.path.join(DN(os.path.abspath(filepath)), lsi['apiurl'])) - - assert ((_get_id_for_sourcetype(lsi['sourcetype']) == LayerSource.TYPE_LAYERINDEX) or apiurl.startswith("/")), (lsi['sourcetype'],apiurl) - - try: - ls, created = LayerSource.objects.get_or_create(sourcetype = _get_id_for_sourcetype(lsi['sourcetype']), apiurl = apiurl) - ls.name = lsi['name'] - ls.save() - except IntegrityError as e: - logger.warning("IntegrityError %s \nWhile setting name %s for layer source %s " % (e, lsi['name'], ls)) - - - layerbranches = [] - for branchname in lsi['branches']: - bo, created = Branch.objects.get_or_create(layer_source = ls, name = branchname) - layerbranches.append(bo) - - if 'layers' in lsi: - for layerinfo in lsi['layers']: - lo, created = Layer.objects.get_or_create(layer_source = ls, name = layerinfo['name']) - if layerinfo['local_path'].startswith("/"): - lo.local_path = layerinfo['local_path'] - else: - lo.local_path = _reduce_canon_path(os.path.join(ls.apiurl, layerinfo['local_path'])) - - if not os.path.exists(lo.local_path): - logger.error("Local layer path %s must exists. Are you trying to import a layer that does not exist ? Check your local toasterconf.json" % lo.local_path) - - if layerinfo['vcs_url'].startswith("remote:"): - lo.vcs_url = _read_git_url_from_local_repository(layerinfo['vcs_url']) - if lo.vcs_url is None: - logger.error("The toaster config file references the local git repo, but Toaster cannot detect it.\nYour local configuration for layer %s is invalid. Make sure that the toasterconf.json file is correct." % layerinfo['name']) - - if lo.vcs_url is None: - lo.vcs_url = layerinfo['vcs_url'] - - if 'layer_index_url' in layerinfo: - lo.layer_index_url = layerinfo['layer_index_url'] - lo.save() - - for branch in layerbranches: - lvo, created = Layer_Version.objects.get_or_create(layer_source = ls, - up_branch = branch, - commit = branch.name, - layer = lo) - lvo.dirpath = layerinfo['dirpath'] - lvo.save() - # set releases - for ri in data['releases']: - bvo = BitbakeVersion.objects.get(name = ri['bitbake']) - assert bvo is not None - - ro, created = Release.objects.get_or_create(name = ri['name'], bitbake_version = bvo, branch_name = ri['branch']) - ro.description = ri['description'] - ro.helptext = ri['helptext'] - ro.save() - - # save layer source priority for release - for ls_name in ri['layersourcepriority'].keys(): - rlspo, created = ReleaseLayerSourcePriority.objects.get_or_create(release = ro, layer_source = LayerSource.objects.get(name=ls_name)) - rlspo.priority = ri['layersourcepriority'][ls_name] - rlspo.save() - - for dli in ri['defaultlayers']: - # find layers with the same name - ReleaseDefaultLayer.objects.get_or_create( release = ro, layer_name = dli) - - # set default release - if ToasterSetting.objects.filter(name = "DEFAULT_RELEASE").count() > 0: - ToasterSetting.objects.filter(name = "DEFAULT_RELEASE").update(value = data['defaultrelease']) - else: - ToasterSetting.objects.create(name = "DEFAULT_RELEASE", value = data['defaultrelease']) - - # set default config variables - for configname in data['config']: - if ToasterSetting.objects.filter(name = "DEFCONF_" + configname).count() > 0: - ToasterSetting.objects.filter(name = "DEFCONF_" + configname).update(value = data['config'][configname]) - else: - ToasterSetting.objects.create(name = "DEFCONF_" + configname, value = data['config'][configname]) - - - def handle(self, *args, **options): - if len(args) == 0: - raise CommandError("Need a path to the toasterconf.json file") - filepath = args[0] - self._import_layer_config(filepath) - - - diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/bldcontrol/management/commands/runbuilds.py b/import-layers/yocto-poky/bitbake/lib/toaster/bldcontrol/management/commands/runbuilds.py index 27289be5f..7f7a5a955 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/bldcontrol/management/commands/runbuilds.py +++ b/import-layers/yocto-poky/bitbake/lib/toaster/bldcontrol/management/commands/runbuilds.py @@ -1,31 +1,27 @@ -from django.core.management.base import NoArgsCommand, CommandError +from django.core.management.base import NoArgsCommand from django.db import transaction from django.db.models import Q from bldcontrol.bbcontroller import getBuildEnvironmentController -from bldcontrol.bbcontroller import ShellCmdException, BuildSetupException from bldcontrol.models import BuildRequest, BuildEnvironment from bldcontrol.models import BRError, BRVariable -from orm.models import Build, ToasterSetting, LogMessage, Target +from orm.models import Build, LogMessage, Target -import os import logging -import time -import sys import traceback +import signal logger = logging.getLogger("toaster") class Command(NoArgsCommand): - args = "" - help = "Schedules and executes build requests as possible." - "Does not return (interrupt with Ctrl-C)" - + args = "" + help = "Schedules and executes build requests as possible. "\ + "Does not return (interrupt with Ctrl-C)" @transaction.atomic def _selectBuildEnvironment(self): - bec = getBuildEnvironmentController(lock = BuildEnvironment.LOCK_FREE) + bec = getBuildEnvironmentController(lock=BuildEnvironment.LOCK_FREE) bec.be.lock = BuildEnvironment.LOCK_LOCK bec.be.save() return bec @@ -54,8 +50,8 @@ class Command(NoArgsCommand): logger.debug("runbuilds: No build env") return - logger.debug("runbuilds: starting build %s, environment %s" % \ - (str(br).decode('utf-8'), bec.be)) + logger.info("runbuilds: starting build %s, environment %s" % \ + (br, bec.be)) # let the build request know where it is being executed br.environment = bec.be @@ -68,24 +64,22 @@ class Command(NoArgsCommand): except Exception as e: logger.error("runbuilds: Error launching build %s" % e) - traceback.print_exc(e) + traceback.print_exc() if "[Errno 111] Connection refused" in str(e): # Connection refused, read toaster_server.out errmsg = bec.readServerLogFile() else: errmsg = str(e) - BRError.objects.create(req = br, - errtype = str(type(e)), - errmsg = errmsg, - traceback = traceback.format_exc(e)) + BRError.objects.create(req=br, errtype=str(type(e)), errmsg=errmsg, + traceback=traceback.format_exc()) br.state = BuildRequest.REQ_FAILED br.save() bec.be.lock = BuildEnvironment.LOCK_FREE bec.be.save() def archive(self): - for br in BuildRequest.objects.filter(state = BuildRequest.REQ_ARCHIVE): + for br in BuildRequest.objects.filter(state=BuildRequest.REQ_ARCHIVE): if br.build == None: br.state = BuildRequest.REQ_FAILED else: @@ -102,14 +96,13 @@ class Command(NoArgsCommand): BuildRequest.REQ_COMPLETED, BuildRequest.REQ_CANCELLING]) & Q(lock=BuildEnvironment.LOCK_LOCK) & - Q(updated__lt=timezone.now() - timedelta(seconds = 30)) + Q(updated__lt=timezone.now() - timedelta(seconds=30)) ).update(lock=BuildEnvironment.LOCK_FREE) # update all Builds that were in progress and failed to start - for br in BuildRequest.objects.filter( - state=BuildRequest.REQ_FAILED, - build__outcome=Build.IN_PROGRESS): + for br in BuildRequest.objects.filter(state=BuildRequest.REQ_FAILED, + build__outcome=Build.IN_PROGRESS): # transpose the launch errors in ToasterExceptions br.build.outcome = Build.FAILED for brerror in br.brerror_set.all(): @@ -126,7 +119,7 @@ class Command(NoArgsCommand): # update all BuildRequests without a build created - for br in BuildRequest.objects.filter(build = None): + for br in BuildRequest.objects.filter(build=None): br.build = Build.objects.create(project=br.project, completed_on=br.updated, started_on=br.created) @@ -151,29 +144,34 @@ class Command(NoArgsCommand): # Make sure the LOCK is removed for builds which have been fully # cancelled - for br in BuildRequest.objects.filter( - Q(build__outcome=Build.CANCELLED) & - Q(state=BuildRequest.REQ_CANCELLING) & - ~Q(environment=None)): + for br in BuildRequest.objects.filter(\ + Q(build__outcome=Build.CANCELLED) & + Q(state=BuildRequest.REQ_CANCELLING) & + ~Q(environment=None)): br.environment.lock = BuildEnvironment.LOCK_FREE br.environment.save() + def runbuild(self): + try: + self.cleanup() + except Exception as e: + logger.warn("runbuilds: cleanup exception %s" % str(e)) - def handle_noargs(self, **options): - while True: - try: - self.cleanup() - except Exception as e: - logger.warn("runbuilds: cleanup exception %s" % str(e)) + try: + self.archive() + except Exception as e: + logger.warn("runbuilds: archive exception %s" % str(e)) - try: - self.archive() - except Exception as e: - logger.warn("runbuilds: archive exception %s" % str(e)) + try: + self.schedule() + except Exception as e: + logger.warn("runbuilds: schedule exception %s" % str(e)) - try: - self.schedule() - except Exception as e: - logger.warn("runbuilds: schedule exception %s" % str(e)) + def handle_noargs(self, **options): + self.runbuild() + + signal.signal(signal.SIGUSR1, lambda sig, frame: None) - time.sleep(1) + while True: + signal.pause() + self.runbuild() diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/bldcontrol/migrations/0004_auto_20160523_1446.py b/import-layers/yocto-poky/bitbake/lib/toaster/bldcontrol/migrations/0004_auto_20160523_1446.py new file mode 100644 index 000000000..3d9062954 --- /dev/null +++ b/import-layers/yocto-poky/bitbake/lib/toaster/bldcontrol/migrations/0004_auto_20160523_1446.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('bldcontrol', '0003_add_cancelling_state'), + ] + + operations = [ + migrations.AlterField( + model_name='buildenvironment', + name='bbstate', + field=models.IntegerField(default=0, choices=[(0, 'stopped'), (1, 'started')]), + ), + migrations.AlterField( + model_name='buildenvironment', + name='betype', + field=models.IntegerField(choices=[(0, 'local')]), + ), + migrations.AlterField( + model_name='buildenvironment', + name='lock', + field=models.IntegerField(default=0, choices=[(0, 'free'), (1, 'lock'), (2, 'running')]), + ), + migrations.AlterField( + model_name='buildrequest', + name='state', + field=models.IntegerField(default=0, choices=[(0, 'created'), (1, 'queued'), (2, 'in progress'), (3, 'completed'), (4, 'failed'), (5, 'deleted'), (6, 'cancelling'), (7, 'archive')]), + ), + ] diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/bldcontrol/migrations/0005_reorder_buildrequest_states.py b/import-layers/yocto-poky/bitbake/lib/toaster/bldcontrol/migrations/0005_reorder_buildrequest_states.py new file mode 100644 index 000000000..4bb951776 --- /dev/null +++ b/import-layers/yocto-poky/bitbake/lib/toaster/bldcontrol/migrations/0005_reorder_buildrequest_states.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('bldcontrol', '0004_auto_20160523_1446'), + ] + + operations = [ + migrations.AlterField( + model_name='buildrequest', + name='state', + field=models.IntegerField(choices=[(0, 'created'), (1, 'queued'), (2, 'in progress'), (3, 'failed'), (4, 'deleted'), (5, 'cancelling'), (6, 'completed'), (7, 'archive')], default=0), + ), + ] diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/bldcontrol/migrations/0006_brlayer_local_source_dir.py b/import-layers/yocto-poky/bitbake/lib/toaster/bldcontrol/migrations/0006_brlayer_local_source_dir.py new file mode 100644 index 000000000..2460002f0 --- /dev/null +++ b/import-layers/yocto-poky/bitbake/lib/toaster/bldcontrol/migrations/0006_brlayer_local_source_dir.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('bldcontrol', '0005_reorder_buildrequest_states'), + ] + + operations = [ + migrations.AddField( + model_name='brlayer', + name='local_source_dir', + field=models.CharField(max_length=254, null=True), + ), + ] diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/bldcontrol/migrations/0007_brlayers_optional_gitinfo.py b/import-layers/yocto-poky/bitbake/lib/toaster/bldcontrol/migrations/0007_brlayers_optional_gitinfo.py new file mode 100644 index 000000000..4be42a4cf --- /dev/null +++ b/import-layers/yocto-poky/bitbake/lib/toaster/bldcontrol/migrations/0007_brlayers_optional_gitinfo.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('bldcontrol', '0006_brlayer_local_source_dir'), + ] + + operations = [ + migrations.AlterField( + model_name='brlayer', + name='commit', + field=models.CharField(max_length=254, null=True), + ), + migrations.AlterField( + model_name='brlayer', + name='dirpath', + field=models.CharField(max_length=254, null=True), + ), + migrations.AlterField( + model_name='brlayer', + name='giturl', + field=models.CharField(max_length=254, null=True), + ), + ] diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/bldcontrol/models.py b/import-layers/yocto-poky/bitbake/lib/toaster/bldcontrol/models.py index cb49a58c4..409614b9e 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/bldcontrol/models.py +++ b/import-layers/yocto-poky/bitbake/lib/toaster/bldcontrol/models.py @@ -1,7 +1,7 @@ from __future__ import unicode_literals from django.db import models from django.core.validators import MaxValueValidator, MinValueValidator -from django.utils.encoding import force_bytes +from django.utils.encoding import force_text from orm.models import Project, ProjectLayer, ProjectVariable, ProjectTarget, Build, Layer_Version import logging @@ -63,20 +63,20 @@ class BuildRequest(models.Model): REQ_CREATED = 0 REQ_QUEUED = 1 REQ_INPROGRESS = 2 - REQ_COMPLETED = 3 - REQ_FAILED = 4 - REQ_DELETED = 5 - REQ_CANCELLING = 6 + REQ_FAILED = 3 + REQ_DELETED = 4 + REQ_CANCELLING = 5 + REQ_COMPLETED = 6 REQ_ARCHIVE = 7 REQUEST_STATE = ( (REQ_CREATED, "created"), (REQ_QUEUED, "queued"), (REQ_INPROGRESS, "in progress"), - (REQ_COMPLETED, "completed"), (REQ_FAILED, "failed"), (REQ_DELETED, "deleted"), (REQ_CANCELLING, "cancelling"), + (REQ_COMPLETED, "completed"), (REQ_ARCHIVE, "archive"), ) @@ -91,18 +91,18 @@ class BuildRequest(models.Model): def __init__(self, *args, **kwargs): super(BuildRequest, self).__init__(*args, **kwargs) - # Save the old state incase it's about to be modified + # Save the old state in case it's about to be modified self.old_state = self.state def save(self, *args, **kwargs): # Check that the state we're trying to set is not going backwards # e.g. from REQ_FAILED to REQ_INPROGRESS if self.old_state != self.state and self.old_state > self.state: - logger.warn("Invalid state change requested: " - "Cannot go from %s to %s - ignoring request" % - (BuildRequest.REQUEST_STATE[self.old_state][1], - BuildRequest.REQUEST_STATE[self.state][1]) - ) + logger.warning("Invalid state change requested: " + "Cannot go from %s to %s - ignoring request" % + (BuildRequest.REQUEST_STATE[self.old_state][1], + BuildRequest.REQUEST_STATE[self.state][1]) + ) # Set property back to the old value self.state = self.old_state return @@ -121,17 +121,19 @@ class BuildRequest(models.Model): return self.brvariable_set.get(name="MACHINE").value def __str__(self): - return force_bytes('%s %s' % (self.project, self.get_state_display())) + return force_text('%s %s' % (self.project, self.get_state_display())) # These tables specify the settings for running an actual build. # They MUST be kept in sync with the tables in orm.models.Project* + class BRLayer(models.Model): - req = models.ForeignKey(BuildRequest) - name = models.CharField(max_length = 100) - giturl = models.CharField(max_length = 254) - commit = models.CharField(max_length = 254) - dirpath = models.CharField(max_length = 254) + req = models.ForeignKey(BuildRequest) + name = models.CharField(max_length=100) + giturl = models.CharField(max_length=254, null=True) + local_source_dir = models.CharField(max_length=254, null=True) + commit = models.CharField(max_length=254, null=True) + dirpath = models.CharField(max_length=254, null=True) layer_version = models.ForeignKey(Layer_Version, null=True) class BRBitbake(models.Model): diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/bldcontrol/tests.py b/import-layers/yocto-poky/bitbake/lib/toaster/bldcontrol/tests.py index f20cc7d4b..475ac0a16 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/bldcontrol/tests.py +++ b/import-layers/yocto-poky/bitbake/lib/toaster/bldcontrol/tests.py @@ -53,7 +53,7 @@ class BEControllerTests(object): # setting layers, skip any layer info bc.setLayers(BITBAKE_LAYER, POKY_LAYERS) except NotImplementedError: - print "Test skipped due to command not implemented yet" + print("Test skipped due to command not implemented yet") return True # We are ok with the exception as we're handling the git already exists except BuildSetupException: @@ -79,7 +79,7 @@ class BEControllerTests(object): # setting layers, skip any layer info layerSet = bc.setLayers(BITBAKE_LAYER, POKY_LAYERS) except NotImplementedError: - print "Test skipped due to command not implemented yet" + print("Test skipped due to command not implemented yet") return True # We are ok with the exception as we're handling the git already exists except BuildSetupException: @@ -139,22 +139,3 @@ class RunBuildsCommandTests(TestCase): self.assertTrue(br.state == BuildRequest.REQ_INPROGRESS, "Request is not updated") # no more selections possible here self.assertRaises(IndexError, command._selectBuildRequest) - - -class UtilityTests(TestCase): - def test_reduce_path(self): - from bldcontrol.management.commands.loadconf import _reduce_canon_path, _get_id_for_sourcetype - - self.assertTrue( _reduce_canon_path("/") == "/") - self.assertTrue( _reduce_canon_path("/home/..") == "/") - self.assertTrue( _reduce_canon_path("/home/../ana") == "/ana") - self.assertTrue( _reduce_canon_path("/home/../ana/..") == "/") - self.assertTrue( _reduce_canon_path("/home/ana/mihai/../maria") == "/home/ana/maria") - - def test_get_id_for_sorucetype(self): - from bldcontrol.management.commands.loadconf import _reduce_canon_path, _get_id_for_sourcetype - self.assertTrue( _get_id_for_sourcetype("layerindex") == 1) - self.assertTrue( _get_id_for_sourcetype("local") == 0) - self.assertTrue( _get_id_for_sourcetype("imported") == 2) - with self.assertRaises(Exception): - _get_id_for_sourcetype("unknown") diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/config.py b/import-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/config.py index 40d45f3b7..87b427cc3 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/config.py +++ b/import-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/config.py @@ -74,7 +74,7 @@ BACKLOGFILE = os.path.join(os.path.dirname(__file__), "backlog.txt") # task states def enum(*sequential, **named): enums = dict(zip(sequential, range(len(sequential))), **named) - reverse = dict((value, key) for key, value in enums.iteritems()) + reverse = dict((value, key) for key, value in enums.items()) enums['reverse_mapping'] = reverse return type('Enum', (), enums) diff --git a/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/hig/__init__.py b/import-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/log/.create index e69de29bb..e69de29bb 100644 --- a/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/hig/__init__.py +++ b/import-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/log/.create diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/runner.py b/import-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/runner.py index bed665196..d01386acf 100755 --- a/import-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/runner.py +++ b/import-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/runner.py @@ -146,7 +146,7 @@ def execute_tests(dir_under_test, testname): except Exception as exc: import traceback - config.logger.error("Exception while running test. Tracedump: \n%s", traceback.format_exc(exc)) + config.logger.error("Exception while running test. Tracedump: \n%s", traceback.format_exc()) finally: os.chdir(crt_dir) return len(result.failures) @@ -211,7 +211,7 @@ def main(): except ShellCmdException as exc: import traceback - config.logger.error("Error while setting up testing. Traceback: \n%s", traceback.format_exc(exc)) + config.logger.error("Error while setting up testing. Traceback: \n%s", traceback.format_exc()) finally: if need_cleanup and testdir is not None: clean_up(testdir) diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/shellutils.py b/import-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/shellutils.py index c2012edf8..ce64c0634 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/shellutils.py +++ b/import-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/shellutils.py @@ -133,7 +133,7 @@ def run_shell_cmd(command, cwd=None): err = "command: %s \n%s" % (command, out) else: err = "command: %s \n%s" % (command, err) - config.logger.warn("_shellcmd: error \n%s\n%s", out, err) + config.logger.warning("_shellcmd: error \n%s\n%s", out, err) raise ShellCmdException(err) else: #config.logger.debug("localhostbecontroller: shellcmd success\n%s" % out) diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/toasteruitest/run_toastertests.py b/import-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/toasteruitest/run_toastertests.py index 754636f0f..8ca45a813 100755 --- a/import-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/toasteruitest/run_toastertests.py +++ b/import-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/toasteruitest/run_toastertests.py @@ -74,7 +74,7 @@ def get_tests_from_cfg(suite=None): try: tests_from_cfg = eval(config.get('toaster_test_' + target_suite, 'test_cases')) except: - print 'Failed to get test cases from cfg file. Make sure the format is correct.' + print('Failed to get test cases from cfg file. Make sure the format is correct.') return None prefix = 'toaster_automation_test.toaster_cases.test_' @@ -100,7 +100,7 @@ def main(): testslist = get_tests_from_cfg() if not testslist: - print 'Failed to get test cases.' + print('Failed to get test cases.') exit(1) suite = unittest.TestSuite() diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/toasteruitest/toaster_automation_test.py b/import-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/toasteruitest/toaster_automation_test.py index d8f838aea..1a786fa02 100755 --- a/import-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/toasteruitest/toaster_automation_test.py +++ b/import-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/toasteruitest/toaster_automation_test.py @@ -61,7 +61,7 @@ def get_log_root_dir(): break if number == (max_depth - 1): - print 'No log dir found. Please check' + print('No log dir found. Please check') raise Exception return log_root_dir @@ -152,7 +152,7 @@ def is_list_sequenced(testlist): return (sorted(list_number) == list_number) else: - print 'Unrecognized list type, please check' + print('Unrecognized list type, please check') return False @@ -201,7 +201,7 @@ def is_list_inverted(testlist): return (sorted(list_number, reverse = True) == list_number) else: - print 'Unrecognized list type, please check' + print('Unrecognized list type, please check') return False def replace_file_content(filename, item, option): @@ -361,7 +361,7 @@ class toaster_cases_base(unittest.TestCase): def setup_browser(self, *browser_path): self.browser = eval(self.parser.get('toaster_test_' + self.target_suite, 'test_browser')) - print self.browser + print(self.browser) if self.browser == "firefox": driver = webdriver.Firefox() elif self.browser == "chrome": @@ -370,7 +370,7 @@ class toaster_cases_base(unittest.TestCase): driver = webdriver.Ie() else: driver = None - print "unrecognized browser type, please check" + print("unrecognized browser type, please check") self.driver = driver self.driver.implicitly_wait(30) return self.driver @@ -440,8 +440,8 @@ class toaster_cases_base(unittest.TestCase): try: table_element = self.get_table_element(table_id) element = table_element.find_element_by_xpath("//*[text()='" + text_string + "']") - except NoSuchElementException, e: - print 'no element found' + except NoSuchElementException as e: + print('no element found') raise return element @@ -454,8 +454,8 @@ class toaster_cases_base(unittest.TestCase): try: table_element = self.get_table_element(table_id) element = table_element.find_element_by_link_text(link_text) - except NoSuchElementException, e: - print 'no element found' + except NoSuchElementException as e: + print('no element found') raise return element @@ -467,8 +467,8 @@ class toaster_cases_base(unittest.TestCase): try: table_element = self.get_table_element(table_id) element_list = table_element.find_elements_by_link_text(link_text) - except NoSuchElementException, e: - print 'no element found' + except NoSuchElementException as e: + print('no element found') raise return element_list @@ -481,8 +481,8 @@ class toaster_cases_base(unittest.TestCase): table_element = self.get_table_element(table_id) element = table_element.find_element_by_partial_link_text(link_text) return element - except NoSuchElementException, e: - print 'no element found' + except NoSuchElementException as e: + print('no element found') raise @@ -494,8 +494,8 @@ class toaster_cases_base(unittest.TestCase): table_element = self.get_table_element(table_id) element_list = table_element.find_elements_by_partial_link_text(link_text) return element_list - except NoSuchElementException, e: - print 'no element found' + except NoSuchElementException as e: + print('no element found') raise @@ -506,8 +506,8 @@ class toaster_cases_base(unittest.TestCase): try: table_element = self.get_table_element(table_id) element = table_element.find_element_by_xpath(xpath) - except NoSuchElementException, e: - print 'no element found' + except NoSuchElementException as e: + print('no element found') raise return element @@ -519,8 +519,8 @@ class toaster_cases_base(unittest.TestCase): try: table_element = self.get_table_element(table_id) element_list = table_element.find_elements_by_xpath(xpath) - except NoSuchElementException, e: - print 'no elements found' + except NoSuchElementException as e: + print('no elements found') raise return element_list @@ -570,7 +570,7 @@ class toaster_cases_base(unittest.TestCase): element_xpath = "//*[@id='" + table_id + "']" try: element = self.driver.find_element_by_xpath(element_xpath) - except NoSuchElementException, e: + except NoSuchElementException as e: raise return element row = coordinate[0] @@ -580,7 +580,7 @@ class toaster_cases_base(unittest.TestCase): element_xpath = "//*[@id='" + table_id + "']/tbody/tr[" + str(row) + "]" try: element = self.driver.find_element_by_xpath(element_xpath) - except NoSuchElementException, e: + except NoSuchElementException as e: return False return element #now we are looking for an element with specified X and Y @@ -589,7 +589,7 @@ class toaster_cases_base(unittest.TestCase): element_xpath = "//*[@id='" + table_id + "']/tbody/tr[" + str(row) + "]/td[" + str(column) + "]" try: element = self.driver.find_element_by_xpath(element_xpath) - except NoSuchElementException, e: + except NoSuchElementException as e: return False return element @@ -607,7 +607,7 @@ class toaster_cases_base(unittest.TestCase): column = column + 1 print("row_content=",row_content) Lists.extend(row_content) - print Lists[row-1][0] + print(Lists[row-1][0]) row = row + 1 return Lists @@ -617,7 +617,7 @@ class toaster_cases_base(unittest.TestCase): def is_text_present (self, patterns): for pattern in patterns: if str(pattern) not in self.driver.page_source: - print 'Text "'+pattern+'" is missing' + print('Text "'+pattern+'" is missing') return False return True @@ -625,15 +625,15 @@ class toaster_cases_base(unittest.TestCase): def is_element_present(self, how, what): try: self.driver.find_element(how, what) - except NoSuchElementException, e: - print 'Could not find element '+str(what)+' by ' + str(how) + except NoSuchElementException as e: + print('Could not find element '+str(what)+' by ' + str(how)) return False return True def is_alert_present(self): try: self.driver.switch_to_alert() - except NoAlertPresentException, e: return False + except NoAlertPresentException as e: return False return True @@ -658,7 +658,7 @@ class toaster_cases_base(unittest.TestCase): try: caseno = int(caseno_str) except ValueError: - print "get case number error! please check if func name is test_xxx" + print("get case number error! please check if func name is test_xxx") return False return caseno @@ -706,7 +706,7 @@ class toaster_cases(toaster_cases_base): for key in table_head_dict: try: self.driver.find_element_by_link_text(key).click() - except Exception, e: + except Exception as e: self.log.error("%s cannot be found on page" % key) raise column_list = self.get_table_column_text("class", table_head_dict[key]) @@ -736,7 +736,7 @@ class toaster_cases(toaster_cases_base): patterns = ["minimal", "sato"] for pattern in patterns: ori_target_column_texts = self.get_table_column_text("class", "target") - print ori_target_column_texts + print(ori_target_column_texts) self.driver.find_element_by_id("search").clear() self.driver.find_element_by_id("search").send_keys(pattern) self.driver.find_element_by_id("search-button").click() @@ -771,7 +771,7 @@ class toaster_cases(toaster_cases_base): temp_element = self.find_element_by_text_in_table('otable', item) # this is how we find "filter icon" in the same level as temp_element(where "a" means clickable, "i" means icon) self.assertTrue(temp_element.find_element_by_xpath("..//*/a/i[@class='icon-filter filtered']")) - except Exception,e: + except Exception as e: self.assertFalse(True, msg=(" %s cannot be found! %s" % (item, e))) raise # step 5-6 @@ -812,7 +812,7 @@ class toaster_cases(toaster_cases_base): self.table_name = 'otable' # This is how we find the "default" rows-number! rows_displayed = int(Select(self.driver.find_element_by_css_selector("select.pagesize")).first_selected_option.text) - print rows_displayed + print(rows_displayed) self.assertTrue(self.get_table_element(self.table_name, rows_displayed), msg=("not enough rows displayed")) self.assertFalse(self.get_table_element(self.table_name, rows_displayed + 1), \ msg=("more rows displayed than expected")) @@ -888,7 +888,7 @@ class toaster_cases(toaster_cases_base): temp_element.find_element_by_xpath("..//*[@class='icon-filter filtered']").click() avail_options = self.driver.find_elements_by_xpath("//*[@id='" + filter_dict[key] + "']//*[@name='filter'][not(@disabled)]") except: - print "in exception" + print("in exception") self.find_element_by_text("Show all tasks").click() # self.driver.find_element_by_xpath("//*[@id='searchform']/button[2]").click() temp_element = self.find_element_by_link_text_in_table(self.table_name, key) @@ -899,9 +899,9 @@ class toaster_cases(toaster_cases_base): for item in ['order', 'task_name', 'executed', 'outcome', 'recipe_name', 'recipe_version']: try: self.find_element_by_xpath_in_table(self.table_name, "./tbody/tr[1]/*[@class='" + item + "']/a").click() - except NoSuchElementException, e: + except NoSuchElementException as e: # let it go... - print 'no item in the colum' + item + print('no item in the colum' + item) # insert screen shot here self.save_screenshot(screenshot_type='selenium', append_name='step11') self.driver.back() @@ -992,13 +992,13 @@ class toaster_cases(toaster_cases_base): self.table_name = 'otable' # This is how we find the "default" rows-number! rows_displayed = int(Select(self.driver.find_element_by_css_selector("select.pagesize")).first_selected_option.text) - print rows_displayed + print(rows_displayed) self.assertTrue(self.get_table_element(self.table_name, rows_displayed)) self.assertFalse(self.get_table_element(self.table_name, rows_displayed + 1)) # Check the default table is sorted by Recipe tasks_column_count = len(self.driver.find_elements_by_xpath("/html/body/div[2]/div/div[2]/div[2]/table/tbody/tr/td[1]")) - print tasks_column_count + print(tasks_column_count) default_column_list = self.get_table_column_text_by_column_number(self.table_name, 1) #print default_column_list @@ -1095,7 +1095,7 @@ class toaster_cases(toaster_cases_base): # Bug 5919 for key in table_head_dict: - print key + print(key) self.find_element_by_link_text_in_table(self.table_name, key).click() self.driver.find_element_by_id("edit-columns-button").click() self.driver.find_element_by_id(table_head_dict[key]).click() @@ -1167,7 +1167,7 @@ class toaster_cases(toaster_cases_base): check_list = ['Dependencies', 'Layer branch', 'Layer commit', 'Reverse dependencies'] head_list = self.get_table_head_text('otable') time.sleep(2) - print head_list + print(head_list) for item in check_list: self.assertTrue(item in head_list, msg=("item %s not in head row" % item)) # un-check 'em all @@ -1226,8 +1226,8 @@ class toaster_cases(toaster_cases_base): tasks_row_count = len(driver.find_elements_by_xpath("//*[@id='"+self.table_name+"']/table/tbody/tr/td[1]")) tasks_column_count = len(driver.find_elements_by_xpath("//*[@id='"+self.table_name+"']/table/tbody/tr[1]/td")) - print 'rows: '+str(tasks_row_count) - print 'columns: '+str(tasks_column_count) + print('rows: '+str(tasks_row_count)) + print('columns: '+str(tasks_column_count)) Tasks_column = self.get_table_column_text_by_column_number(self.table_name, 2) print ("Tasks_column=", Tasks_column) @@ -1253,9 +1253,9 @@ class toaster_cases(toaster_cases_base): driver.find_element_by_partial_link_text("Packages (").click() packages_name = driver.find_element_by_partial_link_text("Packages (").text - print packages_name + print(packages_name) packages_num = int(filter(str.isdigit, repr(packages_name))) - print packages_num + print(packages_num) #switch the table to show more than 10 rows at a time self.driver.find_element_by_xpath("//*[@id='packages-built']/div[1]/div/select").click() @@ -1263,7 +1263,7 @@ class toaster_cases(toaster_cases_base): self.driver.find_element_by_xpath("//*[@id='packages-built']/div[1]/div/select").send_keys(Keys.ENTER) packages_row_count = len(driver.find_elements_by_xpath("//*[@id='otable']/tbody/tr/td[1]")) - print packages_row_count + print(packages_row_count) if packages_num != packages_row_count: print ("Error! The packages number is not correct") @@ -1272,20 +1272,20 @@ class toaster_cases(toaster_cases_base): driver.find_element_by_partial_link_text("Build dependencies (").click() depends_name = driver.find_element_by_partial_link_text("Build dependencies (").text - print depends_name - depends_num = int(filter(str.isdigit, repr(depends_name))) - print depends_num + print(depends_name) + depends_num = int(list(filter(str.isdigit, repr(depends_name)))) + print(depends_num) if depends_num == 0: depends_message = repr(driver.find_element_by_css_selector("div.alert.alert-info").text) - print depends_message + print(depends_message) if depends_message.find("has no build dependencies.") < 0: print ("Error! The message isn't expected.") else: print ("The message is expected") else: depends_row_count = len(driver.find_elements_by_xpath("//*[@id='dependencies']/table/tbody/tr/td[1]")) - print depends_row_count + print(depends_row_count) if depends_num != depends_row_count: print ("Error! The dependent packages number is not correct") else: @@ -1293,13 +1293,13 @@ class toaster_cases(toaster_cases_base): driver.find_element_by_partial_link_text("Reverse build dependencies (").click() rdepends_name = driver.find_element_by_partial_link_text("Reverse build dependencies (").text - print rdepends_name + print(rdepends_name) rdepends_num = int(filter(str.isdigit, repr(rdepends_name))) - print rdepends_num + print(rdepends_num) if rdepends_num == 0: rdepends_message = repr(driver.find_element_by_css_selector("#brought-in-by > div.alert.alert-info").text) - print rdepends_message + print(rdepends_message) if rdepends_message.find("has no reverse build dependencies.") < 0: print ("Error! The message isn't expected.") else: @@ -1319,8 +1319,8 @@ class toaster_cases(toaster_cases_base): native_tasks_row_count = len(driver.find_elements_by_xpath("//*[@id='information']/table/tbody/tr/td[1]")) native_tasks_column_count = len(driver.find_elements_by_xpath("//*[@id='information']/table/tbody/tr[1]/td")) - print native_tasks_row_count - print native_tasks_column_count + print(native_tasks_row_count) + print(native_tasks_column_count) Native_Tasks_column = self.get_table_column_text_by_column_number(self.table_name, 2) print ("Native_Tasks_column=", Native_Tasks_column) @@ -1346,15 +1346,15 @@ class toaster_cases(toaster_cases_base): driver.find_element_by_partial_link_text("Packages (").click() native_packages_name = driver.find_element_by_partial_link_text("Packages (").text - print native_packages_name + print(native_packages_name) native_packages_num = int(filter(str.isdigit, repr(native_packages_name))) - print native_packages_num + print(native_packages_num) if native_packages_num != 0: print ("Error! Native task shouldn't have any packages.") else: native_package_message = repr(driver.find_element_by_css_selector("#packages-built > div.alert.alert-info").text) - print native_package_message + print(native_package_message) if native_package_message.find("does not build any packages.") < 0: print ("Error! The message for native task isn't expected.") else: @@ -1362,12 +1362,12 @@ class toaster_cases(toaster_cases_base): driver.find_element_by_partial_link_text("Build dependencies (").click() native_depends_name = driver.find_element_by_partial_link_text("Build dependencies (").text - print native_depends_name + print(native_depends_name) native_depends_num = int(filter(str.isdigit, repr(native_depends_name))) - print native_depends_num + print(native_depends_num) native_depends_row_count = len(driver.find_elements_by_xpath("//*[@id='dependencies']/table/tbody/tr/td[1]")) - print native_depends_row_count + print(native_depends_row_count) if native_depends_num != native_depends_row_count: print ("Error! The dependent packages number is not correct") @@ -1376,12 +1376,12 @@ class toaster_cases(toaster_cases_base): driver.find_element_by_partial_link_text("Reverse build dependencies (").click() native_rdepends_name = driver.find_element_by_partial_link_text("Reverse build dependencies (").text - print native_rdepends_name + print(native_rdepends_name) native_rdepends_num = int(filter(str.isdigit, repr(native_rdepends_name))) - print native_rdepends_num + print(native_rdepends_num) native_rdepends_row_count = len(driver.find_elements_by_xpath("//*[@id='brought-in-by']/table/tbody/tr/td[1]")) - print native_rdepends_row_count + print(native_rdepends_row_count) if native_rdepends_num != native_rdepends_row_count: print ("Error! The reverse dependent packages number is not correct") @@ -1413,8 +1413,8 @@ class toaster_cases(toaster_cases_base): # step 5 self.driver.find_element_by_css_selector("i.icon-remove").click() head_list = self.get_table_head_text('otable') - print head_list - print len(head_list) + print(head_list) + print(len(head_list)) self.assertTrue(head_list == ['Variable', 'Value', 'Set in file', 'Description'], \ msg=("head row contents wrong")) # step 8 @@ -1830,7 +1830,7 @@ class toaster_cases(toaster_cases_base): try: self.driver.find_element_by_partial_link_text("Packages included") self.driver.find_element_by_partial_link_text("Directory structure") - except Exception,e: + except Exception as e: self.log.error(e) self.assertFalse(True) # step 4 @@ -2014,13 +2014,13 @@ class toaster_cases(toaster_cases_base): for i in range(0,4): data[i] = data[i][0] data.sort() - print data + print(data) json_parse = json.loads(open('toasterconf.json').read()) json_data = [] for i in range (0,4): json_data.append(json_parse['releases'][i]['name']) json_data.sort() - print json_data + print(json_data) self.failUnless(data == json_data) ############## @@ -2036,11 +2036,11 @@ class toaster_cases(toaster_cases_base): data = cursor.fetchall() for i in range(0,6): data[i] = data[i][0] - print data + print(data) json_parse = json.loads(open('toasterconf.json').read()) json_data=json_parse['config'] json_data = json_data.values() - print json_data + print(json_data) self.failUnless(data == json_data) @@ -2057,12 +2057,12 @@ class toaster_cases(toaster_cases_base): data = cursor.fetchall() for i in range(0,3): data[i] = data[i][0] - print data + print(data) json_parse = json.loads(open('toasterconf.json').read()) json_data = [] for i in range(0,3): json_data.append(json_parse['layersources'][i]['name']) - print json_data + print(json_data) self.failUnless(set(data) == set(json_data)) ############## @@ -2077,10 +2077,10 @@ class toaster_cases(toaster_cases_base): cursor.execute(query) data = cursor.fetchall() data = data[0][0] - print data + print(data) json_parse = json.loads(open('toasterconf.json').read()) json_data = json_parse['defaultrelease'] - print json_data + print(json_data) self.failUnless(set(data) == set(json_data)) ############## @@ -2090,7 +2090,7 @@ class toaster_cases(toaster_cases_base): self.case_no = self.get_case_number() self.log.info(' CASE %s log: ' % str(self.case_no)) - print 'Checking branches for "Local Yocto Project"' + print('Checking branches for "Local Yocto Project"') con=sqlite.connect('toaster.sqlite') cursor = con.cursor() query = "select name from orm_branch where layer_source_id=1;" @@ -2102,15 +2102,15 @@ class toaster_cases(toaster_cases_base): data[i] = data[i][0] except: pass - print data + print(data) json_parse = json.loads(open('toasterconf.json').read()) json_location = json_parse['layersources'][0]['name'] - print json_location + print(json_location) json_data = json_parse['layersources'][0]['branches'] - print json_data + print(json_data) self.failUnless(set(data) == set(json_data)) - print 'Checking branches for "OpenEmbedded"' + print('Checking branches for "OpenEmbedded"') con=sqlite.connect('toaster.sqlite') cursor = con.cursor() query = "select name from orm_branch where layer_source_id=3;" @@ -2119,15 +2119,15 @@ class toaster_cases(toaster_cases_base): lenght = len(data) for i in range(0,lenght): data[i] = data[i][0] - print data + print(data) json_parse = json.loads(open('toasterconf.json').read()) json_location = json_parse['layersources'][1]['name'] - print json_location + print(json_location) json_data = json_parse['layersources'][1]['branches'] - print json_data + print(json_data) self.failUnless(set(data) == set(json_data)) - print 'Checking branches for "Imported layers"' + print('Checking branches for "Imported layers"') con=sqlite.connect('toaster.sqlite') cursor = con.cursor() query = "select name from orm_branch where layer_source_id=2;" @@ -2136,12 +2136,12 @@ class toaster_cases(toaster_cases_base): lenght = len(data) for i in range(0,lenght): data[i] = data[i][0] - print data + print(data) json_parse = json.loads(open('toasterconf.json').read()) json_location = json_parse['layersources'][2]['name'] - print json_location + print(json_location) json_data = json_parse['layersources'][2]['branches'] - print json_data + print(json_data) self.failUnless(set(data) == set(json_data)) @@ -2158,12 +2158,12 @@ class toaster_cases(toaster_cases_base): data = cursor.fetchall() for i in range(0,4): data[i] = data[i][0] - print data + print(data) json_parse = json.loads(open('toasterconf.json').read()) json_data = [] for i in range(0,4): json_data.append(json_parse['bitbake'][i]['name']) - print json_data + print(json_data) self.failUnless(set(data) == set(json_data)) ############## @@ -2182,7 +2182,7 @@ class toaster_cases(toaster_cases_base): query = "select count(name) from orm_project where name = 'new-test-project';" cursor.execute(query) data = cursor.fetchone() - print 'data: %s' % data + print('data: %s' % data) self.failUnless(data >= 1) ############## @@ -2275,7 +2275,7 @@ class toaster_cases(toaster_cases_base): data = data[0][0] except: pass - print data + print(data) self.failUnless(data == 'toaster_admin') ############## @@ -2324,7 +2324,7 @@ class toaster_cases(toaster_cases_base): query = "select a.name, a.value from orm_projectvariable a, orm_project b where a.project_id = b.id and b.name = 'new-default-project';" cursor.execute(query) data = dict(cursor.fetchall()) - print data + print(data) default_values = {u'IMAGE_INSTALL_append': u'', u'PACKAGE_CLASSES': u'package_rpm', u'MACHINE': u'qemux86', u'SDKMACHINE': u'x86_64', u'DISTRO': u'poky', u'IMAGE_FSTYPES': u'ext3 jffs2 tar.bz2'} self.failUnless(data == default_values) @@ -2341,7 +2341,7 @@ class toaster_cases(toaster_cases_base): query = "select layercommit_id from orm_projectlayer a, orm_project b where a.project_id=b.id and b.name='new-default-project';" cursor.execute(query) data_initial = cursor.fetchall() - print data_initial + print(data_initial) self.driver.maximize_window() self.driver.get('localhost:8000')#self.base_url) @@ -2362,7 +2362,7 @@ class toaster_cases(toaster_cases_base): query = "select layercommit_id from orm_projectlayer a, orm_project b where a.project_id=b.id and b.name='new-default-project';" cursor.execute(query) data_changed = cursor.fetchall() - print data_changed + print(data_changed) #resetting release to default self.driver.find_element_by_id('release-change-toggle').click() diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/urlcheck.py b/import-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/urlcheck.py index 0820f82e2..001fcee96 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/urlcheck.py +++ b/import-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/urlcheck.py @@ -26,12 +26,12 @@ def validate_html5(url): warnings = int(resp['x-w3c-validator-warnings']) if status == 'Invalid': - config.logger.warn("Failed %s is %s\terrors %s warnings %s (check at %s)", url, status, errors, warnings, urlrequest) + config.logger.warning("Failed %s is %s\terrors %s warnings %s (check at %s)", url, status, errors, warnings, urlrequest) else: config.logger.debug("OK! %s", url) except Exception as exc: - config.logger.warn("Failed validation call: %s", exc) + config.logger.warning("Failed validation call: %s", exc) return (status, errors, warnings) diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/manage.py b/import-layers/yocto-poky/bitbake/lib/toaster/manage.py index ceaa11bfc..0c7ea5088 100755 --- a/import-layers/yocto-poky/bitbake/lib/toaster/manage.py +++ b/import-layers/yocto-poky/bitbake/lib/toaster/manage.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 import os import sys diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/orm/fixtures/README b/import-layers/yocto-poky/bitbake/lib/toaster/orm/fixtures/README new file mode 100644 index 000000000..1b1c660aa --- /dev/null +++ b/import-layers/yocto-poky/bitbake/lib/toaster/orm/fixtures/README @@ -0,0 +1,30 @@ +# Fixtures directory + +Fixtures are data dumps that can be loaded into Toaster's database to provide +configuration and data. + +In this directory we have the fixtures which are loaded the first time you start Toaster. +This is to provide useful default values and metadata to Toaster. + + - settings.xml This Contains Toaster wide settings, such as the default values for + certain bitbake variables. + + - poky.xml This is the default release data for supported poky based setup + + - oe-core.xml This is the default release data for supported oe-core based setups + +# Custom data/configuration + + - custom.xml + +To add custom initial data/configuration to Toaster place a file called +"custom.xml" in this directory. If present it will be loaded into the database. +We suggest that this is used to overlay any configuration already done. +All objects loaded with the same primary keys overwrite the existing data. +Data can be provided in XML, JSON and if installed YAML formats. + +# To load data at any point in time + +Use the django management command manage.py loaddata <your fixture file> +For further information see the Django command documentation at: +https://docs.djangoproject.com/en/1.8/ref/django-admin/#django-admin-loaddata diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/orm/fixtures/oe-core.xml b/import-layers/yocto-poky/bitbake/lib/toaster/orm/fixtures/oe-core.xml new file mode 100644 index 000000000..a6c834f44 --- /dev/null +++ b/import-layers/yocto-poky/bitbake/lib/toaster/orm/fixtures/oe-core.xml @@ -0,0 +1,59 @@ +<?xml version="1.0" encoding="utf-8"?> +<django-objects version="1.0"> + <!-- Set the project default value for DISTRO --> + <object model="orm.toastersetting" pk="1"> + <field type="CharField" name="name">DEFCONF_DISTRO</field> + <field type="CharField" name="value">nodistro</field> + </object> + + <!-- Bitbake versions which correspond to the metadata release --> + <object model="orm.bitbakeversion" pk="1"> + <field type="CharField" name="name">morty</field> + <field type="CharField" name="giturl">git://git.openembedded.org/bitbake</field> + <field type="CharField" name="branch">1.32</field> + </object> + <object model="orm.bitbakeversion" pk="2"> + <field type="CharField" name="name">HEAD</field> + </object> + + <!-- Releases available --> + <object model="orm.release" pk="1"> + <field type="CharField" name="name">morty</field> + <field type="CharField" name="description">Openembedded Morty</field> + <field rel="ManyToOneRel" to="orm.bitbakeversion" name="bitbake_version">1</field> + <field type="CharField" name="branch_name">morty</field> + <field type="TextField" name="helptext">Toaster will run your builds using the tip of the <a href=\"http://cgit.openembedded.org/openembedded-core/log/?h=morty\">OpenEmbedded Morty</a> branch.</field> + </object> + <object model="orm.release" pk="2"> + <field type="CharField" name="name">local</field> + <field type="CharField" name="description">Local Openembedded</field> + <field rel="ManyToOneRel" to="orm.bitbakeversion" name="bitbake_version">2</field> + <field type="CharField" name="branch_name">HEAD</field> + <field type="TextField" name="helptext">Toaster will run your builds with the version of OpenEmbedded that you have cloned or downloaded to your computer.</field> + </object> + + <!-- Default layers for each release --> + <object model="orm.releasedefaultlayer" pk="1"> + <field rel="ManyToOneRel" to="orm.release" name="release">1</field> + <field type="CharField" name="layer_name">openembedded-core</field> + </object> + <object model="orm.releasedefaultlayer" pk="4"> + <field rel="ManyToOneRel" to="orm.release" name="release">2</field> + <field type="CharField" name="layer_name">openembedded-core</field> + </object> + + <!-- TYPE_LOCAL = 0 Layers for the Local release --> + <object model="orm.layer" pk="1"> + <field type="CharField" name="name">openembedded-core</field> + <field type="CharField" name="vcs_url">git://git.openembedded.org/openembedded-core</field> + </object> + <object model="orm.layer_version" pk="1"> + <field rel="ManyToOneRel" to="orm.layer" name="layer">1</field> + <field rel="ManyToOneRel" to="orm.release" name="release">2</field> + <field type="CharField" name="local_path">OE-CORE-LAYER-DIR</field> + <field type="CharField" name="branch">HEAD</field> + <field type="CharField" name="dirpath">meta</field> + <field type="IntegerField" name="layer_source">0</field> + </object> + +</django-objects> diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/orm/fixtures/poky.xml b/import-layers/yocto-poky/bitbake/lib/toaster/orm/fixtures/poky.xml new file mode 100644 index 000000000..c192baa42 --- /dev/null +++ b/import-layers/yocto-poky/bitbake/lib/toaster/orm/fixtures/poky.xml @@ -0,0 +1,111 @@ +<?xml version="1.0" encoding="utf-8"?> +<django-objects version="1.0"> + <!-- Set the project default value for DISTRO --> + <object model="orm.toastersetting" pk="1"> + <field type="CharField" name="name">DEFCONF_DISTRO</field> + <field type="CharField" name="value">poky</field> + </object> + + <!-- Bitbake versions which correspond to the metadata release --> + <object model="orm.bitbakeversion" pk="1"> + <field type="CharField" name="name">morty</field> + <field type="CharField" name="giturl">git://git.yoctoproject.org/poky</field> + <field type="CharField" name="branch">morty</field> + <field type="CharField" name="dirpath">bitbake</field> + </object> + <object model="orm.bitbakeversion" pk="2"> + <field type="CharField" name="name">HEAD</field> + <field type="CharField" name="giturl">git://git.yoctoproject.org/poky</field> + <field type="CharField" name="branch">HEAD</field> + <field type="CharField" name="dirpath">bitbake</field> + </object> + + <!-- Releases available --> + <object model="orm.release" pk="1"> + <field type="CharField" name="name">morty</field> + <field type="CharField" name="description">Yocto Project 2.2 "Morty"</field> + <field rel="ManyToOneRel" to="orm.bitbakeversion" name="bitbake_version">1</field> + <field type="CharField" name="branch_name">morty</field> + <field type="TextField" name="helptext">Toaster will run your builds using the tip of the <a href="http://git.yoctoproject.org/cgit/cgit.cgi/poky/log/?h=morty">Yocto Project Morty branch</a>.</field> + </object> + <object model="orm.release" pk="2"> + <field type="CharField" name="name">local</field> + <field type="CharField" name="description">Local Yocto Project</field> + <field rel="ManyToOneRel" to="orm.bitbakeversion" name="bitbake_version">2</field> + <field type="CharField" name="branch_name">HEAD</field> + <field type="TextField" name="helptext">Toaster will run your builds with the version of the Yocto Project you have cloned or downloaded to your computer.</field> + </object> + + <!-- Default layers for each release --> + <object model="orm.releasedefaultlayer" pk="1"> + <field rel="ManyToOneRel" to="orm.release" name="release">1</field> + <field type="CharField" name="layer_name">openembedded-core</field> + </object> + <object model="orm.releasedefaultlayer" pk="2"> + <field rel="ManyToOneRel" to="orm.release" name="release">1</field> + <field type="CharField" name="layer_name">meta-poky</field> + </object> + <object model="orm.releasedefaultlayer" pk="3"> + <field rel="ManyToOneRel" to="orm.release" name="release">1</field> + <field type="CharField" name="layer_name">meta-yocto-bsp</field> + </object> + <object model="orm.releasedefaultlayer" pk="4"> + <field rel="ManyToOneRel" to="orm.release" name="release">2</field> + <field type="CharField" name="layer_name">openembedded-core</field> + </object> + <object model="orm.releasedefaultlayer" pk="5"> + <field rel="ManyToOneRel" to="orm.release" name="release">2</field> + <field type="CharField" name="layer_name">meta-poky</field> + </object> + <object model="orm.releasedefaultlayer" pk="6"> + <field rel="ManyToOneRel" to="orm.release" name="release">2</field> + <field type="CharField" name="layer_name">meta-yocto-bsp</field> + </object> + + <!-- Layers for the Local release + layersource TYPE_LOCAL = 0 + --> + <object model="orm.layer" pk="1"> + <field type="CharField" name="name">openembedded-core</field> + <field type="CharField" name="layer_index_url"></field> + <field type="CharField" name="vcs_url">git://git.yoctoproject.org/poky</field> + </object> + <object model="orm.layer_version" pk="1"> + <field rel="ManyToOneRel" to="orm.layer" name="layer">1</field> + <field type="IntegerField" name="layer_source">0</field> + <field rel="ManyToOneRel" to="orm.release" name="release">2</field> + <field type="CharField" name="branch">HEAD</field> + <field type="CharField" name="commit">HEAD</field> + <field type="CharField" name="dirpath">meta</field> + </object> + + + <object model="orm.layer" pk="2"> + <field type="CharField" name="name">meta-poky</field> + <field type="CharField" name="layer_index_url"></field> + <field type="CharField" name="vcs_url">git://git.yoctoproject.org/poky</field> + </object> + <object model="orm.layer_version" pk="2"> + <field rel="ManyToOneRel" to="orm.layer" name="layer">2</field> + <field type="IntegerField" name="layer_source">0</field> + <field rel="ManyToOneRel" to="orm.release" name="release">2</field> + <field type="CharField" name="branch">HEAD</field> + <field type="CharField" name="commit">HEAD</field> + <field type="CharField" name="dirpath">meta-poky</field> + </object> + + + <object model="orm.layer" pk="3"> + <field type="CharField" name="name">meta-yocto-bsp</field> + <field type="CharField" name="layer_index_url"></field> + <field type="CharField" name="vcs_url">git://git.yoctoproject.org/poky</field> + </object> + <object model="orm.layer_version" pk="3"> + <field rel="ManyToOneRel" to="orm.layer" name="layer">3</field> + <field type="IntegerField" name="layer_source">0</field> + <field rel="ManyToOneRel" to="orm.release" name="release">2</field> + <field type="CharField" name="branch">HEAD</field> + <field type="CharField" name="commit">HEAD</field> + <field type="CharField" name="dirpath">meta-yocto-bsp</field> + </object> +</django-objects> diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/orm/fixtures/settings.xml b/import-layers/yocto-poky/bitbake/lib/toaster/orm/fixtures/settings.xml new file mode 100644 index 000000000..ee6a20285 --- /dev/null +++ b/import-layers/yocto-poky/bitbake/lib/toaster/orm/fixtures/settings.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="utf-8"?> +<django-objects version="1.0"> + <!-- Default project settings --> + <!-- pk=1 is DISTRO --> + <object model="orm.toastersetting" pk="2"> + <field type="CharField" name="name">DEFAULT_RELEASE</field> + <field type="CharField" name="value">morty</field> + </object> + <object model="orm.toastersetting" pk="3"> + <field type="CharField" name="name">DEFCONF_PACKAGE_CLASSES</field> + <field type="CharField" name="value">package_rpm</field> + </object> + <object model="orm.toastersetting" pk="4"> + <field type="CharField" name="name">DEFCONF_MACHINE</field> + <field type="CharField" name="value">qemux86</field> + </object> + <object model="orm.toastersetting" pk="5"> + <field type="CharField" name="name">DEFCONF_SSTATE_DIR</field> + <field type="CharField" name="value">${TOPDIR}/../sstate-cache</field> + </object> + <object model="orm.toastersetting" pk="6"> + <field type="CharField" name="name">DEFCONF_IMAGE_INSTALL_append</field> + <field type="CharField" name="value"></field> + </object> + <object model="orm.toastersetting" pk="7"> + <field type="CharField" name="name">DEFCONF_IMAGE_FSTYPES</field> + <field type="CharField" name="value">ext3 jffs2 tar.bz2</field> + </object> + <object model="orm.toastersetting" pk="8"> + <field type="CharField" name="name">DEFCONF_DL_DIR</field> + <field type="CharField" name="value">${TOPDIR}/../downloads</field> + </object> +</django-objects> diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/orm/management/commands/lsupdates.py b/import-layers/yocto-poky/bitbake/lib/toaster/orm/management/commands/lsupdates.py index 75e9513fc..8ff120e0b 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/orm/management/commands/lsupdates.py +++ b/import-layers/yocto-poky/bitbake/lib/toaster/orm/management/commands/lsupdates.py @@ -1,12 +1,334 @@ -from django.core.management.base import NoArgsCommand, CommandError -from orm.models import LayerSource +# +# ex:ts=4:sw=4:sts=4:et +# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- +# +# BitBake Toaster Implementation +# +# Copyright (C) 2016 Intel Corporation +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# 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, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +from django.core.management.base import NoArgsCommand + +from orm.models import LayerSource, Layer, Release, Layer_Version +from orm.models import LayerVersionDependency, Machine, Recipe + import os +import sys + +import json +import logging +import threading +import time +logger = logging.getLogger("toaster") + +DEFAULT_LAYERINDEX_SERVER = "http://layers.openembedded.org/layerindex/api/" + + +class Spinner(threading.Thread): + """ A simple progress spinner to indicate download/parsing is happening""" + def __init__(self, *args, **kwargs): + super(Spinner, self).__init__(*args, **kwargs) + self.setDaemon(True) + self.signal = True + + def run(self): + os.system('setterm -cursor off') + while self.signal: + for char in ["/", "-", "\\", "|"]: + sys.stdout.write("\r" + char) + sys.stdout.flush() + time.sleep(0.25) + os.system('setterm -cursor on') + + def stop(self): + self.signal = False + class Command(NoArgsCommand): - args = "" - help = "Updates locally cached information from all LayerSources" + args = "" + help = "Updates locally cached information from a layerindex server" + + def mini_progress(self, what, i, total): + i = i + 1 + pec = (float(i)/float(total))*100 + + sys.stdout.write("\rUpdating %s %d%%" % + (what, + pec)) + sys.stdout.flush() + if int(pec) is 100: + sys.stdout.write("\n") + sys.stdout.flush() + + def update(self): + """ + Fetches layer, recipe and machine information from a layerindex + server + """ + os.system('setterm -cursor off') + + self.apiurl = DEFAULT_LAYERINDEX_SERVER + + assert self.apiurl is not None + try: + from urllib.request import urlopen, URLError + from urllib.parse import urlparse + except ImportError: + from urllib2 import urlopen, URLError + from urlparse import urlparse + + proxy_settings = os.environ.get("http_proxy", None) + oe_core_layer = 'openembedded-core' + + def _get_json_response(apiurl=DEFAULT_LAYERINDEX_SERVER): + http_progress = Spinner() + http_progress.start() + + _parsedurl = urlparse(apiurl) + path = _parsedurl.path + + # logger.debug("Fetching %s", apiurl) + try: + res = urlopen(apiurl) + except URLError as e: + raise Exception("Failed to read %s: %s" % (path, e.reason)) + + parsed = json.loads(res.read().decode('utf-8')) + + http_progress.stop() + return parsed + + # verify we can get the basic api + try: + apilinks = _get_json_response() + except Exception as e: + import traceback + if proxy_settings is not None: + logger.info("EE: Using proxy %s" % proxy_settings) + logger.warning("EE: could not connect to %s, skipping update:" + "%s\n%s" % (self.apiurl, e, traceback.format_exc())) + return + + # update branches; only those that we already have names listed in the + # Releases table + whitelist_branch_names = [rel.branch_name + for rel in Release.objects.all()] + if len(whitelist_branch_names) == 0: + raise Exception("Failed to make list of branches to fetch") + + logger.info("Fetching metadata releases for %s", + " ".join(whitelist_branch_names)) + + branches_info = _get_json_response(apilinks['branches'] + + "?filter=name:%s" + % "OR".join(whitelist_branch_names)) + + # Map the layer index branches to toaster releases + li_branch_id_to_toaster_release = {} + + total = len(branches_info) + for i, branch in enumerate(branches_info): + li_branch_id_to_toaster_release[branch['id']] = \ + Release.objects.get(name=branch['name']) + self.mini_progress("Releases", i, total) + + # keep a track of the layerindex (li) id mappings so that + # layer_versions can be created for these layers later on + li_layer_id_to_toaster_layer_id = {} + + logger.info("Fetching layers") + + layers_info = _get_json_response(apilinks['layerItems']) + + total = len(layers_info) + for i, li in enumerate(layers_info): + # Special case for the openembedded-core layer + if li['name'] == oe_core_layer: + try: + # If we have an existing openembedded-core for example + # from the toasterconf.json augment the info using the + # layerindex rather than duplicate it + oe_core_l = Layer.objects.get(name=oe_core_layer) + # Take ownership of the layer as now coming from the + # layerindex + oe_core_l.summary = li['summary'] + oe_core_l.description = li['description'] + oe_core_l.vcs_web_url = li['vcs_web_url'] + oe_core_l.vcs_web_tree_base_url = \ + li['vcs_web_tree_base_url'] + oe_core_l.vcs_web_file_base_url = \ + li['vcs_web_file_base_url'] + + oe_core_l.save() + li_layer_id_to_toaster_layer_id[li['id']] = oe_core_l.pk + self.mini_progress("layers", i, total) + continue + + except Layer.DoesNotExist: + pass + + try: + l, created = Layer.objects.get_or_create(name=li['name'], + vcs_url=li['vcs_url']) + l.up_date = li['updated'] + l.vcs_url = li['vcs_url'] + l.vcs_web_url = li['vcs_web_url'] + l.vcs_web_tree_base_url = li['vcs_web_tree_base_url'] + l.vcs_web_file_base_url = li['vcs_web_file_base_url'] + l.summary = li['summary'] + l.description = li['description'] + l.save() + except Layer.MultipleObjectsReturned: + logger.info("Skipped %s as we found multiple layers and " + "don't know which to update" % + li['name']) + + li_layer_id_to_toaster_layer_id[li['id']] = l.pk + + self.mini_progress("layers", i, total) + + # update layer_versions + logger.info("Fetching layer versions") + layerbranches_info = _get_json_response( + apilinks['layerBranches'] + "?filter=branch__name:%s" % + "OR".join(whitelist_branch_names)) + + # Map Layer index layer_branch object id to + # layer_version toaster object id + li_layer_branch_id_to_toaster_lv_id = {} + + total = len(layerbranches_info) + for i, lbi in enumerate(layerbranches_info): + + try: + lv, created = Layer_Version.objects.get_or_create( + layer_source=LayerSource.TYPE_LAYERINDEX, + layer=Layer.objects.get( + pk=li_layer_id_to_toaster_layer_id[lbi['layer']]) + ) + except KeyError: + logger.warning( + "No such layerindex layer referenced by layerbranch %d" % + lbi['layer']) + continue + + lv.release = li_branch_id_to_toaster_release[lbi['branch']] + lv.up_date = lbi['updated'] + lv.commit = lbi['actual_branch'] + lv.dirpath = lbi['vcs_subdir'] + lv.save() + + li_layer_branch_id_to_toaster_lv_id[lbi['id']] =\ + lv.pk + self.mini_progress("layer versions", i, total) + + logger.info("Fetching layer version dependencies") + # update layer dependencies + layerdependencies_info = _get_json_response( + apilinks['layerDependencies'] + + "?filter=layerbranch__branch__name:%s" % + "OR".join(whitelist_branch_names)) + + dependlist = {} + for ldi in layerdependencies_info: + try: + lv = Layer_Version.objects.get( + pk=li_layer_branch_id_to_toaster_lv_id[ldi['layerbranch']]) + except Layer_Version.DoesNotExist as e: + continue + + if lv not in dependlist: + dependlist[lv] = [] + try: + layer_id = li_layer_id_to_toaster_layer_id[ldi['dependency']] + + dependlist[lv].append( + Layer_Version.objects.get( + layer_source=LayerSource.TYPE_LAYERINDEX, + layer__pk=layer_id)) + + except Layer_Version.DoesNotExist: + logger.warning("Cannot find layer version (ls:%s)," + "up_id:%s lv:%s" % + (self, ldi['dependency'], lv)) + + total = len(dependlist) + for i, lv in enumerate(dependlist): + LayerVersionDependency.objects.filter(layer_version=lv).delete() + for lvd in dependlist[lv]: + LayerVersionDependency.objects.get_or_create(layer_version=lv, + depends_on=lvd) + self.mini_progress("Layer version dependencies", i, total) + + # update machines + logger.info("Fetching machine information") + machines_info = _get_json_response( + apilinks['machines'] + "?filter=layerbranch__branch__name:%s" % + "OR".join(whitelist_branch_names)) + + total = len(machines_info) + for i, mi in enumerate(machines_info): + mo, created = Machine.objects.get_or_create( + name=mi['name'], + layer_version=Layer_Version.objects.get( + pk=li_layer_branch_id_to_toaster_lv_id[mi['layerbranch']])) + mo.up_date = mi['updated'] + mo.name = mi['name'] + mo.description = mi['description'] + mo.save() + self.mini_progress("machines", i, total) + + # update recipes; paginate by layer version / layer branch + logger.info("Fetching recipe information") + recipes_info = _get_json_response( + apilinks['recipes'] + "?filter=layerbranch__branch__name:%s" % + "OR".join(whitelist_branch_names)) + + total = len(recipes_info) + for i, ri in enumerate(recipes_info): + try: + lv_id = li_layer_branch_id_to_toaster_lv_id[ri['layerbranch']] + lv = Layer_Version.objects.get(pk=lv_id) + + ro, created = Recipe.objects.get_or_create( + layer_version=lv, + name=ri['pn'] + ) + + ro.layer_version = lv + ro.up_date = ri['updated'] + ro.name = ri['pn'] + ro.version = ri['pv'] + ro.summary = ri['summary'] + ro.description = ri['description'] + ro.section = ri['section'] + ro.license = ri['license'] + ro.homepage = ri['homepage'] + ro.bugtracker = ri['bugtracker'] + ro.file_path = ri['filepath'] + "/" + ri['filename'] + if 'inherits' in ri: + ro.is_image = 'image' in ri['inherits'].split() + else: # workaround for old style layer index + ro.is_image = "-image-" in ri['pn'] + ro.save() + except Exception as e: + logger.warning("Failed saving recipe %s", e) + + self.mini_progress("recipes", i, total) + os.system('setterm -cursor on') def handle_noargs(self, **options): - for ls in LayerSource.objects.all(): - ls.update() + self.update() diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/orm/migrations/0007_auto_20160523_1446.py b/import-layers/yocto-poky/bitbake/lib/toaster/orm/migrations/0007_auto_20160523_1446.py new file mode 100644 index 000000000..b472e7cf0 --- /dev/null +++ b/import-layers/yocto-poky/bitbake/lib/toaster/orm/migrations/0007_auto_20160523_1446.py @@ -0,0 +1,89 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('orm', '0006_add_cancelled_state'), + ] + + operations = [ + migrations.AlterField( + model_name='build', + name='outcome', + field=models.IntegerField(default=2, choices=[(0, 'Succeeded'), (1, 'Failed'), (2, 'In Progress'), (3, 'Cancelled')]), + ), + migrations.AlterField( + model_name='helptext', + name='area', + field=models.IntegerField(choices=[(0, 'variable')]), + ), + migrations.AlterField( + model_name='layer', + name='summary', + field=models.TextField(default=None, null=True, help_text='One-line description of the layer'), + ), + migrations.AlterField( + model_name='layer_version', + name='local_path', + field=models.FilePathField(default='/', max_length=1024), + ), + migrations.AlterField( + model_name='layersource', + name='sourcetype', + field=models.IntegerField(choices=[(0, 'local'), (1, 'layerindex'), (2, 'imported')]), + ), + migrations.AlterField( + model_name='logmessage', + name='level', + field=models.IntegerField(default=0, choices=[(0, 'info'), (1, 'warn'), (2, 'error'), (3, 'critical'), (-1, 'toaster exception')]), + ), + migrations.AlterField( + model_name='package', + name='installed_name', + field=models.CharField(default='', max_length=100), + ), + migrations.AlterField( + model_name='package_dependency', + name='dep_type', + field=models.IntegerField(choices=[(0, 'depends'), (1, 'depends'), (3, 'recommends'), (2, 'recommends'), (4, 'suggests'), (5, 'provides'), (6, 'replaces'), (7, 'conflicts')]), + ), + migrations.AlterField( + model_name='recipe_dependency', + name='dep_type', + field=models.IntegerField(choices=[(0, 'depends'), (1, 'rdepends')]), + ), + migrations.AlterField( + model_name='release', + name='branch_name', + field=models.CharField(default='', max_length=50), + ), + migrations.AlterField( + model_name='releasedefaultlayer', + name='layer_name', + field=models.CharField(default='', max_length=100), + ), + migrations.AlterField( + model_name='target_file', + name='inodetype', + field=models.IntegerField(choices=[(1, 'regular'), (2, 'directory'), (3, 'symlink'), (4, 'socket'), (5, 'fifo'), (6, 'character'), (7, 'block')]), + ), + migrations.AlterField( + model_name='task', + name='outcome', + field=models.IntegerField(default=-1, choices=[(-1, 'Not Available'), (0, 'Succeeded'), (1, 'Covered'), (2, 'Cached'), (3, 'Prebuilt'), (4, 'Failed'), (5, 'Empty')]), + ), + migrations.AlterField( + model_name='task', + name='script_type', + field=models.IntegerField(default=0, choices=[(0, 'N/A'), (2, 'Python'), (3, 'Shell')]), + ), + migrations.AlterField( + model_name='task', + name='sstate_result', + field=models.IntegerField(default=0, choices=[(0, 'Not Applicable'), (1, 'File not in cache'), (2, 'Failed'), (3, 'Succeeded')]), + ), + ] diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/orm/migrations/0008_refactor_artifact_models.py b/import-layers/yocto-poky/bitbake/lib/toaster/orm/migrations/0008_refactor_artifact_models.py new file mode 100644 index 000000000..3367582a8 --- /dev/null +++ b/import-layers/yocto-poky/bitbake/lib/toaster/orm/migrations/0008_refactor_artifact_models.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('orm', '0007_auto_20160523_1446'), + ] + + operations = [ + migrations.CreateModel( + name='TargetKernelFile', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, verbose_name='ID', serialize=False)), + ('file_name', models.FilePathField()), + ('file_size', models.IntegerField()), + ('target', models.ForeignKey(to='orm.Target')), + ], + ), + migrations.CreateModel( + name='TargetSDKFile', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, verbose_name='ID', serialize=False)), + ('file_name', models.FilePathField()), + ('file_size', models.IntegerField()), + ('target', models.ForeignKey(to='orm.Target')), + ], + ), + migrations.RemoveField( + model_name='buildartifact', + name='build', + ), + migrations.DeleteModel( + name='BuildArtifact', + ), + ] diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/orm/migrations/0009_target_package_manifest_path.py b/import-layers/yocto-poky/bitbake/lib/toaster/orm/migrations/0009_target_package_manifest_path.py new file mode 100644 index 000000000..c958f3070 --- /dev/null +++ b/import-layers/yocto-poky/bitbake/lib/toaster/orm/migrations/0009_target_package_manifest_path.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('orm', '0008_refactor_artifact_models'), + ] + + operations = [ + migrations.AddField( + model_name='target', + name='package_manifest_path', + field=models.CharField(null=True, max_length=500), + ), + ] diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/orm/migrations/0010_delete_layer_source_references.py b/import-layers/yocto-poky/bitbake/lib/toaster/orm/migrations/0010_delete_layer_source_references.py new file mode 100644 index 000000000..f67388e99 --- /dev/null +++ b/import-layers/yocto-poky/bitbake/lib/toaster/orm/migrations/0010_delete_layer_source_references.py @@ -0,0 +1,118 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ('orm', '0009_target_package_manifest_path'), + ] + + operations = [ + migrations.AlterUniqueTogether( + name='releaselayersourcepriority', + unique_together=set([]), + ), + migrations.RemoveField( + model_name='releaselayersourcepriority', + name='layer_source', + ), + migrations.RemoveField( + model_name='releaselayersourcepriority', + name='release', + ), + migrations.DeleteModel( + name='ImportedLayerSource', + ), + migrations.DeleteModel( + name='LayerIndexLayerSource', + ), + migrations.DeleteModel( + name='LocalLayerSource', + ), + migrations.RemoveField( + model_name='recipe', + name='layer_source', + ), + migrations.RemoveField( + model_name='recipe', + name='up_id', + ), + migrations.AlterField( + model_name='layer', + name='up_date', + field=models.DateTimeField(default=django.utils.timezone.now, null=True), + ), + migrations.AlterField( + model_name='layer_version', + name='layer_source', + field=models.IntegerField(default=0, choices=[(0, 'local'), (1, 'layerindex'), (2, 'imported'), (3, 'build')]), + ), + migrations.AlterField( + model_name='layer_version', + name='up_date', + field=models.DateTimeField(default=django.utils.timezone.now, null=True), + ), + migrations.AlterUniqueTogether( + name='branch', + unique_together=set([]), + ), + migrations.AlterUniqueTogether( + name='layer', + unique_together=set([]), + ), + migrations.AlterUniqueTogether( + name='layer_version', + unique_together=set([]), + ), + migrations.AlterUniqueTogether( + name='layerversiondependency', + unique_together=set([]), + ), + migrations.AlterUniqueTogether( + name='machine', + unique_together=set([]), + ), + migrations.DeleteModel( + name='ReleaseLayerSourcePriority', + ), + migrations.RemoveField( + model_name='branch', + name='layer_source', + ), + migrations.RemoveField( + model_name='branch', + name='up_id', + ), + migrations.RemoveField( + model_name='layer', + name='layer_source', + ), + migrations.RemoveField( + model_name='layer', + name='up_id', + ), + migrations.RemoveField( + model_name='layer_version', + name='up_id', + ), + migrations.RemoveField( + model_name='layerversiondependency', + name='layer_source', + ), + migrations.RemoveField( + model_name='layerversiondependency', + name='up_id', + ), + migrations.RemoveField( + model_name='machine', + name='layer_source', + ), + migrations.RemoveField( + model_name='machine', + name='up_id', + ), + ] diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/orm/migrations/0011_delete_layersource.py b/import-layers/yocto-poky/bitbake/lib/toaster/orm/migrations/0011_delete_layersource.py new file mode 100644 index 000000000..75506961a --- /dev/null +++ b/import-layers/yocto-poky/bitbake/lib/toaster/orm/migrations/0011_delete_layersource.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('orm', '0010_delete_layer_source_references'), + ] + + operations = [ + migrations.DeleteModel( + name='LayerSource', + ), + ] diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/orm/migrations/0012_use_release_instead_of_up_branch.py b/import-layers/yocto-poky/bitbake/lib/toaster/orm/migrations/0012_use_release_instead_of_up_branch.py new file mode 100644 index 000000000..0e6bb8331 --- /dev/null +++ b/import-layers/yocto-poky/bitbake/lib/toaster/orm/migrations/0012_use_release_instead_of_up_branch.py @@ -0,0 +1,62 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models +from django.db.models import Q + + +def branch_to_release(apps, schema_editor): + Layer_Version = apps.get_model('orm', 'Layer_Version') + Release = apps.get_model('orm', 'Release') + + print("Converting all layer version up_branches to releases") + # Find all the layer versions which have an upbranch and convert them to + # the release that they're for. + for layer_version in Layer_Version.objects.filter( + Q(release=None) & ~Q(up_branch=None)): + try: + # HEAD and local are equivalent + if "HEAD" in layer_version.up_branch.name: + release = Release.objects.get(name="local") + layer_version.commit = "HEAD" + layer_version.branch = "HEAD" + else: + release = Release.objects.get( + name=layer_version.up_branch.name) + + layer_version.release = release + layer_version.save() + except Exception as e: + print("Couldn't work out an appropriate release for %s " + "the up_branch was %s " + "user the django admin interface to correct it" % + (layer_version.layer.name, layer_version.up_branch.name)) + print(e) + + continue + + +class Migration(migrations.Migration): + + dependencies = [ + ('orm', '0011_delete_layersource'), + ] + + operations = [ + migrations.AddField( + model_name='layer_version', + name='release', + field=models.ForeignKey(to='orm.Release', default=None, null=True), + ), + migrations.RunPython(branch_to_release, + reverse_code=migrations.RunPython.noop), + + migrations.RemoveField( + model_name='layer_version', + name='up_branch', + ), + + migrations.DeleteModel( + name='Branch', + ), + ] diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/orm/migrations/0013_recipe_parse_progress_fields.py b/import-layers/yocto-poky/bitbake/lib/toaster/orm/migrations/0013_recipe_parse_progress_fields.py new file mode 100644 index 000000000..cc5c96d2d --- /dev/null +++ b/import-layers/yocto-poky/bitbake/lib/toaster/orm/migrations/0013_recipe_parse_progress_fields.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('orm', '0012_use_release_instead_of_up_branch'), + ] + + operations = [ + migrations.AddField( + model_name='build', + name='recipes_parsed', + field=models.IntegerField(default=0), + ), + migrations.AddField( + model_name='build', + name='recipes_to_parse', + field=models.IntegerField(default=1), + ), + ] diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/orm/migrations/0014_allow_empty_buildname.py b/import-layers/yocto-poky/bitbake/lib/toaster/orm/migrations/0014_allow_empty_buildname.py new file mode 100644 index 000000000..4749a14b2 --- /dev/null +++ b/import-layers/yocto-poky/bitbake/lib/toaster/orm/migrations/0014_allow_empty_buildname.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('orm', '0013_recipe_parse_progress_fields'), + ] + + operations = [ + migrations.AlterField( + model_name='build', + name='build_name', + field=models.CharField(default='', max_length=100), + ), + ] diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/orm/migrations/0015_layer_local_source_dir.py b/import-layers/yocto-poky/bitbake/lib/toaster/orm/migrations/0015_layer_local_source_dir.py new file mode 100644 index 000000000..9539cd72a --- /dev/null +++ b/import-layers/yocto-poky/bitbake/lib/toaster/orm/migrations/0015_layer_local_source_dir.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('orm', '0014_allow_empty_buildname'), + ] + + operations = [ + migrations.AddField( + model_name='layer', + name='local_source_dir', + field=models.TextField(null=True, default=None), + ), + ] diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/orm/models.py b/import-layers/yocto-poky/bitbake/lib/toaster/orm/models.py index 0b83b991b..a7de57c25 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/orm/models.py +++ b/import-layers/yocto-poky/bitbake/lib/toaster/orm/models.py @@ -21,8 +21,8 @@ from __future__ import unicode_literals -from django.db import models, IntegrityError -from django.db.models import F, Q, Avg, Max, Sum +from django.db import models, IntegrityError, DataError +from django.db.models import F, Q, Sum, Count from django.utils import timezone from django.utils.encoding import force_bytes @@ -32,9 +32,11 @@ from django.core import validators from django.conf import settings import django.db.models.signals -import os.path +import sys +import os import re import itertools +from signal import SIGUSR1 import logging logger = logging.getLogger("toaster") @@ -77,7 +79,7 @@ if 'sqlite' in settings.DATABASES['default']['ENGINE']: try: obj = self.create(**params) return obj, True - except IntegrityError: + except (IntegrityError, DataError): exc_info = sys.exc_info() try: return self.get(**lookup), False @@ -102,7 +104,7 @@ class GitURLValidator(validators.URLValidator): def GitURLField(**kwargs): r = models.URLField(**kwargs) - for i in xrange(len(r.validators)): + for i in range(len(r.validators)): if isinstance(r.validators[i], validators.URLValidator): r.validators[i] = GitURLValidator() return r @@ -116,39 +118,48 @@ class ToasterSetting(models.Model): def __unicode__(self): return "Setting %s = %s" % (self.name, self.value) + class ProjectManager(models.Manager): def create_project(self, name, release): if release is not None: - prj = self.model(name = name, bitbake_version = release.bitbake_version, release = release) + prj = self.model(name=name, + bitbake_version=release.bitbake_version, + release=release) else: - prj = self.model(name = name, bitbake_version = None, release = None) + prj = self.model(name=name, + bitbake_version=None, + release=None) prj.save() - for defaultconf in ToasterSetting.objects.filter(name__startswith="DEFCONF_"): + for defaultconf in ToasterSetting.objects.filter( + name__startswith="DEFCONF_"): name = defaultconf.name[8:] - ProjectVariable.objects.create( project = prj, - name = name, - value = defaultconf.value) + ProjectVariable.objects.create(project=prj, + name=name, + value=defaultconf.value) if release is None: return prj for rdl in release.releasedefaultlayer_set.all(): - try: - lv = Layer_Version.objects.filter(layer__name = rdl.layer_name, up_branch__name = release.branch_name)[0].get_equivalents_wpriority(prj)[0] - ProjectLayer.objects.create( project = prj, - layercommit = lv, - optional = False ) - except IndexError: - # we may have no valid layer version objects, and that's ok - pass + lv = Layer_Version.objects.filter( + layer__name=rdl.layer_name, + release=release).first() + + if lv: + ProjectLayer.objects.create(project=prj, + layercommit=lv, + optional=False) + else: + logger.warning("Default project layer %s not found" % + rdl.layer_name) return prj # return single object with is_default = True def get_or_create_default_project(self): - projects = super(ProjectManager, self).filter(is_default = True) + projects = super(ProjectManager, self).filter(is_default=True) if len(projects) > 1: raise Exception('Inconsistent project data: multiple ' + @@ -156,7 +167,8 @@ class ProjectManager(models.Manager): elif len(projects) < 1: options = { 'name': 'Command line builds', - 'short_description': 'Project for builds started outside Toaster', + 'short_description': + 'Project for builds started outside Toaster', 'is_default': True } project = Project.objects.create(**options) @@ -269,7 +281,7 @@ class Project(models.Model): # guard on release, as it can be null if self.release: queryset = Layer_Version.objects.filter( - (Q(up_branch__name=self.release.branch_name) & + (Q(release=self.release) & Q(build=None) & Q(project=None)) | Q(project=self)) @@ -335,7 +347,15 @@ class Project(models.Model): for l in self.projectlayer_set.all().order_by("pk"): commit = l.layercommit.get_vcs_reference() print("ii Building layer ", l.layercommit.layer.name, " at vcs point ", commit) - BRLayer.objects.create(req = br, name = l.layercommit.layer.name, giturl = l.layercommit.layer.vcs_url, commit = commit, dirpath = l.layercommit.dirpath, layer_version=l.layercommit) + BRLayer.objects.create( + req=br, + name=l.layercommit.layer.name, + giturl=l.layercommit.layer.vcs_url, + commit=commit, + dirpath=l.layercommit.dirpath, + layer_version=l.layercommit, + local_source_dir=l.layercommit.layer.local_source_dir + ) br.state = BuildRequest.REQ_QUEUED now = timezone.now() @@ -357,6 +377,8 @@ class Project(models.Model): except ProjectVariable.DoesNotExist: pass br.save() + signal_runbuilds() + except Exception: # revert the build request creation since we're not done cleanly br.delete() @@ -386,9 +408,15 @@ class Build(models.Model): completed_on = models.DateTimeField() outcome = models.IntegerField(choices=BUILD_OUTCOME, default=IN_PROGRESS) cooker_log_path = models.CharField(max_length=500) - build_name = models.CharField(max_length=100) + build_name = models.CharField(max_length=100, default='') bitbake_version = models.CharField(max_length=50) + # number of recipes to parse for this build + recipes_to_parse = models.IntegerField(default=1) + + # number of recipes parsed so far for this build + recipes_parsed = models.IntegerField(default=0) + @staticmethod def get_recent(project=None): """ @@ -415,14 +443,30 @@ class Build(models.Model): # to show build progress in mrb_section.html for build in recent_builds: build.percentDone = build.completeper() + build.outcomeText = build.get_outcome_text() return recent_builds + def started(self): + """ + As build variables are only added for a build when its BuildStarted event + is received, a build with no build variables is counted as + "in preparation" and not properly started yet. This method + will return False if a build has no build variables (it never properly + started), or True otherwise. + + Note that this is a temporary workaround for the fact that we don't + have a fine-grained state variable on a build which would allow us + to record "in progress" (BuildStarted received) vs. "in preparation". + """ + variables = Variable.objects.filter(build=self) + return len(variables) > 0 + def completeper(self): tf = Task.objects.filter(build = self) tfc = tf.count() if tfc > 0: - completeper = tf.exclude(order__isnull=True).count()*100/tfc + completeper = tf.exclude(order__isnull=True).count()*100 // tfc else: completeper = 0 return completeper @@ -434,58 +478,62 @@ class Build(models.Model): eta += ((eta - self.started_on)*(100-completeper))/completeper return eta + def has_images(self): + """ + Returns True if at least one of the targets for this build has an + image file associated with it, False otherwise + """ + targets = Target.objects.filter(build_id=self.id) + has_images = False + for target in targets: + if target.has_images(): + has_images = True + break + return has_images + + def has_image_recipes(self): + """ + Returns True if a build has any targets which were built from + image recipes. + """ + image_recipes = self.get_image_recipes() + return len(image_recipes) > 0 + def get_image_file_extensions(self): """ - Get list of file name extensions for images produced by this build + Get string of file name extensions for images produced by this build; + note that this is the actual list of extensions stored on Target objects + for this build, and not the value of IMAGE_FSTYPES. + + Returns comma-separated string, e.g. "vmdk, ext4" """ - targets = Target.objects.filter(build_id = self.id) extensions = [] - # pattern to match against file path for building extension string - pattern = re.compile('\.([^\.]+?)$') - + targets = Target.objects.filter(build_id = self.id) for target in targets: - if (not target.is_image): + if not target.is_image: continue - target_image_files = Target_Image_File.objects.filter(target_id = target.id) + target_image_files = Target_Image_File.objects.filter( + target_id=target.id) for target_image_file in target_image_files: - file_name = os.path.basename(target_image_file.file_name) - suffix = '' - - continue_matching = True - - # incrementally extract the suffix from the file path, - # checking it against the list of valid suffixes at each - # step; if the path is stripped of all potential suffix - # parts without matching a valid suffix, this returns all - # characters after the first '.' in the file name - while continue_matching: - matches = pattern.search(file_name) - - if None == matches: - continue_matching = False - suffix = re.sub('^\.', '', suffix) - continue - else: - suffix = matches.group(1) + suffix - - if suffix in Target_Image_File.SUFFIXES: - continue_matching = False - continue - else: - # reduce the file name and try to find the next - # segment from the path which might be part - # of the suffix - file_name = re.sub('.' + matches.group(1), '', file_name) - suffix = '.' + suffix - - if not suffix in extensions: - extensions.append(suffix) + extensions.append(target_image_file.suffix) + + extensions = list(set(extensions)) + extensions.sort() return ', '.join(extensions) + def get_image_fstypes(self): + """ + Get the IMAGE_FSTYPES variable value for this build as a de-duplicated + list of image file suffixes. + """ + image_fstypes = Variable.objects.get( + build=self, variable_name='IMAGE_FSTYPES').variable_value + return list(set(re.split(r' {1,}', image_fstypes))) + def get_sorted_target_list(self): tgts = Target.objects.filter(build_id = self.id).order_by( 'target' ); return( tgts ); @@ -576,49 +624,69 @@ class Build(models.Model): return target_labels - def get_current_status(self): - """ - get the status string from the build request if the build - has one, or the text for the build outcome if it doesn't - """ - - from bldcontrol.models import BuildRequest - - build_request = None + def get_buildrequest(self): + buildrequest = None if hasattr(self, 'buildrequest'): - build_request = self.buildrequest + buildrequest = self.buildrequest + return buildrequest - if (build_request - and build_request.state != BuildRequest.REQ_INPROGRESS - and self.outcome == Build.IN_PROGRESS): - return self.buildrequest.get_state_display() + def is_queued(self): + from bldcontrol.models import BuildRequest + buildrequest = self.get_buildrequest() + if buildrequest: + return buildrequest.state == BuildRequest.REQ_QUEUED else: - return self.get_outcome_text() + return False - def __str__(self): - return "%d %s %s" % (self.id, self.project, ",".join([t.target for t in self.target_set.all()])) + def is_cancelling(self): + from bldcontrol.models import BuildRequest + buildrequest = self.get_buildrequest() + if buildrequest: + return self.outcome == Build.IN_PROGRESS and \ + buildrequest.state == BuildRequest.REQ_CANCELLING + else: + return False + def is_parsing(self): + """ + True if the build is still parsing recipes + """ + return self.outcome == Build.IN_PROGRESS and \ + self.recipes_parsed < self.recipes_to_parse -# an Artifact is anything that results from a Build, and may be of interest to the user, and is not stored elsewhere -class BuildArtifact(models.Model): - build = models.ForeignKey(Build) - file_name = models.FilePathField() - file_size = models.IntegerField() + def is_starting(self): + """ + True if the build has no completed tasks yet and is still just starting + tasks. - def get_local_file_name(self): - try: - deploydir = Variable.objects.get(build = self.build, variable_name="DEPLOY_DIR").variable_value - return self.file_name[len(deploydir)+1:] - except: - raise + Note that the mechanism for testing whether a Task is "done" is whether + its order field is set, as per the completeper() method. + """ + return self.outcome == Build.IN_PROGRESS and \ + self.task_build.filter(order__isnull=False).count() == 0 - return self.file_name + def get_state(self): + """ + Get the state of the build; one of 'Succeeded', 'Failed', 'In Progress', + 'Cancelled' (Build outcomes); or 'Queued', 'Cancelling' (states + dependent on the BuildRequest state). - def get_basename(self): - return os.path.basename(self.file_name) + This works around the fact that we have BuildRequest states as well + as Build states, but really we just want to know the state of the build. + """ + if self.is_cancelling(): + return 'Cancelling'; + elif self.is_queued(): + return 'Queued' + elif self.is_parsing(): + return 'Parsing' + elif self.is_starting(): + return 'Starting' + else: + return self.get_outcome_text() - def is_available(self): - return self.build.buildrequest.environment.has_artifact(self.file_name) + def __str__(self): + return "%d %s %s" % (self.id, self.project, ",".join([t.target for t in self.target_set.all()])) class ProjectTarget(models.Model): project = models.ForeignKey(Project) @@ -633,6 +701,7 @@ class Target(models.Model): is_image = models.BooleanField(default = False) image_size = models.IntegerField(default=0) license_manifest_path = models.CharField(max_length=500, null=True) + package_manifest_path = models.CharField(max_length=500, null=True) def package_count(self): return Target_Installed_Package.objects.filter(target_id__exact=self.id).count() @@ -640,14 +709,180 @@ class Target(models.Model): def __unicode__(self): return self.target + def get_similar_targets(self): + """ + Get target sfor the same machine, task and target name + (e.g. 'core-image-minimal') from a successful build for this project + (but excluding this target). + + Note that we only look for targets built by this project because + projects can have different configurations from each other, and put + their artifacts in different directories. + + The possibility of error when retrieving candidate targets + is minimised by the fact that bitbake will rebuild artifacts if MACHINE + (or various other variables) change. In this case, there is no need to + clone artifacts from another target, as those artifacts will have + been re-generated for this target anyway. + """ + query = ~Q(pk=self.pk) & \ + Q(target=self.target) & \ + Q(build__machine=self.build.machine) & \ + Q(build__outcome=Build.SUCCEEDED) & \ + Q(build__project=self.build.project) + + return Target.objects.filter(query) + + def get_similar_target_with_image_files(self): + """ + Get the most recent similar target with Target_Image_Files associated + with it, for the purpose of cloning those files onto this target. + """ + similar_target = None + + candidates = self.get_similar_targets() + if candidates.count() == 0: + return similar_target + + task_subquery = Q(task=self.task) + + # we can look for a 'build' task if this task is a 'populate_sdk_ext' + # task, as the latter also creates images; and vice versa; note that + # 'build' targets can have their task set to ''; + # also note that 'populate_sdk' does not produce image files + image_tasks = [ + '', # aka 'build' + 'build', + 'image', + 'populate_sdk_ext' + ] + if self.task in image_tasks: + task_subquery = Q(task__in=image_tasks) + + # annotate with the count of files, to exclude any targets which + # don't have associated files + candidates = candidates.annotate(num_files=Count('target_image_file')) + + query = task_subquery & Q(num_files__gt=0) + + candidates = candidates.filter(query) + + if candidates.count() > 0: + candidates.order_by('build__completed_on') + similar_target = candidates.last() + + return similar_target + + def get_similar_target_with_sdk_files(self): + """ + Get the most recent similar target with TargetSDKFiles associated + with it, for the purpose of cloning those files onto this target. + """ + similar_target = None + + candidates = self.get_similar_targets() + if candidates.count() == 0: + return similar_target + + # annotate with the count of files, to exclude any targets which + # don't have associated files + candidates = candidates.annotate(num_files=Count('targetsdkfile')) + + query = Q(task=self.task) & Q(num_files__gt=0) + + candidates = candidates.filter(query) + + if candidates.count() > 0: + candidates.order_by('build__completed_on') + similar_target = candidates.last() + + return similar_target + + def clone_image_artifacts_from(self, target): + """ + Make clones of the Target_Image_Files and TargetKernelFile objects + associated with Target target, then associate them with this target. + + Note that for Target_Image_Files, we only want files from the previous + build whose suffix matches one of the suffixes defined in this + target's build's IMAGE_FSTYPES configuration variable. This prevents the + Target_Image_File object for an ext4 image being associated with a + target for a project which didn't produce an ext4 image (for example). + + Also sets the license_manifest_path and package_manifest_path + of this target to the same path as that of target being cloned from, as + the manifests are also build artifacts but are treated differently. + """ + + image_fstypes = self.build.get_image_fstypes() + + # filter out any image files whose suffixes aren't in the + # IMAGE_FSTYPES suffixes variable for this target's build + image_files = [target_image_file \ + for target_image_file in target.target_image_file_set.all() \ + if target_image_file.suffix in image_fstypes] + + for image_file in image_files: + image_file.pk = None + image_file.target = self + image_file.save() + + kernel_files = target.targetkernelfile_set.all() + for kernel_file in kernel_files: + kernel_file.pk = None + kernel_file.target = self + kernel_file.save() + + self.license_manifest_path = target.license_manifest_path + self.package_manifest_path = target.package_manifest_path + self.save() + + def clone_sdk_artifacts_from(self, target): + """ + Clone TargetSDKFile objects from target and associate them with this + target. + """ + sdk_files = target.targetsdkfile_set.all() + for sdk_file in sdk_files: + sdk_file.pk = None + sdk_file.target = self + sdk_file.save() + + def has_images(self): + """ + Returns True if this target has one or more image files attached to it. + """ + return self.target_image_file_set.all().count() > 0 + +# kernel artifacts for a target: bzImage and modules* +class TargetKernelFile(models.Model): + target = models.ForeignKey(Target) + file_name = models.FilePathField() + file_size = models.IntegerField() + + @property + def basename(self): + return os.path.basename(self.file_name) + +# SDK artifacts for a target: sh and manifest files +class TargetSDKFile(models.Model): + target = models.ForeignKey(Target) + file_name = models.FilePathField() + file_size = models.IntegerField() + + @property + def basename(self): + return os.path.basename(self.file_name) + class Target_Image_File(models.Model): # valid suffixes for image files produced by a build SUFFIXES = { 'btrfs', 'cpio', 'cpio.gz', 'cpio.lz4', 'cpio.lzma', 'cpio.xz', 'cramfs', 'elf', 'ext2', 'ext2.bz2', 'ext2.gz', 'ext2.lzma', 'ext4', - 'ext4.gz', 'ext3', 'ext3.gz', 'hddimg', 'iso', 'jffs2', 'jffs2.sum', - 'squashfs', 'squashfs-lzo', 'squashfs-xz', 'tar.bz2', 'tar.lz4', - 'tar.xz', 'tartar.gz', 'ubi', 'ubifs', 'vmdk' + 'ext4.gz', 'ext3', 'ext3.gz', 'hdddirect', 'hddimg', 'iso', 'jffs2', + 'jffs2.sum', 'multiubi', 'qcow2', 'squashfs', 'squashfs-lzo', + 'squashfs-xz', 'tar', 'tar.bz2', 'tar.gz', 'tar.lz4', 'tar.xz', 'ubi', + 'ubifs', 'vdi', 'vmdk', 'wic', 'wic.bz2', 'wic.gz', 'wic.lzma' } target = models.ForeignKey(Target) @@ -656,6 +891,13 @@ class Target_Image_File(models.Model): @property def suffix(self): + """ + Suffix for image file, minus leading "." + """ + for suffix in Target_Image_File.SUFFIXES: + if self.file_name.endswith(suffix): + return suffix + filename, suffix = os.path.splitext(self.file_name) suffix = suffix.lstrip('.') return suffix @@ -860,31 +1102,70 @@ class CustomImagePackage(Package): related_name='appends_set') - class Package_DependencyManager(models.Manager): use_for_related_fields = True + TARGET_LATEST = "use-latest-target-for-target" def get_queryset(self): return super(Package_DependencyManager, self).get_queryset().exclude(package_id = F('depends_on__id')) - def get_total_source_deps_size(self): - """ Returns the total file size of all the packages that depend on - thispackage. - """ - return self.all().aggregate(Sum('depends_on__size')) + def for_target_or_none(self, target): + """ filter the dependencies to be displayed by the supplied target + if no dependences are found for the target then try None as the target + which will return the dependences calculated without the context of a + target e.g. non image recipes. - def get_total_revdeps_size(self): - """ Returns the total file size of all the packages that depend on - this package. + returns: { size, packages } """ - return self.all().aggregate(Sum('package_id__size')) + package_dependencies = self.all_depends().order_by('depends_on__name') + + if target is self.TARGET_LATEST: + installed_deps =\ + package_dependencies.filter(~Q(target__target=None)) + else: + installed_deps =\ + package_dependencies.filter(Q(target__target=target)) + + packages_list = None + total_size = 0 + + # If we have installed depdencies for this package and target then use + # these to display + if installed_deps.count() > 0: + packages_list = installed_deps + total_size = installed_deps.aggregate( + Sum('depends_on__size'))['depends_on__size__sum'] + else: + new_list = [] + package_names = [] + # Find dependencies for the package that we know about even if + # it's not installed on a target e.g. from a non-image recipe + for p in package_dependencies.filter(Q(target=None)): + if p.depends_on.name in package_names: + continue + else: + package_names.append(p.depends_on.name) + new_list.append(p.pk) + # while we're here we may as well total up the size to + # avoid iterating again + total_size += p.depends_on.size + + # We want to return a queryset here for consistency so pick the + # deps from the new_list + packages_list = package_dependencies.filter(Q(pk__in=new_list)) + + return {'packages': packages_list, + 'size': total_size} def all_depends(self): - """ Returns just the depends packages and not any other dep_type """ + """ Returns just the depends packages and not any other dep_type + Note that this is for any target + """ return self.filter(Q(dep_type=Package_Dependency.TYPE_RDEPENDS) | Q(dep_type=Package_Dependency.TYPE_TRDEPENDS)) + class Package_Dependency(models.Model): TYPE_RDEPENDS = 0 TYPE_TRDEPENDS = 1 @@ -930,21 +1211,27 @@ class Target_Installed_Package(models.Model): target = models.ForeignKey(Target) package = models.ForeignKey(Package, related_name='buildtargetlist_package') + class Package_File(models.Model): package = models.ForeignKey(Package, related_name='buildfilelist_package') path = models.FilePathField(max_length=255, blank=True) size = models.IntegerField() + class Recipe(models.Model): - search_allowed_fields = ['name', 'version', 'file_path', 'section', 'summary', 'description', 'license', 'layer_version__layer__name', 'layer_version__branch', 'layer_version__commit', 'layer_version__local_path', 'layer_version__layer_source__name'] + search_allowed_fields = ['name', 'version', 'file_path', 'section', + 'summary', 'description', 'license', + 'layer_version__layer__name', + 'layer_version__branch', 'layer_version__commit', + 'layer_version__local_path', + 'layer_version__layer_source'] - layer_source = models.ForeignKey('LayerSource', default = None, null = True) # from where did we get this recipe - up_id = models.IntegerField(null = True, default = None) # id of entry in the source - up_date = models.DateTimeField(null = True, default = None) + up_date = models.DateTimeField(null=True, default=None) - name = models.CharField(max_length=100, blank=True) # pn - version = models.CharField(max_length=100, blank=True) # pv - layer_version = models.ForeignKey('Layer_Version', related_name='recipe_layer_version') + name = models.CharField(max_length=100, blank=True) + version = models.CharField(max_length=100, blank=True) + layer_version = models.ForeignKey('Layer_Version', + related_name='recipe_layer_version') summary = models.TextField(blank=True) description = models.TextField(blank=True) section = models.CharField(max_length=100, blank=True) @@ -955,13 +1242,6 @@ class Recipe(models.Model): pathflags = models.CharField(max_length=200, blank=True) is_image = models.BooleanField(default=False) - def get_layersource_view_url(self): - if self.layer_source is None: - return "" - - url = self.layer_source.get_object_view(self.layer_version.up_branch, "recipes", self.name) - return url - def __unicode__(self): return "Recipe " + self.name + ":" + self.version @@ -1007,8 +1287,6 @@ class Recipe_Dependency(models.Model): class Machine(models.Model): search_allowed_fields = ["name", "description", "layer_version__layer__name"] - layer_source = models.ForeignKey('LayerSource', default = None, null = True) # from where did we get this machine - up_id = models.IntegerField(null = True, default = None) # id of entry in the source up_date = models.DateTimeField(null = True, default = None) layer_version = models.ForeignKey('Layer_Version') @@ -1023,285 +1301,9 @@ class Machine(models.Model): def __unicode__(self): return "Machine " + self.name + "(" + self.description + ")" - class Meta: - unique_together = ("layer_source", "up_id") - - -from django.db.models.base import ModelBase - -class InheritanceMetaclass(ModelBase): - def __call__(cls, *args, **kwargs): - obj = super(InheritanceMetaclass, cls).__call__(*args, **kwargs) - return obj.get_object() - - -class LayerSource(models.Model): - __metaclass__ = InheritanceMetaclass - - class Meta: - unique_together = (('sourcetype', 'apiurl'), ) - - TYPE_LOCAL = 0 - TYPE_LAYERINDEX = 1 - TYPE_IMPORTED = 2 - SOURCE_TYPE = ( - (TYPE_LOCAL, "local"), - (TYPE_LAYERINDEX, "layerindex"), - (TYPE_IMPORTED, "imported"), - ) - - name = models.CharField(max_length=63, unique = True) - sourcetype = models.IntegerField(choices=SOURCE_TYPE) - apiurl = models.CharField(max_length=255, null=True, default=None) - - def __init__(self, *args, **kwargs): - super(LayerSource, self).__init__(*args, **kwargs) - if self.sourcetype == LayerSource.TYPE_LOCAL: - self.__class__ = LocalLayerSource - elif self.sourcetype == LayerSource.TYPE_LAYERINDEX: - self.__class__ = LayerIndexLayerSource - elif self.sourcetype == LayerSource.TYPE_IMPORTED: - self.__class__ = ImportedLayerSource - elif self.sourcetype == None: - raise Exception("Unknown LayerSource-derived class. If you added a new layer source type, fill out all code stubs.") - - - def update(self): - """ - Updates the local database information from the upstream layer source - """ - raise Exception("Abstract, update() must be implemented by all LayerSource-derived classes (object is %s)" % str(vars(self))) - - def save(self, *args, **kwargs): - return super(LayerSource, self).save(*args, **kwargs) - - def get_object(self): - # preset an un-initilized object - if None == self.name: - self.name="" - if None == self.apiurl: - self.apiurl="" - if None == self.sourcetype: - self.sourcetype=LayerSource.TYPE_LOCAL - - if self.sourcetype == LayerSource.TYPE_LOCAL: - self.__class__ = LocalLayerSource - elif self.sourcetype == LayerSource.TYPE_LAYERINDEX: - self.__class__ = LayerIndexLayerSource - elif self.sourcetype == LayerSource.TYPE_IMPORTED: - self.__class__ = ImportedLayerSource - else: - raise Exception("Unknown LayerSource type. If you added a new layer source type, fill out all code stubs.") - return self - - def __unicode__(self): - return "%s (%s)" % (self.name, self.sourcetype) - - -class LocalLayerSource(LayerSource): - class Meta(LayerSource._meta.__class__): - proxy = True - - def __init__(self, *args, **kwargs): - super(LocalLayerSource, self).__init__(args, kwargs) - self.sourcetype = LayerSource.TYPE_LOCAL - - def update(self): - """ - Fetches layer, recipe and machine information from local repository - """ - pass - -class ImportedLayerSource(LayerSource): - class Meta(LayerSource._meta.__class__): - proxy = True - - def __init__(self, *args, **kwargs): - super(ImportedLayerSource, self).__init__(args, kwargs) - self.sourcetype = LayerSource.TYPE_IMPORTED - - def update(self): - """ - Fetches layer, recipe and machine information from local repository - """ - pass - - -class LayerIndexLayerSource(LayerSource): - class Meta(LayerSource._meta.__class__): - proxy = True - def __init__(self, *args, **kwargs): - super(LayerIndexLayerSource, self).__init__(args, kwargs) - self.sourcetype = LayerSource.TYPE_LAYERINDEX - - def get_object_view(self, branch, objectype, upid): - return self.apiurl + "../branch/" + branch.name + "/" + objectype + "/?q=" + str(upid) - def update(self): - """ - Fetches layer, recipe and machine information from remote repository - """ - assert self.apiurl is not None - from django.db import transaction, connection - import urllib2, urlparse, json - import os - proxy_settings = os.environ.get("http_proxy", None) - oe_core_layer = 'openembedded-core' - - def _get_json_response(apiurl = self.apiurl): - _parsedurl = urlparse.urlparse(apiurl) - path = _parsedurl.path - - try: - res = urllib2.urlopen(apiurl) - except urllib2.URLError as e: - raise Exception("Failed to read %s: %s" % (path, e.reason)) - - return json.loads(res.read()) - - # verify we can get the basic api - try: - apilinks = _get_json_response() - except Exception as e: - import traceback - if proxy_settings is not None: - logger.info("EE: Using proxy %s" % proxy_settings) - logger.warning("EE: could not connect to %s, skipping update: %s\n%s" % (self.apiurl, e, traceback.format_exc(e))) - return - - # update branches; only those that we already have names listed in the - # Releases table - whitelist_branch_names = map(lambda x: x.branch_name, Release.objects.all()) - if len(whitelist_branch_names) == 0: - raise Exception("Failed to make list of branches to fetch") - - logger.debug("Fetching branches") - branches_info = _get_json_response(apilinks['branches'] - + "?filter=name:%s" % "OR".join(whitelist_branch_names)) - for bi in branches_info: - b, created = Branch.objects.get_or_create(layer_source = self, name = bi['name']) - b.up_id = bi['id'] - b.up_date = bi['updated'] - b.name = bi['name'] - b.short_description = bi['short_description'] - b.save() - - # update layers - layers_info = _get_json_response(apilinks['layerItems']) - - for li in layers_info: - # Special case for the openembedded-core layer - if li['name'] == oe_core_layer: - try: - # If we have an existing openembedded-core for example - # from the toasterconf.json augment the info using the - # layerindex rather than duplicate it - oe_core_l = Layer.objects.get(name=oe_core_layer) - # Take ownership of the layer as now coming from the - # layerindex - oe_core_l.layer_source = self - oe_core_l.up_id = li['id'] - oe_core_l.summary = li['summary'] - oe_core_l.description = li['description'] - oe_core_l.save() - continue - - except Layer.DoesNotExist: - pass - - l, created = Layer.objects.get_or_create(layer_source = self, name = li['name']) - l.up_id = li['id'] - l.up_date = li['updated'] - l.vcs_url = li['vcs_url'] - l.vcs_web_url = li['vcs_web_url'] - l.vcs_web_tree_base_url = li['vcs_web_tree_base_url'] - l.vcs_web_file_base_url = li['vcs_web_file_base_url'] - l.summary = li['summary'] - l.description = li['description'] - l.save() - - # update layerbranches/layer_versions - logger.debug("Fetching layer information") - layerbranches_info = _get_json_response(apilinks['layerBranches'] - + "?filter=branch:%s" % "OR".join(map(lambda x: str(x.up_id), [i for i in Branch.objects.filter(layer_source = self) if i.up_id is not None] )) - ) - - for lbi in layerbranches_info: - lv, created = Layer_Version.objects.get_or_create(layer_source = self, - up_id = lbi['id'], - layer=Layer.objects.get(layer_source = self, up_id = lbi['layer']) - ) - - lv.up_date = lbi['updated'] - lv.up_branch = Branch.objects.get(layer_source = self, up_id = lbi['branch']) - lv.branch = lbi['actual_branch'] - lv.commit = lbi['actual_branch'] - lv.dirpath = lbi['vcs_subdir'] - lv.save() - - # update layer dependencies - layerdependencies_info = _get_json_response(apilinks['layerDependencies']) - dependlist = {} - for ldi in layerdependencies_info: - try: - lv = Layer_Version.objects.get(layer_source = self, up_id = ldi['layerbranch']) - except Layer_Version.DoesNotExist as e: - continue - - if lv not in dependlist: - dependlist[lv] = [] - try: - dependlist[lv].append(Layer_Version.objects.get(layer_source = self, layer__up_id = ldi['dependency'], up_branch = lv.up_branch)) - except Layer_Version.DoesNotExist: - logger.warning("Cannot find layer version (ls:%s), up_id:%s lv:%s" % (self, ldi['dependency'], lv)) - - for lv in dependlist: - LayerVersionDependency.objects.filter(layer_version = lv).delete() - for lvd in dependlist[lv]: - LayerVersionDependency.objects.get_or_create(layer_version = lv, depends_on = lvd) - - - # update machines - logger.debug("Fetching machine information") - machines_info = _get_json_response(apilinks['machines'] - + "?filter=layerbranch:%s" % "OR".join(map(lambda x: str(x.up_id), Layer_Version.objects.filter(layer_source = self))) - ) - - for mi in machines_info: - mo, created = Machine.objects.get_or_create(layer_source = self, up_id = mi['id'], layer_version = Layer_Version.objects.get(layer_source = self, up_id = mi['layerbranch'])) - mo.up_date = mi['updated'] - mo.name = mi['name'] - mo.description = mi['description'] - mo.save() - - # update recipes; paginate by layer version / layer branch - logger.debug("Fetching target information") - recipes_info = _get_json_response(apilinks['recipes'] - + "?filter=layerbranch:%s" % "OR".join(map(lambda x: str(x.up_id), Layer_Version.objects.filter(layer_source = self))) - ) - for ri in recipes_info: - try: - ro, created = Recipe.objects.get_or_create(layer_source = self, up_id = ri['id'], layer_version = Layer_Version.objects.get(layer_source = self, up_id = ri['layerbranch'])) - ro.up_date = ri['updated'] - ro.name = ri['pn'] - ro.version = ri['pv'] - ro.summary = ri['summary'] - ro.description = ri['description'] - ro.section = ri['section'] - ro.license = ri['license'] - ro.homepage = ri['homepage'] - ro.bugtracker = ri['bugtracker'] - ro.file_path = ri['filepath'] + "/" + ri['filename'] - if 'inherits' in ri: - ro.is_image = 'image' in ri['inherits'].split() - else: # workaround for old style layer index - ro.is_image = "-image-" in ri['pn'] - ro.save() - except IntegrityError as e: - logger.debug("Failed saving recipe, ignoring: %s (%s:%s)" % (e, ro.layer_version, ri['filepath']+"/"+ri['filename'])) - ro.delete() class BitbakeVersion(models.Model): @@ -1325,87 +1327,94 @@ class Release(models.Model): def __unicode__(self): return "%s (%s)" % (self.name, self.branch_name) -class ReleaseLayerSourcePriority(models.Model): - """ Each release selects layers from the set up layer sources, ordered by priority """ - release = models.ForeignKey("Release") - layer_source = models.ForeignKey("LayerSource") - priority = models.IntegerField(default = 0) - - def __unicode__(self): - return "%s-%s:%d" % (self.release.name, self.layer_source.name, self.priority) - class Meta: - unique_together = (('release', 'layer_source'),) - + def __str__(self): + return self.name class ReleaseDefaultLayer(models.Model): release = models.ForeignKey(Release) layer_name = models.CharField(max_length=100, default="") -# Branch class is synced with layerindex.Branch, branches can only come from remote layer indexes -class Branch(models.Model): - layer_source = models.ForeignKey('LayerSource', null = True, default = True) - up_id = models.IntegerField(null = True, default = None) # id of branch in the source - up_date = models.DateTimeField(null = True, default = None) - - name = models.CharField(max_length=50) - short_description = models.CharField(max_length=50, blank=True) +class LayerSource(object): + """ Where the layer metadata came from """ + TYPE_LOCAL = 0 + TYPE_LAYERINDEX = 1 + TYPE_IMPORTED = 2 + TYPE_BUILD = 3 - class Meta: - verbose_name_plural = "Branches" - unique_together = (('layer_source', 'name'),('layer_source', 'up_id')) + SOURCE_TYPE = ( + (TYPE_LOCAL, "local"), + (TYPE_LAYERINDEX, "layerindex"), + (TYPE_IMPORTED, "imported"), + (TYPE_BUILD, "build"), + ) - def __unicode__(self): - return self.name + def types_dict(): + """ Turn the TYPES enums into a simple dictionary """ + dictionary = {} + for key in LayerSource.__dict__: + if "TYPE" in key: + dictionary[key] = getattr(LayerSource, key) + return dictionary -# Layer class synced with layerindex.LayerItem class Layer(models.Model): - layer_source = models.ForeignKey(LayerSource, null = True, default = None) # from where did we got this layer - up_id = models.IntegerField(null = True, default = None) # id of layer in the remote source - up_date = models.DateTimeField(null = True, default = None) + + up_date = models.DateTimeField(null=True, default=timezone.now) name = models.CharField(max_length=100) layer_index_url = models.URLField() - vcs_url = GitURLField(default = None, null = True) - vcs_web_url = models.URLField(null = True, default = None) - vcs_web_tree_base_url = models.URLField(null = True, default = None) - vcs_web_file_base_url = models.URLField(null = True, default = None) + vcs_url = GitURLField(default=None, null=True) + local_source_dir = models.TextField(null = True, default = None) + vcs_web_url = models.URLField(null=True, default=None) + vcs_web_tree_base_url = models.URLField(null=True, default=None) + vcs_web_file_base_url = models.URLField(null=True, default=None) - summary = models.TextField(help_text='One-line description of the layer', null = True, default = None) - description = models.TextField(null = True, default = None) + summary = models.TextField(help_text='One-line description of the layer', + null=True, default=None) + description = models.TextField(null=True, default=None) def __unicode__(self): - return "%s / %s " % (self.name, self.layer_source) - - class Meta: - unique_together = (("layer_source", "up_id"), ("layer_source", "name")) + return "%s / %s " % (self.name, self.summary) -# LayerCommit class is synced with layerindex.LayerBranch class Layer_Version(models.Model): """ A Layer_Version either belongs to a single project or no project """ - search_allowed_fields = ["layer__name", "layer__summary", "layer__description", "layer__vcs_url", "dirpath", "up_branch__name", "commit", "branch"] - build = models.ForeignKey(Build, related_name='layer_version_build', default = None, null = True) + search_allowed_fields = ["layer__name", "layer__summary", + "layer__description", "layer__vcs_url", + "dirpath", "release__name", "commit", "branch"] + + build = models.ForeignKey(Build, related_name='layer_version_build', + default=None, null=True) + layer = models.ForeignKey(Layer, related_name='layer_version_layer') - layer_source = models.ForeignKey(LayerSource, null = True, default = None) # from where did we get this Layer Version - up_id = models.IntegerField(null = True, default = None) # id of layerbranch in the remote source - up_date = models.DateTimeField(null = True, default = None) - up_branch = models.ForeignKey(Branch, null = True, default = None) + layer_source = models.IntegerField(choices=LayerSource.SOURCE_TYPE, + default=0) + + up_date = models.DateTimeField(null=True, default=timezone.now) - branch = models.CharField(max_length=80) # LayerBranch.actual_branch - commit = models.CharField(max_length=100) # LayerBranch.vcs_last_rev - dirpath = models.CharField(max_length=255, null = True, default = None) # LayerBranch.vcs_subdir - priority = models.IntegerField(default = 0) # if -1, this is a default layer + # To which metadata release does this layer version belong to + release = models.ForeignKey(Release, null=True, default=None) - local_path = models.FilePathField(max_length=1024, default = "/") # where this layer was checked-out + branch = models.CharField(max_length=80) + commit = models.CharField(max_length=100) + # If the layer is in a subdir + dirpath = models.CharField(max_length=255, null=True, default=None) - project = models.ForeignKey('Project', null = True, default = None) # Set if this layer is project-specific; always set for imported layers, and project-set branches + # if -1, this is a default layer + priority = models.IntegerField(default=0) - # code lifted, with adaptations, from the layerindex-web application https://git.yoctoproject.org/cgit/cgit.cgi/layerindex-web/ + # where this layer exists on the filesystem + local_path = models.FilePathField(max_length=1024, default="/") + + # Set if this layer is restricted to a particular project + project = models.ForeignKey('Project', null=True, default=None) + + # code lifted, with adaptations, from the layerindex-web application + # https://git.yoctoproject.org/cgit/cgit.cgi/layerindex-web/ def _handle_url_path(self, base_url, path): import re, posixpath if base_url: @@ -1422,7 +1431,7 @@ class Layer_Version(models.Model): extra_path = self.dirpath else: extra_path = path - branchname = self.up_branch.name + branchname = self.release.name url = base_url.replace('%branch%', branchname) # If there's a % in the path (e.g. a wildcard bbappend) we need to encode it @@ -1447,23 +1456,19 @@ class Layer_Version(models.Model): def get_vcs_file_link_url(self, file_path=""): if self.layer.vcs_web_file_base_url is None: return None - return self._handle_url_path(self.layer.vcs_web_file_base_url, file_path) + return self._handle_url_path(self.layer.vcs_web_file_base_url, + file_path) def get_vcs_dirpath_link_url(self): if self.layer.vcs_web_tree_base_url is None: return None return self._handle_url_path(self.layer.vcs_web_tree_base_url, '') - def get_equivalents_wpriority(self, project): - layer_versions = project.get_all_compatible_layer_versions() - filtered = layer_versions.filter(layer__name = self.layer.name) - return filtered.order_by("-layer_source__releaselayersourcepriority__priority") - def get_vcs_reference(self): if self.branch is not None and len(self.branch) > 0: return self.branch - if self.up_branch is not None: - return self.up_branch.name + if self.release is not None: + return self.release.name if self.commit is not None and len(self.commit) > 0: return self.commit return 'N/A' @@ -1491,20 +1496,23 @@ class Layer_Version(models.Model): return sorted(result, key=lambda x: x.layer.name) def __unicode__(self): - return "%d %s (VCS %s, Project %s)" % (self.pk, str(self.layer), self.get_vcs_reference(), self.build.project if self.build is not None else "No project") + return ("id %d belongs to layer: %s" % (self.pk, self.layer.name)) - class Meta: - unique_together = ("layer_source", "up_id") + def __str__(self): + if self.release: + release = self.release.name + else: + release = "No release set" -class LayerVersionDependency(models.Model): - layer_source = models.ForeignKey(LayerSource, null = True, default = None) # from where did we got this layer - up_id = models.IntegerField(null = True, default = None) # id of layerbranch in the remote source + return "%d %s (%s)" % (self.pk, self.layer.name, release) - layer_version = models.ForeignKey(Layer_Version, related_name="dependencies") - depends_on = models.ForeignKey(Layer_Version, related_name="dependees") - class Meta: - unique_together = ("layer_source", "up_id") +class LayerVersionDependency(models.Model): + + layer_version = models.ForeignKey(Layer_Version, + related_name="dependencies") + depends_on = models.ForeignKey(Layer_Version, + related_name="dependees") class ProjectLayer(models.Model): project = models.ForeignKey(Project) @@ -1585,6 +1593,21 @@ class CustomImageRecipe(Recipe): Q(recipe_includes=self)) & ~Q(recipe_excludes=self)) + def get_base_recipe_file(self): + """Get the base recipe file path if it exists on the file system""" + path_schema_one = "%s/%s" % (self.base_recipe.layer_version.dirpath, + self.base_recipe.file_path) + + path_schema_two = self.base_recipe.file_path + + if os.path.exists(path_schema_one): + return path_schema_one + + # The path may now be the full path if the recipe has been built + if os.path.exists(path_schema_two): + return path_schema_two + + return None def generate_recipe_file_contents(self): """Generate the contents for the recipe file.""" @@ -1599,17 +1622,16 @@ class CustomImageRecipe(Recipe): # We add all the known packages to be built by this recipe apart # from locale packages which are are controlled with IMAGE_LINGUAS. for pkg in self.get_all_packages().exclude( - name__icontains="locale"): + name__icontains="locale"): packages_conf += pkg.name+' ' packages_conf += "\"" - try: - base_recipe = open("%s/%s" % - (self.base_recipe.layer_version.dirpath, - self.base_recipe.file_path), 'r').read() - except IOError: - # The path may now be the full path if the recipe has been built - base_recipe = open(self.base_recipe.file_path, 'r').read() + + base_recipe_path = self.get_base_recipe_file() + if base_recipe_path: + base_recipe = open(base_recipe_path, 'r').read() + else: + raise IOError("Based on recipe file not found") # Add a special case for when the recipe we have based a custom image # recipe on requires another recipe. @@ -1618,8 +1640,8 @@ class CustomImageRecipe(Recipe): # "require recipes-core/images/core-image-minimal.bb" req_search = re.search(r'(require\s+)(.+\.bb\s*$)', - base_recipe, - re.MULTILINE) + base_recipe, + re.MULTILINE) if req_search: require_filename = req_search.group(2).strip() @@ -1629,19 +1651,19 @@ class CustomImageRecipe(Recipe): new_require_line = "require %s" % corrected_location - base_recipe = \ - base_recipe.replace(req_search.group(0), new_require_line) - + base_recipe = base_recipe.replace(req_search.group(0), + new_require_line) - info = {"date" : timezone.now().strftime("%Y-%m-%d %H:%M:%S"), - "base_recipe" : base_recipe, - "recipe_name" : self.name, - "base_recipe_name" : self.base_recipe.name, - "license" : self.license, - "summary" : self.summary, - "description" : self.description, - "packages_conf" : packages_conf.strip(), - } + info = { + "date": timezone.now().strftime("%Y-%m-%d %H:%M:%S"), + "base_recipe": base_recipe, + "recipe_name": self.name, + "base_recipe_name": self.base_recipe.name, + "license": self.license, + "summary": self.summary, + "description": self.description, + "packages_conf": packages_conf.strip() + } recipe_contents = ("# Original recipe %(base_recipe_name)s \n" "%(base_recipe)s\n\n" @@ -1717,6 +1739,11 @@ def invalidate_cache(**kwargs): except Exception as e: logger.warning("Problem with cache backend: Failed to clear cache: %s" % e) +def signal_runbuilds(): + """Send SIGUSR1 to runbuilds process""" + with open(os.path.join(os.getenv('BUILDDIR'), '.runbuilds.pid')) as pidf: + os.kill(int(pidf.read()), SIGUSR1) + django.db.models.signals.post_save.connect(invalidate_cache) django.db.models.signals.post_delete.connect(invalidate_cache) django.db.models.signals.m2m_changed.connect(invalidate_cache) diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/orm/tests.py b/import-layers/yocto-poky/bitbake/lib/toaster/orm/tests.py deleted file mode 100644 index 719266e6d..000000000 --- a/import-layers/yocto-poky/bitbake/lib/toaster/orm/tests.py +++ /dev/null @@ -1,180 +0,0 @@ -#! /usr/bin/env python -# ex:ts=4:sw=4:sts=4:et -# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- -# -# BitBake Toaster Implementation -# -# Copyright (C) 2013-2015 Intel Corporation -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 as -# published by the Free Software Foundation. -# -# 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, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -"""Test cases for Toaster ORM.""" - -from django.test import TestCase, TransactionTestCase -from orm.models import LocalLayerSource, LayerIndexLayerSource, ImportedLayerSource, LayerSource -from orm.models import Branch, LayerVersionDependency - -from orm.models import Project, Layer, Layer_Version, Branch, ProjectLayer -from orm.models import Release, ReleaseLayerSourcePriority, BitbakeVersion - -from django.db import IntegrityError - -import os - -# set TTS_LAYER_INDEX to the base url to use a different instance of the layer index - -class LayerSourceVerifyInheritanceSaveLoad(TestCase): - """ - Tests to verify inheritance for the LayerSource proxy-inheritance classes. - """ - def test_object_creation(self): - """Test LayerSource object creation.""" - for name, sourcetype in [("a1", LayerSource.TYPE_LOCAL), - ("a2", LayerSource.TYPE_LAYERINDEX), - ("a3", LayerSource.TYPE_IMPORTED)]: - LayerSource.objects.create(name=name, sourcetype=sourcetype) - - objects = LayerSource.objects.all() - self.assertTrue(isinstance(objects[0], LocalLayerSource)) - self.assertTrue(isinstance(objects[1], LayerIndexLayerSource)) - self.assertTrue(isinstance(objects[2], ImportedLayerSource)) - - def test_duplicate_error(self): - """Test creation of duplicate LayerSource objects.""" - stype = LayerSource.TYPE_LOCAL - LayerSource.objects.create(name="a1", sourcetype=stype) - with self.assertRaises(IntegrityError): - LayerSource.objects.create(name="a1", sourcetype=stype) - - -class LILSUpdateTestCase(TransactionTestCase): - """Test Layer Source update.""" - - def setUp(self): - """Create release.""" - bbv = BitbakeVersion.objects.create(\ - name="master", giturl="git://git.openembedded.org/bitbake") - Release.objects.create(name="default-release", bitbake_version=bbv, - branch_name="master") - - def test_update(self): - """Check if LayerSource.update can fetch branches.""" - url = os.getenv("TTS_LAYER_INDEX", - default="http://layers.openembedded.org/") - - lsobj = LayerSource.objects.create(\ - name="b1", sourcetype=LayerSource.TYPE_LAYERINDEX, - apiurl=url + "layerindex/api/") - lsobj.update() - self.assertTrue(lsobj.branch_set.all().count() > 0, - "no branches fetched") - -class LayerVersionEquivalenceTestCase(TestCase): - """Verify Layer_Version priority selection.""" - - def setUp(self): - """Create required objects.""" - # create layer source - self.lsrc = LayerSource.objects.create(name="dummy-layersource", - sourcetype=LayerSource.TYPE_LOCAL) - # create release - bbv = BitbakeVersion.objects.create(\ - name="master", giturl="git://git.openembedded.org/bitbake") - self.release = Release.objects.create(name="default-release", - bitbake_version=bbv, - branch_name="master") - # attach layer source to release - ReleaseLayerSourcePriority.objects.create(\ - release=self.release, layer_source=self.lsrc, priority=1) - - # create a layer version for the layer on the specified branch - self.layer = Layer.objects.create(name="meta-testlayer", - layer_source=self.lsrc) - self.branch = Branch.objects.create(name="master", layer_source=self.lsrc) - self.lver = Layer_Version.objects.create(\ - layer=self.layer, layer_source=self.lsrc, up_branch=self.branch) - - # create project and project layer - self.project = Project.objects.create_project(name="test-project", - release=self.release) - ProjectLayer.objects.create(project=self.project, - layercommit=self.lver) - - # create spoof layer that should not appear in the search results - layer = Layer.objects.create(name="meta-notvalid", - layer_source=self.lsrc) - self.lver2 = Layer_Version.objects.create(layer=layer, - layer_source=self.lsrc, - up_branch=self.branch) - - def test_single_layersource(self): - """ - When we have a single layer version, - get_equivalents_wpriority() should return a list with - just this layer_version. - """ - equivqs = self.lver.get_equivalents_wpriority(self.project) - self.assertEqual(list(equivqs), [self.lver]) - - def test_dual_layersource(self): - """ - If we have two layers with the same name, from different layer sources, - we expect both layers in, in increasing priority of the layer source. - """ - lsrc2 = LayerSource.objects.create(\ - name="dummy-layersource2", - sourcetype=LayerSource.TYPE_LOCAL, - apiurl="test") - - # assign a lower priority for the second layer source - self.release.releaselayersourcepriority_set.create(layer_source=lsrc2, - priority=2) - - # create a new layer_version for a layer with the same name - # coming from the second layer source - layer2 = Layer.objects.create(name="meta-testlayer", - layer_source=lsrc2) - lver2 = Layer_Version.objects.create(layer=layer2, layer_source=lsrc2, - up_branch=self.branch) - - # expect two layer versions, in the priority order - equivqs = self.lver.get_equivalents_wpriority(self.project) - self.assertEqual(list(equivqs), [lver2, self.lver]) - - def test_compatible_layer_versions(self): - """ - When we have a 2 layer versions, get_all_compatible_layerversions() - should return a queryset with both. - """ - compat_lv = self.project.get_all_compatible_layer_versions() - self.assertEqual(list(compat_lv), [self.lver, self.lver2]) - - def test_layerversion_get_alldeps(self): - """Test Layer_Version.get_alldeps API.""" - lvers = {} - for i in range(10): - name = "layer%d" % i - lvers[name] = Layer_Version.objects.create(layer=Layer.objects.create(name=name), - project=self.project) - if i: - LayerVersionDependency.objects.create(layer_version=lvers["layer%d" % (i - 1)], - depends_on=lvers[name]) - # Check dinamically added deps - self.assertEqual(lvers['layer0'].get_alldeps(self.project.id), - [lvers['layer%d' % n] for n in range(1, i+1)]) - - # Check chain of deps created in previous loop - for i in range(10): - self.assertEqual(lvers['layer%d' % i].get_alldeps(self.project.id), - [lvers['layer%d' % n] for n in range(i+1, 10)]) diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/tests/browser/README b/import-layers/yocto-poky/bitbake/lib/toaster/tests/browser/README index 63e8169c1..6b09d20d8 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/tests/browser/README +++ b/import-layers/yocto-poky/bitbake/lib/toaster/tests/browser/README @@ -4,15 +4,16 @@ These tests require Selenium to be installed in your Python environment. The simplest way to install this is via pip: - pip install selenium + pip install selenium==2.53.2 -Alternatively, if you used pip to install the libraries required by Toaster, -selenium will already be installed. +Note that if you use other versions of Selenium, some of the tests (such as +tests.browser.test_js_unit_tests.TestJsUnitTests) may fail, as these rely on +a Selenium test report with a version-specific format. To run tests against Chrome: * Download chromedriver for your host OS from - https://code.google.com/p/chromedriver/downloads/list + https://sites.google.com/a/chromium.org/chromedriver/downloads * On *nix systems, put chromedriver on PATH * On Windows, put chromedriver.exe in the same directory as chrome.exe @@ -23,15 +24,30 @@ To run tests against PhantomJS (headless): * On *nix systems, put phantomjs on PATH * Not tested on Windows -Firefox should work without requiring additional software to be installed. +To run tests against Firefox, you may need to install the Marionette driver, +depending on how new your version of Firefox is. One clue that you need to do +this is if you see an exception like: -The test case will instantiate a Selenium driver set by the + selenium.common.exceptions.WebDriverException: Message: The browser + appears to have exited before we could connect. If you specified + a log_file in the FirefoxBinary constructor, check it for details. + +See https://developer.mozilla.org/en-US/docs/Mozilla/QA/Marionette/WebDriver +for installation instructions. Ensure that the Marionette executable (renamed +as wires on Linux or wires.exe on Windows) is on your PATH; and use "marionette" +as the browser string passed via TOASTER_TESTS_BROWSER (see below). + +(Note: The Toaster tests have been checked against Firefox 47 with the +Marionette driver.) + +The test cases will instantiate a Selenium driver set by the TOASTER_TESTS_BROWSER environment variable, or Chrome if this is not specified. Available drivers: * chrome (default) * firefox +* marionette (for newer Firefoxes) * ie * phantomjs diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/tests/browser/selenium_helpers.py b/import-layers/yocto-poky/bitbake/lib/toaster/tests/browser/selenium_helpers.py index 56dbe2b34..08711e455 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/tests/browser/selenium_helpers.py +++ b/import-layers/yocto-poky/bitbake/lib/toaster/tests/browser/selenium_helpers.py @@ -27,178 +27,8 @@ Helper methods for creating Toaster Selenium tests which run within the context of Django unit tests. """ - -import os -import time - from django.contrib.staticfiles.testing import StaticLiveServerTestCase -from selenium import webdriver -from selenium.webdriver.support.ui import WebDriverWait -from selenium.common.exceptions import NoSuchElementException, \ - StaleElementReferenceException, TimeoutException - -def create_selenium_driver(browser='chrome'): - # set default browser string based on env (if available) - env_browser = os.environ.get('TOASTER_TESTS_BROWSER') - if env_browser: - browser = env_browser - - if browser == 'chrome': - return webdriver.Chrome( - service_args=["--verbose", "--log-path=selenium.log"] - ) - elif browser == 'firefox': - return webdriver.Firefox() - elif browser == 'ie': - return webdriver.Ie() - elif browser == 'phantomjs': - return webdriver.PhantomJS() - else: - msg = 'Selenium driver for browser %s is not available' % browser - raise RuntimeError(msg) - -class Wait(WebDriverWait): - """ - Subclass of WebDriverWait with predetermined timeout and poll - frequency. Also deals with a wider variety of exceptions. - """ - _TIMEOUT = 10 - _POLL_FREQUENCY = 0.5 - - def __init__(self, driver): - super(Wait, self).__init__(driver, self._TIMEOUT, self._POLL_FREQUENCY) - - def until(self, method, message=''): - """ - Calls the method provided with the driver as an argument until the - return value is not False. - """ - - end_time = time.time() + self._timeout - while True: - try: - value = method(self._driver) - if value: - return value - except NoSuchElementException: - pass - except StaleElementReferenceException: - pass - - time.sleep(self._poll) - if time.time() > end_time: - break - - raise TimeoutException(message) - - def until_not(self, method, message=''): - """ - Calls the method provided with the driver as an argument until the - return value is False. - """ - - end_time = time.time() + self._timeout - while True: - try: - value = method(self._driver) - if not value: - return value - except NoSuchElementException: - return True - except StaleElementReferenceException: - pass - - time.sleep(self._poll) - if time.time() > end_time: - break - - raise TimeoutException(message) - -class SeleniumTestCase(StaticLiveServerTestCase): - """ - NB StaticLiveServerTestCase is used as the base test case so that - static files are served correctly in a Selenium test run context; see - https://docs.djangoproject.com/en/1.9/ref/contrib/staticfiles/#specialized-test-case-to-support-live-testing - """ - - @classmethod - def setUpClass(cls): - """ Create a webdriver driver at the class level """ - - super(SeleniumTestCase, cls).setUpClass() - - # instantiate the Selenium webdriver once for all the test methods - # in this test case - cls.driver = create_selenium_driver() - - @classmethod - def tearDownClass(cls): - """ Clean up webdriver driver """ - - cls.driver.quit() - super(SeleniumTestCase, cls).tearDownClass() - - def get(self, url): - """ - Selenium requires absolute URLs, so convert Django URLs returned - by resolve() or similar to absolute ones and get using the - webdriver instance. - - url: a relative URL - """ - abs_url = '%s%s' % (self.live_server_url, url) - self.driver.get(abs_url) - - def find(self, selector): - """ Find single element by CSS selector """ - return self.driver.find_element_by_css_selector(selector) - - def find_all(self, selector): - """ Find all elements matching CSS selector """ - return self.driver.find_elements_by_css_selector(selector) - - def focused_element(self): - """ Return the element which currently has focus on the page """ - return self.driver.switch_to.active_element - - def wait_until_present(self, selector): - """ Wait until element matching CSS selector is on the page """ - is_present = lambda driver: self.find(selector) - msg = 'An element matching "%s" should be on the page' % selector - element = Wait(self.driver).until(is_present, msg) - return element - - def wait_until_visible(self, selector): - """ Wait until element matching CSS selector is visible on the page """ - is_visible = lambda driver: self.find(selector).is_displayed() - msg = 'An element matching "%s" should be visible' % selector - Wait(self.driver).until(is_visible, msg) - return self.find(selector) - - def wait_until_focused(self, selector): - """ Wait until element matching CSS selector has focus """ - is_focused = \ - lambda driver: self.find(selector) == self.focused_element() - msg = 'An element matching "%s" should be focused' % selector - Wait(self.driver).until(is_focused, msg) - return self.find(selector) - - def enter_text(self, selector, value): - """ Insert text into element matching selector """ - # note that keyup events don't occur until the element is clicked - # (in the case of <input type="text"...>, for example), so simulate - # user clicking the element before inserting text into it - field = self.click(selector) - - field.send_keys(value) - return field - - def click(self, selector): - """ Click on element which matches CSS selector """ - element = self.wait_until_visible(selector) - element.click() - return element +from tests.browser.selenium_helpers_base import SeleniumTestCaseBase - def get_page_source(self): - """ Get raw HTML for the current page """ - return self.driver.page_source +class SeleniumTestCase(SeleniumTestCaseBase, StaticLiveServerTestCase): + pass diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/tests/browser/selenium_helpers_base.py b/import-layers/yocto-poky/bitbake/lib/toaster/tests/browser/selenium_helpers_base.py new file mode 100644 index 000000000..14e9c1564 --- /dev/null +++ b/import-layers/yocto-poky/bitbake/lib/toaster/tests/browser/selenium_helpers_base.py @@ -0,0 +1,218 @@ +#! /usr/bin/env python +# ex:ts=4:sw=4:sts=4:et +# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- +# +# BitBake Toaster Implementation +# +# Copyright (C) 2013-2016 Intel Corporation +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# 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, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# The Wait class and some of SeleniumDriverHelper and SeleniumTestCase are +# modified from Patchwork, released under the same licence terms as Toaster: +# https://github.com/dlespiau/patchwork/blob/master/patchwork/tests.browser.py + +""" +Helper methods for creating Toaster Selenium tests which run within +the context of Django unit tests. +""" + +import os +import time +import unittest + +from django.contrib.staticfiles.testing import StaticLiveServerTestCase +from selenium import webdriver +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.common.desired_capabilities import DesiredCapabilities +from selenium.common.exceptions import NoSuchElementException, \ + StaleElementReferenceException, TimeoutException + +def create_selenium_driver(browser='chrome'): + # set default browser string based on env (if available) + env_browser = os.environ.get('TOASTER_TESTS_BROWSER') + if env_browser: + browser = env_browser + + if browser == 'chrome': + return webdriver.Chrome( + service_args=["--verbose", "--log-path=selenium.log"] + ) + elif browser == 'firefox': + return webdriver.Firefox() + elif browser == 'marionette': + capabilities = DesiredCapabilities.FIREFOX + capabilities['marionette'] = True + return webdriver.Firefox(capabilities=capabilities) + elif browser == 'ie': + return webdriver.Ie() + elif browser == 'phantomjs': + return webdriver.PhantomJS() + else: + msg = 'Selenium driver for browser %s is not available' % browser + raise RuntimeError(msg) + +class Wait(WebDriverWait): + """ + Subclass of WebDriverWait with predetermined timeout and poll + frequency. Also deals with a wider variety of exceptions. + """ + _TIMEOUT = 10 + _POLL_FREQUENCY = 0.5 + + def __init__(self, driver): + super(Wait, self).__init__(driver, self._TIMEOUT, self._POLL_FREQUENCY) + + def until(self, method, message=''): + """ + Calls the method provided with the driver as an argument until the + return value is not False. + """ + + end_time = time.time() + self._timeout + while True: + try: + value = method(self._driver) + if value: + return value + except NoSuchElementException: + pass + except StaleElementReferenceException: + pass + + time.sleep(self._poll) + if time.time() > end_time: + break + + raise TimeoutException(message) + + def until_not(self, method, message=''): + """ + Calls the method provided with the driver as an argument until the + return value is False. + """ + + end_time = time.time() + self._timeout + while True: + try: + value = method(self._driver) + if not value: + return value + except NoSuchElementException: + return True + except StaleElementReferenceException: + pass + + time.sleep(self._poll) + if time.time() > end_time: + break + + raise TimeoutException(message) + +class SeleniumTestCaseBase(unittest.TestCase): + """ + NB StaticLiveServerTestCase is used as the base test case so that + static files are served correctly in a Selenium test run context; see + https://docs.djangoproject.com/en/1.9/ref/contrib/staticfiles/#specialized-test-case-to-support-live-testing + """ + + @classmethod + def setUpClass(cls): + """ Create a webdriver driver at the class level """ + + super(SeleniumTestCaseBase, cls).setUpClass() + + # instantiate the Selenium webdriver once for all the test methods + # in this test case + cls.driver = create_selenium_driver() + cls.driver.maximize_window() + + @classmethod + def tearDownClass(cls): + """ Clean up webdriver driver """ + + cls.driver.quit() + super(SeleniumTestCaseBase, cls).tearDownClass() + + def get(self, url): + """ + Selenium requires absolute URLs, so convert Django URLs returned + by resolve() or similar to absolute ones and get using the + webdriver instance. + + url: a relative URL + """ + abs_url = '%s%s' % (self.live_server_url, url) + self.driver.get(abs_url) + + def find(self, selector): + """ Find single element by CSS selector """ + return self.driver.find_element_by_css_selector(selector) + + def find_all(self, selector): + """ Find all elements matching CSS selector """ + return self.driver.find_elements_by_css_selector(selector) + + def element_exists(self, selector): + """ + Return True if one element matching selector exists, + False otherwise + """ + return len(self.find_all(selector)) == 1 + + def focused_element(self): + """ Return the element which currently has focus on the page """ + return self.driver.switch_to.active_element + + def wait_until_present(self, selector): + """ Wait until element matching CSS selector is on the page """ + is_present = lambda driver: self.find(selector) + msg = 'An element matching "%s" should be on the page' % selector + element = Wait(self.driver).until(is_present, msg) + return element + + def wait_until_visible(self, selector): + """ Wait until element matching CSS selector is visible on the page """ + is_visible = lambda driver: self.find(selector).is_displayed() + msg = 'An element matching "%s" should be visible' % selector + Wait(self.driver).until(is_visible, msg) + return self.find(selector) + + def wait_until_focused(self, selector): + """ Wait until element matching CSS selector has focus """ + is_focused = \ + lambda driver: self.find(selector) == self.focused_element() + msg = 'An element matching "%s" should be focused' % selector + Wait(self.driver).until(is_focused, msg) + return self.find(selector) + + def enter_text(self, selector, value): + """ Insert text into element matching selector """ + # note that keyup events don't occur until the element is clicked + # (in the case of <input type="text"...>, for example), so simulate + # user clicking the element before inserting text into it + field = self.click(selector) + + field.send_keys(value) + return field + + def click(self, selector): + """ Click on element which matches CSS selector """ + element = self.wait_until_visible(selector) + element.click() + return element + + def get_page_source(self): + """ Get raw HTML for the current page """ + return self.driver.page_source diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/tests/browser/test_all_builds_page.py b/import-layers/yocto-poky/bitbake/lib/toaster/tests/browser/test_all_builds_page.py index e4223f482..b86f29bdd 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/tests/browser/test_all_builds_page.py +++ b/import-layers/yocto-poky/bitbake/lib/toaster/tests/browser/test_all_builds_page.py @@ -27,6 +27,7 @@ from tests.browser.selenium_helpers import SeleniumTestCase from orm.models import BitbakeVersion, Release, Project, Build, Target + class TestAllBuildsPage(SeleniumTestCase): """ Tests for all builds page /builds/ """ @@ -57,6 +58,13 @@ class TestAllBuildsPage(SeleniumTestCase): 'outcome': Build.SUCCEEDED } + self.project1_build_failure = { + 'project': self.project1, + 'started_on': now, + 'completed_on': now, + 'outcome': Build.FAILED + } + self.default_project_build_success = { 'project': self.default_project, 'started_on': now, @@ -64,6 +72,46 @@ class TestAllBuildsPage(SeleniumTestCase): 'outcome': Build.SUCCEEDED } + def _get_build_time_element(self, build): + """ + Return the HTML element containing the build time for a build + in the recent builds area + """ + selector = 'div[data-latest-build-result="%s"] ' \ + '[data-role="data-recent-build-buildtime-field"]' % build.id + + # because this loads via Ajax, wait for it to be visible + self.wait_until_present(selector) + + build_time_spans = self.find_all(selector) + + self.assertEqual(len(build_time_spans), 1) + + return build_time_spans[0] + + def _get_row_for_build(self, build): + """ Get the table row for the build from the all builds table """ + self.wait_until_present('#allbuildstable') + + rows = self.find_all('#allbuildstable tr') + + # look for the row with a download link on the recipe which matches the + # build ID + url = reverse('builddashboard', args=(build.id,)) + selector = 'td.target a[href="%s"]' % url + + found_row = None + for row in rows: + + outcome_links = row.find_elements_by_css_selector(selector) + if len(outcome_links) == 1: + found_row = row + break + + self.assertNotEqual(found_row, None) + + return found_row + def test_show_tasks_with_suffix(self): """ Task should be shown as suffix on build name """ build = Build.objects.create(**self.project1_build_success) @@ -95,17 +143,17 @@ class TestAllBuildsPage(SeleniumTestCase): url = reverse('all-builds') self.get(url) - # shouldn't see a run again button for command-line builds - selector = 'div[data-latest-build-result="%s"] button' % default_build.id + # shouldn't see a rebuild button for command-line builds + selector = 'div[data-latest-build-result="%s"] .rebuild-btn' % default_build.id run_again_button = self.find_all(selector) self.assertEqual(len(run_again_button), 0, - 'should not see a run again button for cli builds') + 'should not see a rebuild button for cli builds') - # should see a run again button for non-command-line builds - selector = 'div[data-latest-build-result="%s"] button' % build1.id + # should see a rebuild button for non-command-line builds + selector = 'div[data-latest-build-result="%s"] .rebuild-btn' % build1.id run_again_button = self.find_all(selector) self.assertEqual(len(run_again_button), 1, - 'should see a run again button for non-cli builds') + 'should see a rebuild button for non-cli builds') def test_tooltips_on_project_name(self): """ @@ -124,7 +172,7 @@ class TestAllBuildsPage(SeleniumTestCase): # get the project name cells from the table cells = self.find_all('#allbuildstable td[class="project"]') - selector = 'i.get-help' + selector = 'span.get-help' for cell in cells: content = cell.get_attribute('innerHTML') @@ -141,3 +189,45 @@ class TestAllBuildsPage(SeleniumTestCase): else: msg = 'found unexpected project name cell in all builds table' self.fail(msg) + + def test_builds_time_links(self): + """ + Successful builds should have links on the time column and in the + recent builds area; failed builds should not have links on the time column, + or in the recent builds area + """ + build1 = Build.objects.create(**self.project1_build_success) + build2 = Build.objects.create(**self.project1_build_failure) + + # add some targets to these builds so they have recipe links + # (and so we can find the row in the ToasterTable corresponding to + # a particular build) + Target.objects.create(build=build1, target='foo') + Target.objects.create(build=build2, target='bar') + + url = reverse('all-builds') + self.get(url) + + # test recent builds area for successful build + element = self._get_build_time_element(build1) + links = element.find_elements_by_css_selector('a') + msg = 'should be a link on the build time for a successful recent build' + self.assertEquals(len(links), 1, msg) + + # test recent builds area for failed build + element = self._get_build_time_element(build2) + links = element.find_elements_by_css_selector('a') + msg = 'should not be a link on the build time for a failed recent build' + self.assertEquals(len(links), 0, msg) + + # test the time column for successful build + build1_row = self._get_row_for_build(build1) + links = build1_row.find_elements_by_css_selector('td.time a') + msg = 'should be a link on the build time for a successful build' + self.assertEquals(len(links), 1, msg) + + # test the time column for failed build + build2_row = self._get_row_for_build(build2) + links = build2_row.find_elements_by_css_selector('td.time a') + msg = 'should not be a link on the build time for a failed build' + self.assertEquals(len(links), 0, msg) diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/tests/browser/test_all_projects_page.py b/import-layers/yocto-poky/bitbake/lib/toaster/tests/browser/test_all_projects_page.py index ed8e620db..44da64075 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/tests/browser/test_all_projects_page.py +++ b/import-layers/yocto-poky/bitbake/lib/toaster/tests/browser/test_all_projects_page.py @@ -93,7 +93,7 @@ class TestAllProjectsPage(SeleniumTestCase): """ url = reverse('all-projects') self.get(url) - self.wait_until_visible('#no-results-projectstable') + self.wait_until_visible('#empty-state-projectstable') rows = self.find_all('#projectstable tbody tr') self.assertEqual(len(rows), 0, 'should be no projects displayed') @@ -122,12 +122,13 @@ class TestAllProjectsPage(SeleniumTestCase): self._add_non_default_project() self.get(reverse('all-projects')) + self.wait_until_visible("#projectstable tr") # find the row for the default project default_project_row = self._get_row_for_project(self.default_project.name) # check the release text for the default project - selector = 'span[data-project-field="release"] span.muted' + selector = 'span[data-project-field="release"] span.text-muted' element = default_project_row.find_element_by_css_selector(selector) text = element.text.strip() self.assertEqual(text, 'Not applicable', @@ -137,7 +138,7 @@ class TestAllProjectsPage(SeleniumTestCase): other_project_row = self._get_row_for_project(self.project.name) # check the link in the release cell for the other project - selector = 'span[data-project-field="release"] a' + selector = 'span[data-project-field="release"]' element = other_project_row.find_element_by_css_selector(selector) text = element.text.strip() self.assertEqual(text, self.release.name, @@ -156,11 +157,13 @@ class TestAllProjectsPage(SeleniumTestCase): self.get(reverse('all-projects')) + self.wait_until_visible("#projectstable tr") + # find the row for the default project default_project_row = self._get_row_for_project(self.default_project.name) # check the machine cell for the default project - selector = 'span[data-project-field="machine"] span.muted' + selector = 'span[data-project-field="machine"] span.text-muted' element = default_project_row.find_element_by_css_selector(selector) text = element.text.strip() self.assertEqual(text, 'Not applicable', @@ -170,7 +173,7 @@ class TestAllProjectsPage(SeleniumTestCase): other_project_row = self._get_row_for_project(self.project.name) # check the link in the machine cell for the other project - selector = 'span[data-project-field="machine"] a' + selector = 'span[data-project-field="machine"]' element = other_project_row.find_element_by_css_selector(selector) text = element.text.strip() self.assertEqual(text, self.MACHINE_NAME, diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/tests/browser/test_builddashboard_page.py b/import-layers/yocto-poky/bitbake/lib/toaster/tests/browser/test_builddashboard_page.py index 5e0874947..f8ccb5452 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/tests/browser/test_builddashboard_page.py +++ b/import-layers/yocto-poky/bitbake/lib/toaster/tests/browser/test_builddashboard_page.py @@ -22,10 +22,10 @@ from django.core.urlresolvers import reverse from django.utils import timezone -from selenium_helpers import SeleniumTestCase +from tests.browser.selenium_helpers import SeleniumTestCase from orm.models import Project, Release, BitbakeVersion, Build, LogMessage -from orm.models import Layer, Layer_Version, Recipe, CustomImageRecipe +from orm.models import Layer, Layer_Version, Recipe, CustomImageRecipe, Variable class TestBuildDashboardPage(SeleniumTestCase): """ Tests for the build dashboard /build/X """ @@ -42,11 +42,27 @@ class TestBuildDashboardPage(SeleniumTestCase): self.build1 = Build.objects.create(project=project, started_on=now, - completed_on=now) + completed_on=now, + outcome=Build.SUCCEEDED) self.build2 = Build.objects.create(project=project, started_on=now, - completed_on=now) + completed_on=now, + outcome=Build.SUCCEEDED) + + self.build3 = Build.objects.create(project=project, + started_on=now, + completed_on=now, + outcome=Build.FAILED) + + # add Variable objects to the successful builds, as this is the criterion + # used to determine whether the left-hand panel should be displayed + Variable.objects.create(build=self.build1, + variable_name='Foo', + variable_value='Bar') + Variable.objects.create(build=self.build2, + variable_name='Foo', + variable_value='Bar') # exception msg1 = 'an exception was thrown' @@ -64,6 +80,22 @@ class TestBuildDashboardPage(SeleniumTestCase): message=msg2 ) + # error on the failed build + msg3 = 'an error occurred' + self.error_message = LogMessage.objects.create( + build=self.build3, + level=LogMessage.ERROR, + message=msg3 + ) + + # warning on the failed build + msg4 = 'DANGER WILL ROBINSON' + self.warning_message = LogMessage.objects.create( + build=self.build3, + level=LogMessage.WARNING, + message=msg4 + ) + # recipes related to the build, for testing the edit custom image/new # custom image buttons layer = Layer.objects.create(name='alayer') @@ -71,6 +103,11 @@ class TestBuildDashboardPage(SeleniumTestCase): layer=layer, build=self.build1 ) + # non-image recipes related to a build, for testing the new custom + # image button + layer_version2 = Layer_Version.objects.create(layer=layer, + build=self.build3) + # image recipes self.image_recipe1 = Recipe.objects.create( name='recipeA', @@ -140,38 +177,47 @@ class TestBuildDashboardPage(SeleniumTestCase): dashboard for the Build object build """ self._get_build_dashboard(build) - return self.find_all('#errors div.alert-error') + return self.find_all('#errors div.alert-danger') - def _check_for_log_message(self, build, log_message): - """ - Check whether the LogMessage instance <log_message> is - represented as an HTML error in the dashboard page for the Build object - build + def _check_for_log_message(self, message_elements, log_message): """ - errors = self._get_build_dashboard_errors(build) - self.assertEqual(len(errors), 2) + Check that the LogMessage <log_message> has a representation in + the HTML elements <message_elements>. + + message_elements: WebElements representing the log messages shown + in the build dashboard; each should have a <pre> element inside + it with a data-log-message-id attribute + log_message: orm.models.LogMessage instance + """ expected_text = log_message.message - expected_id = str(log_message.id) + expected_pk = str(log_message.pk) found = False - for error in errors: - error_text = error.find_element_by_tag_name('pre').text - text_matches = (error_text == expected_text) + for element in message_elements: + log_message_text = element.find_element_by_tag_name('pre').text.strip() + text_matches = (log_message_text == expected_text) - error_id = error.get_attribute('data-error') - id_matches = (error_id == expected_id) + log_message_pk = element.get_attribute('data-log-message-id') + id_matches = (log_message_pk == expected_pk) if text_matches and id_matches: found = True break - template_vars = (expected_text, error_text, - expected_id, error_id) - assertion_error_msg = 'exception not found as error: ' \ - 'expected text "%s" and got "%s"; ' \ - 'expected ID %s and got %s' % template_vars - self.assertTrue(found, assertion_error_msg) + template_vars = (expected_text, expected_pk) + assertion_failed_msg = 'message not found: ' \ + 'expected text "%s" and ID %s' % template_vars + self.assertTrue(found, assertion_failed_msg) + + def _check_for_error_message(self, build, log_message): + """ + Check whether the LogMessage instance <log_message> is + represented as an HTML error in the dashboard page for the Build object + build + """ + errors = self._get_build_dashboard_errors(build) + self._check_for_log_message(errors, log_message) def _check_labels_in_modal(self, modal, expected): """ @@ -179,37 +225,29 @@ class TestBuildDashboardPage(SeleniumTestCase): the WebElement modal match the list of text values in expected """ # labels containing the radio buttons we're testing for - labels = modal.find_elements_by_tag_name('label') - - # because the label content has the structure - # label text - # <input...> - # we have to regex on its innerHTML, as we can't just retrieve the - # "label text" on its own via the Selenium API - labels_text = sorted(map( - lambda label: label.get_attribute('innerHTML'), labels - )) - - expected = sorted(expected) + labels = modal.find_elements_by_css_selector(".radio") + labels_text = [lab.text for lab in labels] self.assertEqual(len(labels_text), len(expected)) - for idx, label_text in enumerate(labels_text): - self.assertRegexpMatches(label_text, expected[idx]) + for expected_text in expected: + self.assertTrue(expected_text in labels_text, + "Could not find %s in %s" % (expected_text, + labels_text)) def test_exceptions_show_as_errors(self): """ LogMessages with level EXCEPTION should display in the errors section of the page """ - self._check_for_log_message(self.build1, self.exception_message) + self._check_for_error_message(self.build1, self.exception_message) def test_criticals_show_as_errors(self): """ LogMessages with level CRITICAL should display in the errors section of the page """ - self._check_for_log_message(self.build1, self.critical_message) + self._check_for_error_message(self.build1, self.critical_message) def test_edit_custom_image_button(self): """ @@ -217,7 +255,13 @@ class TestBuildDashboardPage(SeleniumTestCase): the user choose one of them to edit """ self._get_build_dashboard(self.build1) + + # click the "edit custom image" button, which populates the modal + selector = '[data-role="edit-custom-image-trigger"]' + self.click(selector) + modal = self.driver.find_element_by_id('edit-custom-image-modal') + self.wait_until_visible("#edit-custom-image-modal") # recipes we expect to see in the edit custom image modal expected_recipes = [ @@ -235,10 +279,11 @@ class TestBuildDashboardPage(SeleniumTestCase): self._get_build_dashboard(self.build1) # click the "new custom image" button, which populates the modal - selector = '[data-role="new-custom-image-trigger"] button' + selector = '[data-role="new-custom-image-trigger"]' self.click(selector) modal = self.driver.find_element_by_id('new-custom-image-modal') + self.wait_until_visible("#new-custom-image-modal") # recipes we expect to see in the new custom image modal expected_recipes = [ @@ -249,3 +294,54 @@ class TestBuildDashboardPage(SeleniumTestCase): ] self._check_labels_in_modal(modal, expected_recipes) + + def test_new_custom_image_button_no_image(self): + """ + Check that a build which builds non-image recipes doesn't show + the new custom image button on the dashboard. + """ + self._get_build_dashboard(self.build3) + selector = '[data-role="new-custom-image-trigger"]' + self.assertFalse(self.element_exists(selector), + 'new custom image button should not show for builds which ' \ + 'don\'t have any image recipes') + + def test_left_panel(self): + """" + Builds which succeed should have a left panel and a build summary + """ + self._get_build_dashboard(self.build1) + + left_panel = self.find_all('#nav') + self.assertEqual(len(left_panel), 1) + + build_summary = self.find_all('[data-role="build-summary-heading"]') + self.assertEqual(len(build_summary), 1) + + def test_failed_no_left_panel(self): + """ + Builds which fail should have no left panel and no build summary + """ + self._get_build_dashboard(self.build3) + + left_panel = self.find_all('#nav') + self.assertEqual(len(left_panel), 0) + + build_summary = self.find_all('[data-role="build-summary-heading"]') + self.assertEqual(len(build_summary), 0) + + def test_failed_shows_errors_and_warnings(self): + """ + Failed builds should still show error and warning messages + """ + self._get_build_dashboard(self.build3) + + errors = self.find_all('#errors div.alert-danger') + self._check_for_log_message(errors, self.error_message) + + # expand the warnings area + self.click('#warning-toggle') + self.wait_until_visible('#warnings div.alert-warning') + + warnings = self.find_all('#warnings div.alert-warning') + self._check_for_log_message(warnings, self.warning_message) diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/tests/browser/test_builddashboard_page_artifacts.py b/import-layers/yocto-poky/bitbake/lib/toaster/tests/browser/test_builddashboard_page_artifacts.py new file mode 100644 index 000000000..1c627ad49 --- /dev/null +++ b/import-layers/yocto-poky/bitbake/lib/toaster/tests/browser/test_builddashboard_page_artifacts.py @@ -0,0 +1,222 @@ +#! /usr/bin/env python +# ex:ts=4:sw=4:sts=4:et +# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- +# +# BitBake Toaster Implementation +# +# Copyright (C) 2013-2016 Intel Corporation +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# 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, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +from django.core.urlresolvers import reverse +from django.utils import timezone + +from tests.browser.selenium_helpers import SeleniumTestCase + +from orm.models import Project, Release, BitbakeVersion, Build, Target, Package +from orm.models import Target_Image_File, TargetSDKFile, TargetKernelFile +from orm.models import Target_Installed_Package, Variable + +class TestBuildDashboardPageArtifacts(SeleniumTestCase): + """ Tests for artifacts on the build dashboard /build/X """ + + def setUp(self): + bbv = BitbakeVersion.objects.create(name='bbv1', giturl='/tmp/', + branch='master', dirpath="") + release = Release.objects.create(name='release1', + bitbake_version=bbv) + self.project = Project.objects.create_project(name='test project', + release=release) + + def _get_build_dashboard(self, build): + """ + Navigate to the build dashboard for build + """ + url = reverse('builddashboard', args=(build.id,)) + self.get(url) + + def _has_build_artifacts_heading(self): + """ + Check whether the "Build artifacts" heading is visible (True if it + is, False otherwise). + """ + return self.element_exists('[data-heading="build-artifacts"]') + + def _has_images_menu_option(self): + """ + Try to get the "Images" list element from the left-hand menu in the + build dashboard, and return True if it is present, False otherwise. + """ + return self.element_exists('li.nav-header[data-menu-heading="images"]') + + def test_no_artifacts(self): + """ + If a build produced no artifacts, the artifacts heading and images + menu option shouldn't show. + """ + now = timezone.now() + build = Build.objects.create(project=self.project, + started_on=now, completed_on=now, outcome=Build.SUCCEEDED) + + Target.objects.create(is_image=False, build=build, task='', + target='mpfr-native') + + self._get_build_dashboard(build) + + # check build artifacts heading + msg = 'Build artifacts heading should not be displayed for non-image' \ + 'builds' + self.assertFalse(self._has_build_artifacts_heading(), msg) + + # check "Images" option in left-hand menu (should not be there) + msg = 'Images option should not be shown in left-hand menu' + self.assertFalse(self._has_images_menu_option(), msg) + + def test_sdk_artifacts(self): + """ + If a build produced SDK artifacts, they should be shown, but the section + for image files and the images menu option should be hidden. + + The packages count and size should also be hidden. + """ + now = timezone.now() + build = Build.objects.create(project=self.project, + started_on=now, completed_on=timezone.now(), + outcome=Build.SUCCEEDED) + + target = Target.objects.create(is_image=True, build=build, + task='populate_sdk', target='core-image-minimal') + + sdk_file1 = TargetSDKFile.objects.create(target=target, + file_size=100000, + file_name='/home/foo/core-image-minimal.toolchain.sh') + + sdk_file2 = TargetSDKFile.objects.create(target=target, + file_size=120000, + file_name='/home/foo/x86_64.toolchain.sh') + + self._get_build_dashboard(build) + + # check build artifacts heading + msg = 'Build artifacts heading should be displayed for SDK ' \ + 'builds which generate artifacts' + self.assertTrue(self._has_build_artifacts_heading(), msg) + + # check "Images" option in left-hand menu (should not be there) + msg = 'Images option should not be shown in left-hand menu for ' \ + 'builds which didn\'t generate an image file' + self.assertFalse(self._has_images_menu_option(), msg) + + # check links to SDK artifacts + sdk_artifact_links = self.find_all('[data-links="sdk-artifacts"] li') + self.assertEqual(len(sdk_artifact_links), 2, + 'should be links to 2 SDK artifacts') + + # package count and size should not be visible, no link on + # target name + selector = '[data-value="target-package-count"]' + self.assertFalse(self.element_exists(selector), + 'package count should not be shown for non-image builds') + + selector = '[data-value="target-package-size"]' + self.assertFalse(self.element_exists(selector), + 'package size should not be shown for non-image builds') + + selector = '[data-link="target-packages"]' + self.assertFalse(self.element_exists(selector), + 'link to target packages should not be on target heading') + + def test_image_artifacts(self): + """ + If a build produced image files, kernel artifacts, and manifests, + they should all be shown, as well as the image link in the left-hand + menu. + + The packages count and size should be shown, with a link to the + package display page. + """ + now = timezone.now() + build = Build.objects.create(project=self.project, + started_on=now, completed_on=timezone.now(), + outcome=Build.SUCCEEDED) + + # add a variable to the build so that it counts as "started" + Variable.objects.create(build=build, + variable_name='Christopher', + variable_value='Lee') + + target = Target.objects.create(is_image=True, build=build, + task='', target='core-image-minimal', + license_manifest_path='/home/foo/license.manifest', + package_manifest_path='/home/foo/package.manifest') + + image_file = Target_Image_File.objects.create(target=target, + file_name='/home/foo/core-image-minimal.ext4', file_size=9000) + + kernel_file1 = TargetKernelFile.objects.create(target=target, + file_name='/home/foo/bzImage', file_size=2000) + + kernel_file2 = TargetKernelFile.objects.create(target=target, + file_name='/home/foo/bzImage', file_size=2000) + + package = Package.objects.create(build=build, name='foo', size=1024, + installed_name='foo1') + installed_package = Target_Installed_Package.objects.create( + target=target, package=package) + + self._get_build_dashboard(build) + + # check build artifacts heading + msg = 'Build artifacts heading should be displayed for image ' \ + 'builds' + self.assertTrue(self._has_build_artifacts_heading(), msg) + + # check "Images" option in left-hand menu (should be there) + msg = 'Images option should be shown in left-hand menu for image builds' + self.assertTrue(self._has_images_menu_option(), msg) + + # check link to image file + selector = '[data-links="image-artifacts"] li' + self.assertTrue(self.element_exists(selector), + 'should be a link to the image file (selector %s)' % selector) + + # check links to kernel artifacts + kernel_artifact_links = \ + self.find_all('[data-links="kernel-artifacts"] li') + self.assertEqual(len(kernel_artifact_links), 2, + 'should be links to 2 kernel artifacts') + + # check manifest links + selector = 'a[data-link="license-manifest"]' + self.assertTrue(self.element_exists(selector), + 'should be a link to the license manifest (selector %s)' % selector) + + selector = 'a[data-link="package-manifest"]' + self.assertTrue(self.element_exists(selector), + 'should be a link to the package manifest (selector %s)' % selector) + + # check package count and size, link on target name + selector = '[data-value="target-package-count"]' + element = self.find(selector) + self.assertEquals(element.text, '1', + 'package count should be shown for image builds') + + selector = '[data-value="target-package-size"]' + element = self.find(selector) + self.assertEquals(element.text, '1.0 KB', + 'package size should be shown for image builds') + + selector = '[data-link="target-packages"]' + self.assertTrue(self.element_exists(selector), + 'link to target packages should be on target heading') diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/tests/browser/test_builddashboard_page_recipes.py b/import-layers/yocto-poky/bitbake/lib/toaster/tests/browser/test_builddashboard_page_recipes.py new file mode 100644 index 000000000..ed18324e5 --- /dev/null +++ b/import-layers/yocto-poky/bitbake/lib/toaster/tests/browser/test_builddashboard_page_recipes.py @@ -0,0 +1,66 @@ +#! /usr/bin/env python +# ex:ts=4:sw=4:sts=4:et +# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- +# +# BitBake Toaster Implementation +# +# Copyright (C) 2013-2016 Intel Corporation +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# 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, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +from django.core.urlresolvers import reverse +from django.utils import timezone +from tests.browser.selenium_helpers import SeleniumTestCase +from orm.models import Project, Build, Recipe, Task, Layer, Layer_Version +from orm.models import Target + +class TestBuilddashboardPageRecipes(SeleniumTestCase): + """ Test build dashboard recipes sub-page """ + + def setUp(self): + project = Project.objects.get_or_create_default_project() + + now = timezone.now() + + self.build = Build.objects.create(project=project, + started_on=now, + completed_on=now) + + layer = Layer.objects.create() + + layer_version = Layer_Version.objects.create(layer=layer, + build=self.build) + + recipe = Recipe.objects.create(layer_version=layer_version) + + task = Task.objects.create(build=self.build, recipe=recipe, order=1) + + Target.objects.create(build=self.build, task=task, target='do_build') + + def test_build_recipes_columns(self): + """ + Check that non-hideable columns of the table on the recipes sub-page + are disabled on the edit columns dropdown. + """ + url = reverse('recipes', args=(self.build.id,)) + self.get(url) + + self.wait_until_visible('#edit-columns-button') + + # check that options for the non-hideable columns are disabled + non_hideable = ['name', 'version'] + + for column in non_hideable: + selector = 'input#checkbox-%s[disabled="disabled"]' % column + self.wait_until_present(selector) diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/tests/browser/test_builddashboard_page_tasks.py b/import-layers/yocto-poky/bitbake/lib/toaster/tests/browser/test_builddashboard_page_tasks.py new file mode 100644 index 000000000..da50f1601 --- /dev/null +++ b/import-layers/yocto-poky/bitbake/lib/toaster/tests/browser/test_builddashboard_page_tasks.py @@ -0,0 +1,65 @@ +#! /usr/bin/env python +# ex:ts=4:sw=4:sts=4:et +# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- +# +# BitBake Toaster Implementation +# +# Copyright (C) 2013-2016 Intel Corporation +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# 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, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +from django.core.urlresolvers import reverse +from django.utils import timezone +from tests.browser.selenium_helpers import SeleniumTestCase +from orm.models import Project, Build, Recipe, Task, Layer, Layer_Version +from orm.models import Target + +class TestBuilddashboardPageTasks(SeleniumTestCase): + """ Test build dashboard tasks sub-page """ + + def setUp(self): + project = Project.objects.get_or_create_default_project() + + now = timezone.now() + + self.build = Build.objects.create(project=project, + started_on=now, + completed_on=now) + + layer = Layer.objects.create() + + layer_version = Layer_Version.objects.create(layer=layer) + + recipe = Recipe.objects.create(layer_version=layer_version) + + task = Task.objects.create(build=self.build, recipe=recipe, order=1) + + Target.objects.create(build=self.build, task=task, target='do_build') + + def test_build_tasks_columns(self): + """ + Check that non-hideable columns of the table on the tasks sub-page + are disabled on the edit columns dropdown. + """ + url = reverse('tasks', args=(self.build.id,)) + self.get(url) + + self.wait_until_visible('#edit-columns-button') + + # check that options for the non-hideable columns are disabled + non_hideable = ['order', 'task_name', 'recipe__name'] + + for column in non_hideable: + selector = 'input#checkbox-%s[disabled="disabled"]' % column + self.wait_until_present(selector) diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/tests/browser/test_js_unit_tests.py b/import-layers/yocto-poky/bitbake/lib/toaster/tests/browser/test_js_unit_tests.py index e63da8e7a..3c0b96252 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/tests/browser/test_js_unit_tests.py +++ b/import-layers/yocto-poky/bitbake/lib/toaster/tests/browser/test_js_unit_tests.py @@ -38,11 +38,11 @@ class TestJsUnitTests(SeleniumTestCase): def test_that_js_unit_tests_pass(self): url = reverse('js-unit-tests') self.get(url) - self.wait_until_present('#tests-failed') + self.wait_until_present('#qunit-testresult .failed') - failed = self.find("#tests-failed").text - passed = self.find("#tests-passed").text - total = self.find("#tests-total").text + failed = self.find("#qunit-testresult .failed").text + passed = self.find("#qunit-testresult .passed").text + total = self.find("#qunit-testresult .total").text logger.info("Js unit tests completed %s out of %s passed, %s failed", passed, diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/tests/browser/test_layerdetails_page.py b/import-layers/yocto-poky/bitbake/lib/toaster/tests/browser/test_layerdetails_page.py new file mode 100644 index 000000000..6392d1efb --- /dev/null +++ b/import-layers/yocto-poky/bitbake/lib/toaster/tests/browser/test_layerdetails_page.py @@ -0,0 +1,215 @@ +#! /usr/bin/env python +# ex:ts=4:sw=4:sts=4:et +# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- +# +# BitBake Toaster Implementation +# +# Copyright (C) 2013-2016 Intel Corporation +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# 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, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +from django.core.urlresolvers import reverse +from tests.browser.selenium_helpers import SeleniumTestCase + +from orm.models import Layer, Layer_Version, Project, LayerSource, Release +from orm.models import BitbakeVersion + +from selenium.webdriver.support import expected_conditions as EC +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.common.by import By + + +class TestLayerDetailsPage(SeleniumTestCase): + """ Test layerdetails page works correctly """ + + def __init__(self, *args, **kwargs): + super(TestLayerDetailsPage, self).__init__(*args, **kwargs) + + self.initial_values = None + self.url = None + self.imported_layer_version = None + + def setUp(self): + release = Release.objects.create( + name='baz', + bitbake_version=BitbakeVersion.objects.create(name='v1') + ) + + # project to add new custom images to + self.project = Project.objects.create(name='foo', release=release) + + name = "meta-imported" + vcs_url = "git://example.com/meta-imported" + subdir = "/layer" + gitrev = "d33d" + summary = "A imported layer" + description = "This was imported" + + imported_layer = Layer.objects.create(name=name, + vcs_url=vcs_url, + summary=summary, + description=description) + + self.imported_layer_version = Layer_Version.objects.create( + layer=imported_layer, + layer_source=LayerSource.TYPE_IMPORTED, + branch=gitrev, + commit=gitrev, + dirpath=subdir, + project=self.project) + + self.initial_values = [name, vcs_url, subdir, gitrev, summary, + description] + self.url = reverse('layerdetails', + args=(self.project.pk, + self.imported_layer_version.pk)) + + def test_edit_layerdetails(self): + """ Edit all the editable fields for the layer refresh the page and + check that the new values exist""" + + self.get(self.url) + + self.click("#add-remove-layer-btn") + self.click("#edit-layer-source") + self.click("#repo") + + self.wait_until_visible("#layer-git-repo-url") + + # Open every edit box + for btn in self.find_all("dd .glyphicon-edit"): + btn.click() + + # Wait for the inputs to become visible + self.wait_until_visible("#layer-git input[type=text]") + self.wait_until_visible("dd textarea") + + # Edit each value + for inputs in self.find_all("#layer-git input[type=text]") + \ + self.find_all("dd textarea"): + # ignore the tt inputs (twitter typeahead input) + if "tt-" in inputs.get_attribute("class"): + continue + + value = inputs.get_attribute("value") + + self.assertTrue(value in self.initial_values, + "Expecting any of \"%s\"but got \"%s\"" % + (self.initial_values, value)) + + inputs.send_keys("-edited") + + # Save the new values + for save_btn in self.find_all(".change-btn"): + save_btn.click() + + self.click("#save-changes-for-switch") + self.wait_until_visible("#edit-layer-source") + + # Refresh the page to see if the new values are returned + self.get(self.url) + + new_values = ["%s-edited" % old_val + for old_val in self.initial_values] + + for inputs in self.find_all('#layer-git input[type="text"]') + \ + self.find_all('dd textarea'): + # ignore the tt inputs (twitter typeahead input) + if "tt-" in inputs.get_attribute("class"): + continue + + value = inputs.get_attribute("value") + + self.assertTrue(value in new_values, + "Expecting any of \"%s\" but got \"%s\"" % + (new_values, value)) + + # Now convert it to a local layer + self.click("#edit-layer-source") + self.click("#dir") + dir_input = self.wait_until_visible("#layer-dir-path-in-details") + + new_dir = "/home/test/my-meta-dir" + dir_input.send_keys(new_dir) + + self.click("#save-changes-for-switch") + self.wait_until_visible("#edit-layer-source") + + # Refresh the page to see if the new values are returned + self.get(self.url) + dir_input = self.find("#layer-dir-path-in-details") + self.assertTrue(new_dir in dir_input.get_attribute("value"), + "Expected %s in the dir value for layer directory" % + new_dir) + + def test_delete_layer(self): + """ Delete the layer """ + + self.get(self.url) + + # Wait for the tables to load to avoid a race condition where the + # toaster tables have made an async request. If the layer is deleted + # before the request finishes it will cause an exception and fail this + # test. + wait = WebDriverWait(self.driver, 30) + + wait.until(EC.text_to_be_present_in_element( + (By.CLASS_NAME, + "table-count-recipestable"), "0")) + + wait.until(EC.text_to_be_present_in_element( + (By.CLASS_NAME, + "table-count-machinestable"), "0")) + + self.click('a[data-target="#delete-layer-modal"]') + self.wait_until_visible("#delete-layer-modal") + self.click("#layer-delete-confirmed") + + notification = self.wait_until_visible("#change-notification-msg") + expected_text = "You have deleted 1 layer from your project: %s" % \ + self.imported_layer_version.layer.name + + self.assertTrue(expected_text in notification.text, + "Expected notification text \"%s\" not found instead" + "it was \"%s\"" % + (expected_text, notification.text)) + + def test_addrm_to_project(self): + self.get(self.url) + + # Add the layer + self.click("#add-remove-layer-btn") + + notification = self.wait_until_visible("#change-notification-msg") + + expected_text = "You have added 1 layer to your project: %s" % \ + self.imported_layer_version.layer.name + + self.assertTrue(expected_text in notification.text, + "Expected notification text %s not found was " + " \"%s\" instead" % + (expected_text, notification.text)) + + # Remove the layer + self.click("#add-remove-layer-btn") + + notification = self.wait_until_visible("#change-notification-msg") + + expected_text = "You have removed 1 layer from your project: %s" % \ + self.imported_layer_version.layer.name + + self.assertTrue(expected_text in notification.text, + "Expected notification text %s not found was " + " \"%s\" instead" % + (expected_text, notification.text)) diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/tests/browser/test_most_recent_builds_states.py b/import-layers/yocto-poky/bitbake/lib/toaster/tests/browser/test_most_recent_builds_states.py new file mode 100644 index 000000000..abc0b0bc8 --- /dev/null +++ b/import-layers/yocto-poky/bitbake/lib/toaster/tests/browser/test_most_recent_builds_states.py @@ -0,0 +1,211 @@ +#! /usr/bin/env python +# ex:ts=4:sw=4:sts=4:et +# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- +# +# BitBake Toaster Implementation +# +# Copyright (C) 2013-2016 Intel Corporation +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# 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, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +from django.core.urlresolvers import reverse +from django.utils import timezone +from tests.browser.selenium_helpers import SeleniumTestCase +from tests.browser.selenium_helpers_base import Wait +from orm.models import Project, Build, Task, Recipe, Layer, Layer_Version +from bldcontrol.models import BuildRequest + +class TestMostRecentBuildsStates(SeleniumTestCase): + """ Test states update correctly in most recent builds area """ + + def _create_build_request(self): + project = Project.objects.get_or_create_default_project() + + now = timezone.now() + + build = Build.objects.create(project=project, build_name='fakebuild', + started_on=now, completed_on=now) + + return BuildRequest.objects.create(build=build, project=project, + state=BuildRequest.REQ_QUEUED) + + def _create_recipe(self): + """ Add a recipe to the database and return it """ + layer = Layer.objects.create() + layer_version = Layer_Version.objects.create(layer=layer) + return Recipe.objects.create(name='foo', layer_version=layer_version) + + def _check_build_states(self, build_request): + recipes_to_parse = 10 + url = reverse('all-builds') + self.get(url) + + build = build_request.build + base_selector = '[data-latest-build-result="%s"] ' % build.id + + # build queued; check shown as queued + selector = base_selector + '[data-build-state="Queued"]' + element = self.wait_until_visible(selector) + self.assertRegexpMatches(element.get_attribute('innerHTML'), + 'Build queued', 'build should show queued status') + + # waiting for recipes to be parsed + build.outcome = Build.IN_PROGRESS + build.recipes_to_parse = recipes_to_parse + build.recipes_parsed = 0 + + build_request.state = BuildRequest.REQ_INPROGRESS + build_request.save() + + self.get(url) + + selector = base_selector + '[data-build-state="Parsing"]' + element = self.wait_until_visible(selector) + + bar_selector = '#recipes-parsed-percentage-bar-%s' % build.id + bar_element = element.find_element_by_css_selector(bar_selector) + self.assertEqual(bar_element.value_of_css_property('width'), '0px', + 'recipe parse progress should be at 0') + + # recipes being parsed; check parse progress + build.recipes_parsed = 5 + build.save() + + self.get(url) + + element = self.wait_until_visible(selector) + bar_element = element.find_element_by_css_selector(bar_selector) + recipe_bar_updated = lambda driver: \ + bar_element.get_attribute('style') == 'width: 50%;' + msg = 'recipe parse progress bar should update to 50%' + element = Wait(self.driver).until(recipe_bar_updated, msg) + + # all recipes parsed, task started, waiting for first task to finish; + # check status is shown as "Tasks starting..." + build.recipes_parsed = recipes_to_parse + build.save() + + recipe = self._create_recipe() + task1 = Task.objects.create(build=build, recipe=recipe, + task_name='Lionel') + task2 = Task.objects.create(build=build, recipe=recipe, + task_name='Jeffries') + + self.get(url) + + selector = base_selector + '[data-build-state="Starting"]' + element = self.wait_until_visible(selector) + self.assertRegexpMatches(element.get_attribute('innerHTML'), + 'Tasks starting', 'build should show "tasks starting" status') + + # first task finished; check tasks progress bar + task1.order = 1 + task1.save() + + self.get(url) + + selector = base_selector + '[data-build-state="In Progress"]' + element = self.wait_until_visible(selector) + + bar_selector = '#build-pc-done-bar-%s' % build.id + bar_element = element.find_element_by_css_selector(bar_selector) + + task_bar_updated = lambda driver: \ + bar_element.get_attribute('style') == 'width: 50%;' + msg = 'tasks progress bar should update to 50%' + element = Wait(self.driver).until(task_bar_updated, msg) + + # last task finished; check tasks progress bar updates + task2.order = 2 + task2.save() + + self.get(url) + + element = self.wait_until_visible(selector) + bar_element = element.find_element_by_css_selector(bar_selector) + task_bar_updated = lambda driver: \ + bar_element.get_attribute('style') == 'width: 100%;' + msg = 'tasks progress bar should update to 100%' + element = Wait(self.driver).until(task_bar_updated, msg) + + def test_states_to_success(self): + """ + Test state transitions in the recent builds area for a build which + completes successfully. + """ + build_request = self._create_build_request() + + self._check_build_states(build_request) + + # all tasks complete and build succeeded; check success state shown + build = build_request.build + build.outcome = Build.SUCCEEDED + build.save() + + selector = '[data-latest-build-result="%s"] ' \ + '[data-build-state="Succeeded"]' % build.id + element = self.wait_until_visible(selector) + + def test_states_to_failure(self): + """ + Test state transitions in the recent builds area for a build which + completes in a failure. + """ + build_request = self._create_build_request() + + self._check_build_states(build_request) + + # all tasks complete and build succeeded; check fail state shown + build = build_request.build + build.outcome = Build.FAILED + build.save() + + selector = '[data-latest-build-result="%s"] ' \ + '[data-build-state="Failed"]' % build.id + element = self.wait_until_visible(selector) + + def test_states_cancelling(self): + """ + Test that most recent build area updates correctly for a build + which is cancelled. + """ + url = reverse('all-builds') + + build_request = self._create_build_request() + build = build_request.build + + # cancel the build + build_request.state = BuildRequest.REQ_CANCELLING + build_request.save() + + self.get(url) + + # check cancelling state + selector = '[data-latest-build-result="%s"] ' \ + '[data-build-state="Cancelling"]' % build.id + element = self.wait_until_visible(selector) + self.assertRegexpMatches(element.get_attribute('innerHTML'), + 'Cancelling the build', 'build should show "cancelling" status') + + # check cancelled state + build.outcome = Build.CANCELLED + build.save() + + self.get(url) + + selector = '[data-latest-build-result="%s"] ' \ + '[data-build-state="Cancelled"]' % build.id + element = self.wait_until_visible(selector) + self.assertRegexpMatches(element.get_attribute('innerHTML'), + 'Build cancelled', 'build should show "cancelled" status') diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/tests/browser/test_new_custom_image_page.py b/import-layers/yocto-poky/bitbake/lib/toaster/tests/browser/test_new_custom_image_page.py index 8906cb27d..ab5a8e66b 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/tests/browser/test_new_custom_image_page.py +++ b/import-layers/yocto-poky/bitbake/lib/toaster/tests/browser/test_new_custom_image_page.py @@ -25,6 +25,7 @@ from tests.browser.selenium_helpers import SeleniumTestCase from orm.models import BitbakeVersion, Release, Project, ProjectLayer, Layer from orm.models import Layer_Version, Recipe, CustomImageRecipe + class TestNewCustomImagePage(SeleniumTestCase): CUSTOM_IMAGE_NAME = 'roopa-doopa' @@ -140,7 +141,7 @@ class TestNewCustomImagePage(SeleniumTestCase): self._create_custom_image(self.recipe.name) element = self.wait_until_visible('#invalid-name-help') self.assertRegexpMatches(element.text.strip(), - 'recipe with this name already exists') + 'image with this name already exists') def test_new_duplicates_project_image(self): """ diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/tests/browser/test_new_project_page.py b/import-layers/yocto-poky/bitbake/lib/toaster/tests/browser/test_new_project_page.py new file mode 100644 index 000000000..77e5f1526 --- /dev/null +++ b/import-layers/yocto-poky/bitbake/lib/toaster/tests/browser/test_new_project_page.py @@ -0,0 +1,113 @@ +#! /usr/bin/env python +# ex:ts=4:sw=4:sts=4:et +# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- +# +# BitBake Toaster Implementation +# +# Copyright (C) 2013-2016 Intel Corporation +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# 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, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +from django.core.urlresolvers import reverse +from tests.browser.selenium_helpers import SeleniumTestCase +from selenium.webdriver.support.ui import Select +from selenium.common.exceptions import InvalidElementStateException + +from orm.models import Project, Release, BitbakeVersion + + +class TestNewProjectPage(SeleniumTestCase): + """ Test project data at /project/X/ is displayed correctly """ + + def setUp(self): + bitbake, c = BitbakeVersion.objects.get_or_create( + name="master", + giturl="git://master", + branch="master", + dirpath="master") + + release, c = Release.objects.get_or_create(name="msater", + description="master" + "release", + branch_name="master", + helptext="latest", + bitbake_version=bitbake) + + self.release, c = Release.objects.get_or_create( + name="msater2", + description="master2" + "release2", + branch_name="master2", + helptext="latest2", + bitbake_version=bitbake) + + def test_create_new_project(self): + """ Test creating a project """ + + project_name = "masterproject" + + url = reverse('newproject') + self.get(url) + + self.enter_text('#new-project-name', project_name) + + select = Select(self.find('#projectversion')) + select.select_by_value(str(self.release.pk)) + + self.click("#create-project-button") + + # We should get redirected to the new project's page with the + # notification at the top + element = self.wait_until_visible('#project-created-notification') + + self.assertTrue(project_name in element.text, + "New project name not in new project notification") + + self.assertTrue(Project.objects.filter(name=project_name).count(), + "New project not found in database") + + def test_new_duplicates_project_name(self): + """ + Should not be able to create a new project whose name is the same + as an existing project + """ + + project_name = "dupproject" + + Project.objects.create_project(name=project_name, + release=self.release) + + url = reverse('newproject') + self.get(url) + + self.enter_text('#new-project-name', project_name) + + select = Select(self.find('#projectversion')) + select.select_by_value(str(self.release.pk)) + + element = self.wait_until_visible('#hint-error-project-name') + + self.assertTrue(("Project names must be unique" in element.text), + "Did not find unique project name error message") + + # Try and click it anyway, if it submits we'll have a new project in + # the db and assert then + try: + self.click("#create-project-button") + except InvalidElementStateException: + pass + + self.assertTrue( + (Project.objects.filter(name=project_name).count() == 1), + "New project not found in database") diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/tests/browser/test_project_config_page.py b/import-layers/yocto-poky/bitbake/lib/toaster/tests/browser/test_project_config_page.py new file mode 100644 index 000000000..071008499 --- /dev/null +++ b/import-layers/yocto-poky/bitbake/lib/toaster/tests/browser/test_project_config_page.py @@ -0,0 +1,231 @@ +#! /usr/bin/env python +# ex:ts=4:sw=4:sts=4:et +# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- +# +# BitBake Toaster Implementation +# +# Copyright (C) 2013-2016 Intel Corporation +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# 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, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import re + +from django.core.urlresolvers import reverse +from django.utils import timezone +from tests.browser.selenium_helpers import SeleniumTestCase + +from orm.models import BitbakeVersion, Release, Project, ProjectVariable + +class TestProjectConfigsPage(SeleniumTestCase): + """ Test data at /project/X/builds is displayed correctly """ + + PROJECT_NAME = 'test project' + INVALID_PATH_START_TEXT = 'The directory path should either start with a /' + INVALID_PATH_CHAR_TEXT = 'The directory path cannot include spaces or ' \ + 'any of these characters' + + def setUp(self): + bbv = BitbakeVersion.objects.create(name='bbv1', giturl='/tmp/', + branch='master', dirpath='') + release = Release.objects.create(name='release1', + bitbake_version=bbv) + self.project1 = Project.objects.create_project(name=self.PROJECT_NAME, + release=release) + self.project1.save() + + + def test_no_underscore_iamgefs_type(self): + """ + Should not accept IMAGEFS_TYPE with an underscore + """ + + imagefs_type = "foo_bar" + + ProjectVariable.objects.get_or_create(project = self.project1, name = "IMAGE_FSTYPES", value = "abcd ") + url = reverse('projectconf', args=(self.project1.id,)); + self.get(url); + + self.click('#change-image_fstypes-icon') + + self.enter_text('#new-imagefs_types', imagefs_type) + + element = self.wait_until_visible('#hintError-image-fs_type') + + self.assertTrue(("A valid image type cannot include underscores" in element.text), + "Did not find underscore error message") + + + def test_checkbox_verification(self): + """ + Should automatically check the checkbox if user enters value + text box, if value is there in the checkbox. + """ + imagefs_type = "btrfs" + + ProjectVariable.objects.get_or_create(project = self.project1, name = "IMAGE_FSTYPES", value = "abcd ") + url = reverse('projectconf', args=(self.project1.id,)); + self.get(url); + + self.click('#change-image_fstypes-icon') + + self.enter_text('#new-imagefs_types', imagefs_type) + + checkboxes = self.driver.find_elements_by_xpath("//input[@class='fs-checkbox-fstypes']") + + for checkbox in checkboxes: + if checkbox.get_attribute("value") == "btrfs": + self.assertEqual(checkbox.is_selected(), True) + + + def test_textbox_with_checkbox_verification(self): + """ + Should automatically add or remove value in textbox, if user checks + or unchecks checkboxes. + """ + + ProjectVariable.objects.get_or_create(project = self.project1, name = "IMAGE_FSTYPES", value = "abcd ") + url = reverse('projectconf', args=(self.project1.id,)); + self.get(url); + + self.click('#change-image_fstypes-icon') + + self.wait_until_visible('#new-imagefs_types') + + checkboxes_selector = '.fs-checkbox-fstypes' + + self.wait_until_visible(checkboxes_selector) + checkboxes = self.find_all(checkboxes_selector) + + for checkbox in checkboxes: + if checkbox.get_attribute("value") == "cpio": + checkbox.click() + element = self.driver.find_element_by_id('new-imagefs_types') + + self.wait_until_visible('#new-imagefs_types') + + self.assertTrue(("cpio" in element.get_attribute('value'), + "Imagefs not added into the textbox")) + checkbox.click() + self.assertTrue(("cpio" not in element.text), + "Image still present in the textbox") + + def test_set_download_dir(self): + """ + Validate the allowed and disallowed types in the directory field for + DL_DIR + """ + + ProjectVariable.objects.get_or_create(project=self.project1, + name='DL_DIR') + url = reverse('projectconf', args=(self.project1.id,)) + self.get(url) + + # activate the input to edit download dir + self.click('#change-dl_dir-icon') + self.wait_until_visible('#new-dl_dir') + + # downloads dir path doesn't start with / or ${...} + self.enter_text('#new-dl_dir', 'home/foo') + element = self.wait_until_visible('#hintError-initialChar-dl_dir') + + msg = 'downloads directory path starts with invalid character but ' \ + 'treated as valid' + self.assertTrue((self.INVALID_PATH_START_TEXT in element.text), msg) + + # downloads dir path has a space + self.driver.find_element_by_id('new-dl_dir').clear() + self.enter_text('#new-dl_dir', '/foo/bar a') + + element = self.wait_until_visible('#hintError-dl_dir') + msg = 'downloads directory path characters invalid but treated as valid' + self.assertTrue((self.INVALID_PATH_CHAR_TEXT in element.text), msg) + + # downloads dir path starts with ${...} but has a space + self.driver.find_element_by_id('new-dl_dir').clear() + self.enter_text('#new-dl_dir', '${TOPDIR}/down foo') + + element = self.wait_until_visible('#hintError-dl_dir') + msg = 'downloads directory path characters invalid but treated as valid' + self.assertTrue((self.INVALID_PATH_CHAR_TEXT in element.text), msg) + + # downloads dir path starts with / + self.driver.find_element_by_id('new-dl_dir').clear() + self.enter_text('#new-dl_dir', '/bar/foo') + + hidden_element = self.driver.find_element_by_id('hintError-dl_dir') + self.assertEqual(hidden_element.is_displayed(), False, + 'downloads directory path valid but treated as invalid') + + # downloads dir path starts with ${...} + self.driver.find_element_by_id('new-dl_dir').clear() + self.enter_text('#new-dl_dir', '${TOPDIR}/down') + + hidden_element = self.driver.find_element_by_id('hintError-dl_dir') + self.assertEqual(hidden_element.is_displayed(), False, + 'downloads directory path valid but treated as invalid') + + def test_set_sstate_dir(self): + """ + Validate the allowed and disallowed types in the directory field for + SSTATE_DIR + """ + + ProjectVariable.objects.get_or_create(project=self.project1, + name='SSTATE_DIR') + url = reverse('projectconf', args=(self.project1.id,)) + self.get(url) + + self.click('#change-sstate_dir-icon') + + self.wait_until_visible('#new-sstate_dir') + + # path doesn't start with / or ${...} + self.enter_text('#new-sstate_dir', 'home/foo') + element = self.wait_until_visible('#hintError-initialChar-sstate_dir') + + msg = 'sstate directory path starts with invalid character but ' \ + 'treated as valid' + self.assertTrue((self.INVALID_PATH_START_TEXT in element.text), msg) + + # path has a space + self.driver.find_element_by_id('new-sstate_dir').clear() + self.enter_text('#new-sstate_dir', '/foo/bar a') + + element = self.wait_until_visible('#hintError-sstate_dir') + msg = 'sstate directory path characters invalid but treated as valid' + self.assertTrue((self.INVALID_PATH_CHAR_TEXT in element.text), msg) + + # path starts with ${...} but has a space + self.driver.find_element_by_id('new-sstate_dir').clear() + self.enter_text('#new-sstate_dir', '${TOPDIR}/down foo') + + element = self.wait_until_visible('#hintError-sstate_dir') + msg = 'sstate directory path characters invalid but treated as valid' + self.assertTrue((self.INVALID_PATH_CHAR_TEXT in element.text), msg) + + # path starts with / + self.driver.find_element_by_id('new-sstate_dir').clear() + self.enter_text('#new-sstate_dir', '/bar/foo') + + hidden_element = self.driver.find_element_by_id('hintError-sstate_dir') + self.assertEqual(hidden_element.is_displayed(), False, + 'sstate directory path valid but treated as invalid') + + # paths starts with ${...} + self.driver.find_element_by_id('new-sstate_dir').clear() + self.enter_text('#new-sstate_dir', '${TOPDIR}/down') + + hidden_element = self.driver.find_element_by_id('hintError-sstate_dir') + self.assertEqual(hidden_element.is_displayed(), False, + 'sstate directory path valid but treated as invalid')
\ No newline at end of file diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/tests/browser/test_project_page.py b/import-layers/yocto-poky/bitbake/lib/toaster/tests/browser/test_project_page.py index 786bef1c6..018646332 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/tests/browser/test_project_page.py +++ b/import-layers/yocto-poky/bitbake/lib/toaster/tests/browser/test_project_page.py @@ -55,5 +55,5 @@ class TestProjectPage(SeleniumTestCase): self.get(url) # check that we get a project page with the correct heading - project_name = self.find('#project-name').text.strip() + project_name = self.find('.project-name').text.strip() self.assertEqual(project_name, self.CLI_BUILDS_PROJECT_NAME) diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/tests/browser/test_sample.py b/import-layers/yocto-poky/bitbake/lib/toaster/tests/browser/test_sample.py index 7bb8b97e8..20ec53c28 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/tests/browser/test_sample.py +++ b/import-layers/yocto-poky/bitbake/lib/toaster/tests/browser/test_sample.py @@ -37,5 +37,5 @@ class TestSample(SeleniumTestCase): def test_landing_page_has_brand(self): url = reverse('landing') self.get(url) - brand_link = self.find('span.brand a') + brand_link = self.find('.toaster-navbar-brand a.brand') self.assertEqual(brand_link.text.strip(), 'Toaster') diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/tests/browser/test_task_page.py b/import-layers/yocto-poky/bitbake/lib/toaster/tests/browser/test_task_page.py new file mode 100644 index 000000000..690d116cb --- /dev/null +++ b/import-layers/yocto-poky/bitbake/lib/toaster/tests/browser/test_task_page.py @@ -0,0 +1,76 @@ +#! /usr/bin/env python +# ex:ts=4:sw=4:sts=4:et +# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- +# +# BitBake Toaster Implementation +# +# Copyright (C) 2013-2016 Intel Corporation +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# 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, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +from django.core.urlresolvers import reverse +from django.utils import timezone +from tests.browser.selenium_helpers import SeleniumTestCase +from orm.models import Project, Build, Layer, Layer_Version, Recipe, Target +from orm.models import Task, Task_Dependency + +class TestTaskPage(SeleniumTestCase): + """ Test page which shows an individual task """ + RECIPE_NAME = 'bar' + RECIPE_VERSION = '0.1' + TASK_NAME = 'do_da_doo_ron_ron' + + def setUp(self): + now = timezone.now() + + project = Project.objects.get_or_create_default_project() + + self.build = Build.objects.create(project=project, started_on=now, + completed_on=now) + + Target.objects.create(target='foo', build=self.build) + + layer = Layer.objects.create() + + layer_version = Layer_Version.objects.create(layer=layer) + + recipe = Recipe.objects.create(name=TestTaskPage.RECIPE_NAME, + layer_version=layer_version, version=TestTaskPage.RECIPE_VERSION) + + self.task = Task.objects.create(build=self.build, recipe=recipe, + order=1, outcome=Task.OUTCOME_COVERED, task_executed=False, + task_name=TestTaskPage.TASK_NAME) + + def test_covered_task(self): + """ + Check that covered tasks are displayed for tasks which have + dependencies on themselves + """ + + # the infinite loop which of bug 9952 was down to tasks which + # depend on themselves, so add self-dependent tasks to replicate the + # situation which caused the infinite loop (now fixed) + Task_Dependency.objects.create(task=self.task, depends_on=self.task) + + url = reverse('task', args=(self.build.id, self.task.id,)) + self.get(url) + + # check that we see the task name + self.wait_until_visible('.page-header h1') + + heading = self.find('.page-header h1') + expected_heading = '%s_%s %s' % (TestTaskPage.RECIPE_NAME, + TestTaskPage.RECIPE_VERSION, TestTaskPage.TASK_NAME) + self.assertEqual(heading.text, expected_heading, + 'Heading should show recipe name, version and task') diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/tests/browser/test_toastertable_ui.py b/import-layers/yocto-poky/bitbake/lib/toaster/tests/browser/test_toastertable_ui.py new file mode 100644 index 000000000..53ddf30c3 --- /dev/null +++ b/import-layers/yocto-poky/bitbake/lib/toaster/tests/browser/test_toastertable_ui.py @@ -0,0 +1,160 @@ +#! /usr/bin/env python +# ex:ts=4:sw=4:sts=4:et +# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- +# +# BitBake Toaster Implementation +# +# Copyright (C) 2013-2016 Intel Corporation +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# 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, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +from datetime import datetime + +from django.core.urlresolvers import reverse +from django.utils import timezone +from tests.browser.selenium_helpers import SeleniumTestCase +from orm.models import BitbakeVersion, Release, Project, Build + +class TestToasterTableUI(SeleniumTestCase): + """ + Tests for the UI elements of ToasterTable (sorting etc.); + note that the tests cover generic functionality of ToasterTable which + manifests as UI elements in the browser, and can only be tested via + Selenium. + """ + + def setUp(self): + pass + + def _get_orderby_heading(self, table): + """ + Get the current order by finding the column heading in <table> with + the sorted class on it. + + table: WebElement for a ToasterTable + """ + selector = 'thead a.sorted' + heading = table.find_element_by_css_selector(selector) + return heading.get_attribute('innerHTML').strip() + + def _get_datetime_from_cell(self, row, selector): + """ + Return the value in the cell selected by <selector> on <row> as a + datetime. + + row: <tr> WebElement for a row in the ToasterTable + selector: CSS selector to use to find the cell containing the date time + string + """ + cell = row.find_element_by_css_selector(selector) + cell_text = cell.get_attribute('innerHTML').strip() + return datetime.strptime(cell_text, '%d/%m/%y %H:%M') + + def test_revert_orderby(self): + """ + Test that sort order for a table reverts to the default sort order + if the current sort column is hidden. + """ + now = timezone.now() + later = now + timezone.timedelta(hours=1) + even_later = later + timezone.timedelta(hours=1) + + bbv = BitbakeVersion.objects.create(name='test bbv', giturl='/tmp/', + branch='master', dirpath='') + release = Release.objects.create(name='test release', + branch_name='master', + bitbake_version=bbv) + + project = Project.objects.create_project('project', release) + + # set up two builds which will order differently when sorted by + # started_on or completed_on + + # started first, finished last + build1 = Build.objects.create(project=project, + started_on=now, + completed_on=even_later, + outcome=Build.SUCCEEDED) + + # started second, finished first + build2 = Build.objects.create(project=project, + started_on=later, + completed_on=later, + outcome=Build.SUCCEEDED) + + url = reverse('all-builds') + self.get(url) + table = self.wait_until_visible('#allbuildstable') + + # check ordering (default is by -completed_on); so build1 should be + # first as it finished last + active_heading = self._get_orderby_heading(table) + self.assertEqual(active_heading, 'Completed on', + 'table should be sorted by "Completed on" by default') + + row_selector = '#allbuildstable tbody tr' + cell_selector = 'td.completed_on' + + rows = self.find_all(row_selector) + row1_completed_on = self._get_datetime_from_cell(rows[0], cell_selector) + row2_completed_on = self._get_datetime_from_cell(rows[1], cell_selector) + self.assertTrue(row1_completed_on > row2_completed_on, + 'table should be sorted by -completed_on') + + # turn on started_on column + self.click('#edit-columns-button') + self.click('#checkbox-started_on') + + # sort by started_on column + links = table.find_elements_by_css_selector('th.started_on a') + for link in links: + if link.get_attribute('innerHTML').strip() == 'Started on': + link.click() + break + + # wait for table data to reload in response to new sort + self.wait_until_visible('#allbuildstable') + + # check ordering; build1 should be first + active_heading = self._get_orderby_heading(table) + self.assertEqual(active_heading, 'Started on', + 'table should be sorted by "Started on"') + + cell_selector = 'td.started_on' + + rows = self.find_all(row_selector) + row1_started_on = self._get_datetime_from_cell(rows[0], cell_selector) + row2_started_on = self._get_datetime_from_cell(rows[1], cell_selector) + self.assertTrue(row1_started_on < row2_started_on, + 'table should be sorted by started_on') + + # turn off started_on column + self.click('#edit-columns-button') + self.click('#checkbox-started_on') + + # wait for table data to reload in response to new sort + self.wait_until_visible('#allbuildstable') + + # check ordering (should revert to completed_on); build2 should be first + active_heading = self._get_orderby_heading(table) + self.assertEqual(active_heading, 'Completed on', + 'table should be sorted by "Completed on" after hiding sort column') + + cell_selector = 'td.completed_on' + + rows = self.find_all(row_selector) + row1_completed_on = self._get_datetime_from_cell(rows[0], cell_selector) + row2_completed_on = self._get_datetime_from_cell(rows[1], cell_selector) + self.assertTrue(row1_completed_on > row2_completed_on, + 'table should be sorted by -completed_on') diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/tests/builds/README b/import-layers/yocto-poky/bitbake/lib/toaster/tests/builds/README new file mode 100644 index 000000000..4a3b5328b --- /dev/null +++ b/import-layers/yocto-poky/bitbake/lib/toaster/tests/builds/README @@ -0,0 +1,14 @@ +# Running build tests + +These tests are to test the running of builds and the data produced by the builds. +Your oe build environment must be sourced/initialised for these tests to run. + +The simplest way to run the tests are the following commands: + +$ . oe-init-build-env +$ cd bitbake/lib/toaster/ # path my vary but this is into toaster's directory +$ DJANGO_SETTINGS_MODULE='toastermain.settings_test' ./manage.py test tests.builds + +Optional environment variables: + - TOASTER_DIR (where toaster keeps it's artifacts) + - TOASTER_CONF a path to the toasterconf.json file. This will need to be set if you don't execute the tests from toaster's own directory. diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/tests/builds/__init__.py b/import-layers/yocto-poky/bitbake/lib/toaster/tests/builds/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/import-layers/yocto-poky/bitbake/lib/toaster/tests/builds/__init__.py diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/tests/builds/buildtest.py b/import-layers/yocto-poky/bitbake/lib/toaster/tests/builds/buildtest.py new file mode 100644 index 000000000..fc7bd5b64 --- /dev/null +++ b/import-layers/yocto-poky/bitbake/lib/toaster/tests/builds/buildtest.py @@ -0,0 +1,134 @@ +#! /usr/bin/env python +# ex:ts=4:sw=4:sts=4:et +# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- +# +# BitBake Toaster Implementation +# +# Copyright (C) 2016 Intel Corporation +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# 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, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import os +import sys +import time +import unittest + +from orm.models import Project, Release, ProjectTarget, Build +from bldcontrol.models import BuildEnvironment + +from bldcontrol.management.commands.loadconf import Command\ + as LoadConfigCommand + +from bldcontrol.management.commands.runbuilds import Command\ + as RunBuildsCommand + +import subprocess + +# We use unittest.TestCase instead of django.test.TestCase because we don't +# want to wrap everything in a database transaction as an external process +# (bitbake needs access to the database) + + +class BuildTest(unittest.TestCase): + + PROJECT_NAME = "Testbuild" + + def build(self, target): + # So that the buildinfo helper uses the test database' + self.assertEqual( + os.environ.get('DJANGO_SETTINGS_MODULE', ''), + 'toastermain.settings-test', + "Please initialise django with the tests settings: " + "DJANGO_SETTINGS_MODULE='toastermain.settings-test'") + + if self.target_already_built(target): + return + + # Take a guess at the location of the toasterconf + poky_toaster_conf = '../../../meta-poky/conf/toasterconf.json' + oe_toaster_conf = '../../../meta/conf/toasterconf.json' + env_toaster_conf = os.environ.get('TOASTER_CONF') + + config_file = None + if env_toaster_conf: + config_file = env_toaster_conf + else: + if os.path.exists(poky_toaster_conf): + config_file = poky_toaster_conf + elif os.path.exists(oe_toaster_conf): + config_file = oe_toaster_conf + + self.assertIsNotNone(config_file, + "Default locations for toasterconf not found" + "please set $TOASTER_CONF manually") + + # Setup the release information and default layers + print("\nImporting file: %s" % config_file) + os.environ['TOASTER_CONF'] = config_file + LoadConfigCommand()._import_layer_config(config_file) + + os.environ['TOASTER_DIR'] = \ + os.path.abspath(os.environ['BUILDDIR'] + "/../") + + os.environ['BBBASEDIR'] = \ + subprocess.check_output('which bitbake', shell=True) + + BuildEnvironment.objects.get_or_create( + betype=BuildEnvironment.TYPE_LOCAL, + sourcedir=os.environ['TOASTER_DIR'], + builddir=os.environ['BUILDDIR'] + ) + + release = Release.objects.get(name='local') + + # Create a project for this build to run in + try: + project = Project.objects.get(name=BuildTest.PROJECT_NAME) + except Project.DoesNotExist: + project = Project.objects.create_project( + name=BuildTest.PROJECT_NAME, + release=release + ) + + ProjectTarget.objects.create(project=project, + target=target, + task="") + build_request = project.schedule_build() + + # run runbuilds command to dispatch the build + # e.g. manage.py runubilds + RunBuildsCommand().runbuild() + + build_pk = build_request.build.pk + while Build.objects.get(pk=build_pk).outcome == Build.IN_PROGRESS: + sys.stdout.write("\rBuilding %s %d%%" % + (target, + build_request.build.completeper())) + sys.stdout.flush() + time.sleep(1) + + self.assertNotEqual(build_request.build.outcome, + Build.SUCCEEDED, "Build did not SUCCEEDED") + print("\nBuild finished") + return build_request.build + + def target_already_built(self, target): + """ If the target is already built no need to build it again""" + for build in Build.objects.filter( + project__name=BuildTest.PROJECT_NAME): + targets = build.target_set.values_list('target', flat=True) + if target in targets: + return True + + return False diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/tests/builds/test_core_image_min.py b/import-layers/yocto-poky/bitbake/lib/toaster/tests/builds/test_core_image_min.py new file mode 100644 index 000000000..dec0bfa7f --- /dev/null +++ b/import-layers/yocto-poky/bitbake/lib/toaster/tests/builds/test_core_image_min.py @@ -0,0 +1,396 @@ +#! /usr/bin/env python +# ex:ts=4:sw=4:sts=4:et +# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- +# +# BitBake Toaster Implementation +# +# Copyright (C) 2016 Intel Corporation +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# 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, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +# Tests were part of openembedded-core oe selftest Authored by: Lucian Musat +# Ionut Chisanovici, Paul Eggleton and Cristian Iorga + +import os + +from django.db.models import Q + +from orm.models import Target_Image_File, Target_Installed_Package, Task +from orm.models import Package_Dependency, Recipe_Dependency, Build +from orm.models import Task_Dependency, Package, Target, Recipe +from orm.models import CustomImagePackage + +from buildtest import BuildTest + + +class BuildCoreImageMinimal(BuildTest): + """Build core-image-minimal and test the results""" + + def setUp(self): + self.build("core-image-minimal") + + # Check if build name is unique - tc_id=795 + def test_Build_Unique_Name(self): + all_builds = Build.objects.all().count() + distinct_builds = Build.objects.values('id').distinct().count() + self.assertEqual(distinct_builds, + all_builds, + msg='Build name is not unique') + + # Check if build cooker log path is unique - tc_id=819 + def test_Build_Unique_Cooker_Log_Path(self): + distinct_path = Build.objects.values( + 'cooker_log_path').distinct().count() + total_builds = Build.objects.values('id').count() + self.assertEqual(distinct_path, + total_builds, + msg='Build cooker log path is not unique') + + # Check if task order is unique for one build - tc=824 + def test_Task_Unique_Order(self): + builds = Build.objects.values('id') + cnt_err = [] + + for build in builds: + total_task_order = Task.objects.filter( + build=build['id']).values('order').count() + distinct_task_order = Task.objects.filter( + build=build['id']).values('order').distinct().count() + + if (total_task_order != distinct_task_order): + cnt_err.append(build['id']) + + self.assertEqual(len(cnt_err), + 0, + msg='Errors for build id: %s' % cnt_err) + + # Check task order sequence for one build - tc=825 + def test_Task_Order_Sequence(self): + builds = builds = Build.objects.values('id') + cnt_err = [] + for build in builds: + tasks = Task.objects.filter( + Q(build=build['id']), + ~Q(order=None), + ~Q(task_name__contains='_setscene') + ).values('id', 'order').order_by("order") + + cnt_tasks = 0 + for task in tasks: + cnt_tasks += 1 + if (task['order'] != cnt_tasks): + cnt_err.append(task['id']) + self.assertEqual( + len(cnt_err), 0, msg='Errors for task id: %s' % cnt_err) + + # Check if disk_io matches the difference between EndTimeIO and + # StartTimeIO in build stats - tc=828 + # def test_Task_Disk_IO_TC828(self): + + # Check if outcome = 2 (SSTATE) then sstate_result must be 3 (RESTORED) - + # tc=832 + def test_Task_If_Outcome_2_Sstate_Result_Must_Be_3(self): + tasks = Task.objects.filter(outcome=2).values('id', 'sstate_result') + cnt_err = [] + for task in tasks: + if (task['sstate_result'] != 3): + cnt_err.append(task['id']) + + self.assertEqual(len(cnt_err), + 0, + msg='Errors for task id: %s' % cnt_err) + + # Check if outcome = 1 (COVERED) or 3 (EXISTING) then sstate_result must + # be 0 (SSTATE_NA) - tc=833 + def test_Task_If_Outcome_1_3_Sstate_Result_Must_Be_0(self): + tasks = Task.objects.filter( + outcome__in=(Task.OUTCOME_COVERED, + Task.OUTCOME_PREBUILT)).values('id', + 'task_name', + 'sstate_result') + cnt_err = [] + + for task in tasks: + if (task['sstate_result'] != Task.SSTATE_NA and + task['sstate_result'] != Task.SSTATE_MISS): + cnt_err.append({'id': task['id'], + 'name': task['task_name'], + 'sstate_result': task['sstate_result'], + }) + + self.assertEqual(len(cnt_err), + 0, + msg='Errors for task id: %s' % cnt_err) + + # Check if outcome is 0 (SUCCESS) or 4 (FAILED) then sstate_result must be + # 0 (NA), 1 (MISS) or 2 (FAILED) - tc=834 + def test_Task_If_Outcome_0_4_Sstate_Result_Must_Be_0_1_2(self): + tasks = Task.objects.filter( + outcome__in=(0, 4)).values('id', 'sstate_result') + cnt_err = [] + + for task in tasks: + if (task['sstate_result'] not in [0, 1, 2]): + cnt_err.append(task['id']) + + self.assertEqual(len(cnt_err), + 0, + msg='Errors for task id: %s' % cnt_err) + + # Check if task_executed = TRUE (1), script_type must be 0 (CODING_NA), 2 + # (CODING_PYTHON), 3 (CODING_SHELL) - tc=891 + def test_Task_If_Task_Executed_True_Script_Type_0_2_3(self): + tasks = Task.objects.filter( + task_executed=1).values('id', 'script_type') + cnt_err = [] + + for task in tasks: + if (task['script_type'] not in [0, 2, 3]): + cnt_err.append(task['id']) + self.assertEqual(len(cnt_err), + 0, + msg='Errors for task id: %s' % cnt_err) + + # Check if task_executed = TRUE (1), outcome must be 0 (SUCCESS) or 4 + # (FAILED) - tc=836 + def test_Task_If_Task_Executed_True_Outcome_0_4(self): + tasks = Task.objects.filter(task_executed=1).values('id', 'outcome') + cnt_err = [] + + for task in tasks: + if (task['outcome'] not in [0, 4]): + cnt_err.append(task['id']) + + self.assertEqual(len(cnt_err), + 0, + msg='Errors for task id: %s' % cnt_err) + + # Check if task_executed = FALSE (0), script_type must be 0 - tc=890 + def test_Task_If_Task_Executed_False_Script_Type_0(self): + tasks = Task.objects.filter( + task_executed=0).values('id', 'script_type') + cnt_err = [] + + for task in tasks: + if (task['script_type'] != 0): + cnt_err.append(task['id']) + + self.assertEqual(len(cnt_err), + 0, + msg='Errors for task id: %s' % cnt_err) + + # Check if task_executed = FALSE (0) and build outcome = SUCCEEDED (0), + # task outcome must be 1 (COVERED), 2 (CACHED), 3 (PREBUILT), 5 (EMPTY) - + # tc=837 + def test_Task_If_Task_Executed_False_Outcome_1_2_3_5(self): + builds = Build.objects.filter(outcome=0).values('id') + cnt_err = [] + for build in builds: + tasks = Task.objects.filter( + build=build['id'], task_executed=0).values('id', 'outcome') + for task in tasks: + if (task['outcome'] not in [1, 2, 3, 5]): + cnt_err.append(task['id']) + + self.assertEqual(len(cnt_err), + 0, + msg='Errors for task id: %s' % cnt_err) + + # Key verification - tc=888 + def test_Target_Installed_Package(self): + rows = Target_Installed_Package.objects.values('id', + 'target_id', + 'package_id') + cnt_err = [] + + for row in rows: + target = Target.objects.filter(id=row['target_id']).values('id') + package = Package.objects.filter(id=row['package_id']).values('id') + if (not target or not package): + cnt_err.append(row['id']) + self.assertEqual(len(cnt_err), + 0, + msg='Errors for target installed package id: %s' % + cnt_err) + + # Key verification - tc=889 + def test_Task_Dependency(self): + rows = Task_Dependency.objects.values('id', + 'task_id', + 'depends_on_id') + cnt_err = [] + for row in rows: + task_id = Task.objects.filter(id=row['task_id']).values('id') + depends_on_id = Task.objects.filter( + id=row['depends_on_id']).values('id') + if (not task_id or not depends_on_id): + cnt_err.append(row['id']) + self.assertEqual(len(cnt_err), + 0, + msg='Errors for task dependency id: %s' % cnt_err) + + # Check if build target file_name is populated only if is_image=true AND + # orm_build.outcome=0 then if the file exists and its size matches + # the file_size value. Need to add the tc in the test run + def test_Target_File_Name_Populated(self): + builds = Build.objects.filter(outcome=0).values('id') + for build in builds: + targets = Target.objects.filter( + build_id=build['id'], is_image=1).values('id') + for target in targets: + target_files = Target_Image_File.objects.filter( + target_id=target['id']).values('id', + 'file_name', + 'file_size') + cnt_err = [] + for file_info in target_files: + target_id = file_info['id'] + target_file_name = file_info['file_name'] + target_file_size = file_info['file_size'] + if (not target_file_name or not target_file_size): + cnt_err.append(target_id) + else: + if (not os.path.exists(target_file_name)): + cnt_err.append(target_id) + else: + if (os.path.getsize(target_file_name) != + target_file_size): + cnt_err.append(target_id) + self.assertEqual(len(cnt_err), 0, + msg='Errors for target image file id: %s' % + cnt_err) + + # Key verification - tc=884 + def test_Package_Dependency(self): + cnt_err = [] + deps = Package_Dependency.objects.values( + 'id', 'package_id', 'depends_on_id') + for dep in deps: + if (dep['package_id'] == dep['depends_on_id']): + cnt_err.append(dep['id']) + self.assertEqual(len(cnt_err), 0, + msg='Errors for package dependency id: %s' % cnt_err) + + # Recipe key verification, recipe name does not depends on a recipe having + # the same name - tc=883 + def test_Recipe_Dependency(self): + deps = Recipe_Dependency.objects.values( + 'id', 'recipe_id', 'depends_on_id') + cnt_err = [] + for dep in deps: + if (not dep['recipe_id'] or not dep['depends_on_id']): + cnt_err.append(dep['id']) + else: + name = Recipe.objects.filter( + id=dep['recipe_id']).values('name') + dep_name = Recipe.objects.filter( + id=dep['depends_on_id']).values('name') + if (name == dep_name): + cnt_err.append(dep['id']) + self.assertEqual(len(cnt_err), 0, + msg='Errors for recipe dependency id: %s' % cnt_err) + + # Check if package name does not start with a number (0-9) - tc=846 + def test_Package_Name_For_Number(self): + packages = Package.objects.filter(~Q(size=-1)).values('id', 'name') + cnt_err = [] + for package in packages: + if (package['name'][0].isdigit() is True): + cnt_err.append(package['id']) + self.assertEqual( + len(cnt_err), 0, msg='Errors for package id: %s' % cnt_err) + + # Check if package version starts with a number (0-9) - tc=847 + def test_Package_Version_Starts_With_Number(self): + packages = Package.objects.filter( + ~Q(size=-1)).values('id', 'version') + cnt_err = [] + for package in packages: + if (package['version'][0].isdigit() is False): + cnt_err.append(package['id']) + self.assertEqual( + len(cnt_err), 0, msg='Errors for package id: %s' % cnt_err) + + # Check if package revision starts with 'r' - tc=848 + def test_Package_Revision_Starts_With_r(self): + packages = Package.objects.filter( + ~Q(size=-1)).values('id', 'revision') + cnt_err = [] + for package in packages: + if (package['revision'][0].startswith("r") is False): + cnt_err.append(package['id']) + self.assertEqual( + len(cnt_err), 0, msg='Errors for package id: %s' % cnt_err) + + # Check the validity of the package build_id + # TC must be added in test run + def test_Package_Build_Id(self): + packages = Package.objects.filter( + ~Q(size=-1)).values('id', 'build_id') + cnt_err = [] + for package in packages: + build_id = Build.objects.filter( + id=package['build_id']).values('id') + if (not build_id): + # They have no build_id but if they are + # CustomImagePackage that's expected + try: + CustomImagePackage.objects.get(pk=package['id']) + except CustomImagePackage.DoesNotExist: + cnt_err.append(package['id']) + + self.assertEqual(len(cnt_err), + 0, + msg="Errors for package id: %s they have no build" + "associated with them" % cnt_err) + + # Check the validity of package recipe_id + # TC must be added in test run + def test_Package_Recipe_Id(self): + packages = Package.objects.filter( + ~Q(size=-1)).values('id', 'recipe_id') + cnt_err = [] + for package in packages: + recipe_id = Recipe.objects.filter( + id=package['recipe_id']).values('id') + if (not recipe_id): + cnt_err.append(package['id']) + self.assertEqual( + len(cnt_err), 0, msg='Errors for package id: %s' % cnt_err) + + # Check if package installed_size field is not null + # TC must be aded in test run + def test_Package_Installed_Size_Not_NULL(self): + packages = Package.objects.filter( + installed_size__isnull=True).values('id') + cnt_err = [] + for package in packages: + cnt_err.append(package['id']) + self.assertEqual( + len(cnt_err), 0, msg='Errors for package id: %s' % cnt_err) + + def test_custom_packages_generated(self): + """Test if there is a corresponding generated CustomImagePackage""" + """ for each of the packages generated""" + missing_packages = [] + + for package in Package.objects.all(): + try: + CustomImagePackage.objects.get(name=package.name) + except CustomImagePackage.DoesNotExist: + missing_packages.append(package.name) + + self.assertEqual(len(missing_packages), 0, + "Some package were created from the build but their" + " corresponding CustomImagePackage was not found") diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/tests/db/__init__.py b/import-layers/yocto-poky/bitbake/lib/toaster/tests/db/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/import-layers/yocto-poky/bitbake/lib/toaster/tests/db/__init__.py diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/tests/db/test_db.py b/import-layers/yocto-poky/bitbake/lib/toaster/tests/db/test_db.py new file mode 100644 index 000000000..a0f5f6ec0 --- /dev/null +++ b/import-layers/yocto-poky/bitbake/lib/toaster/tests/db/test_db.py @@ -0,0 +1,55 @@ +# The MIT License (MIT) +# +# Copyright (c) 2016 Damien Lespiau +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +import sys + +try: + from StringIO import StringIO +except ImportError: + from io import StringIO + +from contextlib import contextmanager + +from django.core import management +from django.test import TestCase + + +@contextmanager +def capture(command, *args, **kwargs): + out, sys.stdout = sys.stdout, StringIO() + command(*args, **kwargs) + sys.stdout.seek(0) + yield sys.stdout.read() + sys.stdout = out + + +def makemigrations(): + management.call_command('makemigrations') + + +class MigrationTest(TestCase): + + def testPendingMigration(self): + """Make sure there's no pending migration.""" + + with capture(makemigrations) as output: + self.assertEqual(output, "No changes detected\n") diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/tests/functional/README b/import-layers/yocto-poky/bitbake/lib/toaster/tests/functional/README new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/import-layers/yocto-poky/bitbake/lib/toaster/tests/functional/README diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/api.py b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/api.py index 42901f750..ae1f15077 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/api.py +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/api.py @@ -16,14 +16,32 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# Please run flake8 on this file before sending patches -# Temporary home for the UI's misc API +import re +import logging +from collections import Counter -from orm.models import Project, ProjectTarget, Build +from orm.models import Project, ProjectTarget, Build, Layer_Version +from orm.models import LayerVersionDependency, LayerSource, ProjectLayer +from orm.models import Recipe, CustomImageRecipe, CustomImagePackage +from orm.models import Layer, Target, Package, Package_Dependency +from orm.models import ProjectVariable from bldcontrol.models import BuildRequest from bldcontrol import bbcontroller + from django.http import HttpResponse, JsonResponse from django.views.generic import View +from django.core.urlresolvers import reverse +from django.db.models import Q, F +from django.db import Error +from toastergui.templatetags.projecttags import filtered_filesizeformat + +logger = logging.getLogger("toaster") + + +def error_response(error): + return JsonResponse({"error": error}) class XhrBuildRequest(View): @@ -31,6 +49,28 @@ class XhrBuildRequest(View): def get(self, request, *args, **kwargs): return HttpResponse() + @staticmethod + def cancel_build(br): + """Cancel a build request""" + try: + bbctrl = bbcontroller.BitbakeController(br.environment) + bbctrl.forceShutDown() + except: + # We catch a bunch of exceptions here because + # this is where the server has not had time to start up + # and the build request or build is in transit between + # processes. + # We can safely just set the build as cancelled + # already as it never got started + build = br.build + build.outcome = Build.CANCELLED + build.save() + + # We now hand over to the buildinfohelper to update the + # build state once we've finished cancelling + br.state = BuildRequest.REQ_CANCELLING + br.save() + def post(self, request, *args, **kwargs): """ Build control @@ -56,55 +96,782 @@ class XhrBuildRequest(View): for i in request.POST['buildCancel'].strip().split(" "): try: br = BuildRequest.objects.get(project=project, pk=i) - - try: - bbctrl = bbcontroller.BitbakeController(br.environment) - bbctrl.forceShutDown() - except: - # We catch a bunch of exceptions here because - # this is where the server has not had time to start up - # and the build request or build is in transit between - # processes. - # We can safely just set the build as cancelled - # already as it never got started - build = br.build - build.outcome = Build.CANCELLED - build.save() - - # We now hand over to the buildinfohelper to update the - # build state once we've finished cancelling - br.state = BuildRequest.REQ_CANCELLING - br.save() - + self.cancel_build(br) except BuildRequest.DoesNotExist: - return JsonResponse({'error':'No such build id %s' % i}) + return error_response('No such build request id %s' % i) - return JsonResponse({'error': 'ok'}) + return error_response('ok') if 'buildDelete' in request.POST: for i in request.POST['buildDelete'].strip().split(" "): try: - BuildRequest.objects.select_for_update().get(project = project, pk = i, state__lte = BuildRequest.REQ_DELETED).delete() + BuildRequest.objects.select_for_update().get( + project=project, + pk=i, + state__lte=BuildRequest.REQ_DELETED).delete() + except BuildRequest.DoesNotExist: pass - return JsonResponse({'error': 'ok' }) + return error_response("ok") if 'targets' in request.POST: - ProjectTarget.objects.filter(project = project).delete() + ProjectTarget.objects.filter(project=project).delete() s = str(request.POST['targets']) - for t in s.translate(None, ";%|\"").split(" "): + for t in re.sub(r'[;%|"]', '', s).split(" "): if ":" in t: target, task = t.split(":") else: target = t task = "" - ProjectTarget.objects.create(project = project, - target = target, - task = task) + ProjectTarget.objects.create(project=project, + target=target, + task=task) project.schedule_build() - return JsonResponse({'error': 'ok' }) + return error_response('ok') response = HttpResponse() response.status_code = 500 return response + + +class XhrLayer(View): + """ Get and Update Layer information """ + + def post(self, request, *args, **kwargs): + """ + Update a layer + + Entry point: /xhr_layer/<layerversion_id> + Method: POST + + Args: + vcs_url, dirpath, commit, up_branch, summary, description, + local_source_dir + + add_dep = append a layerversion_id as a dependency + rm_dep = remove a layerversion_id as a depedency + Returns: + {"error": "ok"} + or + {"error": <error message>} + """ + + try: + # We currently only allow Imported layers to be edited + layer_version = Layer_Version.objects.get( + id=kwargs['layerversion_id'], + project=kwargs['pid'], + layer_source=LayerSource.TYPE_IMPORTED) + + except Layer_Version.DoesNotExist: + return error_response("Cannot find imported layer to update") + + if "vcs_url" in request.POST: + layer_version.layer.vcs_url = request.POST["vcs_url"] + if "dirpath" in request.POST: + layer_version.dirpath = request.POST["dirpath"] + if "commit" in request.POST: + layer_version.commit = request.POST["commit"] + layer_version.branch = request.POST["commit"] + if "summary" in request.POST: + layer_version.layer.summary = request.POST["summary"] + if "description" in request.POST: + layer_version.layer.description = request.POST["description"] + if "local_source_dir" in request.POST: + layer_version.layer.local_source_dir = \ + request.POST["local_source_dir"] + + if "add_dep" in request.POST: + lvd = LayerVersionDependency( + layer_version=layer_version, + depends_on_id=request.POST["add_dep"]) + lvd.save() + + if "rm_dep" in request.POST: + rm_dep = LayerVersionDependency.objects.get( + layer_version=layer_version, + depends_on_id=request.POST["rm_dep"]) + rm_dep.delete() + + try: + layer_version.layer.save() + layer_version.save() + except Exception as e: + return error_response("Could not update layer version entry: %s" + % e) + + return JsonResponse({"error": "ok"}) + + def delete(self, request, *args, **kwargs): + try: + # We currently only allow Imported layers to be deleted + layer_version = Layer_Version.objects.get( + id=kwargs['layerversion_id'], + project=kwargs['pid'], + layer_source=LayerSource.TYPE_IMPORTED) + except Layer_Version.DoesNotExist: + return error_response("Cannot find imported layer to delete") + + try: + ProjectLayer.objects.get(project=kwargs['pid'], + layercommit=layer_version).delete() + except ProjectLayer.DoesNotExist: + pass + + layer_version.layer.delete() + layer_version.delete() + + return JsonResponse({ + "error": "ok", + "gotoUrl": reverse('projectlayers', args=(kwargs['pid'],)) + }) + + +class XhrCustomRecipe(View): + """ Create a custom image recipe """ + + def post(self, request, *args, **kwargs): + """ + Custom image recipe REST API + + Entry point: /xhr_customrecipe/ + Method: POST + + Args: + name: name of custom recipe to create + project: target project id of orm.models.Project + base: base recipe id of orm.models.Recipe + + Returns: + {"error": "ok", + "url": <url of the created recipe>} + or + {"error": <error message>} + """ + # check if request has all required parameters + for param in ('name', 'project', 'base'): + if param not in request.POST: + return error_response("Missing parameter '%s'" % param) + + # get project and baserecipe objects + params = {} + for name, model in [("project", Project), + ("base", Recipe)]: + value = request.POST[name] + try: + params[name] = model.objects.get(id=value) + except model.DoesNotExist: + return error_response("Invalid %s id %s" % (name, value)) + + # create custom recipe + try: + + # Only allowed chars in name are a-z, 0-9 and - + if re.search(r'[^a-z|0-9|-]', request.POST["name"]): + return error_response("invalid-name") + + custom_images = CustomImageRecipe.objects.all() + + # Are there any recipes with this name already in our project? + existing_image_recipes_in_project = custom_images.filter( + name=request.POST["name"], project=params["project"]) + + if existing_image_recipes_in_project.count() > 0: + return error_response("image-already-exists") + + # Are there any recipes with this name which aren't custom + # image recipes? + custom_image_ids = custom_images.values_list('id', flat=True) + existing_non_image_recipes = Recipe.objects.filter( + Q(name=request.POST["name"]) & ~Q(pk__in=custom_image_ids) + ) + + if existing_non_image_recipes.count() > 0: + return error_response("recipe-already-exists") + + # create layer 'Custom layer' and verion if needed + layer = Layer.objects.get_or_create( + name=CustomImageRecipe.LAYER_NAME, + summary="Layer for custom recipes", + vcs_url="file:///toaster_created_layer")[0] + + # Check if we have a layer version already + # We don't use get_or_create here because the dirpath will change + # and is a required field + lver = Layer_Version.objects.filter(Q(project=params['project']) & + Q(layer=layer) & + Q(build=None)).last() + if lver is None: + lver, created = Layer_Version.objects.get_or_create( + project=params['project'], + layer=layer, + dirpath="toaster_created_layer") + + # Add a dependency on our layer to the base recipe's layer + LayerVersionDependency.objects.get_or_create( + layer_version=lver, + depends_on=params["base"].layer_version) + + # Add it to our current project if needed + ProjectLayer.objects.get_or_create(project=params['project'], + layercommit=lver, + optional=False) + + # Create the actual recipe + recipe, created = CustomImageRecipe.objects.get_or_create( + name=request.POST["name"], + base_recipe=params["base"], + project=params["project"], + layer_version=lver, + is_image=True) + + # If we created the object then setup these fields. They may get + # overwritten later on and cause the get_or_create to create a + # duplicate if they've changed. + if created: + recipe.file_path = request.POST["name"] + recipe.license = "MIT" + recipe.version = "0.1" + recipe.save() + + except Error as err: + return error_response("Can't create custom recipe: %s" % err) + + # Find the package list from the last build of this recipe/target + target = Target.objects.filter(Q(build__outcome=Build.SUCCEEDED) & + Q(build__project=params['project']) & + (Q(target=params['base'].name) | + Q(target=recipe.name))).last() + if target: + # Copy in every package + # We don't want these packages to be linked to anything because + # that underlying data may change e.g. delete a build + for tpackage in target.target_installed_package_set.all(): + try: + built_package = tpackage.package + # The package had no recipe information so is a ghost + # package skip it + if built_package.recipe is None: + continue + + config_package = CustomImagePackage.objects.get( + name=built_package.name) + + recipe.includes_set.add(config_package) + except Exception as e: + logger.warning("Error adding package %s %s" % + (tpackage.package.name, e)) + pass + + return JsonResponse( + {"error": "ok", + "packages": recipe.get_all_packages().count(), + "url": reverse('customrecipe', args=(params['project'].pk, + recipe.id))}) + + +class XhrCustomRecipeId(View): + """ + Set of ReST API processors working with recipe id. + + Entry point: /xhr_customrecipe/<recipe_id> + + Methods: + GET - Get details of custom image recipe + DELETE - Delete custom image recipe + + Returns: + GET: + {"error": "ok", + "info": dictionary of field name -> value pairs + of the CustomImageRecipe model} + DELETE: + {"error": "ok"} + or + {"error": <error message>} + """ + @staticmethod + def _get_ci_recipe(recipe_id): + """ Get Custom Image recipe or return an error response""" + try: + custom_recipe = \ + CustomImageRecipe.objects.get(pk=recipe_id) + return custom_recipe, None + + except CustomImageRecipe.DoesNotExist: + return None, error_response("Custom recipe with id=%s " + "not found" % recipe_id) + + def get(self, request, *args, **kwargs): + custom_recipe, error = self._get_ci_recipe(kwargs['recipe_id']) + if error: + return error + + if request.method == 'GET': + info = {"id": custom_recipe.id, + "name": custom_recipe.name, + "base_recipe_id": custom_recipe.base_recipe.id, + "project_id": custom_recipe.project.id} + + return JsonResponse({"error": "ok", "info": info}) + + def delete(self, request, *args, **kwargs): + custom_recipe, error = self._get_ci_recipe(kwargs['recipe_id']) + if error: + return error + + project = custom_recipe.project + + custom_recipe.delete() + return JsonResponse({"error": "ok", + "gotoUrl": reverse("projectcustomimages", + args=(project.pk,))}) + + +class XhrCustomRecipePackages(View): + """ + ReST API to add/remove packages to/from custom recipe. + + Entry point: /xhr_customrecipe/<recipe_id>/packages/<package_id> + Methods: + PUT - Add package to the recipe + DELETE - Delete package from the recipe + GET - Get package information + + Returns: + {"error": "ok"} + or + {"error": <error message>} + """ + @staticmethod + def _get_package(package_id): + try: + package = CustomImagePackage.objects.get(pk=package_id) + return package, None + except Package.DoesNotExist: + return None, error_response("Package with id=%s " + "not found" % package_id) + + def _traverse_dependents(self, next_package_id, + rev_deps, all_current_packages, tree_level=0): + """ + Recurse through reverse dependency tree for next_package_id. + Limit the reverse dependency search to packages not already scanned, + that is, not already in rev_deps. + Limit the scan to a depth (tree_level) not exceeding the count of + all packages in the custom image, and if that depth is exceeded + return False, pop out of the recursion, and write a warning + to the log, but this is unlikely, suggesting a dependency loop + not caught by bitbake. + On return, the input/output arg rev_deps is appended with queryset + dictionary elements, annotated for use in the customimage template. + The list has unsorted, but unique elements. + """ + max_dependency_tree_depth = all_current_packages.count() + if tree_level >= max_dependency_tree_depth: + logger.warning( + "The number of reverse dependencies " + "for this package exceeds " + max_dependency_tree_depth + + " and the remaining reverse dependencies will not be removed") + return True + + package = CustomImagePackage.objects.get(id=next_package_id) + dependents = \ + package.package_dependencies_target.annotate( + name=F('package__name'), + pk=F('package__pk'), + size=F('package__size'), + ).values("name", "pk", "size").exclude( + ~Q(pk__in=all_current_packages) + ) + + for pkg in dependents: + if pkg in rev_deps: + # already seen, skip dependent search + continue + + rev_deps.append(pkg) + if (self._traverse_dependents(pkg["pk"], rev_deps, + all_current_packages, + tree_level+1)): + return True + + return False + + def _get_all_dependents(self, package_id, all_current_packages): + """ + Returns sorted list of recursive reverse dependencies for package_id, + as a list of dictionary items, by recursing through dependency + relationships. + """ + rev_deps = [] + self._traverse_dependents(package_id, rev_deps, all_current_packages) + rev_deps = sorted(rev_deps, key=lambda x: x["name"]) + return rev_deps + + def get(self, request, *args, **kwargs): + recipe, error = XhrCustomRecipeId._get_ci_recipe( + kwargs['recipe_id']) + if error: + return error + + # If no package_id then list all the current packages + if not kwargs['package_id']: + total_size = 0 + packages = recipe.get_all_packages().values("id", + "name", + "version", + "size") + for package in packages: + package['size_formatted'] = \ + filtered_filesizeformat(package['size']) + total_size += package['size'] + + return JsonResponse({"error": "ok", + "packages": list(packages), + "total": len(packages), + "total_size": total_size, + "total_size_formatted": + filtered_filesizeformat(total_size)}) + else: + package, error = XhrCustomRecipePackages._get_package( + kwargs['package_id']) + if error: + return error + + all_current_packages = recipe.get_all_packages() + + # Dependencies for package which aren't satisfied by the + # current packages in the custom image recipe + deps = package.package_dependencies_source.for_target_or_none( + recipe.name)['packages'].annotate( + name=F('depends_on__name'), + pk=F('depends_on__pk'), + size=F('depends_on__size'), + ).values("name", "pk", "size").filter( + # There are two depends types we don't know why + (Q(dep_type=Package_Dependency.TYPE_TRDEPENDS) | + Q(dep_type=Package_Dependency.TYPE_RDEPENDS)) & + ~Q(pk__in=all_current_packages) + ) + + # Reverse dependencies which are needed by packages that are + # in the image. Recursive search providing all dependents, + # not just immediate dependents. + reverse_deps = self._get_all_dependents(kwargs['package_id'], + all_current_packages) + total_size_deps = 0 + total_size_reverse_deps = 0 + + for dep in deps: + dep['size_formatted'] = \ + filtered_filesizeformat(dep['size']) + total_size_deps += dep['size'] + + for dep in reverse_deps: + dep['size_formatted'] = \ + filtered_filesizeformat(dep['size']) + total_size_reverse_deps += dep['size'] + + return JsonResponse( + {"error": "ok", + "id": package.pk, + "name": package.name, + "version": package.version, + "unsatisfied_dependencies": list(deps), + "unsatisfied_dependencies_size": total_size_deps, + "unsatisfied_dependencies_size_formatted": + filtered_filesizeformat(total_size_deps), + "reverse_dependencies": list(reverse_deps), + "reverse_dependencies_size": total_size_reverse_deps, + "reverse_dependencies_size_formatted": + filtered_filesizeformat(total_size_reverse_deps)}) + + def put(self, request, *args, **kwargs): + recipe, error = XhrCustomRecipeId._get_ci_recipe(kwargs['recipe_id']) + package, error = self._get_package(kwargs['package_id']) + if error: + return error + + included_packages = recipe.includes_set.values_list('pk', + flat=True) + + # If we're adding back a package which used to be included in this + # image all we need to do is remove it from the excludes + if package.pk in included_packages: + try: + recipe.excludes_set.remove(package) + return {"error": "ok"} + except Package.DoesNotExist: + return error_response("Package %s not found in excludes" + " but was in included list" % + package.name) + + else: + recipe.appends_set.add(package) + # Make sure that package is not in the excludes set + try: + recipe.excludes_set.remove(package) + except: + pass + # Add the dependencies we think will be added to the recipe + # as a result of appending this package. + # TODO this should recurse down the entire deps tree + for dep in package.package_dependencies_source.all_depends(): + try: + cust_package = CustomImagePackage.objects.get( + name=dep.depends_on.name) + + recipe.includes_set.add(cust_package) + try: + # When adding the pre-requisite package, make + # sure it's not in the excluded list from a + # prior removal. + recipe.excludes_set.remove(cust_package) + except package.DoesNotExist: + # Don't care if the package had never been excluded + pass + except: + logger.warning("Could not add package's suggested" + "dependencies to the list") + return JsonResponse({"error": "ok"}) + + def delete(self, request, *args, **kwargs): + recipe, error = XhrCustomRecipeId._get_ci_recipe(kwargs['recipe_id']) + package, error = self._get_package(kwargs['package_id']) + if error: + return error + + try: + included_packages = recipe.includes_set.values_list('pk', + flat=True) + # If we're deleting a package which is included we need to + # Add it to the excludes list. + if package.pk in included_packages: + recipe.excludes_set.add(package) + else: + recipe.appends_set.remove(package) + all_current_packages = recipe.get_all_packages() + + reverse_deps_dictlist = self._get_all_dependents( + package.pk, + all_current_packages) + + ids = [entry['pk'] for entry in reverse_deps_dictlist] + reverse_deps = CustomImagePackage.objects.filter(id__in=ids) + for r in reverse_deps: + try: + if r.id in included_packages: + recipe.excludes_set.add(r) + else: + recipe.appends_set.remove(r) + except: + pass + + return JsonResponse({"error": "ok"}) + except CustomImageRecipe.DoesNotExist: + return error_response("Tried to remove package that wasn't" + " present") + + +class XhrProject(View): + """ Create, delete or edit a project + + Entry point: /xhr_project/<project_id> + """ + def post(self, request, *args, **kwargs): + """ + Edit project control + + Args: + layerAdd = layer_version_id layer_version_id ... + layerDel = layer_version_id layer_version_id ... + projectName = new_project_name + machineName = new_machine_name + + Returns: + {"error": "ok"} + or + {"error": <error message>} + """ + try: + prj = Project.objects.get(pk=kwargs['project_id']) + except Project.DoesNotExist: + return error_response("No such project") + + # Add layers + if 'layerAdd' in request.POST and len(request.POST['layerAdd']) > 0: + for layer_version_id in request.POST['layerAdd'].split(','): + try: + lv = Layer_Version.objects.get(pk=int(layer_version_id)) + ProjectLayer.objects.get_or_create(project=prj, + layercommit=lv) + except Layer_Version.DoesNotExist: + return error_response("Layer version %s asked to add " + "doesn't exist" % layer_version_id) + + # Remove layers + if 'layerDel' in request.POST and len(request.POST['layerDel']) > 0: + layer_version_ids = request.POST['layerDel'].split(',') + ProjectLayer.objects.filter( + project=prj, + layercommit_id__in=layer_version_ids).delete() + + # Project name change + if 'projectName' in request.POST: + prj.name = request.POST['projectName'] + prj.save() + + # Machine name change + if 'machineName' in request.POST: + machinevar = prj.projectvariable_set.get(name="MACHINE") + machinevar.value = request.POST['machineName'] + machinevar.save() + + return JsonResponse({"error": "ok"}) + + def get(self, request, *args, **kwargs): + """ + Returns: + json object representing the current project + or: + {"error": <error message>} + """ + + try: + project = Project.objects.get(pk=kwargs['project_id']) + except Project.DoesNotExist: + return error_response("Project %s does not exist" % + kwargs['project_id']) + + # Create the frequently built targets list + + freqtargets = Counter(Target.objects.filter( + Q(build__project=project), + ~Q(build__outcome=Build.IN_PROGRESS) + ).order_by("target").values_list("target", flat=True)) + + freqtargets = freqtargets.most_common(5) + + # We now have the targets in order of frequency but if there are two + # with the same frequency then we need to make sure those are in + # alphabetical order without losing the frequency ordering + + tmp = [] + switch = None + for i, freqtartget in enumerate(freqtargets): + target, count = freqtartget + try: + target_next, count_next = freqtargets[i+1] + if count == count_next and target > target_next: + switch = target + continue + except IndexError: + pass + + tmp.append(target) + + if switch: + tmp.append(switch) + switch = None + + freqtargets = tmp + + layers = [] + for layer in project.projectlayer_set.all(): + layers.append({ + "id": layer.layercommit.pk, + "name": layer.layercommit.layer.name, + "vcs_url": layer.layercommit.layer.vcs_url, + "local_source_dir": layer.layercommit.layer.local_source_dir, + "vcs_reference": layer.layercommit.get_vcs_reference(), + "url": layer.layercommit.layer.layer_index_url, + "layerdetailurl": layer.layercommit.get_detailspage_url( + project.pk), + "layersource": layer.layercommit.layer_source + }) + + data = { + "name": project.name, + "layers": layers, + "freqtargets": freqtargets, + } + + if project.release is not None: + data['release'] = { + "id": project.release.pk, + "name": project.release.name, + "description": project.release.description + } + + try: + data["machine"] = {"name": + project.projectvariable_set.get( + name="MACHINE").value} + except ProjectVariable.DoesNotExist: + data["machine"] = None + try: + data["distro"] = project.projectvariable_set.get( + name="DISTRO").value + except ProjectVariable.DoesNotExist: + data["distro"] = "-- not set yet" + + data['error'] = "ok" + + return JsonResponse(data) + + def put(self, request, *args, **kwargs): + # TODO create new project api + return HttpResponse() + + def delete(self, request, *args, **kwargs): + """Delete a project. Cancels any builds in progress""" + try: + project = Project.objects.get(pk=kwargs['project_id']) + # Cancel any builds in progress + for br in BuildRequest.objects.filter( + project=project, + state=BuildRequest.REQ_INPROGRESS): + XhrBuildRequest.cancel_build(br) + + project.delete() + + except Project.DoesNotExist: + return error_response("Project %s does not exist" % + kwargs['project_id']) + + return JsonResponse({ + "error": "ok", + "gotoUrl": reverse("all-projects", args=[]) + }) + + +class XhrBuild(View): + """ Delete a build object + + Entry point: /xhr_build/<build_id> + """ + def delete(self, request, *args, **kwargs): + """ + Delete build data + + Args: + build_id = build_id + + Returns: + {"error": "ok"} + or + {"error": <error message>} + """ + try: + build = Build.objects.get(pk=kwargs['build_id']) + project = build.project + build.delete() + except Build.DoesNotExist: + return error_response("Build %s does not exist" % + kwargs['build_id']) + return JsonResponse({ + "error": "ok", + "gotoUrl": reverse("projectbuilds", args=(project.pk,)) + }) diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/buildtables.py b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/buildtables.py new file mode 100644 index 000000000..dd0a6900d --- /dev/null +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/buildtables.py @@ -0,0 +1,606 @@ +# +# ex:ts=4:sw=4:sts=4:et +# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- +# +# BitBake Toaster Implementation +# +# Copyright (C) 2016 Intel Corporation +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# 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, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +from orm.models import Build, Task, Target, Package +from django.db.models import Q, Sum + +import toastergui.tables as tables +from toastergui.widgets import ToasterTable +from toastergui.tablefilter import TableFilter +from toastergui.tablefilter import TableFilterActionToggle + + +class BuildTablesMixin(ToasterTable): + def get_context_data(self, **kwargs): + # We need to be explicit about which superclass we're calling here + # Otherwise the MRO gets in a right mess + context = ToasterTable.get_context_data(self, **kwargs) + context['build'] = Build.objects.get(pk=kwargs['build_id']) + return context + + +class BuiltPackagesTableBase(tables.PackagesTable): + """ Table to display all the packages built in a build """ + def __init__(self, *args, **kwargs): + super(BuiltPackagesTableBase, self).__init__(*args, **kwargs) + self.title = "Packages built" + self.default_orderby = "name" + + def setup_queryset(self, *args, **kwargs): + build = Build.objects.get(pk=kwargs['build_id']) + self.static_context_extra['build'] = build + self.static_context_extra['target_name'] = None + self.queryset = build.package_set.all().exclude(recipe=None) + self.queryset = self.queryset.order_by(self.default_orderby) + + def setup_columns(self, *args, **kwargs): + super(BuiltPackagesTableBase, self).setup_columns(*args, **kwargs) + + def pkg_link_template(val): + """ return the template used for the link with the val as the + element value i.e. inside the <a></a>""" + + return (''' + <a href=" + {%% url "package_built_detail" extra.build.pk data.pk %%} + ">%s</a> + ''' % val) + + def recipe_link_template(val): + return (''' + {%% if data.recipe %%} + <a href=" + {%% url "recipe" extra.build.pk data.recipe.pk %%} + ">%(value)s</a> + {%% else %%} + %(value)s + {%% endif %%} + ''' % {'value': val}) + + add_pkg_link_to = 'name' + add_recipe_link_to = 'recipe__name' + + # Add the recipe and pkg build links to the required columns + for column in self.columns: + # Convert to template field style accessors + tmplv = column['field_name'].replace('__', '.') + tmplv = "{{data.%s}}" % tmplv + + if column['field_name'] is add_pkg_link_to: + # Don't overwrite an existing template + if column['static_data_template']: + column['static_data_template'] =\ + pkg_link_template(column['static_data_template']) + else: + column['static_data_template'] = pkg_link_template(tmplv) + + column['static_data_name'] = column['field_name'] + + elif column['field_name'] is add_recipe_link_to: + # Don't overwrite an existing template + if column['static_data_template']: + column['static_data_template'] =\ + recipe_link_template(column['static_data_template']) + else: + column['static_data_template'] =\ + recipe_link_template(tmplv) + column['static_data_name'] = column['field_name'] + + self.add_column(title="Layer", + field_name="recipe__layer_version__layer__name", + hidden=True, + orderable=True) + + layer_branch_template = ''' + {%if not data.recipe.layer_version.layer.local_source_dir %} + <span class="text-muted">{{data.recipe.layer_version.branch}}</span> + {% else %} + <span class="text-muted">Not applicable</span> + <span class="glyphicon glyphicon-question-sign get-help" data-original-title="" title="The source code of {{data.recipe.layer_version.layer.name}} is not in a Git repository, so there is no branch associated with it"> </span> + {% endif %} + ''' + + self.add_column(title="Layer branch", + field_name="recipe__layer_version__branch", + hidden=True, + static_data_name="recipe__layer_version__branch", + static_data_template=layer_branch_template, + orderable=True) + + git_rev_template = ''' + {% if not data.recipe.layer_version.layer.local_source_dir %} + {% with vcs_ref=data.recipe.layer_version.commit %} + {% include 'snippets/gitrev_popover.html' %} + {% endwith %} + {% else %} + <span class="text-muted">Not applicable</span> + <span class="glyphicon glyphicon-question-sign get-help" data-original-title="" title="The source code of {{data.recipe.layer_version.layer.name}} is not in a Git repository, so there is no revision associated with it"> </span> + {% endif %} + ''' + + self.add_column(title="Layer commit", + static_data_name='vcs_ref', + static_data_template=git_rev_template, + hidden=True) + + +class BuiltPackagesTable(BuildTablesMixin, BuiltPackagesTableBase): + """ Show all the packages built for the selected build """ + def __init__(self, *args, **kwargs): + super(BuiltPackagesTable, self).__init__(*args, **kwargs) + self.title = "Packages built" + self.default_orderby = "name" + + self.empty_state =\ + ('<strong>No packages were built.</strong> How did this happen? ' + 'Well, BitBake reuses as much stuff as possible. ' + 'If all of the packages needed were already built and available ' + 'in your build infrastructure, BitBake ' + 'will not rebuild any of them. This might be slightly confusing, ' + 'but it does make everything faster.') + + def setup_columns(self, *args, **kwargs): + super(BuiltPackagesTable, self).setup_columns(*args, **kwargs) + + def remove_dep_cols(columns): + for column in columns: + # We don't need these fields + if column['static_data_name'] in ['reverse_dependencies', + 'dependencies']: + continue + + yield column + + self.columns = list(remove_dep_cols(self.columns)) + + +class InstalledPackagesTable(BuildTablesMixin, BuiltPackagesTableBase): + """ Show all packages installed in an image """ + def __init__(self, *args, **kwargs): + super(InstalledPackagesTable, self).__init__(*args, **kwargs) + self.title = "Packages Included" + self.default_orderby = "name" + + def make_package_list(self, target): + # The database design means that you get the intermediate objects and + # not package objects like you'd really want so we get them here + pkgs = target.target_installed_package_set.values_list('package', + flat=True) + return Package.objects.filter(pk__in=pkgs) + + def get_context_data(self, **kwargs): + context = super(InstalledPackagesTable, + self).get_context_data(**kwargs) + + target = Target.objects.get(pk=kwargs['target_id']) + packages = self.make_package_list(target) + + context['packages_sum'] = packages.aggregate( + Sum('installed_size'))['installed_size__sum'] + + context['target'] = target + return context + + def setup_queryset(self, *args, **kwargs): + build = Build.objects.get(pk=kwargs['build_id']) + self.static_context_extra['build'] = build + + target = Target.objects.get(pk=kwargs['target_id']) + # We send these separately because in the case of image details table + # we don't have a target just the recipe name as the target + self.static_context_extra['target_name'] = target.target + self.static_context_extra['target_id'] = target.pk + + self.static_context_extra['add_links'] = True + + self.queryset = self.make_package_list(target) + self.queryset = self.queryset.order_by(self.default_orderby) + + def setup_columns(self, *args, **kwargs): + super(InstalledPackagesTable, self).setup_columns(**kwargs) + self.add_column(title="Installed size", + static_data_name="installed_size", + static_data_template="{% load projecttags %}" + "{{data.size|filtered_filesizeformat}}", + orderable=True, + hidden=True) + + # Add the template to show installed name for installed packages + install_name_tmpl =\ + ('<a href="{% url "package_included_detail" extra.build.pk' + ' extra.target_id data.pk %}">{{data.name}}</a>' + '{% if data.installed_name and data.installed_name !=' + ' data.name %}' + '<span class="text-muted"> as {{data.installed_name}}</span>' + ' <span class="glyphicon glyphicon-question-sign get-help hover-help"' + ' title="{{data.name}} was renamed at packaging time and' + ' was installed in your image as {{data.installed_name}}' + '"></span>{% endif %} ') + + for column in self.columns: + if column['static_data_name'] == 'name': + column['static_data_template'] = install_name_tmpl + break + + +class BuiltRecipesTable(BuildTablesMixin): + """ Table to show the recipes that have been built in this build """ + + def __init__(self, *args, **kwargs): + super(BuiltRecipesTable, self).__init__(*args, **kwargs) + self.title = "Recipes built" + self.default_orderby = "name" + + def setup_queryset(self, *args, **kwargs): + build = Build.objects.get(pk=kwargs['build_id']) + self.static_context_extra['build'] = build + self.queryset = build.get_recipes() + self.queryset = self.queryset.order_by(self.default_orderby) + + def setup_columns(self, *args, **kwargs): + recipe_name_tmpl =\ + '<a href="{% url "recipe" extra.build.pk data.pk %}">'\ + '{{data.name}}'\ + '</a>' + + recipe_file_tmpl =\ + '{{data.file_path}}'\ + '{% if data.pathflags %}<i>({{data.pathflags}})</i>'\ + '{% endif %}' + + git_branch_template = ''' + {% if data.layer_version.layer.local_source_dir %} + <span class="text-muted">Not applicable</span> + <span class="glyphicon glyphicon-question-sign get-help" data-original-title="" title="The source code of {{data.layer_version.layer.name}} is not in a Git repository, so there is no branch associated with it"> </span> + {% else %} + <span>{{data.layer_version.branch}}</span> + {% endif %} + ''' + + git_rev_template = ''' + {% if data.layer_version.layer.local_source_dir %} + <span class="text-muted">Not applicable</span> + <span class="glyphicon glyphicon-question-sign get-help" data-original-title="" title="The source code of {{data.layer_version.layer.name}} is not in a Git repository, so there is no commit associated with it"> </span> + {% else %} + {% with vcs_ref=data.layer_version.commit %} + {% include 'snippets/gitrev_popover.html' %} + {% endwith %} + {% endif %} + ''' + + depends_on_tmpl = ''' + {% with deps=data.r_dependencies_recipe.all %} + {% with count=deps|length %} + {% if count %} + <a class="btn btn-default" title=" + <a href='{% url "recipe" extra.build.pk data.pk %}#dependencies'> + {{data.name}}</a> dependencies" + data-content="<ul class='list-unstyled'> + {% for dep in deps|dictsort:"depends_on.name"%} + <li><a href='{% url "recipe" extra.build.pk dep.depends_on.pk %}'> + {{dep.depends_on.name}}</a></li> + {% endfor %} + </ul>"> + {{count}} + </a> + {% endif %}{% endwith %}{% endwith %} + ''' + + rev_depends_tmpl = ''' + {% with revs=data.r_dependencies_depends.all %} + {% with count=revs|length %} + {% if count %} + <a class="btn btn-default" + title=" + <a href='{% url "recipe" extra.build.pk data.pk %}#brought-in-by'> + {{data.name}}</a> reverse dependencies" + data-content="<ul class='list-unstyled'> + {% for dep in revs|dictsort:"recipe.name" %} + <li> + <a href='{% url "recipe" extra.build.pk dep.recipe.pk %}'> + {{dep.recipe.name}} + </a></li> + {% endfor %} + </ul>"> + {{count}} + </a> + {% endif %}{% endwith %}{% endwith %} + ''' + + self.add_column(title="Recipe", + field_name="name", + static_data_name='name', + orderable=True, + hideable=False, + static_data_template=recipe_name_tmpl) + + self.add_column(title="Version", + hideable=False, + field_name="version") + + self.add_column(title="Dependencies", + static_data_name="dependencies", + static_data_template=depends_on_tmpl) + + self.add_column(title="Reverse dependencies", + static_data_name="revdeps", + static_data_template=rev_depends_tmpl, + help_text='Recipe build-time reverse dependencies' + ' (i.e. the recipes that depend on this recipe)') + + self.add_column(title="Recipe file", + field_name="file_path", + static_data_name="file_path", + static_data_template=recipe_file_tmpl, + hidden=True) + + self.add_column(title="Section", + field_name="section", + orderable=True, + hidden=True) + + self.add_column(title="License", + field_name="license", + help_text='Multiple license names separated by the' + ' pipe character indicates a choice between licenses.' + ' Multiple license names separated by the ampersand' + ' character indicates multiple licenses exist that' + ' cover different parts of the source', + orderable=True) + + self.add_column(title="Layer", + field_name="layer_version__layer__name", + orderable=True) + + self.add_column(title="Layer branch", + field_name="layer_version__branch", + static_data_name="layer_version__branch", + static_data_template=git_branch_template, + orderable=True, + hidden=True) + + self.add_column(title="Layer commit", + static_data_name="commit", + static_data_template=git_rev_template, + hidden=True) + + +class BuildTasksTable(BuildTablesMixin): + """ Table to show the tasks that run in this build """ + + def __init__(self, *args, **kwargs): + super(BuildTasksTable, self).__init__(*args, **kwargs) + self.title = "Tasks" + self.default_orderby = "order" + + # Toggle these columns on off for Time/CPU usage/Disk I/O tables + self.toggle_columns = {} + + def setup_queryset(self, *args, **kwargs): + build = Build.objects.get(pk=kwargs['build_id']) + self.static_context_extra['build'] = build + self.queryset = build.task_build.filter(~Q(order=None)) + self.queryset = self.queryset.order_by(self.default_orderby) + + def setup_filters(self, *args, **kwargs): + # Execution outcome types filter + executed_outcome = TableFilter(name="execution_outcome", + title="Filter Tasks by 'Executed") + + exec_outcome_action_exec = TableFilterActionToggle( + "executed", + "Executed Tasks", + Q(task_executed=True)) + + exec_outcome_action_not_exec = TableFilterActionToggle( + "not_executed", + "Not Executed Tasks", + Q(task_executed=False)) + + executed_outcome.add_action(exec_outcome_action_exec) + executed_outcome.add_action(exec_outcome_action_not_exec) + + # Task outcome types filter + task_outcome = TableFilter(name="task_outcome", + title="Filter Task by 'Outcome'") + + for outcome_enum, title in Task.TASK_OUTCOME: + if outcome_enum is Task.OUTCOME_NA: + continue + action = TableFilterActionToggle( + title.replace(" ", "_").lower(), + "%s Tasks" % title, + Q(outcome=outcome_enum)) + + task_outcome.add_action(action) + + # SSTATE outcome types filter + sstate_outcome = TableFilter(name="sstate_outcome", + title="Filter Task by 'Cache attempt'") + + for sstate_result_enum, title in Task.SSTATE_RESULT: + action = TableFilterActionToggle( + title.replace(" ", "_").lower(), + "Tasks with '%s' attempts" % title, + Q(sstate_result=sstate_result_enum)) + + sstate_outcome.add_action(action) + + self.add_filter(sstate_outcome) + self.add_filter(executed_outcome) + self.add_filter(task_outcome) + + def setup_columns(self, *args, **kwargs): + self.toggle_columns['order'] = len(self.columns) + + recipe_name_tmpl =\ + '<a href="{% url "recipe" extra.build.pk data.recipe.pk %}">'\ + '{{data.recipe.name}}'\ + '</a>' + + def task_link_tmpl(val): + return ('<a name="task-{{data.order}}"' + 'href="{%% url "task" extra.build.pk data.pk %%}">' + '%s' + '</a>') % str(val) + + self.add_column(title="Order", + static_data_name="order", + static_data_template='{{data.order}}', + hideable=False, + orderable=True) + + self.add_column(title="Task", + static_data_name="task_name", + static_data_template=task_link_tmpl( + "{{data.task_name}}"), + hideable=False, + orderable=True) + + self.add_column(title="Recipe", + static_data_name='recipe__name', + static_data_template=recipe_name_tmpl, + hideable=False, + orderable=True) + + self.add_column(title="Recipe version", + field_name='recipe__version', + hidden=True) + + self.add_column(title="Executed", + static_data_name="task_executed", + static_data_template='{{data.get_executed_display}}', + filter_name='execution_outcome', + orderable=True) + + self.static_context_extra['OUTCOME_FAILED'] = Task.OUTCOME_FAILED + outcome_tmpl = '{{data.outcome_text}}' + outcome_tmpl = ('%s ' + '{%% if data.outcome = extra.OUTCOME_FAILED %%}' + '<a href="{%% url "build_artifact" extra.build.pk ' + ' "tasklogfile" data.pk %%}">' + ' <span class="glyphicon glyphicon-download-alt' + ' get-help" title="Download task log file"></span>' + '</a> {%% endif %%}' + '<span class="glyphicon glyphicon-question-sign' + ' get-help hover-help" style="visibility: hidden;" ' + 'title="{{data.get_outcome_help}}"></span>' + ) % outcome_tmpl + + self.add_column(title="Outcome", + static_data_name="outcome", + static_data_template=outcome_tmpl, + filter_name="task_outcome", + orderable=True) + + self.toggle_columns['sstate_result'] = len(self.columns) + + self.add_column(title="Cache attempt", + static_data_name="sstate_result", + static_data_template='{{data.sstate_text}}', + filter_name="sstate_outcome", + orderable=True) + + self.toggle_columns['elapsed_time'] = len(self.columns) + + self.add_column( + title="Time (secs)", + static_data_name="elapsed_time", + static_data_template='{% load projecttags %}{% load humanize %}' + '{{data.elapsed_time|format_none_and_zero|floatformat:2}}', + orderable=True, + hidden=True) + + self.toggle_columns['cpu_time_sys'] = len(self.columns) + + self.add_column( + title="System CPU time (secs)", + static_data_name="cpu_time_system", + static_data_template='{% load projecttags %}{% load humanize %}' + '{{data.cpu_time_system|format_none_and_zero|floatformat:2}}', + hidden=True, + orderable=True) + + self.toggle_columns['cpu_time_user'] = len(self.columns) + + self.add_column( + title="User CPU time (secs)", + static_data_name="cpu_time_user", + static_data_template='{% load projecttags %}{% load humanize %}' + '{{data.cpu_time_user|format_none_and_zero|floatformat:2}}', + hidden=True, + orderable=True) + + self.toggle_columns['disk_io'] = len(self.columns) + + self.add_column( + title="Disk I/O (ms)", + static_data_name="disk_io", + static_data_template='{% load projecttags %}{% load humanize %}' + '{{data.disk_io|format_none_and_zero|filtered_filesizeformat}}', + hidden=True, + orderable=True) + + +class BuildTimeTable(BuildTasksTable): + """ Same as tasks table but the Time column is default displayed""" + + def __init__(self, *args, **kwargs): + super(BuildTimeTable, self).__init__(*args, **kwargs) + self.default_orderby = "-elapsed_time" + + def setup_columns(self, *args, **kwargs): + super(BuildTimeTable, self).setup_columns(**kwargs) + + self.columns[self.toggle_columns['order']]['hidden'] = True + self.columns[self.toggle_columns['sstate_result']]['hidden'] = True + self.columns[self.toggle_columns['elapsed_time']]['hidden'] = False + + +class BuildCPUTimeTable(BuildTasksTable): + """ Same as tasks table but the CPU usage columns are default displayed""" + + def __init__(self, *args, **kwargs): + super(BuildCPUTimeTable, self).__init__(*args, **kwargs) + self.default_orderby = "-cpu_time_system" + + def setup_columns(self, *args, **kwargs): + super(BuildCPUTimeTable, self).setup_columns(**kwargs) + + self.columns[self.toggle_columns['order']]['hidden'] = True + self.columns[self.toggle_columns['sstate_result']]['hidden'] = True + self.columns[self.toggle_columns['cpu_time_sys']]['hidden'] = False + self.columns[self.toggle_columns['cpu_time_user']]['hidden'] = False + + +class BuildIOTable(BuildTasksTable): + """ Same as tasks table but the Disk IO column is default displayed""" + + def __init__(self, *args, **kwargs): + super(BuildIOTable, self).__init__(*args, **kwargs) + self.default_orderby = "-disk_io" + + def setup_columns(self, *args, **kwargs): + super(BuildIOTable, self).setup_columns(**kwargs) + + self.columns[self.toggle_columns['order']]['hidden'] = True + self.columns[self.toggle_columns['sstate_result']]['hidden'] = True + self.columns[self.toggle_columns['disk_io']]['hidden'] = False diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/fixtures/toastergui-unittest-data.xml b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/fixtures/toastergui-unittest-data.xml index cf35be4be..4517ed176 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/fixtures/toastergui-unittest-data.xml +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/fixtures/toastergui-unittest-data.xml @@ -1,5 +1,16 @@ <?xml version="1.0" encoding="utf-8"?> <django-objects version="1.0"> + <object pk="1" model="orm.bitbakeversion"> + <field type="CharField" name="name">v2.3</field> + <field type="GitURLField" name="giturl">git://git.openembedded.org/bitbake</field> + <field type="CharField" name="dirpath">b</field> + <field type="CharField" name="branch">a</field> + </object> + <object pk="1" model="orm.release"> + <field type="CharField" name="name">master</field> + <field type="CharField" name="description">master project</field> + <field to="orm.bitbake_version" name="bitbake_version">1</field> + </object> <object pk="1" model="orm.project"> <field type="CharField" name="name">a test project</field> <field type="CharField" name="short_description"></field> @@ -82,9 +93,8 @@ <field to="orm.build" name="build" rel="ManyToOneRel">1</field> <field type="CharField" name="target">a image recipe</field> <field type="CharField" name="task"><None></None></field> - <field type="BooleanField" name="is_image">False</field> - <field type="IntegerField" name="image_size">0</field> - <field type="CharField" name="license_manifest_path"><None></None></field> + <field type="BooleanField" name="is_image">True</field> + <field type="IntegerField" name="image_size">290</field> </object> <object pk="2" model="orm.target"> <field to="orm.build" name="build" rel="ManyToOneRel">2</field> @@ -118,9 +128,9 @@ </object> <object pk="2" model="orm.package"> <field to="orm.build" name="build" rel="ManyToOneRel">1</field> - <field to="orm.recipe" name="recipe" rel="ManyToOneRel">2</field> + <field to="orm.recipe" name="recipe" rel="ManyToOneRel">1</field> <field type="CharField" name="name">f pkg</field> - <field type="CharField" name="installed_name"></field> + <field type="CharField" name="installed_name">f</field> <field type="CharField" name="version"></field> <field type="CharField" name="revision"></field> <field type="TextField" name="summary"></field> @@ -196,8 +206,6 @@ <field to="orm.customimagerecipe" name="recipe_appends" rel="ManyToManyRel"><object pk="3"></object></field> </object> <object pk="1" model="orm.recipe"> - <field to="orm.layersource" name="layer_source" rel="ManyToOneRel">1</field> - <field type="IntegerField" name="up_id"><None></None></field> <field type="DateTimeField" name="up_date"><None></None></field> <field type="CharField" name="name">z recipe</field> <field type="CharField" name="version">5.2</field> @@ -213,8 +221,6 @@ <field type="BooleanField" name="is_image">False</field> </object> <object pk="2" model="orm.recipe"> - <field to="orm.layersource" name="layer_source" rel="ManyToOneRel">1</field> - <field type="IntegerField" name="up_id"><None></None></field> <field type="DateTimeField" name="up_date"><None></None></field> <field type="CharField" name="name">a recipe</field> <field type="CharField" name="version">1.2</field> @@ -230,8 +236,6 @@ <field type="BooleanField" name="is_image">False</field> </object> <object pk="3" model="orm.recipe"> - <field to="orm.layersource" name="layer_source" rel="ManyToOneRel"><None></None></field> - <field type="IntegerField" name="up_id"><None></None></field> <field type="DateTimeField" name="up_date"><None></None></field> <field type="CharField" name="name">a custom recipe</field> <field type="CharField" name="version"></field> @@ -247,8 +251,6 @@ <field type="BooleanField" name="is_image">False</field> </object> <object pk="4" model="orm.recipe"> - <field to="orm.layersource" name="layer_source" rel="ManyToOneRel">1</field> - <field type="IntegerField" name="up_id"><None></None></field> <field type="DateTimeField" name="up_date"><None></None></field> <field type="CharField" name="name">a image recipe</field> <field type="CharField" name="version">1.2</field> @@ -264,8 +266,6 @@ <field type="BooleanField" name="is_image">True</field> </object> <object pk="5" model="orm.recipe"> - <field to="orm.layersource" name="layer_source" rel="ManyToOneRel">1</field> - <field type="IntegerField" name="up_id"><None></None></field> <field type="DateTimeField" name="up_date"><None></None></field> <field type="CharField" name="name">z image recipe</field> <field type="CharField" name="version">1.3</field> @@ -281,8 +281,6 @@ <field type="BooleanField" name="is_image">True</field> </object> <object pk="6" model="orm.recipe"> - <field to="orm.layersource" name="layer_source" rel="ManyToOneRel"><None></None></field> - <field type="IntegerField" name="up_id"><None></None></field> <field type="DateTimeField" name="up_date"><None></None></field> <field type="CharField" name="name">z custom recipe</field> <field type="CharField" name="version"></field> @@ -297,35 +295,34 @@ <field type="CharField" name="pathflags"></field> <field type="BooleanField" name="is_image">False</field> </object> + <!-- Added for an additional built recipe --> + <object pk="6" model="orm.recipe"> + <field type="CharField" name="name">g recipe</field> + <field type="CharField" name="version">1.2.3</field> + <field to="orm.layer_version" name="layer_version" rel="ManyToOneRel">3</field> + <field type="CharField" name="license">g license</field> + <field type="FilePathField" name="file_path">/g</field> + <field type="BooleanField" name="is_image">False</field> + </object> + <object pk="1" model="orm.machine"> - <field to="orm.layersource" name="layer_source" rel="ManyToOneRel"><None></None></field> - <field type="IntegerField" name="up_id"><None></None></field> <field type="DateTimeField" name="up_date"><None></None></field> <field to="orm.layer_version" name="layer_version" rel="ManyToOneRel">1</field> <field type="CharField" name="name">a machine</field> <field type="CharField" name="description">a machine</field> </object> <object pk="2" model="orm.machine"> - <field to="orm.layersource" name="layer_source" rel="ManyToOneRel"><None></None></field> - <field type="IntegerField" name="up_id"><None></None></field> <field type="DateTimeField" name="up_date"><None></None></field> <field to="orm.layer_version" name="layer_version" rel="ManyToOneRel">2</field> <field type="CharField" name="name">z machine</field> <field type="CharField" name="description">z machine</field> </object> <object pk="3" model="orm.machine"> - <field to="orm.layersource" name="layer_source" rel="ManyToOneRel"><None></None></field> - <field type="IntegerField" name="up_id"><None></None></field> <field type="DateTimeField" name="up_date"><None></None></field> <field to="orm.layer_version" name="layer_version" rel="ManyToOneRel">1</field> <field type="CharField" name="name">g machine</field> <field type="CharField" name="description">g machine</field> </object> - <object pk="1" model="orm.layersource"> - <field type="CharField" name="name">local</field> - <field type="IntegerField" name="sourcetype">1</field> - <field type="CharField" name="apiurl"></field> - </object> <object pk="1" model="orm.bitbakeversion"> <field type="CharField" name="name">test bbv</field> <field type="CharField" name="giturl">/tmp/</field> @@ -346,53 +343,24 @@ <field type="CharField" name="branch_name">master</field> <field type="TextField" name="helptext"><None></None></field> </object> - <object pk="1" model="orm.releaselayersourcepriority"> - <field to="orm.release" name="release" rel="ManyToOneRel">1</field> - <field to="orm.layersource" name="layer_source" rel="ManyToOneRel">1</field> - <field type="IntegerField" name="priority">0</field> - </object> - <object pk="1" model="orm.branch"> - <field to="orm.layersource" name="layer_source" rel="ManyToOneRel">1</field> - <field type="IntegerField" name="up_id"><None></None></field> - <field type="DateTimeField" name="up_date"><None></None></field> - <field type="CharField" name="name">master</field> - <field type="CharField" name="short_description"></field> - </object> <object pk="1" model="orm.layer"> - <field to="orm.layersource" name="layer_source" rel="ManyToOneRel"><None></None></field> - <field type="IntegerField" name="up_id"><None></None></field> <field type="DateTimeField" name="up_date"><None></None></field> <field type="CharField" name="name">a layer</field> - <field type="CharField" name="layer_index_url"></field> <field type="CharField" name="vcs_url">/tmp/</field> - <field type="CharField" name="vcs_web_url"><None></None></field> - <field type="CharField" name="vcs_web_tree_base_url"><None></None></field> - <field type="CharField" name="vcs_web_file_base_url"><None></None></field> - <field type="TextField" name="summary"><None></None></field> - <field type="TextField" name="description"><None></None></field> </object> <object pk="2" model="orm.layer"> - <field to="orm.layersource" name="layer_source" rel="ManyToOneRel">1</field> - <field type="IntegerField" name="up_id"><None></None></field> <field type="DateTimeField" name="up_date"><None></None></field> <field type="CharField" name="name">z layer</field> <field type="CharField" name="layer_index_url"></field> <field type="CharField" name="vcs_url">git://two/</field> - <field type="CharField" name="vcs_web_url"><None></None></field> - <field type="CharField" name="vcs_web_tree_base_url"><None></None></field> - <field type="CharField" name="vcs_web_file_base_url"><None></None></field> - <field type="TextField" name="summary"><None></None></field> - <field type="TextField" name="description"><None></None></field> </object> <object pk="1" model="orm.layer_version"> - <field to="orm.build" name="build" rel="ManyToOneRel"><None></None></field> + <field to="orm.build" name="build" rel="ManyToOneRel">1</field> <field to="orm.layer" name="layer" rel="ManyToOneRel">1</field> - <field to="orm.layersource" name="layer_source" rel="ManyToOneRel">1</field> - <field type="IntegerField" name="up_id"><None></None></field> <field type="DateTimeField" name="up_date"><None></None></field> - <field to="orm.branch" name="up_branch" rel="ManyToOneRel">1</field> - <field type="CharField" name="branch"></field> - <field type="CharField" name="commit">master</field> + <field to="orm.release" name="release" rel="ManyToOneRel">1</field> + <field type="CharField" name="branch">master</field> + <field type="CharField" name="commit">abcdef123</field> <field type="CharField" name="dirpath">/tmp/</field> <field type="IntegerField" name="priority">0</field> <field type="FilePathField" name="local_path">/</field> @@ -401,12 +369,22 @@ <object pk="2" model="orm.layer_version"> <field to="orm.build" name="build" rel="ManyToOneRel"><None></None></field> <field to="orm.layer" name="layer" rel="ManyToOneRel">2</field> - <field to="orm.layersource" name="layer_source" rel="ManyToOneRel">1</field> - <field type="IntegerField" name="up_id"><None></None></field> <field type="DateTimeField" name="up_date"><None></None></field> - <field to="orm.branch" name="up_branch" rel="ManyToOneRel">1</field> - <field type="CharField" name="branch"></field> - <field type="CharField" name="commit">master</field> + <field to="orm.release" name="release" rel="ManyToOneRel">1</field> + <field type="CharField" name="branch">testing-branch</field> + <field type="CharField" name="commit">9876fedcba</field> + <field type="CharField" name="dirpath"><None></None></field> + <field type="IntegerField" name="priority">0</field> + <field type="FilePathField" name="local_path">/</field> + <field to="orm.project" name="project" rel="ManyToOneRel"><None></None></field> + </object> + <object pk="3" model="orm.layer_version"> + <field to="orm.build" name="build" rel="ManyToOneRel">1</field> + <field to="orm.layer" name="layer" rel="ManyToOneRel">2</field> + <field type="DateTimeField" name="up_date"><None></None></field> + <field to="orm.release" name="release" rel="ManyToOneRel">1</field> + <field type="CharField" name="branch">testing-branch</field> + <field type="CharField" name="commit">9876fedcba</field> <field type="CharField" name="dirpath"><None></None></field> <field type="IntegerField" name="priority">0</field> <field type="FilePathField" name="local_path">/</field> @@ -443,4 +421,39 @@ <field type="FilePathField" name="pathname"></field> <field type="IntegerField" name="lineno"><None></None></field> </object> + <!-- Some tasks for build 1 to test build tables --> + <object pk="1" model="orm.task"> + <field to="orm.build" name="build" rel="ManyToOneRel">1</field> + <field type="IntegerField" name="order">1</field> + <field type="BooleanField" name="task_executed">False</field> + <field type="IntegerField" name="outcome">-1</field> + <field type="CharField" name="sstate_checksum">abcdef123</field> + <field type="FilePathField" name="path_to_sstate_obj">34/wefw.tar</field> + <field to="orm.recipe" name="recipe" rel="ManyToOneRel">1</field> + <field type="CharField" name="task_name">a_do_compile</field> + <field type="DecimalField" name="elapsed_time">100</field> + <field type="IntegerField" name="disk_io">10</field> + <field type="IntegerField" name="disk_io_read">11</field> + <field type="IntegerField" name="disk_io_write">12</field> + <field type="DecimalField" name="cpu_time_user">10.1</field> + <field type="DecimalField" name="cpu_time_system">10.2</field> + <field type="IntegerField" name="sstate_result">3</field> + </object> + <object pk="2" model="orm.task"> + <field to="orm.build" name="build" rel="ManyToOneRel">1</field> + <field type="IntegerField" name="order">2</field> + <field type="BooleanField" name="task_executed">True</field> + <field type="IntegerField" name="outcome">2</field> + <field type="CharField" name="sstate_checksum">85bccb7802fd5f48</field> + <field type="FilePathField" name="path_to_sstate_obj">85/sstarpm.tgz</field> + <field to="orm.recipe" name="recipe" rel="ManyToOneRel">2</field> + <field type="CharField" name="task_name">z_do_package_write_rpm</field> + <field type="DecimalField" name="elapsed_time">245</field> + <field type="IntegerField" name="disk_io">12424</field> + <field type="IntegerField" name="disk_io_read">23423</field> + <field type="IntegerField" name="disk_io_write">83943</field> + <field type="DecimalField" name="cpu_time_user">20394.3</field> + <field type="DecimalField" name="cpu_time_system">5363.3</field> + <field type="IntegerField" name="sstate_result">1</field> + </object> </django-objects> diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/css/bootstrap-responsive.min.css b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/css/bootstrap-responsive.min.css deleted file mode 100755 index 059786010..000000000 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/css/bootstrap-responsive.min.css +++ /dev/null @@ -1,9 +0,0 @@ -/*! - * Bootstrap Responsive v2.3.0 - * - * Copyright 2012 Twitter, Inc - * Licensed under the Apache License v2.0 - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Designed and built with all the love in the world @twitter by @mdo and @fat. - */.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;line-height:0;content:""}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}@-ms-viewport{width:device-width}.hidden{display:none;visibility:hidden}.visible-phone{display:none!important}.visible-tablet{display:none!important}.hidden-desktop{display:none!important}.visible-desktop{display:inherit!important}@media(min-width:768px) and (max-width:979px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-tablet{display:inherit!important}.hidden-tablet{display:none!important}}@media(max-width:767px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-phone{display:inherit!important}.hidden-phone{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:inherit!important}.hidden-print{display:none!important}}@media(min-width:1200px){.row{margin-left:-30px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:30px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:1170px}.span12{width:1170px}.span11{width:1070px}.span10{width:970px}.span9{width:870px}.span8{width:770px}.span7{width:670px}.span6{width:570px}.span5{width:470px}.span4{width:370px}.span3{width:270px}.span2{width:170px}.span1{width:70px}.offset12{margin-left:1230px}.offset11{margin-left:1130px}.offset10{margin-left:1030px}.offset9{margin-left:930px}.offset8{margin-left:830px}.offset7{margin-left:730px}.offset6{margin-left:630px}.offset5{margin-left:530px}.offset4{margin-left:430px}.offset3{margin-left:330px}.offset2{margin-left:230px}.offset1{margin-left:130px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.564102564102564%;*margin-left:2.5109110747408616%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.564102564102564%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.45299145299145%;*width:91.39979996362975%}.row-fluid .span10{width:82.90598290598291%;*width:82.8527914166212%}.row-fluid .span9{width:74.35897435897436%;*width:74.30578286961266%}.row-fluid .span8{width:65.81196581196582%;*width:65.75877432260411%}.row-fluid .span7{width:57.26495726495726%;*width:57.21176577559556%}.row-fluid .span6{width:48.717948717948715%;*width:48.664757228587014%}.row-fluid .span5{width:40.17094017094017%;*width:40.11774868157847%}.row-fluid .span4{width:31.623931623931625%;*width:31.570740134569924%}.row-fluid .span3{width:23.076923076923077%;*width:23.023731587561375%}.row-fluid .span2{width:14.52991452991453%;*width:14.476723040552828%}.row-fluid .span1{width:5.982905982905983%;*width:5.929714493544281%}.row-fluid .offset12{margin-left:105.12820512820512%;*margin-left:105.02182214948171%}.row-fluid .offset12:first-child{margin-left:102.56410256410257%;*margin-left:102.45771958537915%}.row-fluid .offset11{margin-left:96.58119658119658%;*margin-left:96.47481360247316%}.row-fluid .offset11:first-child{margin-left:94.01709401709402%;*margin-left:93.91071103837061%}.row-fluid .offset10{margin-left:88.03418803418803%;*margin-left:87.92780505546462%}.row-fluid .offset10:first-child{margin-left:85.47008547008548%;*margin-left:85.36370249136206%}.row-fluid .offset9{margin-left:79.48717948717949%;*margin-left:79.38079650845607%}.row-fluid .offset9:first-child{margin-left:76.92307692307693%;*margin-left:76.81669394435352%}.row-fluid .offset8{margin-left:70.94017094017094%;*margin-left:70.83378796144753%}.row-fluid .offset8:first-child{margin-left:68.37606837606839%;*margin-left:68.26968539734497%}.row-fluid .offset7{margin-left:62.393162393162385%;*margin-left:62.28677941443899%}.row-fluid .offset7:first-child{margin-left:59.82905982905982%;*margin-left:59.72267685033642%}.row-fluid .offset6{margin-left:53.84615384615384%;*margin-left:53.739770867430444%}.row-fluid .offset6:first-child{margin-left:51.28205128205128%;*margin-left:51.175668303327875%}.row-fluid .offset5{margin-left:45.299145299145295%;*margin-left:45.1927623204219%}.row-fluid .offset5:first-child{margin-left:42.73504273504273%;*margin-left:42.62865975631933%}.row-fluid .offset4{margin-left:36.75213675213675%;*margin-left:36.645753773413354%}.row-fluid .offset4:first-child{margin-left:34.18803418803419%;*margin-left:34.081651209310785%}.row-fluid .offset3{margin-left:28.205128205128204%;*margin-left:28.0987452264048%}.row-fluid .offset3:first-child{margin-left:25.641025641025642%;*margin-left:25.53464266230224%}.row-fluid .offset2{margin-left:19.65811965811966%;*margin-left:19.551736679396257%}.row-fluid .offset2:first-child{margin-left:17.094017094017094%;*margin-left:16.98763411529369%}.row-fluid .offset1{margin-left:11.11111111111111%;*margin-left:11.004728132387708%}.row-fluid .offset1:first-child{margin-left:8.547008547008547%;*margin-left:8.440625568285142%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:30px}input.span12,textarea.span12,.uneditable-input.span12{width:1156px}input.span11,textarea.span11,.uneditable-input.span11{width:1056px}input.span10,textarea.span10,.uneditable-input.span10{width:956px}input.span9,textarea.span9,.uneditable-input.span9{width:856px}input.span8,textarea.span8,.uneditable-input.span8{width:756px}input.span7,textarea.span7,.uneditable-input.span7{width:656px}input.span6,textarea.span6,.uneditable-input.span6{width:556px}input.span5,textarea.span5,.uneditable-input.span5{width:456px}input.span4,textarea.span4,.uneditable-input.span4{width:356px}input.span3,textarea.span3,.uneditable-input.span3{width:256px}input.span2,textarea.span2,.uneditable-input.span2{width:156px}input.span1,textarea.span1,.uneditable-input.span1{width:56px}.thumbnails{margin-left:-30px}.thumbnails>li{margin-left:30px}.row-fluid .thumbnails{margin-left:0}}@media(min-width:768px) and (max-width:979px){.row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:20px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:724px}.span12{width:724px}.span11{width:662px}.span10{width:600px}.span9{width:538px}.span8{width:476px}.span7{width:414px}.span6{width:352px}.span5{width:290px}.span4{width:228px}.span3{width:166px}.span2{width:104px}.span1{width:42px}.offset12{margin-left:764px}.offset11{margin-left:702px}.offset10{margin-left:640px}.offset9{margin-left:578px}.offset8{margin-left:516px}.offset7{margin-left:454px}.offset6{margin-left:392px}.offset5{margin-left:330px}.offset4{margin-left:268px}.offset3{margin-left:206px}.offset2{margin-left:144px}.offset1{margin-left:82px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.7624309392265194%;*margin-left:2.709239449864817%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.7624309392265194%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.43646408839778%;*width:91.38327259903608%}.row-fluid .span10{width:82.87292817679558%;*width:82.81973668743387%}.row-fluid .span9{width:74.30939226519337%;*width:74.25620077583166%}.row-fluid .span8{width:65.74585635359117%;*width:65.69266486422946%}.row-fluid .span7{width:57.18232044198895%;*width:57.12912895262725%}.row-fluid .span6{width:48.61878453038674%;*width:48.56559304102504%}.row-fluid .span5{width:40.05524861878453%;*width:40.00205712942283%}.row-fluid .span4{width:31.491712707182323%;*width:31.43852121782062%}.row-fluid .span3{width:22.92817679558011%;*width:22.87498530621841%}.row-fluid .span2{width:14.3646408839779%;*width:14.311449394616199%}.row-fluid .span1{width:5.801104972375691%;*width:5.747913483013988%}.row-fluid .offset12{margin-left:105.52486187845304%;*margin-left:105.41847889972962%}.row-fluid .offset12:first-child{margin-left:102.76243093922652%;*margin-left:102.6560479605031%}.row-fluid .offset11{margin-left:96.96132596685082%;*margin-left:96.8549429881274%}.row-fluid .offset11:first-child{margin-left:94.1988950276243%;*margin-left:94.09251204890089%}.row-fluid .offset10{margin-left:88.39779005524862%;*margin-left:88.2914070765252%}.row-fluid .offset10:first-child{margin-left:85.6353591160221%;*margin-left:85.52897613729868%}.row-fluid .offset9{margin-left:79.8342541436464%;*margin-left:79.72787116492299%}.row-fluid .offset9:first-child{margin-left:77.07182320441989%;*margin-left:76.96544022569647%}.row-fluid .offset8{margin-left:71.2707182320442%;*margin-left:71.16433525332079%}.row-fluid .offset8:first-child{margin-left:68.50828729281768%;*margin-left:68.40190431409427%}.row-fluid .offset7{margin-left:62.70718232044199%;*margin-left:62.600799341718584%}.row-fluid .offset7:first-child{margin-left:59.94475138121547%;*margin-left:59.838368402492065%}.row-fluid .offset6{margin-left:54.14364640883978%;*margin-left:54.037263430116376%}.row-fluid .offset6:first-child{margin-left:51.38121546961326%;*margin-left:51.27483249088986%}.row-fluid .offset5{margin-left:45.58011049723757%;*margin-left:45.47372751851417%}.row-fluid .offset5:first-child{margin-left:42.81767955801105%;*margin-left:42.71129657928765%}.row-fluid .offset4{margin-left:37.01657458563536%;*margin-left:36.91019160691196%}.row-fluid .offset4:first-child{margin-left:34.25414364640884%;*margin-left:34.14776066768544%}.row-fluid .offset3{margin-left:28.45303867403315%;*margin-left:28.346655695309746%}.row-fluid .offset3:first-child{margin-left:25.69060773480663%;*margin-left:25.584224756083227%}.row-fluid .offset2{margin-left:19.88950276243094%;*margin-left:19.783119783707537%}.row-fluid .offset2:first-child{margin-left:17.12707182320442%;*margin-left:17.02068884448102%}.row-fluid .offset1{margin-left:11.32596685082873%;*margin-left:11.219583872105325%}.row-fluid .offset1:first-child{margin-left:8.56353591160221%;*margin-left:8.457152932878806%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:20px}input.span12,textarea.span12,.uneditable-input.span12{width:710px}input.span11,textarea.span11,.uneditable-input.span11{width:648px}input.span10,textarea.span10,.uneditable-input.span10{width:586px}input.span9,textarea.span9,.uneditable-input.span9{width:524px}input.span8,textarea.span8,.uneditable-input.span8{width:462px}input.span7,textarea.span7,.uneditable-input.span7{width:400px}input.span6,textarea.span6,.uneditable-input.span6{width:338px}input.span5,textarea.span5,.uneditable-input.span5{width:276px}input.span4,textarea.span4,.uneditable-input.span4{width:214px}input.span3,textarea.span3,.uneditable-input.span3{width:152px}input.span2,textarea.span2,.uneditable-input.span2{width:90px}input.span1,textarea.span1,.uneditable-input.span1{width:28px}}@media(max-width:767px){body{padding-right:20px;padding-left:20px}.navbar-fixed-top,.navbar-fixed-bottom,.navbar-static-top{margin-right:-20px;margin-left:-20px}.container-fluid{padding:0}.dl-horizontal dt{float:none;width:auto;clear:none;text-align:left}.dl-horizontal dd{margin-left:0}.container{width:auto}.row-fluid{width:100%}.row,.thumbnails{margin-left:0}.thumbnails>li{float:none;margin-left:0}[class*="span"],.uneditable-input[class*="span"],.row-fluid [class*="span"]{display:block;float:none;width:100%;margin-left:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.span12,.row-fluid .span12{width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="offset"]:first-child{margin-left:0}.input-large,.input-xlarge,.input-xxlarge,input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.input-prepend input,.input-append input,.input-prepend input[class*="span"],.input-append input[class*="span"]{display:inline-block;width:auto}.controls-row [class*="span"]+[class*="span"]{margin-left:0}.modal{position:fixed;top:20px;right:20px;left:20px;width:auto;margin:0}.modal.fade{top:-100px}.modal.fade.in{top:20px}}@media(max-width:480px){.nav-collapse{-webkit-transform:translate3d(0,0,0)}.page-header h1 small{display:block;line-height:20px}input[type="checkbox"],input[type="radio"]{border:1px solid #ccc}.form-horizontal .control-label{float:none;width:auto;padding-top:0;text-align:left}.form-horizontal .controls{margin-left:0}.form-horizontal .control-list{padding-top:0}.form-horizontal .form-actions{padding-right:10px;padding-left:10px}.media .pull-left,.media .pull-right{display:block;float:none;margin-bottom:10px}.media-object{margin-right:0;margin-left:0}.modal{top:10px;right:10px;left:10px}.modal-header .close{padding:10px;margin:-10px}.carousel-caption{position:static}}@media(max-width:979px){body{padding-top:0}.navbar-fixed-top,.navbar-fixed-bottom{position:static}.navbar-fixed-top{margin-bottom:20px}.navbar-fixed-bottom{margin-top:20px}.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding:5px}.navbar .container{width:auto;padding:0}.navbar .brand{padding-right:10px;padding-left:10px;margin:0 0 0 -5px}.nav-collapse{clear:both}.nav-collapse .nav{float:none;margin:0 0 10px}.nav-collapse .nav>li{float:none}.nav-collapse .nav>li>a{margin-bottom:2px}.nav-collapse .nav>.divider-vertical{display:none}.nav-collapse .nav .nav-header{color:#777;text-shadow:none}.nav-collapse .nav>li>a,.nav-collapse .dropdown-menu a{padding:9px 15px;font-weight:bold;color:#777;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.nav-collapse .btn{padding:4px 10px 4px;font-weight:normal;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.nav-collapse .dropdown-menu li+li a{margin-bottom:2px}.nav-collapse .nav>li>a:hover,.nav-collapse .nav>li>a:focus,.nav-collapse .dropdown-menu a:hover,.nav-collapse .dropdown-menu a:focus{background-color:#f2f2f2}.navbar-inverse .nav-collapse .nav>li>a,.navbar-inverse .nav-collapse .dropdown-menu a{color:#999}.navbar-inverse .nav-collapse .nav>li>a:hover,.navbar-inverse .nav-collapse .nav>li>a:focus,.navbar-inverse .nav-collapse .dropdown-menu a:hover,.navbar-inverse .nav-collapse .dropdown-menu a:focus{background-color:#111}.nav-collapse.in .btn-group{padding:0;margin-top:5px}.nav-collapse .dropdown-menu{position:static;top:auto;left:auto;display:none;float:none;max-width:none;padding:0;margin:0 15px;background-color:transparent;border:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.nav-collapse .open>.dropdown-menu{display:block}.nav-collapse .dropdown-menu:before,.nav-collapse .dropdown-menu:after{display:none}.nav-collapse .dropdown-menu .divider{display:none}.nav-collapse .nav>li>.dropdown-menu:before,.nav-collapse .nav>li>.dropdown-menu:after{display:none}.nav-collapse .navbar-form,.nav-collapse .navbar-search{float:none;padding:10px 15px;margin:10px 0;border-top:1px solid #f2f2f2;border-bottom:1px solid #f2f2f2;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1)}.navbar-inverse .nav-collapse .navbar-form,.navbar-inverse .nav-collapse .navbar-search{border-top-color:#111;border-bottom-color:#111}.navbar .nav-collapse .nav.pull-right{float:none;margin-left:0}.nav-collapse,.nav-collapse.collapse{height:0;overflow:hidden}.navbar .btn-navbar{display:block}.navbar-static .navbar-inner{padding-right:10px;padding-left:10px}}@media(min-width:980px){.nav-collapse.collapse{height:auto!important;overflow:visible!important}} diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/css/bootstrap.css b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/css/bootstrap.css new file mode 100644 index 000000000..42c79d6e4 --- /dev/null +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/css/bootstrap.css @@ -0,0 +1,6760 @@ +/*! + * Bootstrap v3.3.6 (http://getbootstrap.com) + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ +/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */ +html { + font-family: sans-serif; + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; +} +body { + margin: 0; +} +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +main, +menu, +nav, +section, +summary { + display: block; +} +audio, +canvas, +progress, +video { + display: inline-block; + vertical-align: baseline; +} +audio:not([controls]) { + display: none; + height: 0; +} +[hidden], +template { + display: none; +} +a { + background-color: transparent; +} +a:active, +a:hover { + outline: 0; +} +abbr[title] { + border-bottom: 1px dotted; +} +b, +strong { + font-weight: bold; +} +dfn { + font-style: italic; +} +h1 { + margin: .67em 0; + font-size: 2em; +} +mark { + color: #000; + background: #ff0; +} +small { + font-size: 80%; +} +sub, +sup { + position: relative; + font-size: 75%; + line-height: 0; + vertical-align: baseline; +} +sup { + top: -.5em; +} +sub { + bottom: -.25em; +} +img { + border: 0; +} +svg:not(:root) { + overflow: hidden; +} +figure { + margin: 1em 40px; +} +hr { + height: 0; + -webkit-box-sizing: content-box; + -moz-box-sizing: content-box; + box-sizing: content-box; +} +pre { + overflow: auto; +} +code, +kbd, +pre, +samp { + font-family: monospace, monospace; + font-size: 1em; +} +button, +input, +optgroup, +select, +textarea { + margin: 0; + font: inherit; + color: inherit; +} +button { + overflow: visible; +} +button, +select { + text-transform: none; +} +button, +html input[type="button"], +input[type="reset"], +input[type="submit"] { + -webkit-appearance: button; + cursor: pointer; +} +button[disabled], +html input[disabled] { + cursor: default; +} +button::-moz-focus-inner, +input::-moz-focus-inner { + padding: 0; + border: 0; +} +input { + line-height: normal; +} +input[type="checkbox"], +input[type="radio"] { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + padding: 0; +} +input[type="number"]::-webkit-inner-spin-button, +input[type="number"]::-webkit-outer-spin-button { + height: auto; +} +input[type="search"] { + -webkit-box-sizing: content-box; + -moz-box-sizing: content-box; + box-sizing: content-box; + -webkit-appearance: textfield; +} +input[type="search"]::-webkit-search-cancel-button, +input[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} +fieldset { + padding: .35em .625em .75em; + margin: 0 2px; + border: 1px solid #c0c0c0; +} +legend { + padding: 0; + border: 0; +} +textarea { + overflow: auto; +} +optgroup { + font-weight: bold; +} +table { + border-spacing: 0; + border-collapse: collapse; +} +td, +th { + padding: 0; +} +/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */ +@media print { + *, + *:before, + *:after { + color: #000 !important; + text-shadow: none !important; + background: transparent !important; + -webkit-box-shadow: none !important; + box-shadow: none !important; + } + a, + a:visited { + text-decoration: underline; + } + a[href]:after { + content: " (" attr(href) ")"; + } + abbr[title]:after { + content: " (" attr(title) ")"; + } + a[href^="#"]:after, + a[href^="javascript:"]:after { + content: ""; + } + pre, + blockquote { + border: 1px solid #999; + + page-break-inside: avoid; + } + thead { + display: table-header-group; + } + tr, + img { + page-break-inside: avoid; + } + img { + max-width: 100% !important; + } + p, + h2, + h3 { + orphans: 3; + widows: 3; + } + h2, + h3 { + page-break-after: avoid; + } + .navbar { + display: none; + } + .btn > .caret, + .dropup > .btn > .caret { + border-top-color: #000 !important; + } + .label { + border: 1px solid #000; + } + .table { + border-collapse: collapse !important; + } + .table td, + .table th { + background-color: #fff !important; + } + .table-bordered th, + .table-bordered td { + border: 1px solid #ddd !important; + } +} +@font-face { + font-family: 'Glyphicons Halflings'; + + src: url('../fonts/glyphicons-halflings-regular.eot'); + src: url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), url('../fonts/glyphicons-halflings-regular.woff2') format('woff2'), url('../fonts/glyphicons-halflings-regular.woff') format('woff'), url('../fonts/glyphicons-halflings-regular.ttf') format('truetype'), url('../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg'); +} +.glyphicon { + position: relative; + top: 1px; + display: inline-block; + font-family: 'Glyphicons Halflings'; + font-style: normal; + font-weight: normal; + line-height: 1; + + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} +.glyphicon-asterisk:before { + content: "\002a"; +} +.glyphicon-plus:before { + content: "\002b"; +} +.glyphicon-euro:before, +.glyphicon-eur:before { + content: "\20ac"; +} +.glyphicon-minus:before { + content: "\2212"; +} +.glyphicon-cloud:before { + content: "\2601"; +} +.glyphicon-envelope:before { + content: "\2709"; +} +.glyphicon-pencil:before { + content: "\270f"; +} +.glyphicon-glass:before { + content: "\e001"; +} +.glyphicon-music:before { + content: "\e002"; +} +.glyphicon-search:before { + content: "\e003"; +} +.glyphicon-heart:before { + content: "\e005"; +} +.glyphicon-star:before { + content: "\e006"; +} +.glyphicon-star-empty:before { + content: "\e007"; +} +.glyphicon-user:before { + content: "\e008"; +} +.glyphicon-film:before { + content: "\e009"; +} +.glyphicon-th-large:before { + content: "\e010"; +} +.glyphicon-th:before { + content: "\e011"; +} +.glyphicon-th-list:before { + content: "\e012"; +} +.glyphicon-ok:before { + content: "\e013"; +} +.glyphicon-remove:before { + content: "\e014"; +} +.glyphicon-zoom-in:before { + content: "\e015"; +} +.glyphicon-zoom-out:before { + content: "\e016"; +} +.glyphicon-off:before { + content: "\e017"; +} +.glyphicon-signal:before { + content: "\e018"; +} +.glyphicon-cog:before { + content: "\e019"; +} +.glyphicon-trash:before { + content: "\e020"; +} +.glyphicon-home:before { + content: "\e021"; +} +.glyphicon-file:before { + content: "\e022"; +} +.glyphicon-time:before { + content: "\e023"; +} +.glyphicon-road:before { + content: "\e024"; +} +.glyphicon-download-alt:before { + content: "\e025"; +} +.glyphicon-download:before { + content: "\e026"; +} +.glyphicon-upload:before { + content: "\e027"; +} +.glyphicon-inbox:before { + content: "\e028"; +} +.glyphicon-play-circle:before { + content: "\e029"; +} +.glyphicon-repeat:before { + content: "\e030"; +} +.glyphicon-refresh:before { + content: "\e031"; +} +.glyphicon-list-alt:before { + content: "\e032"; +} +.glyphicon-lock:before { + content: "\e033"; +} +.glyphicon-flag:before { + content: "\e034"; +} +.glyphicon-headphones:before { + content: "\e035"; +} +.glyphicon-volume-off:before { + content: "\e036"; +} +.glyphicon-volume-down:before { + content: "\e037"; +} +.glyphicon-volume-up:before { + content: "\e038"; +} +.glyphicon-qrcode:before { + content: "\e039"; +} +.glyphicon-barcode:before { + content: "\e040"; +} +.glyphicon-tag:before { + content: "\e041"; +} +.glyphicon-tags:before { + content: "\e042"; +} +.glyphicon-book:before { + content: "\e043"; +} +.glyphicon-bookmark:before { + content: "\e044"; +} +.glyphicon-print:before { + content: "\e045"; +} +.glyphicon-camera:before { + content: "\e046"; +} +.glyphicon-font:before { + content: "\e047"; +} +.glyphicon-bold:before { + content: "\e048"; +} +.glyphicon-italic:before { + content: "\e049"; +} +.glyphicon-text-height:before { + content: "\e050"; +} +.glyphicon-text-width:before { + content: "\e051"; +} +.glyphicon-align-left:before { + content: "\e052"; +} +.glyphicon-align-center:before { + content: "\e053"; +} +.glyphicon-align-right:before { + content: "\e054"; +} +.glyphicon-align-justify:before { + content: "\e055"; +} +.glyphicon-list:before { + content: "\e056"; +} +.glyphicon-indent-left:before { + content: "\e057"; +} +.glyphicon-indent-right:before { + content: "\e058"; +} +.glyphicon-facetime-video:before { + content: "\e059"; +} +.glyphicon-picture:before { + content: "\e060"; +} +.glyphicon-map-marker:before { + content: "\e062"; +} +.glyphicon-adjust:before { + content: "\e063"; +} +.glyphicon-tint:before { + content: "\e064"; +} +.glyphicon-edit:before { + content: "\e065"; +} +.glyphicon-share:before { + content: "\e066"; +} +.glyphicon-check:before { + content: "\e067"; +} +.glyphicon-move:before { + content: "\e068"; +} +.glyphicon-step-backward:before { + content: "\e069"; +} +.glyphicon-fast-backward:before { + content: "\e070"; +} +.glyphicon-backward:before { + content: "\e071"; +} +.glyphicon-play:before { + content: "\e072"; +} +.glyphicon-pause:before { + content: "\e073"; +} +.glyphicon-stop:before { + content: "\e074"; +} +.glyphicon-forward:before { + content: "\e075"; +} +.glyphicon-fast-forward:before { + content: "\e076"; +} +.glyphicon-step-forward:before { + content: "\e077"; +} +.glyphicon-eject:before { + content: "\e078"; +} +.glyphicon-chevron-left:before { + content: "\e079"; +} +.glyphicon-chevron-right:before { + content: "\e080"; +} +.glyphicon-plus-sign:before { + content: "\e081"; +} +.glyphicon-minus-sign:before { + content: "\e082"; +} +.glyphicon-remove-sign:before { + content: "\e083"; +} +.glyphicon-ok-sign:before { + content: "\e084"; +} +.glyphicon-question-sign:before { + content: "\e085"; +} +.glyphicon-info-sign:before { + content: "\e086"; +} +.glyphicon-screenshot:before { + content: "\e087"; +} +.glyphicon-remove-circle:before { + content: "\e088"; +} +.glyphicon-ok-circle:before { + content: "\e089"; +} +.glyphicon-ban-circle:before { + content: "\e090"; +} +.glyphicon-arrow-left:before { + content: "\e091"; +} +.glyphicon-arrow-right:before { + content: "\e092"; +} +.glyphicon-arrow-up:before { + content: "\e093"; +} +.glyphicon-arrow-down:before { + content: "\e094"; +} +.glyphicon-share-alt:before { + content: "\e095"; +} +.glyphicon-resize-full:before { + content: "\e096"; +} +.glyphicon-resize-small:before { + content: "\e097"; +} +.glyphicon-exclamation-sign:before { + content: "\e101"; +} +.glyphicon-gift:before { + content: "\e102"; +} +.glyphicon-leaf:before { + content: "\e103"; +} +.glyphicon-fire:before { + content: "\e104"; +} +.glyphicon-eye-open:before { + content: "\e105"; +} +.glyphicon-eye-close:before { + content: "\e106"; +} +.glyphicon-warning-sign:before { + content: "\e107"; +} +.glyphicon-plane:before { + content: "\e108"; +} +.glyphicon-calendar:before { + content: "\e109"; +} +.glyphicon-random:before { + content: "\e110"; +} +.glyphicon-comment:before { + content: "\e111"; +} +.glyphicon-magnet:before { + content: "\e112"; +} +.glyphicon-chevron-up:before { + content: "\e113"; +} +.glyphicon-chevron-down:before { + content: "\e114"; +} +.glyphicon-retweet:before { + content: "\e115"; +} +.glyphicon-shopping-cart:before { + content: "\e116"; +} +.glyphicon-folder-close:before { + content: "\e117"; +} +.glyphicon-folder-open:before { + content: "\e118"; +} +.glyphicon-resize-vertical:before { + content: "\e119"; +} +.glyphicon-resize-horizontal:before { + content: "\e120"; +} +.glyphicon-hdd:before { + content: "\e121"; +} +.glyphicon-bullhorn:before { + content: "\e122"; +} +.glyphicon-bell:before { + content: "\e123"; +} +.glyphicon-certificate:before { + content: "\e124"; +} +.glyphicon-thumbs-up:before { + content: "\e125"; +} +.glyphicon-thumbs-down:before { + content: "\e126"; +} +.glyphicon-hand-right:before { + content: "\e127"; +} +.glyphicon-hand-left:before { + content: "\e128"; +} +.glyphicon-hand-up:before { + content: "\e129"; +} +.glyphicon-hand-down:before { + content: "\e130"; +} +.glyphicon-circle-arrow-right:before { + content: "\e131"; +} +.glyphicon-circle-arrow-left:before { + content: "\e132"; +} +.glyphicon-circle-arrow-up:before { + content: "\e133"; +} +.glyphicon-circle-arrow-down:before { + content: "\e134"; +} +.glyphicon-globe:before { + content: "\e135"; +} +.glyphicon-wrench:before { + content: "\e136"; +} +.glyphicon-tasks:before { + content: "\e137"; +} +.glyphicon-filter:before { + content: "\e138"; +} +.glyphicon-briefcase:before { + content: "\e139"; +} +.glyphicon-fullscreen:before { + content: "\e140"; +} +.glyphicon-dashboard:before { + content: "\e141"; +} +.glyphicon-paperclip:before { + content: "\e142"; +} +.glyphicon-heart-empty:before { + content: "\e143"; +} +.glyphicon-link:before { + content: "\e144"; +} +.glyphicon-phone:before { + content: "\e145"; +} +.glyphicon-pushpin:before { + content: "\e146"; +} +.glyphicon-usd:before { + content: "\e148"; +} +.glyphicon-gbp:before { + content: "\e149"; +} +.glyphicon-sort:before { + content: "\e150"; +} +.glyphicon-sort-by-alphabet:before { + content: "\e151"; +} +.glyphicon-sort-by-alphabet-alt:before { + content: "\e152"; +} +.glyphicon-sort-by-order:before { + content: "\e153"; +} +.glyphicon-sort-by-order-alt:before { + content: "\e154"; +} +.glyphicon-sort-by-attributes:before { + content: "\e155"; +} +.glyphicon-sort-by-attributes-alt:before { + content: "\e156"; +} +.glyphicon-unchecked:before { + content: "\e157"; +} +.glyphicon-expand:before { + content: "\e158"; +} +.glyphicon-collapse-down:before { + content: "\e159"; +} +.glyphicon-collapse-up:before { + content: "\e160"; +} +.glyphicon-log-in:before { + content: "\e161"; +} +.glyphicon-flash:before { + content: "\e162"; +} +.glyphicon-log-out:before { + content: "\e163"; +} +.glyphicon-new-window:before { + content: "\e164"; +} +.glyphicon-record:before { + content: "\e165"; +} +.glyphicon-save:before { + content: "\e166"; +} +.glyphicon-open:before { + content: "\e167"; +} +.glyphicon-saved:before { + content: "\e168"; +} +.glyphicon-import:before { + content: "\e169"; +} +.glyphicon-export:before { + content: "\e170"; +} +.glyphicon-send:before { + content: "\e171"; +} +.glyphicon-floppy-disk:before { + content: "\e172"; +} +.glyphicon-floppy-saved:before { + content: "\e173"; +} +.glyphicon-floppy-remove:before { + content: "\e174"; +} +.glyphicon-floppy-save:before { + content: "\e175"; +} +.glyphicon-floppy-open:before { + content: "\e176"; +} +.glyphicon-credit-card:before { + content: "\e177"; +} +.glyphicon-transfer:before { + content: "\e178"; +} +.glyphicon-cutlery:before { + content: "\e179"; +} +.glyphicon-header:before { + content: "\e180"; +} +.glyphicon-compressed:before { + content: "\e181"; +} +.glyphicon-earphone:before { + content: "\e182"; +} +.glyphicon-phone-alt:before { + content: "\e183"; +} +.glyphicon-tower:before { + content: "\e184"; +} +.glyphicon-stats:before { + content: "\e185"; +} +.glyphicon-sd-video:before { + content: "\e186"; +} +.glyphicon-hd-video:before { + content: "\e187"; +} +.glyphicon-subtitles:before { + content: "\e188"; +} +.glyphicon-sound-stereo:before { + content: "\e189"; +} +.glyphicon-sound-dolby:before { + content: "\e190"; +} +.glyphicon-sound-5-1:before { + content: "\e191"; +} +.glyphicon-sound-6-1:before { + content: "\e192"; +} +.glyphicon-sound-7-1:before { + content: "\e193"; +} +.glyphicon-copyright-mark:before { + content: "\e194"; +} +.glyphicon-registration-mark:before { + content: "\e195"; +} +.glyphicon-cloud-download:before { + content: "\e197"; +} +.glyphicon-cloud-upload:before { + content: "\e198"; +} +.glyphicon-tree-conifer:before { + content: "\e199"; +} +.glyphicon-tree-deciduous:before { + content: "\e200"; +} +.glyphicon-cd:before { + content: "\e201"; +} +.glyphicon-save-file:before { + content: "\e202"; +} +.glyphicon-open-file:before { + content: "\e203"; +} +.glyphicon-level-up:before { + content: "\e204"; +} +.glyphicon-copy:before { + content: "\e205"; +} +.glyphicon-paste:before { + content: "\e206"; +} +.glyphicon-alert:before { + content: "\e209"; +} +.glyphicon-equalizer:before { + content: "\e210"; +} +.glyphicon-king:before { + content: "\e211"; +} +.glyphicon-queen:before { + content: "\e212"; +} +.glyphicon-pawn:before { + content: "\e213"; +} +.glyphicon-bishop:before { + content: "\e214"; +} +.glyphicon-knight:before { + content: "\e215"; +} +.glyphicon-baby-formula:before { + content: "\e216"; +} +.glyphicon-tent:before { + content: "\26fa"; +} +.glyphicon-blackboard:before { + content: "\e218"; +} +.glyphicon-bed:before { + content: "\e219"; +} +.glyphicon-apple:before { + content: "\f8ff"; +} +.glyphicon-erase:before { + content: "\e221"; +} +.glyphicon-hourglass:before { + content: "\231b"; +} +.glyphicon-lamp:before { + content: "\e223"; +} +.glyphicon-duplicate:before { + content: "\e224"; +} +.glyphicon-piggy-bank:before { + content: "\e225"; +} +.glyphicon-scissors:before { + content: "\e226"; +} +.glyphicon-bitcoin:before { + content: "\e227"; +} +.glyphicon-btc:before { + content: "\e227"; +} +.glyphicon-xbt:before { + content: "\e227"; +} +.glyphicon-yen:before { + content: "\00a5"; +} +.glyphicon-jpy:before { + content: "\00a5"; +} +.glyphicon-ruble:before { + content: "\20bd"; +} +.glyphicon-rub:before { + content: "\20bd"; +} +.glyphicon-scale:before { + content: "\e230"; +} +.glyphicon-ice-lolly:before { + content: "\e231"; +} +.glyphicon-ice-lolly-tasted:before { + content: "\e232"; +} +.glyphicon-education:before { + content: "\e233"; +} +.glyphicon-option-horizontal:before { + content: "\e234"; +} +.glyphicon-option-vertical:before { + content: "\e235"; +} +.glyphicon-menu-hamburger:before { + content: "\e236"; +} +.glyphicon-modal-window:before { + content: "\e237"; +} +.glyphicon-oil:before { + content: "\e238"; +} +.glyphicon-grain:before { + content: "\e239"; +} +.glyphicon-sunglasses:before { + content: "\e240"; +} +.glyphicon-text-size:before { + content: "\e241"; +} +.glyphicon-text-color:before { + content: "\e242"; +} +.glyphicon-text-background:before { + content: "\e243"; +} +.glyphicon-object-align-top:before { + content: "\e244"; +} +.glyphicon-object-align-bottom:before { + content: "\e245"; +} +.glyphicon-object-align-horizontal:before { + content: "\e246"; +} +.glyphicon-object-align-left:before { + content: "\e247"; +} +.glyphicon-object-align-vertical:before { + content: "\e248"; +} +.glyphicon-object-align-right:before { + content: "\e249"; +} +.glyphicon-triangle-right:before { + content: "\e250"; +} +.glyphicon-triangle-left:before { + content: "\e251"; +} +.glyphicon-triangle-bottom:before { + content: "\e252"; +} +.glyphicon-triangle-top:before { + content: "\e253"; +} +.glyphicon-console:before { + content: "\e254"; +} +.glyphicon-superscript:before { + content: "\e255"; +} +.glyphicon-subscript:before { + content: "\e256"; +} +.glyphicon-menu-left:before { + content: "\e257"; +} +.glyphicon-menu-right:before { + content: "\e258"; +} +.glyphicon-menu-down:before { + content: "\e259"; +} +.glyphicon-menu-up:before { + content: "\e260"; +} +* { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +*:before, +*:after { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +html { + font-size: 10px; + + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} +body { + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 14px; + line-height: 1.42857143; + color: #333; + background-color: #fff; +} +input, +button, +select, +textarea { + font-family: inherit; + font-size: inherit; + line-height: inherit; +} +a { + color: #337ab7; + text-decoration: none; +} +a:hover, +a:focus { + color: #23527c; + text-decoration: underline; +} +a:focus { + outline: thin dotted; + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} +figure { + margin: 0; +} +img { + vertical-align: middle; +} +.img-responsive, +.thumbnail > img, +.thumbnail a > img, +.carousel-inner > .item > img, +.carousel-inner > .item > a > img { + display: block; + max-width: 100%; + height: auto; +} +.img-rounded { + border-radius: 6px; +} +.img-thumbnail { + display: inline-block; + max-width: 100%; + height: auto; + padding: 4px; + line-height: 1.42857143; + background-color: #fff; + border: 1px solid #ddd; + border-radius: 4px; + -webkit-transition: all .2s ease-in-out; + -o-transition: all .2s ease-in-out; + transition: all .2s ease-in-out; +} +.img-circle { + border-radius: 50%; +} +hr { + margin-top: 20px; + margin-bottom: 20px; + border: 0; + border-top: 1px solid #eee; +} +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; +} +.sr-only-focusable:active, +.sr-only-focusable:focus { + position: static; + width: auto; + height: auto; + margin: 0; + overflow: visible; + clip: auto; +} +[role="button"] { + cursor: pointer; +} +h1, +h2, +h3, +h4, +h5, +h6, +.h1, +.h2, +.h3, +.h4, +.h5, +.h6 { + font-family: inherit; + font-weight: 500; + line-height: 1.1; + color: inherit; +} +h1 small, +h2 small, +h3 small, +h4 small, +h5 small, +h6 small, +.h1 small, +.h2 small, +.h3 small, +.h4 small, +.h5 small, +.h6 small, +h1 .small, +h2 .small, +h3 .small, +h4 .small, +h5 .small, +h6 .small, +.h1 .small, +.h2 .small, +.h3 .small, +.h4 .small, +.h5 .small, +.h6 .small { + font-weight: normal; + line-height: 1; + color: #777; +} +h1, +.h1, +h2, +.h2, +h3, +.h3 { + margin-top: 20px; + margin-bottom: 10px; +} +h1 small, +.h1 small, +h2 small, +.h2 small, +h3 small, +.h3 small, +h1 .small, +.h1 .small, +h2 .small, +.h2 .small, +h3 .small, +.h3 .small { + font-size: 65%; +} +h4, +.h4, +h5, +.h5, +h6, +.h6 { + margin-top: 10px; + margin-bottom: 10px; +} +h4 small, +.h4 small, +h5 small, +.h5 small, +h6 small, +.h6 small, +h4 .small, +.h4 .small, +h5 .small, +.h5 .small, +h6 .small, +.h6 .small { + font-size: 75%; +} +h1, +.h1 { + font-size: 36px; +} +h2, +.h2 { + font-size: 30px; +} +h3, +.h3 { + font-size: 24px; +} +h4, +.h4 { + font-size: 18px; +} +h5, +.h5 { + font-size: 14px; +} +h6, +.h6 { + font-size: 12px; +} +p { + margin: 0 0 10px; +} +.lead { + margin-bottom: 20px; + font-size: 16px; + font-weight: 300; + line-height: 1.4; +} +@media (min-width: 768px) { + .lead { + font-size: 21px; + } +} +small, +.small { + font-size: 85%; +} +mark, +.mark { + padding: .2em; + background-color: #fcf8e3; +} +.text-left { + text-align: left; +} +.text-right { + text-align: right; +} +.text-center { + text-align: center; +} +.text-justify { + text-align: justify; +} +.text-nowrap { + white-space: nowrap; +} +.text-lowercase { + text-transform: lowercase; +} +.text-uppercase { + text-transform: uppercase; +} +.text-capitalize { + text-transform: capitalize; +} +.text-muted { + color: #777; +} +.text-primary { + color: #337ab7; +} +a.text-primary:hover, +a.text-primary:focus { + color: #286090; +} +.text-success { + color: #3c763d; +} +a.text-success:hover, +a.text-success:focus { + color: #2b542c; +} +.text-info { + color: #31708f; +} +a.text-info:hover, +a.text-info:focus { + color: #245269; +} +.text-warning { + color: #8a6d3b; +} +a.text-warning:hover, +a.text-warning:focus { + color: #66512c; +} +.text-danger { + color: #a94442; +} +a.text-danger:hover, +a.text-danger:focus { + color: #843534; +} +.bg-primary { + color: #fff; + background-color: #337ab7; +} +a.bg-primary:hover, +a.bg-primary:focus { + background-color: #286090; +} +.bg-success { + background-color: #dff0d8; +} +a.bg-success:hover, +a.bg-success:focus { + background-color: #c1e2b3; +} +.bg-info { + background-color: #d9edf7; +} +a.bg-info:hover, +a.bg-info:focus { + background-color: #afd9ee; +} +.bg-warning { + background-color: #fcf8e3; +} +a.bg-warning:hover, +a.bg-warning:focus { + background-color: #f7ecb5; +} +.bg-danger { + background-color: #f2dede; +} +a.bg-danger:hover, +a.bg-danger:focus { + background-color: #e4b9b9; +} +.page-header { + padding-bottom: 9px; + margin: 40px 0 20px; + border-bottom: 1px solid #eee; +} +ul, +ol { + margin-top: 0; + margin-bottom: 10px; +} +ul ul, +ol ul, +ul ol, +ol ol { + margin-bottom: 0; +} +.list-unstyled { + padding-left: 0; + list-style: none; +} +.list-inline { + padding-left: 0; + margin-left: -5px; + list-style: none; +} +.list-inline > li { + display: inline-block; + padding-right: 5px; + padding-left: 5px; +} +dl { + margin-top: 0; + margin-bottom: 20px; +} +dt, +dd { + line-height: 1.42857143; +} +dt { + font-weight: bold; +} +dd { + margin-left: 0; +} +@media (min-width: 768px) { + .dl-horizontal dt { + float: left; + width: 160px; + overflow: hidden; + clear: left; + text-align: right; + text-overflow: ellipsis; + white-space: nowrap; + } + .dl-horizontal dd { + margin-left: 180px; + } +} +abbr[title], +abbr[data-original-title] { + cursor: help; + border-bottom: 1px dotted #777; +} +.initialism { + font-size: 90%; + text-transform: uppercase; +} +blockquote { + padding: 10px 20px; + margin: 0 0 20px; + font-size: 17.5px; + border-left: 5px solid #eee; +} +blockquote p:last-child, +blockquote ul:last-child, +blockquote ol:last-child { + margin-bottom: 0; +} +blockquote footer, +blockquote small, +blockquote .small { + display: block; + font-size: 80%; + line-height: 1.42857143; + color: #777; +} +blockquote footer:before, +blockquote small:before, +blockquote .small:before { + content: '\2014 \00A0'; +} +.blockquote-reverse, +blockquote.pull-right { + padding-right: 15px; + padding-left: 0; + text-align: right; + border-right: 5px solid #eee; + border-left: 0; +} +.blockquote-reverse footer:before, +blockquote.pull-right footer:before, +.blockquote-reverse small:before, +blockquote.pull-right small:before, +.blockquote-reverse .small:before, +blockquote.pull-right .small:before { + content: ''; +} +.blockquote-reverse footer:after, +blockquote.pull-right footer:after, +.blockquote-reverse small:after, +blockquote.pull-right small:after, +.blockquote-reverse .small:after, +blockquote.pull-right .small:after { + content: '\00A0 \2014'; +} +address { + margin-bottom: 20px; + font-style: normal; + line-height: 1.42857143; +} +code, +kbd, +pre, +samp { + font-family: Menlo, Monaco, Consolas, "Courier New", monospace; +} +code { + padding: 2px 4px; + font-size: 90%; + color: #c7254e; + background-color: #f9f2f4; + border-radius: 4px; +} +kbd { + padding: 2px 4px; + font-size: 90%; + color: #fff; + background-color: #333; + border-radius: 3px; + -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .25); + box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .25); +} +kbd kbd { + padding: 0; + font-size: 100%; + font-weight: bold; + -webkit-box-shadow: none; + box-shadow: none; +} +pre { + display: block; + padding: 9.5px; + margin: 0 0 10px; + font-size: 13px; + line-height: 1.42857143; + color: #333; + word-break: break-all; + word-wrap: break-word; + background-color: #f5f5f5; + border: 1px solid #ccc; + border-radius: 4px; +} +pre code { + padding: 0; + font-size: inherit; + color: inherit; + white-space: pre-wrap; + background-color: transparent; + border-radius: 0; +} +.pre-scrollable { + max-height: 340px; + overflow-y: scroll; +} +.container { + padding-right: 15px; + padding-left: 15px; + margin-right: auto; + margin-left: auto; +} +@media (min-width: 768px) { + .container { + width: 750px; + } +} +@media (min-width: 992px) { + .container { + width: 970px; + } +} +@media (min-width: 1200px) { + .container { + width: 1170px; + } +} +.container-fluid { + padding-right: 15px; + padding-left: 15px; + margin-right: auto; + margin-left: auto; +} +.row { + margin-right: -15px; + margin-left: -15px; +} +.col-xs-1, .col-sm-1, .col-md-1, .col-lg-1, .col-xs-2, .col-sm-2, .col-md-2, .col-lg-2, .col-xs-3, .col-sm-3, .col-md-3, .col-lg-3, .col-xs-4, .col-sm-4, .col-md-4, .col-lg-4, .col-xs-5, .col-sm-5, .col-md-5, .col-lg-5, .col-xs-6, .col-sm-6, .col-md-6, .col-lg-6, .col-xs-7, .col-sm-7, .col-md-7, .col-lg-7, .col-xs-8, .col-sm-8, .col-md-8, .col-lg-8, .col-xs-9, .col-sm-9, .col-md-9, .col-lg-9, .col-xs-10, .col-sm-10, .col-md-10, .col-lg-10, .col-xs-11, .col-sm-11, .col-md-11, .col-lg-11, .col-xs-12, .col-sm-12, .col-md-12, .col-lg-12 { + position: relative; + min-height: 1px; + padding-right: 15px; + padding-left: 15px; +} +.col-xs-1, .col-xs-2, .col-xs-3, .col-xs-4, .col-xs-5, .col-xs-6, .col-xs-7, .col-xs-8, .col-xs-9, .col-xs-10, .col-xs-11, .col-xs-12 { + float: left; +} +.col-xs-12 { + width: 100%; +} +.col-xs-11 { + width: 91.66666667%; +} +.col-xs-10 { + width: 83.33333333%; +} +.col-xs-9 { + width: 75%; +} +.col-xs-8 { + width: 66.66666667%; +} +.col-xs-7 { + width: 58.33333333%; +} +.col-xs-6 { + width: 50%; +} +.col-xs-5 { + width: 41.66666667%; +} +.col-xs-4 { + width: 33.33333333%; +} +.col-xs-3 { + width: 25%; +} +.col-xs-2 { + width: 16.66666667%; +} +.col-xs-1 { + width: 8.33333333%; +} +.col-xs-pull-12 { + right: 100%; +} +.col-xs-pull-11 { + right: 91.66666667%; +} +.col-xs-pull-10 { + right: 83.33333333%; +} +.col-xs-pull-9 { + right: 75%; +} +.col-xs-pull-8 { + right: 66.66666667%; +} +.col-xs-pull-7 { + right: 58.33333333%; +} +.col-xs-pull-6 { + right: 50%; +} +.col-xs-pull-5 { + right: 41.66666667%; +} +.col-xs-pull-4 { + right: 33.33333333%; +} +.col-xs-pull-3 { + right: 25%; +} +.col-xs-pull-2 { + right: 16.66666667%; +} +.col-xs-pull-1 { + right: 8.33333333%; +} +.col-xs-pull-0 { + right: auto; +} +.col-xs-push-12 { + left: 100%; +} +.col-xs-push-11 { + left: 91.66666667%; +} +.col-xs-push-10 { + left: 83.33333333%; +} +.col-xs-push-9 { + left: 75%; +} +.col-xs-push-8 { + left: 66.66666667%; +} +.col-xs-push-7 { + left: 58.33333333%; +} +.col-xs-push-6 { + left: 50%; +} +.col-xs-push-5 { + left: 41.66666667%; +} +.col-xs-push-4 { + left: 33.33333333%; +} +.col-xs-push-3 { + left: 25%; +} +.col-xs-push-2 { + left: 16.66666667%; +} +.col-xs-push-1 { + left: 8.33333333%; +} +.col-xs-push-0 { + left: auto; +} +.col-xs-offset-12 { + margin-left: 100%; +} +.col-xs-offset-11 { + margin-left: 91.66666667%; +} +.col-xs-offset-10 { + margin-left: 83.33333333%; +} +.col-xs-offset-9 { + margin-left: 75%; +} +.col-xs-offset-8 { + margin-left: 66.66666667%; +} +.col-xs-offset-7 { + margin-left: 58.33333333%; +} +.col-xs-offset-6 { + margin-left: 50%; +} +.col-xs-offset-5 { + margin-left: 41.66666667%; +} +.col-xs-offset-4 { + margin-left: 33.33333333%; +} +.col-xs-offset-3 { + margin-left: 25%; +} +.col-xs-offset-2 { + margin-left: 16.66666667%; +} +.col-xs-offset-1 { + margin-left: 8.33333333%; +} +.col-xs-offset-0 { + margin-left: 0; +} +@media (min-width: 768px) { + .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12 { + float: left; + } + .col-sm-12 { + width: 100%; + } + .col-sm-11 { + width: 91.66666667%; + } + .col-sm-10 { + width: 83.33333333%; + } + .col-sm-9 { + width: 75%; + } + .col-sm-8 { + width: 66.66666667%; + } + .col-sm-7 { + width: 58.33333333%; + } + .col-sm-6 { + width: 50%; + } + .col-sm-5 { + width: 41.66666667%; + } + .col-sm-4 { + width: 33.33333333%; + } + .col-sm-3 { + width: 25%; + } + .col-sm-2 { + width: 16.66666667%; + } + .col-sm-1 { + width: 8.33333333%; + } + .col-sm-pull-12 { + right: 100%; + } + .col-sm-pull-11 { + right: 91.66666667%; + } + .col-sm-pull-10 { + right: 83.33333333%; + } + .col-sm-pull-9 { + right: 75%; + } + .col-sm-pull-8 { + right: 66.66666667%; + } + .col-sm-pull-7 { + right: 58.33333333%; + } + .col-sm-pull-6 { + right: 50%; + } + .col-sm-pull-5 { + right: 41.66666667%; + } + .col-sm-pull-4 { + right: 33.33333333%; + } + .col-sm-pull-3 { + right: 25%; + } + .col-sm-pull-2 { + right: 16.66666667%; + } + .col-sm-pull-1 { + right: 8.33333333%; + } + .col-sm-pull-0 { + right: auto; + } + .col-sm-push-12 { + left: 100%; + } + .col-sm-push-11 { + left: 91.66666667%; + } + .col-sm-push-10 { + left: 83.33333333%; + } + .col-sm-push-9 { + left: 75%; + } + .col-sm-push-8 { + left: 66.66666667%; + } + .col-sm-push-7 { + left: 58.33333333%; + } + .col-sm-push-6 { + left: 50%; + } + .col-sm-push-5 { + left: 41.66666667%; + } + .col-sm-push-4 { + left: 33.33333333%; + } + .col-sm-push-3 { + left: 25%; + } + .col-sm-push-2 { + left: 16.66666667%; + } + .col-sm-push-1 { + left: 8.33333333%; + } + .col-sm-push-0 { + left: auto; + } + .col-sm-offset-12 { + margin-left: 100%; + } + .col-sm-offset-11 { + margin-left: 91.66666667%; + } + .col-sm-offset-10 { + margin-left: 83.33333333%; + } + .col-sm-offset-9 { + margin-left: 75%; + } + .col-sm-offset-8 { + margin-left: 66.66666667%; + } + .col-sm-offset-7 { + margin-left: 58.33333333%; + } + .col-sm-offset-6 { + margin-left: 50%; + } + .col-sm-offset-5 { + margin-left: 41.66666667%; + } + .col-sm-offset-4 { + margin-left: 33.33333333%; + } + .col-sm-offset-3 { + margin-left: 25%; + } + .col-sm-offset-2 { + margin-left: 16.66666667%; + } + .col-sm-offset-1 { + margin-left: 8.33333333%; + } + .col-sm-offset-0 { + margin-left: 0; + } +} +@media (min-width: 992px) { + .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12 { + float: left; + } + .col-md-12 { + width: 100%; + } + .col-md-11 { + width: 91.66666667%; + } + .col-md-10 { + width: 83.33333333%; + } + .col-md-9 { + width: 75%; + } + .col-md-8 { + width: 66.66666667%; + } + .col-md-7 { + width: 58.33333333%; + } + .col-md-6 { + width: 50%; + } + .col-md-5 { + width: 41.66666667%; + } + .col-md-4 { + width: 33.33333333%; + } + .col-md-3 { + width: 25%; + } + .col-md-2 { + width: 16.66666667%; + } + .col-md-1 { + width: 8.33333333%; + } + .col-md-pull-12 { + right: 100%; + } + .col-md-pull-11 { + right: 91.66666667%; + } + .col-md-pull-10 { + right: 83.33333333%; + } + .col-md-pull-9 { + right: 75%; + } + .col-md-pull-8 { + right: 66.66666667%; + } + .col-md-pull-7 { + right: 58.33333333%; + } + .col-md-pull-6 { + right: 50%; + } + .col-md-pull-5 { + right: 41.66666667%; + } + .col-md-pull-4 { + right: 33.33333333%; + } + .col-md-pull-3 { + right: 25%; + } + .col-md-pull-2 { + right: 16.66666667%; + } + .col-md-pull-1 { + right: 8.33333333%; + } + .col-md-pull-0 { + right: auto; + } + .col-md-push-12 { + left: 100%; + } + .col-md-push-11 { + left: 91.66666667%; + } + .col-md-push-10 { + left: 83.33333333%; + } + .col-md-push-9 { + left: 75%; + } + .col-md-push-8 { + left: 66.66666667%; + } + .col-md-push-7 { + left: 58.33333333%; + } + .col-md-push-6 { + left: 50%; + } + .col-md-push-5 { + left: 41.66666667%; + } + .col-md-push-4 { + left: 33.33333333%; + } + .col-md-push-3 { + left: 25%; + } + .col-md-push-2 { + left: 16.66666667%; + } + .col-md-push-1 { + left: 8.33333333%; + } + .col-md-push-0 { + left: auto; + } + .col-md-offset-12 { + margin-left: 100%; + } + .col-md-offset-11 { + margin-left: 91.66666667%; + } + .col-md-offset-10 { + margin-left: 83.33333333%; + } + .col-md-offset-9 { + margin-left: 75%; + } + .col-md-offset-8 { + margin-left: 66.66666667%; + } + .col-md-offset-7 { + margin-left: 58.33333333%; + } + .col-md-offset-6 { + margin-left: 50%; + } + .col-md-offset-5 { + margin-left: 41.66666667%; + } + .col-md-offset-4 { + margin-left: 33.33333333%; + } + .col-md-offset-3 { + margin-left: 25%; + } + .col-md-offset-2 { + margin-left: 16.66666667%; + } + .col-md-offset-1 { + margin-left: 8.33333333%; + } + .col-md-offset-0 { + margin-left: 0; + } +} +@media (min-width: 1200px) { + .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12 { + float: left; + } + .col-lg-12 { + width: 100%; + } + .col-lg-11 { + width: 91.66666667%; + } + .col-lg-10 { + width: 83.33333333%; + } + .col-lg-9 { + width: 75%; + } + .col-lg-8 { + width: 66.66666667%; + } + .col-lg-7 { + width: 58.33333333%; + } + .col-lg-6 { + width: 50%; + } + .col-lg-5 { + width: 41.66666667%; + } + .col-lg-4 { + width: 33.33333333%; + } + .col-lg-3 { + width: 25%; + } + .col-lg-2 { + width: 16.66666667%; + } + .col-lg-1 { + width: 8.33333333%; + } + .col-lg-pull-12 { + right: 100%; + } + .col-lg-pull-11 { + right: 91.66666667%; + } + .col-lg-pull-10 { + right: 83.33333333%; + } + .col-lg-pull-9 { + right: 75%; + } + .col-lg-pull-8 { + right: 66.66666667%; + } + .col-lg-pull-7 { + right: 58.33333333%; + } + .col-lg-pull-6 { + right: 50%; + } + .col-lg-pull-5 { + right: 41.66666667%; + } + .col-lg-pull-4 { + right: 33.33333333%; + } + .col-lg-pull-3 { + right: 25%; + } + .col-lg-pull-2 { + right: 16.66666667%; + } + .col-lg-pull-1 { + right: 8.33333333%; + } + .col-lg-pull-0 { + right: auto; + } + .col-lg-push-12 { + left: 100%; + } + .col-lg-push-11 { + left: 91.66666667%; + } + .col-lg-push-10 { + left: 83.33333333%; + } + .col-lg-push-9 { + left: 75%; + } + .col-lg-push-8 { + left: 66.66666667%; + } + .col-lg-push-7 { + left: 58.33333333%; + } + .col-lg-push-6 { + left: 50%; + } + .col-lg-push-5 { + left: 41.66666667%; + } + .col-lg-push-4 { + left: 33.33333333%; + } + .col-lg-push-3 { + left: 25%; + } + .col-lg-push-2 { + left: 16.66666667%; + } + .col-lg-push-1 { + left: 8.33333333%; + } + .col-lg-push-0 { + left: auto; + } + .col-lg-offset-12 { + margin-left: 100%; + } + .col-lg-offset-11 { + margin-left: 91.66666667%; + } + .col-lg-offset-10 { + margin-left: 83.33333333%; + } + .col-lg-offset-9 { + margin-left: 75%; + } + .col-lg-offset-8 { + margin-left: 66.66666667%; + } + .col-lg-offset-7 { + margin-left: 58.33333333%; + } + .col-lg-offset-6 { + margin-left: 50%; + } + .col-lg-offset-5 { + margin-left: 41.66666667%; + } + .col-lg-offset-4 { + margin-left: 33.33333333%; + } + .col-lg-offset-3 { + margin-left: 25%; + } + .col-lg-offset-2 { + margin-left: 16.66666667%; + } + .col-lg-offset-1 { + margin-left: 8.33333333%; + } + .col-lg-offset-0 { + margin-left: 0; + } +} +table { + background-color: transparent; +} +caption { + padding-top: 8px; + padding-bottom: 8px; + color: #777; + text-align: left; +} +th { + text-align: left; +} +.table { + width: 100%; + max-width: 100%; + margin-bottom: 20px; +} +.table > thead > tr > th, +.table > tbody > tr > th, +.table > tfoot > tr > th, +.table > thead > tr > td, +.table > tbody > tr > td, +.table > tfoot > tr > td { + padding: 8px; + line-height: 1.42857143; + vertical-align: top; + border-top: 1px solid #ddd; +} +.table > thead > tr > th { + vertical-align: bottom; + border-bottom: 2px solid #ddd; +} +.table > caption + thead > tr:first-child > th, +.table > colgroup + thead > tr:first-child > th, +.table > thead:first-child > tr:first-child > th, +.table > caption + thead > tr:first-child > td, +.table > colgroup + thead > tr:first-child > td, +.table > thead:first-child > tr:first-child > td { + border-top: 0; +} +.table > tbody + tbody { + border-top: 2px solid #ddd; +} +.table .table { + background-color: #fff; +} +.table-condensed > thead > tr > th, +.table-condensed > tbody > tr > th, +.table-condensed > tfoot > tr > th, +.table-condensed > thead > tr > td, +.table-condensed > tbody > tr > td, +.table-condensed > tfoot > tr > td { + padding: 5px; +} +.table-bordered { + border: 1px solid #ddd; +} +.table-bordered > thead > tr > th, +.table-bordered > tbody > tr > th, +.table-bordered > tfoot > tr > th, +.table-bordered > thead > tr > td, +.table-bordered > tbody > tr > td, +.table-bordered > tfoot > tr > td { + border: 1px solid #ddd; +} +.table-bordered > thead > tr > th, +.table-bordered > thead > tr > td { + border-bottom-width: 2px; +} +.table-striped > tbody > tr:nth-of-type(odd) { + background-color: #f9f9f9; +} +.table-hover > tbody > tr:hover { + background-color: #f5f5f5; +} +table col[class*="col-"] { + position: static; + display: table-column; + float: none; +} +table td[class*="col-"], +table th[class*="col-"] { + position: static; + display: table-cell; + float: none; +} +.table > thead > tr > td.active, +.table > tbody > tr > td.active, +.table > tfoot > tr > td.active, +.table > thead > tr > th.active, +.table > tbody > tr > th.active, +.table > tfoot > tr > th.active, +.table > thead > tr.active > td, +.table > tbody > tr.active > td, +.table > tfoot > tr.active > td, +.table > thead > tr.active > th, +.table > tbody > tr.active > th, +.table > tfoot > tr.active > th { + background-color: #f5f5f5; +} +.table-hover > tbody > tr > td.active:hover, +.table-hover > tbody > tr > th.active:hover, +.table-hover > tbody > tr.active:hover > td, +.table-hover > tbody > tr:hover > .active, +.table-hover > tbody > tr.active:hover > th { + background-color: #e8e8e8; +} +.table > thead > tr > td.success, +.table > tbody > tr > td.success, +.table > tfoot > tr > td.success, +.table > thead > tr > th.success, +.table > tbody > tr > th.success, +.table > tfoot > tr > th.success, +.table > thead > tr.success > td, +.table > tbody > tr.success > td, +.table > tfoot > tr.success > td, +.table > thead > tr.success > th, +.table > tbody > tr.success > th, +.table > tfoot > tr.success > th { + background-color: #dff0d8; +} +.table-hover > tbody > tr > td.success:hover, +.table-hover > tbody > tr > th.success:hover, +.table-hover > tbody > tr.success:hover > td, +.table-hover > tbody > tr:hover > .success, +.table-hover > tbody > tr.success:hover > th { + background-color: #d0e9c6; +} +.table > thead > tr > td.info, +.table > tbody > tr > td.info, +.table > tfoot > tr > td.info, +.table > thead > tr > th.info, +.table > tbody > tr > th.info, +.table > tfoot > tr > th.info, +.table > thead > tr.info > td, +.table > tbody > tr.info > td, +.table > tfoot > tr.info > td, +.table > thead > tr.info > th, +.table > tbody > tr.info > th, +.table > tfoot > tr.info > th { + background-color: #d9edf7; +} +.table-hover > tbody > tr > td.info:hover, +.table-hover > tbody > tr > th.info:hover, +.table-hover > tbody > tr.info:hover > td, +.table-hover > tbody > tr:hover > .info, +.table-hover > tbody > tr.info:hover > th { + background-color: #c4e3f3; +} +.table > thead > tr > td.warning, +.table > tbody > tr > td.warning, +.table > tfoot > tr > td.warning, +.table > thead > tr > th.warning, +.table > tbody > tr > th.warning, +.table > tfoot > tr > th.warning, +.table > thead > tr.warning > td, +.table > tbody > tr.warning > td, +.table > tfoot > tr.warning > td, +.table > thead > tr.warning > th, +.table > tbody > tr.warning > th, +.table > tfoot > tr.warning > th { + background-color: #fcf8e3; +} +.table-hover > tbody > tr > td.warning:hover, +.table-hover > tbody > tr > th.warning:hover, +.table-hover > tbody > tr.warning:hover > td, +.table-hover > tbody > tr:hover > .warning, +.table-hover > tbody > tr.warning:hover > th { + background-color: #faf2cc; +} +.table > thead > tr > td.danger, +.table > tbody > tr > td.danger, +.table > tfoot > tr > td.danger, +.table > thead > tr > th.danger, +.table > tbody > tr > th.danger, +.table > tfoot > tr > th.danger, +.table > thead > tr.danger > td, +.table > tbody > tr.danger > td, +.table > tfoot > tr.danger > td, +.table > thead > tr.danger > th, +.table > tbody > tr.danger > th, +.table > tfoot > tr.danger > th { + background-color: #f2dede; +} +.table-hover > tbody > tr > td.danger:hover, +.table-hover > tbody > tr > th.danger:hover, +.table-hover > tbody > tr.danger:hover > td, +.table-hover > tbody > tr:hover > .danger, +.table-hover > tbody > tr.danger:hover > th { + background-color: #ebcccc; +} +.table-responsive { + min-height: .01%; + overflow-x: auto; +} +@media screen and (max-width: 767px) { + .table-responsive { + width: 100%; + margin-bottom: 15px; + overflow-y: hidden; + -ms-overflow-style: -ms-autohiding-scrollbar; + border: 1px solid #ddd; + } + .table-responsive > .table { + margin-bottom: 0; + } + .table-responsive > .table > thead > tr > th, + .table-responsive > .table > tbody > tr > th, + .table-responsive > .table > tfoot > tr > th, + .table-responsive > .table > thead > tr > td, + .table-responsive > .table > tbody > tr > td, + .table-responsive > .table > tfoot > tr > td { + white-space: nowrap; + } + .table-responsive > .table-bordered { + border: 0; + } + .table-responsive > .table-bordered > thead > tr > th:first-child, + .table-responsive > .table-bordered > tbody > tr > th:first-child, + .table-responsive > .table-bordered > tfoot > tr > th:first-child, + .table-responsive > .table-bordered > thead > tr > td:first-child, + .table-responsive > .table-bordered > tbody > tr > td:first-child, + .table-responsive > .table-bordered > tfoot > tr > td:first-child { + border-left: 0; + } + .table-responsive > .table-bordered > thead > tr > th:last-child, + .table-responsive > .table-bordered > tbody > tr > th:last-child, + .table-responsive > .table-bordered > tfoot > tr > th:last-child, + .table-responsive > .table-bordered > thead > tr > td:last-child, + .table-responsive > .table-bordered > tbody > tr > td:last-child, + .table-responsive > .table-bordered > tfoot > tr > td:last-child { + border-right: 0; + } + .table-responsive > .table-bordered > tbody > tr:last-child > th, + .table-responsive > .table-bordered > tfoot > tr:last-child > th, + .table-responsive > .table-bordered > tbody > tr:last-child > td, + .table-responsive > .table-bordered > tfoot > tr:last-child > td { + border-bottom: 0; + } +} +fieldset { + min-width: 0; + padding: 0; + margin: 0; + border: 0; +} +legend { + display: block; + width: 100%; + padding: 0; + margin-bottom: 20px; + font-size: 21px; + line-height: inherit; + color: #333; + border: 0; + border-bottom: 1px solid #e5e5e5; +} +label { + display: inline-block; + max-width: 100%; + margin-bottom: 5px; + font-weight: bold; +} +input[type="search"] { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +input[type="radio"], +input[type="checkbox"] { + margin: 4px 0 0; + margin-top: 1px \9; + line-height: normal; +} +input[type="file"] { + display: block; +} +input[type="range"] { + display: block; + width: 100%; +} +select[multiple], +select[size] { + height: auto; +} +input[type="file"]:focus, +input[type="radio"]:focus, +input[type="checkbox"]:focus { + outline: thin dotted; + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} +output { + display: block; + padding-top: 7px; + font-size: 14px; + line-height: 1.42857143; + color: #555; +} +.form-control { + display: block; + width: 100%; + height: 34px; + padding: 6px 12px; + font-size: 14px; + line-height: 1.42857143; + color: #555; + background-color: #fff; + background-image: none; + border: 1px solid #ccc; + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); + -webkit-transition: border-color ease-in-out .15s, -webkit-box-shadow ease-in-out .15s; + -o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; + transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; +} +.form-control:focus { + border-color: #66afe9; + outline: 0; + -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, .6); + box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, .6); +} +.form-control::-moz-placeholder { + color: #999; + opacity: 1; +} +.form-control:-ms-input-placeholder { + color: #999; +} +.form-control::-webkit-input-placeholder { + color: #999; +} +.form-control::-ms-expand { + background-color: transparent; + border: 0; +} +.form-control[disabled], +.form-control[readonly], +fieldset[disabled] .form-control { + background-color: #eee; + opacity: 1; +} +.form-control[disabled], +fieldset[disabled] .form-control { + cursor: not-allowed; +} +textarea.form-control { + height: auto; +} +input[type="search"] { + -webkit-appearance: none; +} +@media screen and (-webkit-min-device-pixel-ratio: 0) { + input[type="date"].form-control, + input[type="time"].form-control, + input[type="datetime-local"].form-control, + input[type="month"].form-control { + line-height: 34px; + } + input[type="date"].input-sm, + input[type="time"].input-sm, + input[type="datetime-local"].input-sm, + input[type="month"].input-sm, + .input-group-sm input[type="date"], + .input-group-sm input[type="time"], + .input-group-sm input[type="datetime-local"], + .input-group-sm input[type="month"] { + line-height: 30px; + } + input[type="date"].input-lg, + input[type="time"].input-lg, + input[type="datetime-local"].input-lg, + input[type="month"].input-lg, + .input-group-lg input[type="date"], + .input-group-lg input[type="time"], + .input-group-lg input[type="datetime-local"], + .input-group-lg input[type="month"] { + line-height: 46px; + } +} +.form-group { + margin-bottom: 15px; +} +.radio, +.checkbox { + position: relative; + display: block; + margin-top: 10px; + margin-bottom: 10px; +} +.radio label, +.checkbox label { + min-height: 20px; + padding-left: 20px; + margin-bottom: 0; + font-weight: normal; + cursor: pointer; +} +.radio input[type="radio"], +.radio-inline input[type="radio"], +.checkbox input[type="checkbox"], +.checkbox-inline input[type="checkbox"] { + position: absolute; + margin-top: 4px \9; + margin-left: -20px; +} +.radio + .radio, +.checkbox + .checkbox { + margin-top: -5px; +} +.radio-inline, +.checkbox-inline { + position: relative; + display: inline-block; + padding-left: 20px; + margin-bottom: 0; + font-weight: normal; + vertical-align: middle; + cursor: pointer; +} +.radio-inline + .radio-inline, +.checkbox-inline + .checkbox-inline { + margin-top: 0; + margin-left: 10px; +} +input[type="radio"][disabled], +input[type="checkbox"][disabled], +input[type="radio"].disabled, +input[type="checkbox"].disabled, +fieldset[disabled] input[type="radio"], +fieldset[disabled] input[type="checkbox"] { + cursor: not-allowed; +} +.radio-inline.disabled, +.checkbox-inline.disabled, +fieldset[disabled] .radio-inline, +fieldset[disabled] .checkbox-inline { + cursor: not-allowed; +} +.radio.disabled label, +.checkbox.disabled label, +fieldset[disabled] .radio label, +fieldset[disabled] .checkbox label { + cursor: not-allowed; +} +.form-control-static { + min-height: 34px; + padding-top: 7px; + padding-bottom: 7px; + margin-bottom: 0; +} +.form-control-static.input-lg, +.form-control-static.input-sm { + padding-right: 0; + padding-left: 0; +} +.input-sm { + height: 30px; + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} +select.input-sm { + height: 30px; + line-height: 30px; +} +textarea.input-sm, +select[multiple].input-sm { + height: auto; +} +.form-group-sm .form-control { + height: 30px; + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} +.form-group-sm select.form-control { + height: 30px; + line-height: 30px; +} +.form-group-sm textarea.form-control, +.form-group-sm select[multiple].form-control { + height: auto; +} +.form-group-sm .form-control-static { + height: 30px; + min-height: 32px; + padding: 6px 10px; + font-size: 12px; + line-height: 1.5; +} +.input-lg { + height: 46px; + padding: 10px 16px; + font-size: 18px; + line-height: 1.3333333; + border-radius: 6px; +} +select.input-lg { + height: 46px; + line-height: 46px; +} +textarea.input-lg, +select[multiple].input-lg { + height: auto; +} +.form-group-lg .form-control { + height: 46px; + padding: 10px 16px; + font-size: 18px; + line-height: 1.3333333; + border-radius: 6px; +} +.form-group-lg select.form-control { + height: 46px; + line-height: 46px; +} +.form-group-lg textarea.form-control, +.form-group-lg select[multiple].form-control { + height: auto; +} +.form-group-lg .form-control-static { + height: 46px; + min-height: 38px; + padding: 11px 16px; + font-size: 18px; + line-height: 1.3333333; +} +.has-feedback { + position: relative; +} +.has-feedback .form-control { + padding-right: 42.5px; +} +.form-control-feedback { + position: absolute; + top: 0; + right: 0; + z-index: 2; + display: block; + width: 34px; + height: 34px; + line-height: 34px; + text-align: center; + pointer-events: none; +} +.input-lg + .form-control-feedback, +.input-group-lg + .form-control-feedback, +.form-group-lg .form-control + .form-control-feedback { + width: 46px; + height: 46px; + line-height: 46px; +} +.input-sm + .form-control-feedback, +.input-group-sm + .form-control-feedback, +.form-group-sm .form-control + .form-control-feedback { + width: 30px; + height: 30px; + line-height: 30px; +} +.has-success .help-block, +.has-success .control-label, +.has-success .radio, +.has-success .checkbox, +.has-success .radio-inline, +.has-success .checkbox-inline, +.has-success.radio label, +.has-success.checkbox label, +.has-success.radio-inline label, +.has-success.checkbox-inline label { + color: #3c763d; +} +.has-success .form-control { + border-color: #3c763d; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); +} +.has-success .form-control:focus { + border-color: #2b542c; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #67b168; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #67b168; +} +.has-success .input-group-addon { + color: #3c763d; + background-color: #dff0d8; + border-color: #3c763d; +} +.has-success .form-control-feedback { + color: #3c763d; +} +.has-warning .help-block, +.has-warning .control-label, +.has-warning .radio, +.has-warning .checkbox, +.has-warning .radio-inline, +.has-warning .checkbox-inline, +.has-warning.radio label, +.has-warning.checkbox label, +.has-warning.radio-inline label, +.has-warning.checkbox-inline label { + color: #8a6d3b; +} +.has-warning .form-control { + border-color: #8a6d3b; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); +} +.has-warning .form-control:focus { + border-color: #66512c; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #c0a16b; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #c0a16b; +} +.has-warning .input-group-addon { + color: #8a6d3b; + background-color: #fcf8e3; + border-color: #8a6d3b; +} +.has-warning .form-control-feedback { + color: #8a6d3b; +} +.has-error .help-block, +.has-error .control-label, +.has-error .radio, +.has-error .checkbox, +.has-error .radio-inline, +.has-error .checkbox-inline, +.has-error.radio label, +.has-error.checkbox label, +.has-error.radio-inline label, +.has-error.checkbox-inline label { + color: #a94442; +} +.has-error .form-control { + border-color: #a94442; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); +} +.has-error .form-control:focus { + border-color: #843534; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #ce8483; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #ce8483; +} +.has-error .input-group-addon { + color: #a94442; + background-color: #f2dede; + border-color: #a94442; +} +.has-error .form-control-feedback { + color: #a94442; +} +.has-feedback label ~ .form-control-feedback { + top: 25px; +} +.has-feedback label.sr-only ~ .form-control-feedback { + top: 0; +} +.help-block { + display: block; + margin-top: 5px; + margin-bottom: 10px; + color: #737373; +} +@media (min-width: 768px) { + .form-inline .form-group { + display: inline-block; + margin-bottom: 0; + vertical-align: middle; + } + .form-inline .form-control { + display: inline-block; + width: auto; + vertical-align: middle; + } + .form-inline .form-control-static { + display: inline-block; + } + .form-inline .input-group { + display: inline-table; + vertical-align: middle; + } + .form-inline .input-group .input-group-addon, + .form-inline .input-group .input-group-btn, + .form-inline .input-group .form-control { + width: auto; + } + .form-inline .input-group > .form-control { + width: 100%; + } + .form-inline .control-label { + margin-bottom: 0; + vertical-align: middle; + } + .form-inline .radio, + .form-inline .checkbox { + display: inline-block; + margin-top: 0; + margin-bottom: 0; + vertical-align: middle; + } + .form-inline .radio label, + .form-inline .checkbox label { + padding-left: 0; + } + .form-inline .radio input[type="radio"], + .form-inline .checkbox input[type="checkbox"] { + position: relative; + margin-left: 0; + } + .form-inline .has-feedback .form-control-feedback { + top: 0; + } +} +.form-horizontal .radio, +.form-horizontal .checkbox, +.form-horizontal .radio-inline, +.form-horizontal .checkbox-inline { + padding-top: 7px; + margin-top: 0; + margin-bottom: 0; +} +.form-horizontal .radio, +.form-horizontal .checkbox { + min-height: 27px; +} +.form-horizontal .form-group { + margin-right: -15px; + margin-left: -15px; +} +@media (min-width: 768px) { + .form-horizontal .control-label { + padding-top: 7px; + margin-bottom: 0; + text-align: right; + } +} +.form-horizontal .has-feedback .form-control-feedback { + right: 15px; +} +@media (min-width: 768px) { + .form-horizontal .form-group-lg .control-label { + padding-top: 11px; + font-size: 18px; + } +} +@media (min-width: 768px) { + .form-horizontal .form-group-sm .control-label { + padding-top: 6px; + font-size: 12px; + } +} +.btn { + display: inline-block; + padding: 6px 12px; + margin-bottom: 0; + font-size: 14px; + font-weight: normal; + line-height: 1.42857143; + text-align: center; + white-space: nowrap; + vertical-align: middle; + -ms-touch-action: manipulation; + touch-action: manipulation; + cursor: pointer; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + background-image: none; + border: 1px solid transparent; + border-radius: 4px; +} +.btn:focus, +.btn:active:focus, +.btn.active:focus, +.btn.focus, +.btn:active.focus, +.btn.active.focus { + outline: thin dotted; + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} +.btn:hover, +.btn:focus, +.btn.focus { + color: #333; + text-decoration: none; +} +.btn:active, +.btn.active { + background-image: none; + outline: 0; + -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); + box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); +} +.btn.disabled, +.btn[disabled], +fieldset[disabled] .btn { + cursor: not-allowed; + filter: alpha(opacity=65); + -webkit-box-shadow: none; + box-shadow: none; + opacity: .65; +} +a.btn.disabled, +fieldset[disabled] a.btn { + pointer-events: none; +} +.btn-default { + color: #333; + background-color: #fff; + border-color: #ccc; +} +.btn-default:focus, +.btn-default.focus { + color: #333; + background-color: #e6e6e6; + border-color: #8c8c8c; +} +.btn-default:hover { + color: #333; + background-color: #e6e6e6; + border-color: #adadad; +} +.btn-default:active, +.btn-default.active, +.open > .dropdown-toggle.btn-default { + color: #333; + background-color: #e6e6e6; + border-color: #adadad; +} +.btn-default:active:hover, +.btn-default.active:hover, +.open > .dropdown-toggle.btn-default:hover, +.btn-default:active:focus, +.btn-default.active:focus, +.open > .dropdown-toggle.btn-default:focus, +.btn-default:active.focus, +.btn-default.active.focus, +.open > .dropdown-toggle.btn-default.focus { + color: #333; + background-color: #d4d4d4; + border-color: #8c8c8c; +} +.btn-default:active, +.btn-default.active, +.open > .dropdown-toggle.btn-default { + background-image: none; +} +.btn-default.disabled:hover, +.btn-default[disabled]:hover, +fieldset[disabled] .btn-default:hover, +.btn-default.disabled:focus, +.btn-default[disabled]:focus, +fieldset[disabled] .btn-default:focus, +.btn-default.disabled.focus, +.btn-default[disabled].focus, +fieldset[disabled] .btn-default.focus { + background-color: #fff; + border-color: #ccc; +} +.btn-default .badge { + color: #fff; + background-color: #333; +} +.btn-primary { + color: #fff; + background-color: #337ab7; + border-color: #2e6da4; +} +.btn-primary:focus, +.btn-primary.focus { + color: #fff; + background-color: #286090; + border-color: #122b40; +} +.btn-primary:hover { + color: #fff; + background-color: #286090; + border-color: #204d74; +} +.btn-primary:active, +.btn-primary.active, +.open > .dropdown-toggle.btn-primary { + color: #fff; + background-color: #286090; + border-color: #204d74; +} +.btn-primary:active:hover, +.btn-primary.active:hover, +.open > .dropdown-toggle.btn-primary:hover, +.btn-primary:active:focus, +.btn-primary.active:focus, +.open > .dropdown-toggle.btn-primary:focus, +.btn-primary:active.focus, +.btn-primary.active.focus, +.open > .dropdown-toggle.btn-primary.focus { + color: #fff; + background-color: #204d74; + border-color: #122b40; +} +.btn-primary:active, +.btn-primary.active, +.open > .dropdown-toggle.btn-primary { + background-image: none; +} +.btn-primary.disabled:hover, +.btn-primary[disabled]:hover, +fieldset[disabled] .btn-primary:hover, +.btn-primary.disabled:focus, +.btn-primary[disabled]:focus, +fieldset[disabled] .btn-primary:focus, +.btn-primary.disabled.focus, +.btn-primary[disabled].focus, +fieldset[disabled] .btn-primary.focus { + background-color: #337ab7; + border-color: #2e6da4; +} +.btn-primary .badge { + color: #337ab7; + background-color: #fff; +} +.btn-success { + color: #fff; + background-color: #5cb85c; + border-color: #4cae4c; +} +.btn-success:focus, +.btn-success.focus { + color: #fff; + background-color: #449d44; + border-color: #255625; +} +.btn-success:hover { + color: #fff; + background-color: #449d44; + border-color: #398439; +} +.btn-success:active, +.btn-success.active, +.open > .dropdown-toggle.btn-success { + color: #fff; + background-color: #449d44; + border-color: #398439; +} +.btn-success:active:hover, +.btn-success.active:hover, +.open > .dropdown-toggle.btn-success:hover, +.btn-success:active:focus, +.btn-success.active:focus, +.open > .dropdown-toggle.btn-success:focus, +.btn-success:active.focus, +.btn-success.active.focus, +.open > .dropdown-toggle.btn-success.focus { + color: #fff; + background-color: #398439; + border-color: #255625; +} +.btn-success:active, +.btn-success.active, +.open > .dropdown-toggle.btn-success { + background-image: none; +} +.btn-success.disabled:hover, +.btn-success[disabled]:hover, +fieldset[disabled] .btn-success:hover, +.btn-success.disabled:focus, +.btn-success[disabled]:focus, +fieldset[disabled] .btn-success:focus, +.btn-success.disabled.focus, +.btn-success[disabled].focus, +fieldset[disabled] .btn-success.focus { + background-color: #5cb85c; + border-color: #4cae4c; +} +.btn-success .badge { + color: #5cb85c; + background-color: #fff; +} +.btn-info { + color: #fff; + background-color: #5bc0de; + border-color: #46b8da; +} +.btn-info:focus, +.btn-info.focus { + color: #fff; + background-color: #31b0d5; + border-color: #1b6d85; +} +.btn-info:hover { + color: #fff; + background-color: #31b0d5; + border-color: #269abc; +} +.btn-info:active, +.btn-info.active, +.open > .dropdown-toggle.btn-info { + color: #fff; + background-color: #31b0d5; + border-color: #269abc; +} +.btn-info:active:hover, +.btn-info.active:hover, +.open > .dropdown-toggle.btn-info:hover, +.btn-info:active:focus, +.btn-info.active:focus, +.open > .dropdown-toggle.btn-info:focus, +.btn-info:active.focus, +.btn-info.active.focus, +.open > .dropdown-toggle.btn-info.focus { + color: #fff; + background-color: #269abc; + border-color: #1b6d85; +} +.btn-info:active, +.btn-info.active, +.open > .dropdown-toggle.btn-info { + background-image: none; +} +.btn-info.disabled:hover, +.btn-info[disabled]:hover, +fieldset[disabled] .btn-info:hover, +.btn-info.disabled:focus, +.btn-info[disabled]:focus, +fieldset[disabled] .btn-info:focus, +.btn-info.disabled.focus, +.btn-info[disabled].focus, +fieldset[disabled] .btn-info.focus { + background-color: #5bc0de; + border-color: #46b8da; +} +.btn-info .badge { + color: #5bc0de; + background-color: #fff; +} +.btn-warning { + color: #fff; + background-color: #f0ad4e; + border-color: #eea236; +} +.btn-warning:focus, +.btn-warning.focus { + color: #fff; + background-color: #ec971f; + border-color: #985f0d; +} +.btn-warning:hover { + color: #fff; + background-color: #ec971f; + border-color: #d58512; +} +.btn-warning:active, +.btn-warning.active, +.open > .dropdown-toggle.btn-warning { + color: #fff; + background-color: #ec971f; + border-color: #d58512; +} +.btn-warning:active:hover, +.btn-warning.active:hover, +.open > .dropdown-toggle.btn-warning:hover, +.btn-warning:active:focus, +.btn-warning.active:focus, +.open > .dropdown-toggle.btn-warning:focus, +.btn-warning:active.focus, +.btn-warning.active.focus, +.open > .dropdown-toggle.btn-warning.focus { + color: #fff; + background-color: #d58512; + border-color: #985f0d; +} +.btn-warning:active, +.btn-warning.active, +.open > .dropdown-toggle.btn-warning { + background-image: none; +} +.btn-warning.disabled:hover, +.btn-warning[disabled]:hover, +fieldset[disabled] .btn-warning:hover, +.btn-warning.disabled:focus, +.btn-warning[disabled]:focus, +fieldset[disabled] .btn-warning:focus, +.btn-warning.disabled.focus, +.btn-warning[disabled].focus, +fieldset[disabled] .btn-warning.focus { + background-color: #f0ad4e; + border-color: #eea236; +} +.btn-warning .badge { + color: #f0ad4e; + background-color: #fff; +} +.btn-danger { + color: #fff; + background-color: #d9534f; + border-color: #d43f3a; +} +.btn-danger:focus, +.btn-danger.focus { + color: #fff; + background-color: #c9302c; + border-color: #761c19; +} +.btn-danger:hover { + color: #fff; + background-color: #c9302c; + border-color: #ac2925; +} +.btn-danger:active, +.btn-danger.active, +.open > .dropdown-toggle.btn-danger { + color: #fff; + background-color: #c9302c; + border-color: #ac2925; +} +.btn-danger:active:hover, +.btn-danger.active:hover, +.open > .dropdown-toggle.btn-danger:hover, +.btn-danger:active:focus, +.btn-danger.active:focus, +.open > .dropdown-toggle.btn-danger:focus, +.btn-danger:active.focus, +.btn-danger.active.focus, +.open > .dropdown-toggle.btn-danger.focus { + color: #fff; + background-color: #ac2925; + border-color: #761c19; +} +.btn-danger:active, +.btn-danger.active, +.open > .dropdown-toggle.btn-danger { + background-image: none; +} +.btn-danger.disabled:hover, +.btn-danger[disabled]:hover, +fieldset[disabled] .btn-danger:hover, +.btn-danger.disabled:focus, +.btn-danger[disabled]:focus, +fieldset[disabled] .btn-danger:focus, +.btn-danger.disabled.focus, +.btn-danger[disabled].focus, +fieldset[disabled] .btn-danger.focus { + background-color: #d9534f; + border-color: #d43f3a; +} +.btn-danger .badge { + color: #d9534f; + background-color: #fff; +} +.btn-link { + font-weight: normal; + color: #337ab7; + border-radius: 0; +} +.btn-link, +.btn-link:active, +.btn-link.active, +.btn-link[disabled], +fieldset[disabled] .btn-link { + background-color: transparent; + -webkit-box-shadow: none; + box-shadow: none; +} +.btn-link, +.btn-link:hover, +.btn-link:focus, +.btn-link:active { + border-color: transparent; +} +.btn-link:hover, +.btn-link:focus { + color: #23527c; + text-decoration: underline; + background-color: transparent; +} +.btn-link[disabled]:hover, +fieldset[disabled] .btn-link:hover, +.btn-link[disabled]:focus, +fieldset[disabled] .btn-link:focus { + color: #777; + text-decoration: none; +} +.btn-lg, +.btn-group-lg > .btn { + padding: 10px 16px; + font-size: 18px; + line-height: 1.3333333; + border-radius: 6px; +} +.btn-sm, +.btn-group-sm > .btn { + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} +.btn-xs, +.btn-group-xs > .btn { + padding: 1px 5px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} +.btn-block { + display: block; + width: 100%; +} +.btn-block + .btn-block { + margin-top: 5px; +} +input[type="submit"].btn-block, +input[type="reset"].btn-block, +input[type="button"].btn-block { + width: 100%; +} +.fade { + opacity: 0; + -webkit-transition: opacity .15s linear; + -o-transition: opacity .15s linear; + transition: opacity .15s linear; +} +.fade.in { + opacity: 1; +} +.collapse { + display: none; +} +.collapse.in { + display: block; +} +tr.collapse.in { + display: table-row; +} +tbody.collapse.in { + display: table-row-group; +} +.collapsing { + position: relative; + height: 0; + overflow: hidden; + -webkit-transition-timing-function: ease; + -o-transition-timing-function: ease; + transition-timing-function: ease; + -webkit-transition-duration: .35s; + -o-transition-duration: .35s; + transition-duration: .35s; + -webkit-transition-property: height, visibility; + -o-transition-property: height, visibility; + transition-property: height, visibility; +} +.caret { + display: inline-block; + width: 0; + height: 0; + margin-left: 2px; + vertical-align: middle; + border-top: 4px dashed; + border-top: 4px solid \9; + border-right: 4px solid transparent; + border-left: 4px solid transparent; +} +.dropup, +.dropdown { + position: relative; +} +.dropdown-toggle:focus { + outline: 0; +} +.dropdown-menu { + position: absolute; + top: 100%; + left: 0; + z-index: 1000; + display: none; + float: left; + min-width: 160px; + padding: 5px 0; + margin: 2px 0 0; + font-size: 14px; + text-align: left; + list-style: none; + background-color: #fff; + -webkit-background-clip: padding-box; + background-clip: padding-box; + border: 1px solid #ccc; + border: 1px solid rgba(0, 0, 0, .15); + border-radius: 4px; + -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, .175); + box-shadow: 0 6px 12px rgba(0, 0, 0, .175); +} +.dropdown-menu.pull-right { + right: 0; + left: auto; +} +.dropdown-menu .divider { + height: 1px; + margin: 9px 0; + overflow: hidden; + background-color: #e5e5e5; +} +.dropdown-menu > li > a { + display: block; + padding: 3px 20px; + clear: both; + font-weight: normal; + line-height: 1.42857143; + color: #333; + white-space: nowrap; +} +.dropdown-menu > li > a:hover, +.dropdown-menu > li > a:focus { + color: #262626; + text-decoration: none; + background-color: #f5f5f5; +} +.dropdown-menu > .active > a, +.dropdown-menu > .active > a:hover, +.dropdown-menu > .active > a:focus { + color: #fff; + text-decoration: none; + background-color: #337ab7; + outline: 0; +} +.dropdown-menu > .disabled > a, +.dropdown-menu > .disabled > a:hover, +.dropdown-menu > .disabled > a:focus { + color: #777; +} +.dropdown-menu > .disabled > a:hover, +.dropdown-menu > .disabled > a:focus { + text-decoration: none; + cursor: not-allowed; + background-color: transparent; + background-image: none; + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); +} +.open > .dropdown-menu { + display: block; +} +.open > a { + outline: 0; +} +.dropdown-menu-right { + right: 0; + left: auto; +} +.dropdown-menu-left { + right: auto; + left: 0; +} +.dropdown-header { + display: block; + padding: 3px 20px; + font-size: 12px; + line-height: 1.42857143; + color: #777; + white-space: nowrap; +} +.dropdown-backdrop { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 990; +} +.pull-right > .dropdown-menu { + right: 0; + left: auto; +} +.dropup .caret, +.navbar-fixed-bottom .dropdown .caret { + content: ""; + border-top: 0; + border-bottom: 4px dashed; + border-bottom: 4px solid \9; +} +.dropup .dropdown-menu, +.navbar-fixed-bottom .dropdown .dropdown-menu { + top: auto; + bottom: 100%; + margin-bottom: 2px; +} +@media (min-width: 768px) { + .navbar-right .dropdown-menu { + right: 0; + left: auto; + } + .navbar-right .dropdown-menu-left { + right: auto; + left: 0; + } +} +.btn-group, +.btn-group-vertical { + position: relative; + display: inline-block; + vertical-align: middle; +} +.btn-group > .btn, +.btn-group-vertical > .btn { + position: relative; + float: left; +} +.btn-group > .btn:hover, +.btn-group-vertical > .btn:hover, +.btn-group > .btn:focus, +.btn-group-vertical > .btn:focus, +.btn-group > .btn:active, +.btn-group-vertical > .btn:active, +.btn-group > .btn.active, +.btn-group-vertical > .btn.active { + z-index: 2; +} +.btn-group .btn + .btn, +.btn-group .btn + .btn-group, +.btn-group .btn-group + .btn, +.btn-group .btn-group + .btn-group { + margin-left: -1px; +} +.btn-toolbar { + margin-left: -5px; +} +.btn-toolbar .btn, +.btn-toolbar .btn-group, +.btn-toolbar .input-group { + float: left; +} +.btn-toolbar > .btn, +.btn-toolbar > .btn-group, +.btn-toolbar > .input-group { + margin-left: 5px; +} +.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) { + border-radius: 0; +} +.btn-group > .btn:first-child { + margin-left: 0; +} +.btn-group > .btn:first-child:not(:last-child):not(.dropdown-toggle) { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} +.btn-group > .btn:last-child:not(:first-child), +.btn-group > .dropdown-toggle:not(:first-child) { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} +.btn-group > .btn-group { + float: left; +} +.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn { + border-radius: 0; +} +.btn-group > .btn-group:first-child:not(:last-child) > .btn:last-child, +.btn-group > .btn-group:first-child:not(:last-child) > .dropdown-toggle { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} +.btn-group > .btn-group:last-child:not(:first-child) > .btn:first-child { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} +.btn-group .dropdown-toggle:active, +.btn-group.open .dropdown-toggle { + outline: 0; +} +.btn-group > .btn + .dropdown-toggle { + padding-right: 8px; + padding-left: 8px; +} +.btn-group > .btn-lg + .dropdown-toggle { + padding-right: 12px; + padding-left: 12px; +} +.btn-group.open .dropdown-toggle { + -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); + box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); +} +.btn-group.open .dropdown-toggle.btn-link { + -webkit-box-shadow: none; + box-shadow: none; +} +.btn .caret { + margin-left: 0; +} +.btn-lg .caret { + border-width: 5px 5px 0; + border-bottom-width: 0; +} +.dropup .btn-lg .caret { + border-width: 0 5px 5px; +} +.btn-group-vertical > .btn, +.btn-group-vertical > .btn-group, +.btn-group-vertical > .btn-group > .btn { + display: block; + float: none; + width: 100%; + max-width: 100%; +} +.btn-group-vertical > .btn-group > .btn { + float: none; +} +.btn-group-vertical > .btn + .btn, +.btn-group-vertical > .btn + .btn-group, +.btn-group-vertical > .btn-group + .btn, +.btn-group-vertical > .btn-group + .btn-group { + margin-top: -1px; + margin-left: 0; +} +.btn-group-vertical > .btn:not(:first-child):not(:last-child) { + border-radius: 0; +} +.btn-group-vertical > .btn:first-child:not(:last-child) { + border-top-left-radius: 4px; + border-top-right-radius: 4px; + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} +.btn-group-vertical > .btn:last-child:not(:first-child) { + border-top-left-radius: 0; + border-top-right-radius: 0; + border-bottom-right-radius: 4px; + border-bottom-left-radius: 4px; +} +.btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn { + border-radius: 0; +} +.btn-group-vertical > .btn-group:first-child:not(:last-child) > .btn:last-child, +.btn-group-vertical > .btn-group:first-child:not(:last-child) > .dropdown-toggle { + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} +.btn-group-vertical > .btn-group:last-child:not(:first-child) > .btn:first-child { + border-top-left-radius: 0; + border-top-right-radius: 0; +} +.btn-group-justified { + display: table; + width: 100%; + table-layout: fixed; + border-collapse: separate; +} +.btn-group-justified > .btn, +.btn-group-justified > .btn-group { + display: table-cell; + float: none; + width: 1%; +} +.btn-group-justified > .btn-group .btn { + width: 100%; +} +.btn-group-justified > .btn-group .dropdown-menu { + left: auto; +} +[data-toggle="buttons"] > .btn input[type="radio"], +[data-toggle="buttons"] > .btn-group > .btn input[type="radio"], +[data-toggle="buttons"] > .btn input[type="checkbox"], +[data-toggle="buttons"] > .btn-group > .btn input[type="checkbox"] { + position: absolute; + clip: rect(0, 0, 0, 0); + pointer-events: none; +} +.input-group { + position: relative; + display: table; + border-collapse: separate; +} +.input-group[class*="col-"] { + float: none; + padding-right: 0; + padding-left: 0; +} +.input-group .form-control { + position: relative; + z-index: 2; + float: left; + width: 100%; + margin-bottom: 0; +} +.input-group .form-control:focus { + z-index: 3; +} +.input-group-lg > .form-control, +.input-group-lg > .input-group-addon, +.input-group-lg > .input-group-btn > .btn { + height: 46px; + padding: 10px 16px; + font-size: 18px; + line-height: 1.3333333; + border-radius: 6px; +} +select.input-group-lg > .form-control, +select.input-group-lg > .input-group-addon, +select.input-group-lg > .input-group-btn > .btn { + height: 46px; + line-height: 46px; +} +textarea.input-group-lg > .form-control, +textarea.input-group-lg > .input-group-addon, +textarea.input-group-lg > .input-group-btn > .btn, +select[multiple].input-group-lg > .form-control, +select[multiple].input-group-lg > .input-group-addon, +select[multiple].input-group-lg > .input-group-btn > .btn { + height: auto; +} +.input-group-sm > .form-control, +.input-group-sm > .input-group-addon, +.input-group-sm > .input-group-btn > .btn { + height: 30px; + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} +select.input-group-sm > .form-control, +select.input-group-sm > .input-group-addon, +select.input-group-sm > .input-group-btn > .btn { + height: 30px; + line-height: 30px; +} +textarea.input-group-sm > .form-control, +textarea.input-group-sm > .input-group-addon, +textarea.input-group-sm > .input-group-btn > .btn, +select[multiple].input-group-sm > .form-control, +select[multiple].input-group-sm > .input-group-addon, +select[multiple].input-group-sm > .input-group-btn > .btn { + height: auto; +} +.input-group-addon, +.input-group-btn, +.input-group .form-control { + display: table-cell; +} +.input-group-addon:not(:first-child):not(:last-child), +.input-group-btn:not(:first-child):not(:last-child), +.input-group .form-control:not(:first-child):not(:last-child) { + border-radius: 0; +} +.input-group-addon, +.input-group-btn { + width: 1%; + white-space: nowrap; + vertical-align: middle; +} +.input-group-addon { + padding: 6px 12px; + font-size: 14px; + font-weight: normal; + line-height: 1; + color: #555; + text-align: center; + background-color: #eee; + border: 1px solid #ccc; + border-radius: 4px; +} +.input-group-addon.input-sm { + padding: 5px 10px; + font-size: 12px; + border-radius: 3px; +} +.input-group-addon.input-lg { + padding: 10px 16px; + font-size: 18px; + border-radius: 6px; +} +.input-group-addon input[type="radio"], +.input-group-addon input[type="checkbox"] { + margin-top: 0; +} +.input-group .form-control:first-child, +.input-group-addon:first-child, +.input-group-btn:first-child > .btn, +.input-group-btn:first-child > .btn-group > .btn, +.input-group-btn:first-child > .dropdown-toggle, +.input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle), +.input-group-btn:last-child > .btn-group:not(:last-child) > .btn { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} +.input-group-addon:first-child { + border-right: 0; +} +.input-group .form-control:last-child, +.input-group-addon:last-child, +.input-group-btn:last-child > .btn, +.input-group-btn:last-child > .btn-group > .btn, +.input-group-btn:last-child > .dropdown-toggle, +.input-group-btn:first-child > .btn:not(:first-child), +.input-group-btn:first-child > .btn-group:not(:first-child) > .btn { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} +.input-group-addon:last-child { + border-left: 0; +} +.input-group-btn { + position: relative; + font-size: 0; + white-space: nowrap; +} +.input-group-btn > .btn { + position: relative; +} +.input-group-btn > .btn + .btn { + margin-left: -1px; +} +.input-group-btn > .btn:hover, +.input-group-btn > .btn:focus, +.input-group-btn > .btn:active { + z-index: 2; +} +.input-group-btn:first-child > .btn, +.input-group-btn:first-child > .btn-group { + margin-right: -1px; +} +.input-group-btn:last-child > .btn, +.input-group-btn:last-child > .btn-group { + z-index: 2; + margin-left: -1px; +} +.nav { + padding-left: 0; + margin-bottom: 0; + list-style: none; +} +.nav > li { + position: relative; + display: block; +} +.nav > li > a { + position: relative; + display: block; + padding: 10px 15px; +} +.nav > li > a:hover, +.nav > li > a:focus { + text-decoration: none; + background-color: #eee; +} +.nav > li.disabled > a { + color: #777; +} +.nav > li.disabled > a:hover, +.nav > li.disabled > a:focus { + color: #777; + text-decoration: none; + cursor: not-allowed; + background-color: transparent; +} +.nav .open > a, +.nav .open > a:hover, +.nav .open > a:focus { + background-color: #eee; + border-color: #337ab7; +} +.nav .nav-divider { + height: 1px; + margin: 9px 0; + overflow: hidden; + background-color: #e5e5e5; +} +.nav > li > a > img { + max-width: none; +} +.nav-tabs { + border-bottom: 1px solid #ddd; +} +.nav-tabs > li { + float: left; + margin-bottom: -1px; +} +.nav-tabs > li > a { + margin-right: 2px; + line-height: 1.42857143; + border: 1px solid transparent; + border-radius: 4px 4px 0 0; +} +.nav-tabs > li > a:hover { + border-color: #eee #eee #ddd; +} +.nav-tabs > li.active > a, +.nav-tabs > li.active > a:hover, +.nav-tabs > li.active > a:focus { + color: #555; + cursor: default; + background-color: #fff; + border: 1px solid #ddd; + border-bottom-color: transparent; +} +.nav-tabs.nav-justified { + width: 100%; + border-bottom: 0; +} +.nav-tabs.nav-justified > li { + float: none; +} +.nav-tabs.nav-justified > li > a { + margin-bottom: 5px; + text-align: center; +} +.nav-tabs.nav-justified > .dropdown .dropdown-menu { + top: auto; + left: auto; +} +@media (min-width: 768px) { + .nav-tabs.nav-justified > li { + display: table-cell; + width: 1%; + } + .nav-tabs.nav-justified > li > a { + margin-bottom: 0; + } +} +.nav-tabs.nav-justified > li > a { + margin-right: 0; + border-radius: 4px; +} +.nav-tabs.nav-justified > .active > a, +.nav-tabs.nav-justified > .active > a:hover, +.nav-tabs.nav-justified > .active > a:focus { + border: 1px solid #ddd; +} +@media (min-width: 768px) { + .nav-tabs.nav-justified > li > a { + border-bottom: 1px solid #ddd; + border-radius: 4px 4px 0 0; + } + .nav-tabs.nav-justified > .active > a, + .nav-tabs.nav-justified > .active > a:hover, + .nav-tabs.nav-justified > .active > a:focus { + border-bottom-color: #fff; + } +} +.nav-pills > li { + float: left; +} +.nav-pills > li > a { + border-radius: 4px; +} +.nav-pills > li + li { + margin-left: 2px; +} +.nav-pills > li.active > a, +.nav-pills > li.active > a:hover, +.nav-pills > li.active > a:focus { + color: #fff; + background-color: #337ab7; +} +.nav-stacked > li { + float: none; +} +.nav-stacked > li + li { + margin-top: 2px; + margin-left: 0; +} +.nav-justified { + width: 100%; +} +.nav-justified > li { + float: none; +} +.nav-justified > li > a { + margin-bottom: 5px; + text-align: center; +} +.nav-justified > .dropdown .dropdown-menu { + top: auto; + left: auto; +} +@media (min-width: 768px) { + .nav-justified > li { + display: table-cell; + width: 1%; + } + .nav-justified > li > a { + margin-bottom: 0; + } +} +.nav-tabs-justified { + border-bottom: 0; +} +.nav-tabs-justified > li > a { + margin-right: 0; + border-radius: 4px; +} +.nav-tabs-justified > .active > a, +.nav-tabs-justified > .active > a:hover, +.nav-tabs-justified > .active > a:focus { + border: 1px solid #ddd; +} +@media (min-width: 768px) { + .nav-tabs-justified > li > a { + border-bottom: 1px solid #ddd; + border-radius: 4px 4px 0 0; + } + .nav-tabs-justified > .active > a, + .nav-tabs-justified > .active > a:hover, + .nav-tabs-justified > .active > a:focus { + border-bottom-color: #fff; + } +} +.tab-content > .tab-pane { + display: none; +} +.tab-content > .active { + display: block; +} +.nav-tabs .dropdown-menu { + margin-top: -1px; + border-top-left-radius: 0; + border-top-right-radius: 0; +} +.navbar { + position: relative; + min-height: 50px; + margin-bottom: 20px; + border: 1px solid transparent; +} +@media (min-width: 768px) { + .navbar { + border-radius: 4px; + } +} +@media (min-width: 768px) { + .navbar-header { + float: left; + } +} +.navbar-collapse { + padding-right: 15px; + padding-left: 15px; + overflow-x: visible; + -webkit-overflow-scrolling: touch; + border-top: 1px solid transparent; + -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1); +} +.navbar-collapse.in { + overflow-y: auto; +} +@media (min-width: 768px) { + .navbar-collapse { + width: auto; + border-top: 0; + -webkit-box-shadow: none; + box-shadow: none; + } + .navbar-collapse.collapse { + display: block !important; + height: auto !important; + padding-bottom: 0; + overflow: visible !important; + } + .navbar-collapse.in { + overflow-y: visible; + } + .navbar-fixed-top .navbar-collapse, + .navbar-static-top .navbar-collapse, + .navbar-fixed-bottom .navbar-collapse { + padding-right: 0; + padding-left: 0; + } +} +.navbar-fixed-top .navbar-collapse, +.navbar-fixed-bottom .navbar-collapse { + max-height: 340px; +} +@media (max-device-width: 480px) and (orientation: landscape) { + .navbar-fixed-top .navbar-collapse, + .navbar-fixed-bottom .navbar-collapse { + max-height: 200px; + } +} +.container > .navbar-header, +.container-fluid > .navbar-header, +.container > .navbar-collapse, +.container-fluid > .navbar-collapse { + margin-right: -15px; + margin-left: -15px; +} +@media (min-width: 768px) { + .container > .navbar-header, + .container-fluid > .navbar-header, + .container > .navbar-collapse, + .container-fluid > .navbar-collapse { + margin-right: 0; + margin-left: 0; + } +} +.navbar-static-top { + z-index: 1000; + border-width: 0 0 1px; +} +@media (min-width: 768px) { + .navbar-static-top { + border-radius: 0; + } +} +.navbar-fixed-top, +.navbar-fixed-bottom { + position: fixed; + right: 0; + left: 0; + z-index: 1030; +} +@media (min-width: 768px) { + .navbar-fixed-top, + .navbar-fixed-bottom { + border-radius: 0; + } +} +.navbar-fixed-top { + top: 0; + border-width: 0 0 1px; +} +.navbar-fixed-bottom { + bottom: 0; + margin-bottom: 0; + border-width: 1px 0 0; +} +.navbar-brand { + float: left; + height: 50px; + padding: 15px 15px; + font-size: 18px; + line-height: 20px; +} +.navbar-brand:hover, +.navbar-brand:focus { + text-decoration: none; +} +.navbar-brand > img { + display: block; +} +@media (min-width: 768px) { + .navbar > .container .navbar-brand, + .navbar > .container-fluid .navbar-brand { + margin-left: -15px; + } +} +.navbar-toggle { + position: relative; + float: right; + padding: 9px 10px; + margin-top: 8px; + margin-right: 15px; + margin-bottom: 8px; + background-color: transparent; + background-image: none; + border: 1px solid transparent; + border-radius: 4px; +} +.navbar-toggle:focus { + outline: 0; +} +.navbar-toggle .icon-bar { + display: block; + width: 22px; + height: 2px; + border-radius: 1px; +} +.navbar-toggle .icon-bar + .icon-bar { + margin-top: 4px; +} +@media (min-width: 768px) { + .navbar-toggle { + display: none; + } +} +.navbar-nav { + margin: 7.5px -15px; +} +.navbar-nav > li > a { + padding-top: 10px; + padding-bottom: 10px; + line-height: 20px; +} +@media (max-width: 767px) { + .navbar-nav .open .dropdown-menu { + position: static; + float: none; + width: auto; + margin-top: 0; + background-color: transparent; + border: 0; + -webkit-box-shadow: none; + box-shadow: none; + } + .navbar-nav .open .dropdown-menu > li > a, + .navbar-nav .open .dropdown-menu .dropdown-header { + padding: 5px 15px 5px 25px; + } + .navbar-nav .open .dropdown-menu > li > a { + line-height: 20px; + } + .navbar-nav .open .dropdown-menu > li > a:hover, + .navbar-nav .open .dropdown-menu > li > a:focus { + background-image: none; + } +} +@media (min-width: 768px) { + .navbar-nav { + float: left; + margin: 0; + } + .navbar-nav > li { + float: left; + } + .navbar-nav > li > a { + padding-top: 15px; + padding-bottom: 15px; + } +} +.navbar-form { + padding: 10px 15px; + margin-top: 8px; + margin-right: -15px; + margin-bottom: 8px; + margin-left: -15px; + border-top: 1px solid transparent; + border-bottom: 1px solid transparent; + -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 1px 0 rgba(255, 255, 255, .1); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 1px 0 rgba(255, 255, 255, .1); +} +@media (min-width: 768px) { + .navbar-form .form-group { + display: inline-block; + margin-bottom: 0; + vertical-align: middle; + } + .navbar-form .form-control { + display: inline-block; + width: auto; + vertical-align: middle; + } + .navbar-form .form-control-static { + display: inline-block; + } + .navbar-form .input-group { + display: inline-table; + vertical-align: middle; + } + .navbar-form .input-group .input-group-addon, + .navbar-form .input-group .input-group-btn, + .navbar-form .input-group .form-control { + width: auto; + } + .navbar-form .input-group > .form-control { + width: 100%; + } + .navbar-form .control-label { + margin-bottom: 0; + vertical-align: middle; + } + .navbar-form .radio, + .navbar-form .checkbox { + display: inline-block; + margin-top: 0; + margin-bottom: 0; + vertical-align: middle; + } + .navbar-form .radio label, + .navbar-form .checkbox label { + padding-left: 0; + } + .navbar-form .radio input[type="radio"], + .navbar-form .checkbox input[type="checkbox"] { + position: relative; + margin-left: 0; + } + .navbar-form .has-feedback .form-control-feedback { + top: 0; + } +} +@media (max-width: 767px) { + .navbar-form .form-group { + margin-bottom: 5px; + } + .navbar-form .form-group:last-child { + margin-bottom: 0; + } +} +@media (min-width: 768px) { + .navbar-form { + width: auto; + padding-top: 0; + padding-bottom: 0; + margin-right: 0; + margin-left: 0; + border: 0; + -webkit-box-shadow: none; + box-shadow: none; + } +} +.navbar-nav > li > .dropdown-menu { + margin-top: 0; + border-top-left-radius: 0; + border-top-right-radius: 0; +} +.navbar-fixed-bottom .navbar-nav > li > .dropdown-menu { + margin-bottom: 0; + border-top-left-radius: 4px; + border-top-right-radius: 4px; + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} +.navbar-btn { + margin-top: 8px; + margin-bottom: 8px; +} +.navbar-btn.btn-sm { + margin-top: 10px; + margin-bottom: 10px; +} +.navbar-btn.btn-xs { + margin-top: 14px; + margin-bottom: 14px; +} +.navbar-text { + margin-top: 15px; + margin-bottom: 15px; +} +@media (min-width: 768px) { + .navbar-text { + float: left; + margin-right: 15px; + margin-left: 15px; + } +} +@media (min-width: 768px) { + .navbar-left { + float: left !important; + } + .navbar-right { + float: right !important; + margin-right: -15px; + } + .navbar-right ~ .navbar-right { + margin-right: 0; + } +} +.navbar-default { + background-color: #f8f8f8; + border-color: #e7e7e7; +} +.navbar-default .navbar-brand { + color: #777; +} +.navbar-default .navbar-brand:hover, +.navbar-default .navbar-brand:focus { + color: #5e5e5e; + background-color: transparent; +} +.navbar-default .navbar-text { + color: #777; +} +.navbar-default .navbar-nav > li > a { + color: #777; +} +.navbar-default .navbar-nav > li > a:hover, +.navbar-default .navbar-nav > li > a:focus { + color: #333; + background-color: transparent; +} +.navbar-default .navbar-nav > .active > a, +.navbar-default .navbar-nav > .active > a:hover, +.navbar-default .navbar-nav > .active > a:focus { + color: #555; + background-color: #e7e7e7; +} +.navbar-default .navbar-nav > .disabled > a, +.navbar-default .navbar-nav > .disabled > a:hover, +.navbar-default .navbar-nav > .disabled > a:focus { + color: #ccc; + background-color: transparent; +} +.navbar-default .navbar-toggle { + border-color: #ddd; +} +.navbar-default .navbar-toggle:hover, +.navbar-default .navbar-toggle:focus { + background-color: #ddd; +} +.navbar-default .navbar-toggle .icon-bar { + background-color: #888; +} +.navbar-default .navbar-collapse, +.navbar-default .navbar-form { + border-color: #e7e7e7; +} +.navbar-default .navbar-nav > .open > a, +.navbar-default .navbar-nav > .open > a:hover, +.navbar-default .navbar-nav > .open > a:focus { + color: #555; + background-color: #e7e7e7; +} +@media (max-width: 767px) { + .navbar-default .navbar-nav .open .dropdown-menu > li > a { + color: #777; + } + .navbar-default .navbar-nav .open .dropdown-menu > li > a:hover, + .navbar-default .navbar-nav .open .dropdown-menu > li > a:focus { + color: #333; + background-color: transparent; + } + .navbar-default .navbar-nav .open .dropdown-menu > .active > a, + .navbar-default .navbar-nav .open .dropdown-menu > .active > a:hover, + .navbar-default .navbar-nav .open .dropdown-menu > .active > a:focus { + color: #555; + background-color: #e7e7e7; + } + .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a, + .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:hover, + .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:focus { + color: #ccc; + background-color: transparent; + } +} +.navbar-default .navbar-link { + color: #777; +} +.navbar-default .navbar-link:hover { + color: #333; +} +.navbar-default .btn-link { + color: #777; +} +.navbar-default .btn-link:hover, +.navbar-default .btn-link:focus { + color: #333; +} +.navbar-default .btn-link[disabled]:hover, +fieldset[disabled] .navbar-default .btn-link:hover, +.navbar-default .btn-link[disabled]:focus, +fieldset[disabled] .navbar-default .btn-link:focus { + color: #ccc; +} +.navbar-inverse { + background-color: #222; + border-color: #080808; +} +.navbar-inverse .navbar-brand { + color: #9d9d9d; +} +.navbar-inverse .navbar-brand:hover, +.navbar-inverse .navbar-brand:focus { + color: #fff; + background-color: transparent; +} +.navbar-inverse .navbar-text { + color: #9d9d9d; +} +.navbar-inverse .navbar-nav > li > a { + color: #9d9d9d; +} +.navbar-inverse .navbar-nav > li > a:hover, +.navbar-inverse .navbar-nav > li > a:focus { + color: #fff; + background-color: transparent; +} +.navbar-inverse .navbar-nav > .active > a, +.navbar-inverse .navbar-nav > .active > a:hover, +.navbar-inverse .navbar-nav > .active > a:focus { + color: #fff; + background-color: #080808; +} +.navbar-inverse .navbar-nav > .disabled > a, +.navbar-inverse .navbar-nav > .disabled > a:hover, +.navbar-inverse .navbar-nav > .disabled > a:focus { + color: #444; + background-color: transparent; +} +.navbar-inverse .navbar-toggle { + border-color: #333; +} +.navbar-inverse .navbar-toggle:hover, +.navbar-inverse .navbar-toggle:focus { + background-color: #333; +} +.navbar-inverse .navbar-toggle .icon-bar { + background-color: #fff; +} +.navbar-inverse .navbar-collapse, +.navbar-inverse .navbar-form { + border-color: #101010; +} +.navbar-inverse .navbar-nav > .open > a, +.navbar-inverse .navbar-nav > .open > a:hover, +.navbar-inverse .navbar-nav > .open > a:focus { + color: #fff; + background-color: #080808; +} +@media (max-width: 767px) { + .navbar-inverse .navbar-nav .open .dropdown-menu > .dropdown-header { + border-color: #080808; + } + .navbar-inverse .navbar-nav .open .dropdown-menu .divider { + background-color: #080808; + } + .navbar-inverse .navbar-nav .open .dropdown-menu > li > a { + color: #9d9d9d; + } + .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:hover, + .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:focus { + color: #fff; + background-color: transparent; + } + .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a, + .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:hover, + .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:focus { + color: #fff; + background-color: #080808; + } + .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a, + .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:hover, + .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:focus { + color: #444; + background-color: transparent; + } +} +.navbar-inverse .navbar-link { + color: #9d9d9d; +} +.navbar-inverse .navbar-link:hover { + color: #fff; +} +.navbar-inverse .btn-link { + color: #9d9d9d; +} +.navbar-inverse .btn-link:hover, +.navbar-inverse .btn-link:focus { + color: #fff; +} +.navbar-inverse .btn-link[disabled]:hover, +fieldset[disabled] .navbar-inverse .btn-link:hover, +.navbar-inverse .btn-link[disabled]:focus, +fieldset[disabled] .navbar-inverse .btn-link:focus { + color: #444; +} +.breadcrumb { + padding: 8px 15px; + margin-bottom: 20px; + list-style: none; + background-color: #f5f5f5; + border-radius: 4px; +} +.breadcrumb > li { + display: inline-block; +} +.breadcrumb > li + li:before { + padding: 0 5px; + color: #ccc; + content: "/\00a0"; +} +.breadcrumb > .active { + color: #777; +} +.pagination { + display: inline-block; + padding-left: 0; + margin: 20px 0; + border-radius: 4px; +} +.pagination > li { + display: inline; +} +.pagination > li > a, +.pagination > li > span { + position: relative; + float: left; + padding: 6px 12px; + margin-left: -1px; + line-height: 1.42857143; + color: #337ab7; + text-decoration: none; + background-color: #fff; + border: 1px solid #ddd; +} +.pagination > li:first-child > a, +.pagination > li:first-child > span { + margin-left: 0; + border-top-left-radius: 4px; + border-bottom-left-radius: 4px; +} +.pagination > li:last-child > a, +.pagination > li:last-child > span { + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; +} +.pagination > li > a:hover, +.pagination > li > span:hover, +.pagination > li > a:focus, +.pagination > li > span:focus { + z-index: 2; + color: #23527c; + background-color: #eee; + border-color: #ddd; +} +.pagination > .active > a, +.pagination > .active > span, +.pagination > .active > a:hover, +.pagination > .active > span:hover, +.pagination > .active > a:focus, +.pagination > .active > span:focus { + z-index: 3; + color: #fff; + cursor: default; + background-color: #337ab7; + border-color: #337ab7; +} +.pagination > .disabled > span, +.pagination > .disabled > span:hover, +.pagination > .disabled > span:focus, +.pagination > .disabled > a, +.pagination > .disabled > a:hover, +.pagination > .disabled > a:focus { + color: #777; + cursor: not-allowed; + background-color: #fff; + border-color: #ddd; +} +.pagination-lg > li > a, +.pagination-lg > li > span { + padding: 10px 16px; + font-size: 18px; + line-height: 1.3333333; +} +.pagination-lg > li:first-child > a, +.pagination-lg > li:first-child > span { + border-top-left-radius: 6px; + border-bottom-left-radius: 6px; +} +.pagination-lg > li:last-child > a, +.pagination-lg > li:last-child > span { + border-top-right-radius: 6px; + border-bottom-right-radius: 6px; +} +.pagination-sm > li > a, +.pagination-sm > li > span { + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; +} +.pagination-sm > li:first-child > a, +.pagination-sm > li:first-child > span { + border-top-left-radius: 3px; + border-bottom-left-radius: 3px; +} +.pagination-sm > li:last-child > a, +.pagination-sm > li:last-child > span { + border-top-right-radius: 3px; + border-bottom-right-radius: 3px; +} +.pager { + padding-left: 0; + margin: 20px 0; + text-align: center; + list-style: none; +} +.pager li { + display: inline; +} +.pager li > a, +.pager li > span { + display: inline-block; + padding: 5px 14px; + background-color: #fff; + border: 1px solid #ddd; + border-radius: 15px; +} +.pager li > a:hover, +.pager li > a:focus { + text-decoration: none; + background-color: #eee; +} +.pager .next > a, +.pager .next > span { + float: right; +} +.pager .previous > a, +.pager .previous > span { + float: left; +} +.pager .disabled > a, +.pager .disabled > a:hover, +.pager .disabled > a:focus, +.pager .disabled > span { + color: #777; + cursor: not-allowed; + background-color: #fff; +} +.label { + display: inline; + padding: .2em .6em .3em; + font-size: 75%; + font-weight: bold; + line-height: 1; + color: #fff; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + border-radius: .25em; +} +a.label:hover, +a.label:focus { + color: #fff; + text-decoration: none; + cursor: pointer; +} +.label:empty { + display: none; +} +.btn .label { + position: relative; + top: -1px; +} +.label-default { + background-color: #777; +} +.label-default[href]:hover, +.label-default[href]:focus { + background-color: #5e5e5e; +} +.label-primary { + background-color: #337ab7; +} +.label-primary[href]:hover, +.label-primary[href]:focus { + background-color: #286090; +} +.label-success { + background-color: #5cb85c; +} +.label-success[href]:hover, +.label-success[href]:focus { + background-color: #449d44; +} +.label-info { + background-color: #5bc0de; +} +.label-info[href]:hover, +.label-info[href]:focus { + background-color: #31b0d5; +} +.label-warning { + background-color: #f0ad4e; +} +.label-warning[href]:hover, +.label-warning[href]:focus { + background-color: #ec971f; +} +.label-danger { + background-color: #d9534f; +} +.label-danger[href]:hover, +.label-danger[href]:focus { + background-color: #c9302c; +} +.badge { + display: inline-block; + min-width: 10px; + padding: 3px 7px; + font-size: 12px; + font-weight: bold; + line-height: 1; + color: #fff; + text-align: center; + white-space: nowrap; + vertical-align: middle; + background-color: #777; + border-radius: 10px; +} +.badge:empty { + display: none; +} +.btn .badge { + position: relative; + top: -1px; +} +.btn-xs .badge, +.btn-group-xs > .btn .badge { + top: 0; + padding: 1px 5px; +} +a.badge:hover, +a.badge:focus { + color: #fff; + text-decoration: none; + cursor: pointer; +} +.list-group-item.active > .badge, +.nav-pills > .active > a > .badge { + color: #337ab7; + background-color: #fff; +} +.list-group-item > .badge { + float: right; +} +.list-group-item > .badge + .badge { + margin-right: 5px; +} +.nav-pills > li > a > .badge { + margin-left: 3px; +} +.jumbotron { + padding-top: 30px; + padding-bottom: 30px; + margin-bottom: 30px; + color: inherit; + background-color: #eee; +} +.jumbotron h1, +.jumbotron .h1 { + color: inherit; +} +.jumbotron p { + margin-bottom: 15px; + font-size: 21px; + font-weight: 200; +} +.jumbotron > hr { + border-top-color: #d5d5d5; +} +.container .jumbotron, +.container-fluid .jumbotron { + padding-right: 15px; + padding-left: 15px; + border-radius: 6px; +} +.jumbotron .container { + max-width: 100%; +} +@media screen and (min-width: 768px) { + .jumbotron { + padding-top: 48px; + padding-bottom: 48px; + } + .container .jumbotron, + .container-fluid .jumbotron { + padding-right: 60px; + padding-left: 60px; + } + .jumbotron h1, + .jumbotron .h1 { + font-size: 63px; + } +} +.thumbnail { + display: block; + padding: 4px; + margin-bottom: 20px; + line-height: 1.42857143; + background-color: #fff; + border: 1px solid #ddd; + border-radius: 4px; + -webkit-transition: border .2s ease-in-out; + -o-transition: border .2s ease-in-out; + transition: border .2s ease-in-out; +} +.thumbnail > img, +.thumbnail a > img { + margin-right: auto; + margin-left: auto; +} +a.thumbnail:hover, +a.thumbnail:focus, +a.thumbnail.active { + border-color: #337ab7; +} +.thumbnail .caption { + padding: 9px; + color: #333; +} +.alert { + padding: 15px; + margin-bottom: 20px; + border: 1px solid transparent; + border-radius: 4px; +} +.alert h4 { + margin-top: 0; + color: inherit; +} +.alert .alert-link { + font-weight: bold; +} +.alert > p, +.alert > ul { + margin-bottom: 0; +} +.alert > p + p { + margin-top: 5px; +} +.alert-dismissable, +.alert-dismissible { + padding-right: 35px; +} +.alert-dismissable .close, +.alert-dismissible .close { + position: relative; + top: -2px; + right: -21px; + color: inherit; +} +.alert-success { + color: #3c763d; + background-color: #dff0d8; + border-color: #d6e9c6; +} +.alert-success hr { + border-top-color: #c9e2b3; +} +.alert-success .alert-link { + color: #2b542c; +} +.alert-info { + color: #31708f; + background-color: #d9edf7; + border-color: #bce8f1; +} +.alert-info hr { + border-top-color: #a6e1ec; +} +.alert-info .alert-link { + color: #245269; +} +.alert-warning { + color: #8a6d3b; + background-color: #fcf8e3; + border-color: #faebcc; +} +.alert-warning hr { + border-top-color: #f7e1b5; +} +.alert-warning .alert-link { + color: #66512c; +} +.alert-danger { + color: #a94442; + background-color: #f2dede; + border-color: #ebccd1; +} +.alert-danger hr { + border-top-color: #e4b9c0; +} +.alert-danger .alert-link { + color: #843534; +} +@-webkit-keyframes progress-bar-stripes { + from { + background-position: 40px 0; + } + to { + background-position: 0 0; + } +} +@-o-keyframes progress-bar-stripes { + from { + background-position: 40px 0; + } + to { + background-position: 0 0; + } +} +@keyframes progress-bar-stripes { + from { + background-position: 40px 0; + } + to { + background-position: 0 0; + } +} +.progress { + height: 20px; + margin-bottom: 20px; + overflow: hidden; + background-color: #f5f5f5; + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, .1); + box-shadow: inset 0 1px 2px rgba(0, 0, 0, .1); +} +.progress-bar { + float: left; + width: 0; + height: 100%; + font-size: 12px; + line-height: 20px; + color: #fff; + text-align: center; + background-color: #337ab7; + -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .15); + box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .15); + -webkit-transition: width .6s ease; + -o-transition: width .6s ease; + transition: width .6s ease; +} +.progress-striped .progress-bar, +.progress-bar-striped { + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); + -webkit-background-size: 40px 40px; + background-size: 40px 40px; +} +.progress.active .progress-bar, +.progress-bar.active { + -webkit-animation: progress-bar-stripes 2s linear infinite; + -o-animation: progress-bar-stripes 2s linear infinite; + animation: progress-bar-stripes 2s linear infinite; +} +.progress-bar-success { + background-color: #5cb85c; +} +.progress-striped .progress-bar-success { + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); +} +.progress-bar-info { + background-color: #5bc0de; +} +.progress-striped .progress-bar-info { + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); +} +.progress-bar-warning { + background-color: #f0ad4e; +} +.progress-striped .progress-bar-warning { + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); +} +.progress-bar-danger { + background-color: #d9534f; +} +.progress-striped .progress-bar-danger { + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); +} +.media { + margin-top: 15px; +} +.media:first-child { + margin-top: 0; +} +.media, +.media-body { + overflow: hidden; + zoom: 1; +} +.media-body { + width: 10000px; +} +.media-object { + display: block; +} +.media-object.img-thumbnail { + max-width: none; +} +.media-right, +.media > .pull-right { + padding-left: 10px; +} +.media-left, +.media > .pull-left { + padding-right: 10px; +} +.media-left, +.media-right, +.media-body { + display: table-cell; + vertical-align: top; +} +.media-middle { + vertical-align: middle; +} +.media-bottom { + vertical-align: bottom; +} +.media-heading { + margin-top: 0; + margin-bottom: 5px; +} +.media-list { + padding-left: 0; + list-style: none; +} +.list-group { + padding-left: 0; + margin-bottom: 20px; +} +.list-group-item { + position: relative; + display: block; + padding: 10px 15px; + margin-bottom: -1px; + background-color: #fff; + border: 1px solid #ddd; +} +.list-group-item:first-child { + border-top-left-radius: 4px; + border-top-right-radius: 4px; +} +.list-group-item:last-child { + margin-bottom: 0; + border-bottom-right-radius: 4px; + border-bottom-left-radius: 4px; +} +a.list-group-item, +button.list-group-item { + color: #555; +} +a.list-group-item .list-group-item-heading, +button.list-group-item .list-group-item-heading { + color: #333; +} +a.list-group-item:hover, +button.list-group-item:hover, +a.list-group-item:focus, +button.list-group-item:focus { + color: #555; + text-decoration: none; + background-color: #f5f5f5; +} +button.list-group-item { + width: 100%; + text-align: left; +} +.list-group-item.disabled, +.list-group-item.disabled:hover, +.list-group-item.disabled:focus { + color: #777; + cursor: not-allowed; + background-color: #eee; +} +.list-group-item.disabled .list-group-item-heading, +.list-group-item.disabled:hover .list-group-item-heading, +.list-group-item.disabled:focus .list-group-item-heading { + color: inherit; +} +.list-group-item.disabled .list-group-item-text, +.list-group-item.disabled:hover .list-group-item-text, +.list-group-item.disabled:focus .list-group-item-text { + color: #777; +} +.list-group-item.active, +.list-group-item.active:hover, +.list-group-item.active:focus { + z-index: 2; + color: #fff; + background-color: #337ab7; + border-color: #337ab7; +} +.list-group-item.active .list-group-item-heading, +.list-group-item.active:hover .list-group-item-heading, +.list-group-item.active:focus .list-group-item-heading, +.list-group-item.active .list-group-item-heading > small, +.list-group-item.active:hover .list-group-item-heading > small, +.list-group-item.active:focus .list-group-item-heading > small, +.list-group-item.active .list-group-item-heading > .small, +.list-group-item.active:hover .list-group-item-heading > .small, +.list-group-item.active:focus .list-group-item-heading > .small { + color: inherit; +} +.list-group-item.active .list-group-item-text, +.list-group-item.active:hover .list-group-item-text, +.list-group-item.active:focus .list-group-item-text { + color: #c7ddef; +} +.list-group-item-success { + color: #3c763d; + background-color: #dff0d8; +} +a.list-group-item-success, +button.list-group-item-success { + color: #3c763d; +} +a.list-group-item-success .list-group-item-heading, +button.list-group-item-success .list-group-item-heading { + color: inherit; +} +a.list-group-item-success:hover, +button.list-group-item-success:hover, +a.list-group-item-success:focus, +button.list-group-item-success:focus { + color: #3c763d; + background-color: #d0e9c6; +} +a.list-group-item-success.active, +button.list-group-item-success.active, +a.list-group-item-success.active:hover, +button.list-group-item-success.active:hover, +a.list-group-item-success.active:focus, +button.list-group-item-success.active:focus { + color: #fff; + background-color: #3c763d; + border-color: #3c763d; +} +.list-group-item-info { + color: #31708f; + background-color: #d9edf7; +} +a.list-group-item-info, +button.list-group-item-info { + color: #31708f; +} +a.list-group-item-info .list-group-item-heading, +button.list-group-item-info .list-group-item-heading { + color: inherit; +} +a.list-group-item-info:hover, +button.list-group-item-info:hover, +a.list-group-item-info:focus, +button.list-group-item-info:focus { + color: #31708f; + background-color: #c4e3f3; +} +a.list-group-item-info.active, +button.list-group-item-info.active, +a.list-group-item-info.active:hover, +button.list-group-item-info.active:hover, +a.list-group-item-info.active:focus, +button.list-group-item-info.active:focus { + color: #fff; + background-color: #31708f; + border-color: #31708f; +} +.list-group-item-warning { + color: #8a6d3b; + background-color: #fcf8e3; +} +a.list-group-item-warning, +button.list-group-item-warning { + color: #8a6d3b; +} +a.list-group-item-warning .list-group-item-heading, +button.list-group-item-warning .list-group-item-heading { + color: inherit; +} +a.list-group-item-warning:hover, +button.list-group-item-warning:hover, +a.list-group-item-warning:focus, +button.list-group-item-warning:focus { + color: #8a6d3b; + background-color: #faf2cc; +} +a.list-group-item-warning.active, +button.list-group-item-warning.active, +a.list-group-item-warning.active:hover, +button.list-group-item-warning.active:hover, +a.list-group-item-warning.active:focus, +button.list-group-item-warning.active:focus { + color: #fff; + background-color: #8a6d3b; + border-color: #8a6d3b; +} +.list-group-item-danger { + color: #a94442; + background-color: #f2dede; +} +a.list-group-item-danger, +button.list-group-item-danger { + color: #a94442; +} +a.list-group-item-danger .list-group-item-heading, +button.list-group-item-danger .list-group-item-heading { + color: inherit; +} +a.list-group-item-danger:hover, +button.list-group-item-danger:hover, +a.list-group-item-danger:focus, +button.list-group-item-danger:focus { + color: #a94442; + background-color: #ebcccc; +} +a.list-group-item-danger.active, +button.list-group-item-danger.active, +a.list-group-item-danger.active:hover, +button.list-group-item-danger.active:hover, +a.list-group-item-danger.active:focus, +button.list-group-item-danger.active:focus { + color: #fff; + background-color: #a94442; + border-color: #a94442; +} +.list-group-item-heading { + margin-top: 0; + margin-bottom: 5px; +} +.list-group-item-text { + margin-bottom: 0; + line-height: 1.3; +} +.panel { + margin-bottom: 20px; + background-color: #fff; + border: 1px solid transparent; + border-radius: 4px; + -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, .05); + box-shadow: 0 1px 1px rgba(0, 0, 0, .05); +} +.panel-body { + padding: 15px; +} +.panel-heading { + padding: 10px 15px; + border-bottom: 1px solid transparent; + border-top-left-radius: 3px; + border-top-right-radius: 3px; +} +.panel-heading > .dropdown .dropdown-toggle { + color: inherit; +} +.panel-title { + margin-top: 0; + margin-bottom: 0; + font-size: 16px; + color: inherit; +} +.panel-title > a, +.panel-title > small, +.panel-title > .small, +.panel-title > small > a, +.panel-title > .small > a { + color: inherit; +} +.panel-footer { + padding: 10px 15px; + background-color: #f5f5f5; + border-top: 1px solid #ddd; + border-bottom-right-radius: 3px; + border-bottom-left-radius: 3px; +} +.panel > .list-group, +.panel > .panel-collapse > .list-group { + margin-bottom: 0; +} +.panel > .list-group .list-group-item, +.panel > .panel-collapse > .list-group .list-group-item { + border-width: 1px 0; + border-radius: 0; +} +.panel > .list-group:first-child .list-group-item:first-child, +.panel > .panel-collapse > .list-group:first-child .list-group-item:first-child { + border-top: 0; + border-top-left-radius: 3px; + border-top-right-radius: 3px; +} +.panel > .list-group:last-child .list-group-item:last-child, +.panel > .panel-collapse > .list-group:last-child .list-group-item:last-child { + border-bottom: 0; + border-bottom-right-radius: 3px; + border-bottom-left-radius: 3px; +} +.panel > .panel-heading + .panel-collapse > .list-group .list-group-item:first-child { + border-top-left-radius: 0; + border-top-right-radius: 0; +} +.panel-heading + .list-group .list-group-item:first-child { + border-top-width: 0; +} +.list-group + .panel-footer { + border-top-width: 0; +} +.panel > .table, +.panel > .table-responsive > .table, +.panel > .panel-collapse > .table { + margin-bottom: 0; +} +.panel > .table caption, +.panel > .table-responsive > .table caption, +.panel > .panel-collapse > .table caption { + padding-right: 15px; + padding-left: 15px; +} +.panel > .table:first-child, +.panel > .table-responsive:first-child > .table:first-child { + border-top-left-radius: 3px; + border-top-right-radius: 3px; +} +.panel > .table:first-child > thead:first-child > tr:first-child, +.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child, +.panel > .table:first-child > tbody:first-child > tr:first-child, +.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child { + border-top-left-radius: 3px; + border-top-right-radius: 3px; +} +.panel > .table:first-child > thead:first-child > tr:first-child td:first-child, +.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:first-child, +.panel > .table:first-child > tbody:first-child > tr:first-child td:first-child, +.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:first-child, +.panel > .table:first-child > thead:first-child > tr:first-child th:first-child, +.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:first-child, +.panel > .table:first-child > tbody:first-child > tr:first-child th:first-child, +.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:first-child { + border-top-left-radius: 3px; +} +.panel > .table:first-child > thead:first-child > tr:first-child td:last-child, +.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:last-child, +.panel > .table:first-child > tbody:first-child > tr:first-child td:last-child, +.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:last-child, +.panel > .table:first-child > thead:first-child > tr:first-child th:last-child, +.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:last-child, +.panel > .table:first-child > tbody:first-child > tr:first-child th:last-child, +.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:last-child { + border-top-right-radius: 3px; +} +.panel > .table:last-child, +.panel > .table-responsive:last-child > .table:last-child { + border-bottom-right-radius: 3px; + border-bottom-left-radius: 3px; +} +.panel > .table:last-child > tbody:last-child > tr:last-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child, +.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child { + border-bottom-right-radius: 3px; + border-bottom-left-radius: 3px; +} +.panel > .table:last-child > tbody:last-child > tr:last-child td:first-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:first-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child td:first-child, +.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:first-child, +.panel > .table:last-child > tbody:last-child > tr:last-child th:first-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:first-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child th:first-child, +.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:first-child { + border-bottom-left-radius: 3px; +} +.panel > .table:last-child > tbody:last-child > tr:last-child td:last-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:last-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child td:last-child, +.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:last-child, +.panel > .table:last-child > tbody:last-child > tr:last-child th:last-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:last-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child th:last-child, +.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:last-child { + border-bottom-right-radius: 3px; +} +.panel > .panel-body + .table, +.panel > .panel-body + .table-responsive, +.panel > .table + .panel-body, +.panel > .table-responsive + .panel-body { + border-top: 1px solid #ddd; +} +.panel > .table > tbody:first-child > tr:first-child th, +.panel > .table > tbody:first-child > tr:first-child td { + border-top: 0; +} +.panel > .table-bordered, +.panel > .table-responsive > .table-bordered { + border: 0; +} +.panel > .table-bordered > thead > tr > th:first-child, +.panel > .table-responsive > .table-bordered > thead > tr > th:first-child, +.panel > .table-bordered > tbody > tr > th:first-child, +.panel > .table-responsive > .table-bordered > tbody > tr > th:first-child, +.panel > .table-bordered > tfoot > tr > th:first-child, +.panel > .table-responsive > .table-bordered > tfoot > tr > th:first-child, +.panel > .table-bordered > thead > tr > td:first-child, +.panel > .table-responsive > .table-bordered > thead > tr > td:first-child, +.panel > .table-bordered > tbody > tr > td:first-child, +.panel > .table-responsive > .table-bordered > tbody > tr > td:first-child, +.panel > .table-bordered > tfoot > tr > td:first-child, +.panel > .table-responsive > .table-bordered > tfoot > tr > td:first-child { + border-left: 0; +} +.panel > .table-bordered > thead > tr > th:last-child, +.panel > .table-responsive > .table-bordered > thead > tr > th:last-child, +.panel > .table-bordered > tbody > tr > th:last-child, +.panel > .table-responsive > .table-bordered > tbody > tr > th:last-child, +.panel > .table-bordered > tfoot > tr > th:last-child, +.panel > .table-responsive > .table-bordered > tfoot > tr > th:last-child, +.panel > .table-bordered > thead > tr > td:last-child, +.panel > .table-responsive > .table-bordered > thead > tr > td:last-child, +.panel > .table-bordered > tbody > tr > td:last-child, +.panel > .table-responsive > .table-bordered > tbody > tr > td:last-child, +.panel > .table-bordered > tfoot > tr > td:last-child, +.panel > .table-responsive > .table-bordered > tfoot > tr > td:last-child { + border-right: 0; +} +.panel > .table-bordered > thead > tr:first-child > td, +.panel > .table-responsive > .table-bordered > thead > tr:first-child > td, +.panel > .table-bordered > tbody > tr:first-child > td, +.panel > .table-responsive > .table-bordered > tbody > tr:first-child > td, +.panel > .table-bordered > thead > tr:first-child > th, +.panel > .table-responsive > .table-bordered > thead > tr:first-child > th, +.panel > .table-bordered > tbody > tr:first-child > th, +.panel > .table-responsive > .table-bordered > tbody > tr:first-child > th { + border-bottom: 0; +} +.panel > .table-bordered > tbody > tr:last-child > td, +.panel > .table-responsive > .table-bordered > tbody > tr:last-child > td, +.panel > .table-bordered > tfoot > tr:last-child > td, +.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > td, +.panel > .table-bordered > tbody > tr:last-child > th, +.panel > .table-responsive > .table-bordered > tbody > tr:last-child > th, +.panel > .table-bordered > tfoot > tr:last-child > th, +.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > th { + border-bottom: 0; +} +.panel > .table-responsive { + margin-bottom: 0; + border: 0; +} +.panel-group { + margin-bottom: 20px; +} +.panel-group .panel { + margin-bottom: 0; + border-radius: 4px; +} +.panel-group .panel + .panel { + margin-top: 5px; +} +.panel-group .panel-heading { + border-bottom: 0; +} +.panel-group .panel-heading + .panel-collapse > .panel-body, +.panel-group .panel-heading + .panel-collapse > .list-group { + border-top: 1px solid #ddd; +} +.panel-group .panel-footer { + border-top: 0; +} +.panel-group .panel-footer + .panel-collapse .panel-body { + border-bottom: 1px solid #ddd; +} +.panel-default { + border-color: #ddd; +} +.panel-default > .panel-heading { + color: #333; + background-color: #f5f5f5; + border-color: #ddd; +} +.panel-default > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #ddd; +} +.panel-default > .panel-heading .badge { + color: #f5f5f5; + background-color: #333; +} +.panel-default > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #ddd; +} +.panel-primary { + border-color: #337ab7; +} +.panel-primary > .panel-heading { + color: #fff; + background-color: #337ab7; + border-color: #337ab7; +} +.panel-primary > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #337ab7; +} +.panel-primary > .panel-heading .badge { + color: #337ab7; + background-color: #fff; +} +.panel-primary > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #337ab7; +} +.panel-success { + border-color: #d6e9c6; +} +.panel-success > .panel-heading { + color: #3c763d; + background-color: #dff0d8; + border-color: #d6e9c6; +} +.panel-success > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #d6e9c6; +} +.panel-success > .panel-heading .badge { + color: #dff0d8; + background-color: #3c763d; +} +.panel-success > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #d6e9c6; +} +.panel-info { + border-color: #bce8f1; +} +.panel-info > .panel-heading { + color: #31708f; + background-color: #d9edf7; + border-color: #bce8f1; +} +.panel-info > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #bce8f1; +} +.panel-info > .panel-heading .badge { + color: #d9edf7; + background-color: #31708f; +} +.panel-info > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #bce8f1; +} +.panel-warning { + border-color: #faebcc; +} +.panel-warning > .panel-heading { + color: #8a6d3b; + background-color: #fcf8e3; + border-color: #faebcc; +} +.panel-warning > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #faebcc; +} +.panel-warning > .panel-heading .badge { + color: #fcf8e3; + background-color: #8a6d3b; +} +.panel-warning > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #faebcc; +} +.panel-danger { + border-color: #ebccd1; +} +.panel-danger > .panel-heading { + color: #a94442; + background-color: #f2dede; + border-color: #ebccd1; +} +.panel-danger > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #ebccd1; +} +.panel-danger > .panel-heading .badge { + color: #f2dede; + background-color: #a94442; +} +.panel-danger > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #ebccd1; +} +.embed-responsive { + position: relative; + display: block; + height: 0; + padding: 0; + overflow: hidden; +} +.embed-responsive .embed-responsive-item, +.embed-responsive iframe, +.embed-responsive embed, +.embed-responsive object, +.embed-responsive video { + position: absolute; + top: 0; + bottom: 0; + left: 0; + width: 100%; + height: 100%; + border: 0; +} +.embed-responsive-16by9 { + padding-bottom: 56.25%; +} +.embed-responsive-4by3 { + padding-bottom: 75%; +} +.well { + min-height: 20px; + padding: 19px; + margin-bottom: 20px; + background-color: #f5f5f5; + border: 1px solid #e3e3e3; + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05); +} +.well blockquote { + border-color: #ddd; + border-color: rgba(0, 0, 0, .15); +} +.well-lg { + padding: 24px; + border-radius: 6px; +} +.well-sm { + padding: 9px; + border-radius: 3px; +} +.close { + float: right; + font-size: 21px; + font-weight: bold; + line-height: 1; + color: #000; + text-shadow: 0 1px 0 #fff; + filter: alpha(opacity=20); + opacity: .2; +} +.close:hover, +.close:focus { + color: #000; + text-decoration: none; + cursor: pointer; + filter: alpha(opacity=50); + opacity: .5; +} +button.close { + -webkit-appearance: none; + padding: 0; + cursor: pointer; + background: transparent; + border: 0; +} +.modal-open { + overflow: hidden; +} +.modal { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1050; + display: none; + overflow: hidden; + -webkit-overflow-scrolling: touch; + outline: 0; +} +.modal.fade .modal-dialog { + -webkit-transition: -webkit-transform .3s ease-out; + -o-transition: -o-transform .3s ease-out; + transition: transform .3s ease-out; + -webkit-transform: translate(0, -25%); + -ms-transform: translate(0, -25%); + -o-transform: translate(0, -25%); + transform: translate(0, -25%); +} +.modal.in .modal-dialog { + -webkit-transform: translate(0, 0); + -ms-transform: translate(0, 0); + -o-transform: translate(0, 0); + transform: translate(0, 0); +} +.modal-open .modal { + overflow-x: hidden; + overflow-y: auto; +} +.modal-dialog { + position: relative; + width: auto; + margin: 10px; +} +.modal-content { + position: relative; + background-color: #fff; + -webkit-background-clip: padding-box; + background-clip: padding-box; + border: 1px solid #999; + border: 1px solid rgba(0, 0, 0, .2); + border-radius: 6px; + outline: 0; + -webkit-box-shadow: 0 3px 9px rgba(0, 0, 0, .5); + box-shadow: 0 3px 9px rgba(0, 0, 0, .5); +} +.modal-backdrop { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1040; + background-color: #000; +} +.modal-backdrop.fade { + filter: alpha(opacity=0); + opacity: 0; +} +.modal-backdrop.in { + filter: alpha(opacity=50); + opacity: .5; +} +.modal-header { + padding: 15px; + border-bottom: 1px solid #e5e5e5; +} +.modal-header .close { + margin-top: -2px; +} +.modal-title { + margin: 0; + line-height: 1.42857143; +} +.modal-body { + position: relative; + padding: 15px; +} +.modal-footer { + padding: 15px; + text-align: right; + border-top: 1px solid #e5e5e5; +} +.modal-footer .btn + .btn { + margin-bottom: 0; + margin-left: 5px; +} +.modal-footer .btn-group .btn + .btn { + margin-left: -1px; +} +.modal-footer .btn-block + .btn-block { + margin-left: 0; +} +.modal-scrollbar-measure { + position: absolute; + top: -9999px; + width: 50px; + height: 50px; + overflow: scroll; +} +@media (min-width: 768px) { + .modal-dialog { + width: 600px; + margin: 30px auto; + } + .modal-content { + -webkit-box-shadow: 0 5px 15px rgba(0, 0, 0, .5); + box-shadow: 0 5px 15px rgba(0, 0, 0, .5); + } + .modal-sm { + width: 300px; + } +} +@media (min-width: 992px) { + .modal-lg { + width: 900px; + } +} +.tooltip { + position: absolute; + z-index: 1070; + display: block; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 12px; + font-style: normal; + font-weight: normal; + line-height: 1.42857143; + text-align: left; + text-align: start; + text-decoration: none; + text-shadow: none; + text-transform: none; + letter-spacing: normal; + word-break: normal; + word-spacing: normal; + word-wrap: normal; + white-space: normal; + filter: alpha(opacity=0); + opacity: 0; + + line-break: auto; +} +.tooltip.in { + filter: alpha(opacity=90); + opacity: .9; +} +.tooltip.top { + padding: 5px 0; + margin-top: -3px; +} +.tooltip.right { + padding: 0 5px; + margin-left: 3px; +} +.tooltip.bottom { + padding: 5px 0; + margin-top: 3px; +} +.tooltip.left { + padding: 0 5px; + margin-left: -3px; +} +.tooltip-inner { + max-width: 200px; + padding: 3px 8px; + color: #fff; + text-align: center; + background-color: #000; + border-radius: 4px; +} +.tooltip-arrow { + position: absolute; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; +} +.tooltip.top .tooltip-arrow { + bottom: 0; + left: 50%; + margin-left: -5px; + border-width: 5px 5px 0; + border-top-color: #000; +} +.tooltip.top-left .tooltip-arrow { + right: 5px; + bottom: 0; + margin-bottom: -5px; + border-width: 5px 5px 0; + border-top-color: #000; +} +.tooltip.top-right .tooltip-arrow { + bottom: 0; + left: 5px; + margin-bottom: -5px; + border-width: 5px 5px 0; + border-top-color: #000; +} +.tooltip.right .tooltip-arrow { + top: 50%; + left: 0; + margin-top: -5px; + border-width: 5px 5px 5px 0; + border-right-color: #000; +} +.tooltip.left .tooltip-arrow { + top: 50%; + right: 0; + margin-top: -5px; + border-width: 5px 0 5px 5px; + border-left-color: #000; +} +.tooltip.bottom .tooltip-arrow { + top: 0; + left: 50%; + margin-left: -5px; + border-width: 0 5px 5px; + border-bottom-color: #000; +} +.tooltip.bottom-left .tooltip-arrow { + top: 0; + right: 5px; + margin-top: -5px; + border-width: 0 5px 5px; + border-bottom-color: #000; +} +.tooltip.bottom-right .tooltip-arrow { + top: 0; + left: 5px; + margin-top: -5px; + border-width: 0 5px 5px; + border-bottom-color: #000; +} +.popover { + position: absolute; + top: 0; + left: 0; + z-index: 1060; + display: none; + max-width: 276px; + padding: 1px; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 14px; + font-style: normal; + font-weight: normal; + line-height: 1.42857143; + text-align: left; + text-align: start; + text-decoration: none; + text-shadow: none; + text-transform: none; + letter-spacing: normal; + word-break: normal; + word-spacing: normal; + word-wrap: normal; + white-space: normal; + background-color: #fff; + -webkit-background-clip: padding-box; + background-clip: padding-box; + border: 1px solid #ccc; + border: 1px solid rgba(0, 0, 0, .2); + border-radius: 6px; + -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, .2); + box-shadow: 0 5px 10px rgba(0, 0, 0, .2); + + line-break: auto; +} +.popover.top { + margin-top: -10px; +} +.popover.right { + margin-left: 10px; +} +.popover.bottom { + margin-top: 10px; +} +.popover.left { + margin-left: -10px; +} +.popover-title { + padding: 8px 14px; + margin: 0; + font-size: 14px; + background-color: #f7f7f7; + border-bottom: 1px solid #ebebeb; + border-radius: 5px 5px 0 0; +} +.popover-content { + padding: 9px 14px; +} +.popover > .arrow, +.popover > .arrow:after { + position: absolute; + display: block; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; +} +.popover > .arrow { + border-width: 11px; +} +.popover > .arrow:after { + content: ""; + border-width: 10px; +} +.popover.top > .arrow { + bottom: -11px; + left: 50%; + margin-left: -11px; + border-top-color: #999; + border-top-color: rgba(0, 0, 0, .25); + border-bottom-width: 0; +} +.popover.top > .arrow:after { + bottom: 1px; + margin-left: -10px; + content: " "; + border-top-color: #fff; + border-bottom-width: 0; +} +.popover.right > .arrow { + top: 50%; + left: -11px; + margin-top: -11px; + border-right-color: #999; + border-right-color: rgba(0, 0, 0, .25); + border-left-width: 0; +} +.popover.right > .arrow:after { + bottom: -10px; + left: 1px; + content: " "; + border-right-color: #fff; + border-left-width: 0; +} +.popover.bottom > .arrow { + top: -11px; + left: 50%; + margin-left: -11px; + border-top-width: 0; + border-bottom-color: #999; + border-bottom-color: rgba(0, 0, 0, .25); +} +.popover.bottom > .arrow:after { + top: 1px; + margin-left: -10px; + content: " "; + border-top-width: 0; + border-bottom-color: #fff; +} +.popover.left > .arrow { + top: 50%; + right: -11px; + margin-top: -11px; + border-right-width: 0; + border-left-color: #999; + border-left-color: rgba(0, 0, 0, .25); +} +.popover.left > .arrow:after { + right: 1px; + bottom: -10px; + content: " "; + border-right-width: 0; + border-left-color: #fff; +} +.carousel { + position: relative; +} +.carousel-inner { + position: relative; + width: 100%; + overflow: hidden; +} +.carousel-inner > .item { + position: relative; + display: none; + -webkit-transition: .6s ease-in-out left; + -o-transition: .6s ease-in-out left; + transition: .6s ease-in-out left; +} +.carousel-inner > .item > img, +.carousel-inner > .item > a > img { + line-height: 1; +} +@media all and (transform-3d), (-webkit-transform-3d) { + .carousel-inner > .item { + -webkit-transition: -webkit-transform .6s ease-in-out; + -o-transition: -o-transform .6s ease-in-out; + transition: transform .6s ease-in-out; + + -webkit-backface-visibility: hidden; + backface-visibility: hidden; + -webkit-perspective: 1000px; + perspective: 1000px; + } + .carousel-inner > .item.next, + .carousel-inner > .item.active.right { + left: 0; + -webkit-transform: translate3d(100%, 0, 0); + transform: translate3d(100%, 0, 0); + } + .carousel-inner > .item.prev, + .carousel-inner > .item.active.left { + left: 0; + -webkit-transform: translate3d(-100%, 0, 0); + transform: translate3d(-100%, 0, 0); + } + .carousel-inner > .item.next.left, + .carousel-inner > .item.prev.right, + .carousel-inner > .item.active { + left: 0; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} +.carousel-inner > .active, +.carousel-inner > .next, +.carousel-inner > .prev { + display: block; +} +.carousel-inner > .active { + left: 0; +} +.carousel-inner > .next, +.carousel-inner > .prev { + position: absolute; + top: 0; + width: 100%; +} +.carousel-inner > .next { + left: 100%; +} +.carousel-inner > .prev { + left: -100%; +} +.carousel-inner > .next.left, +.carousel-inner > .prev.right { + left: 0; +} +.carousel-inner > .active.left { + left: -100%; +} +.carousel-inner > .active.right { + left: 100%; +} +.carousel-control { + position: absolute; + top: 0; + bottom: 0; + left: 0; + width: 15%; + font-size: 20px; + color: #fff; + text-align: center; + text-shadow: 0 1px 2px rgba(0, 0, 0, .6); + background-color: rgba(0, 0, 0, 0); + filter: alpha(opacity=50); + opacity: .5; +} +.carousel-control.left { + background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, .5) 0%, rgba(0, 0, 0, .0001) 100%); + background-image: -o-linear-gradient(left, rgba(0, 0, 0, .5) 0%, rgba(0, 0, 0, .0001) 100%); + background-image: -webkit-gradient(linear, left top, right top, from(rgba(0, 0, 0, .5)), to(rgba(0, 0, 0, .0001))); + background-image: linear-gradient(to right, rgba(0, 0, 0, .5) 0%, rgba(0, 0, 0, .0001) 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1); + background-repeat: repeat-x; +} +.carousel-control.right { + right: 0; + left: auto; + background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, .0001) 0%, rgba(0, 0, 0, .5) 100%); + background-image: -o-linear-gradient(left, rgba(0, 0, 0, .0001) 0%, rgba(0, 0, 0, .5) 100%); + background-image: -webkit-gradient(linear, left top, right top, from(rgba(0, 0, 0, .0001)), to(rgba(0, 0, 0, .5))); + background-image: linear-gradient(to right, rgba(0, 0, 0, .0001) 0%, rgba(0, 0, 0, .5) 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1); + background-repeat: repeat-x; +} +.carousel-control:hover, +.carousel-control:focus { + color: #fff; + text-decoration: none; + filter: alpha(opacity=90); + outline: 0; + opacity: .9; +} +.carousel-control .icon-prev, +.carousel-control .icon-next, +.carousel-control .glyphicon-chevron-left, +.carousel-control .glyphicon-chevron-right { + position: absolute; + top: 50%; + z-index: 5; + display: inline-block; + margin-top: -10px; +} +.carousel-control .icon-prev, +.carousel-control .glyphicon-chevron-left { + left: 50%; + margin-left: -10px; +} +.carousel-control .icon-next, +.carousel-control .glyphicon-chevron-right { + right: 50%; + margin-right: -10px; +} +.carousel-control .icon-prev, +.carousel-control .icon-next { + width: 20px; + height: 20px; + font-family: serif; + line-height: 1; +} +.carousel-control .icon-prev:before { + content: '\2039'; +} +.carousel-control .icon-next:before { + content: '\203a'; +} +.carousel-indicators { + position: absolute; + bottom: 10px; + left: 50%; + z-index: 15; + width: 60%; + padding-left: 0; + margin-left: -30%; + text-align: center; + list-style: none; +} +.carousel-indicators li { + display: inline-block; + width: 10px; + height: 10px; + margin: 1px; + text-indent: -999px; + cursor: pointer; + background-color: #000 \9; + background-color: rgba(0, 0, 0, 0); + border: 1px solid #fff; + border-radius: 10px; +} +.carousel-indicators .active { + width: 12px; + height: 12px; + margin: 0; + background-color: #fff; +} +.carousel-caption { + position: absolute; + right: 15%; + bottom: 20px; + left: 15%; + z-index: 10; + padding-top: 20px; + padding-bottom: 20px; + color: #fff; + text-align: center; + text-shadow: 0 1px 2px rgba(0, 0, 0, .6); +} +.carousel-caption .btn { + text-shadow: none; +} +@media screen and (min-width: 768px) { + .carousel-control .glyphicon-chevron-left, + .carousel-control .glyphicon-chevron-right, + .carousel-control .icon-prev, + .carousel-control .icon-next { + width: 30px; + height: 30px; + margin-top: -10px; + font-size: 30px; + } + .carousel-control .glyphicon-chevron-left, + .carousel-control .icon-prev { + margin-left: -10px; + } + .carousel-control .glyphicon-chevron-right, + .carousel-control .icon-next { + margin-right: -10px; + } + .carousel-caption { + right: 20%; + left: 20%; + padding-bottom: 30px; + } + .carousel-indicators { + bottom: 20px; + } +} +.clearfix:before, +.clearfix:after, +.dl-horizontal dd:before, +.dl-horizontal dd:after, +.container:before, +.container:after, +.container-fluid:before, +.container-fluid:after, +.row:before, +.row:after, +.form-horizontal .form-group:before, +.form-horizontal .form-group:after, +.btn-toolbar:before, +.btn-toolbar:after, +.btn-group-vertical > .btn-group:before, +.btn-group-vertical > .btn-group:after, +.nav:before, +.nav:after, +.navbar:before, +.navbar:after, +.navbar-header:before, +.navbar-header:after, +.navbar-collapse:before, +.navbar-collapse:after, +.pager:before, +.pager:after, +.panel-body:before, +.panel-body:after, +.modal-header:before, +.modal-header:after, +.modal-footer:before, +.modal-footer:after { + display: table; + content: " "; +} +.clearfix:after, +.dl-horizontal dd:after, +.container:after, +.container-fluid:after, +.row:after, +.form-horizontal .form-group:after, +.btn-toolbar:after, +.btn-group-vertical > .btn-group:after, +.nav:after, +.navbar:after, +.navbar-header:after, +.navbar-collapse:after, +.pager:after, +.panel-body:after, +.modal-header:after, +.modal-footer:after { + clear: both; +} +.center-block { + display: block; + margin-right: auto; + margin-left: auto; +} +.pull-right { + float: right !important; +} +.pull-left { + float: left !important; +} +.hide { + display: none !important; +} +.show { + display: block !important; +} +.invisible { + visibility: hidden; +} +.text-hide { + font: 0/0 a; + color: transparent; + text-shadow: none; + background-color: transparent; + border: 0; +} +.hidden { + display: none !important; +} +.affix { + position: fixed; +} +@-ms-viewport { + width: device-width; +} +.visible-xs, +.visible-sm, +.visible-md, +.visible-lg { + display: none !important; +} +.visible-xs-block, +.visible-xs-inline, +.visible-xs-inline-block, +.visible-sm-block, +.visible-sm-inline, +.visible-sm-inline-block, +.visible-md-block, +.visible-md-inline, +.visible-md-inline-block, +.visible-lg-block, +.visible-lg-inline, +.visible-lg-inline-block { + display: none !important; +} +@media (max-width: 767px) { + .visible-xs { + display: block !important; + } + table.visible-xs { + display: table !important; + } + tr.visible-xs { + display: table-row !important; + } + th.visible-xs, + td.visible-xs { + display: table-cell !important; + } +} +@media (max-width: 767px) { + .visible-xs-block { + display: block !important; + } +} +@media (max-width: 767px) { + .visible-xs-inline { + display: inline !important; + } +} +@media (max-width: 767px) { + .visible-xs-inline-block { + display: inline-block !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .visible-sm { + display: block !important; + } + table.visible-sm { + display: table !important; + } + tr.visible-sm { + display: table-row !important; + } + th.visible-sm, + td.visible-sm { + display: table-cell !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .visible-sm-block { + display: block !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .visible-sm-inline { + display: inline !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .visible-sm-inline-block { + display: inline-block !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .visible-md { + display: block !important; + } + table.visible-md { + display: table !important; + } + tr.visible-md { + display: table-row !important; + } + th.visible-md, + td.visible-md { + display: table-cell !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .visible-md-block { + display: block !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .visible-md-inline { + display: inline !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .visible-md-inline-block { + display: inline-block !important; + } +} +@media (min-width: 1200px) { + .visible-lg { + display: block !important; + } + table.visible-lg { + display: table !important; + } + tr.visible-lg { + display: table-row !important; + } + th.visible-lg, + td.visible-lg { + display: table-cell !important; + } +} +@media (min-width: 1200px) { + .visible-lg-block { + display: block !important; + } +} +@media (min-width: 1200px) { + .visible-lg-inline { + display: inline !important; + } +} +@media (min-width: 1200px) { + .visible-lg-inline-block { + display: inline-block !important; + } +} +@media (max-width: 767px) { + .hidden-xs { + display: none !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .hidden-sm { + display: none !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .hidden-md { + display: none !important; + } +} +@media (min-width: 1200px) { + .hidden-lg { + display: none !important; + } +} +.visible-print { + display: none !important; +} +@media print { + .visible-print { + display: block !important; + } + table.visible-print { + display: table !important; + } + tr.visible-print { + display: table-row !important; + } + th.visible-print, + td.visible-print { + display: table-cell !important; + } +} +.visible-print-block { + display: none !important; +} +@media print { + .visible-print-block { + display: block !important; + } +} +.visible-print-inline { + display: none !important; +} +@media print { + .visible-print-inline { + display: inline !important; + } +} +.visible-print-inline-block { + display: none !important; +} +@media print { + .visible-print-inline-block { + display: inline-block !important; + } +} +@media print { + .hidden-print { + display: none !important; + } +} +/*# sourceMappingURL=bootstrap.css.map */ diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/css/bootstrap.css.map b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/css/bootstrap.css.map new file mode 100644 index 000000000..09f8cda78 --- /dev/null +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/css/bootstrap.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["bootstrap.css","less/normalize.less","less/print.less","less/glyphicons.less","less/scaffolding.less","less/mixins/vendor-prefixes.less","less/mixins/tab-focus.less","less/mixins/image.less","less/type.less","less/mixins/text-emphasis.less","less/mixins/background-variant.less","less/mixins/text-overflow.less","less/code.less","less/grid.less","less/mixins/grid.less","less/mixins/grid-framework.less","less/tables.less","less/mixins/table-row.less","less/forms.less","less/mixins/forms.less","less/buttons.less","less/mixins/buttons.less","less/mixins/opacity.less","less/component-animations.less","less/dropdowns.less","less/mixins/nav-divider.less","less/mixins/reset-filter.less","less/button-groups.less","less/mixins/border-radius.less","less/input-groups.less","less/navs.less","less/navbar.less","less/mixins/nav-vertical-align.less","less/utilities.less","less/breadcrumbs.less","less/pagination.less","less/mixins/pagination.less","less/pager.less","less/labels.less","less/mixins/labels.less","less/badges.less","less/jumbotron.less","less/thumbnails.less","less/alerts.less","less/mixins/alerts.less","less/progress-bars.less","less/mixins/gradients.less","less/mixins/progress-bar.less","less/media.less","less/list-group.less","less/mixins/list-group.less","less/panels.less","less/mixins/panels.less","less/responsive-embed.less","less/wells.less","less/close.less","less/modals.less","less/tooltip.less","less/mixins/reset-text.less","less/popovers.less","less/carousel.less","less/mixins/clearfix.less","less/mixins/center-block.less","less/mixins/hide-text.less","less/responsive-utilities.less","less/mixins/responsive-visibility.less"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,4EAA4E;ACG5E;EACE,wBAAA;EACA,2BAAA;EACA,+BAAA;CDDD;ACQD;EACE,UAAA;CDND;ACmBD;;;;;;;;;;;;;EAaE,eAAA;CDjBD;ACyBD;;;;EAIE,sBAAA;EACA,yBAAA;CDvBD;AC+BD;EACE,cAAA;EACA,UAAA;CD7BD;ACqCD;;EAEE,cAAA;CDnCD;AC6CD;EACE,8BAAA;CD3CD;ACmDD;;EAEE,WAAA;CDjDD;AC2DD;EACE,0BAAA;CDzDD;ACgED;;EAEE,kBAAA;CD9DD;ACqED;EACE,mBAAA;CDnED;AC2ED;EACE,eAAA;EACA,iBAAA;CDzED;ACgFD;EACE,iBAAA;EACA,YAAA;CD9ED;ACqFD;EACE,eAAA;CDnFD;AC0FD;;EAEE,eAAA;EACA,eAAA;EACA,mBAAA;EACA,yBAAA;CDxFD;AC2FD;EACE,YAAA;CDzFD;AC4FD;EACE,gBAAA;CD1FD;ACoGD;EACE,UAAA;CDlGD;ACyGD;EACE,iBAAA;CDvGD;ACiHD;EACE,iBAAA;CD/GD;ACsHD;EACE,gCAAA;KAAA,6BAAA;UAAA,wBAAA;EACA,UAAA;CDpHD;AC2HD;EACE,eAAA;CDzHD;ACgID;;;;EAIE,kCAAA;EACA,eAAA;CD9HD;ACgJD;;;;;EAKE,eAAA;EACA,cAAA;EACA,UAAA;CD9ID;ACqJD;EACE,kBAAA;CDnJD;AC6JD;;EAEE,qBAAA;CD3JD;ACsKD;;;;EAIE,2BAAA;EACA,gBAAA;CDpKD;AC2KD;;EAEE,gBAAA;CDzKD;ACgLD;;EAEE,UAAA;EACA,WAAA;CD9KD;ACsLD;EACE,oBAAA;CDpLD;AC+LD;;EAEE,+BAAA;KAAA,4BAAA;UAAA,uBAAA;EACA,WAAA;CD7LD;ACsMD;;EAEE,aAAA;CDpMD;AC4MD;EACE,8BAAA;EACA,gCAAA;KAAA,6BAAA;UAAA,wBAAA;CD1MD;ACmND;;EAEE,yBAAA;CDjND;ACwND;EACE,0BAAA;EACA,cAAA;EACA,+BAAA;CDtND;AC8ND;EACE,UAAA;EACA,WAAA;CD5ND;ACmOD;EACE,eAAA;CDjOD;ACyOD;EACE,kBAAA;CDvOD;ACiPD;EACE,0BAAA;EACA,kBAAA;CD/OD;ACkPD;;EAEE,WAAA;CDhPD;AACD,qFAAqF;AElFrF;EA7FI;;;IAGI,mCAAA;IACA,uBAAA;IACA,oCAAA;YAAA,4BAAA;IACA,6BAAA;GFkLL;EE/KC;;IAEI,2BAAA;GFiLL;EE9KC;IACI,6BAAA;GFgLL;EE7KC;IACI,8BAAA;GF+KL;EE1KC;;IAEI,YAAA;GF4KL;EEzKC;;IAEI,uBAAA;IACA,yBAAA;GF2KL;EExKC;IACI,4BAAA;GF0KL;EEvKC;;IAEI,yBAAA;GFyKL;EEtKC;IACI,2BAAA;GFwKL;EErKC;;;IAGI,WAAA;IACA,UAAA;GFuKL;EEpKC;;IAEI,wBAAA;GFsKL;EEhKC;IACI,cAAA;GFkKL;EEhKC;;IAGQ,kCAAA;GFiKT;EE9JC;IACI,uBAAA;GFgKL;EE7JC;IACI,qCAAA;GF+JL;EEhKC;;IAKQ,kCAAA;GF+JT;EE5JC;;IAGQ,kCAAA;GF6JT;CACF;AGnPD;EACE,oCAAA;EACA,sDAAA;EACA,gYAAA;CHqPD;AG7OD;EACE,mBAAA;EACA,SAAA;EACA,sBAAA;EACA,oCAAA;EACA,mBAAA;EACA,oBAAA;EACA,eAAA;EACA,oCAAA;EACA,mCAAA;CH+OD;AG3OmC;EAAW,iBAAA;CH8O9C;AG7OmC;EAAW,iBAAA;CHgP9C;AG9OmC;;EAAW,iBAAA;CHkP9C;AGjPmC;EAAW,iBAAA;CHoP9C;AGnPmC;EAAW,iBAAA;CHsP9C;AGrPmC;EAAW,iBAAA;CHwP9C;AGvPmC;EAAW,iBAAA;CH0P9C;AGzPmC;EAAW,iBAAA;CH4P9C;AG3PmC;EAAW,iBAAA;CH8P9C;AG7PmC;EAAW,iBAAA;CHgQ9C;AG/PmC;EAAW,iBAAA;CHkQ9C;AGjQmC;EAAW,iBAAA;CHoQ9C;AGnQmC;EAAW,iBAAA;CHsQ9C;AGrQmC;EAAW,iBAAA;CHwQ9C;AGvQmC;EAAW,iBAAA;CH0Q9C;AGzQmC;EAAW,iBAAA;CH4Q9C;AG3QmC;EAAW,iBAAA;CH8Q9C;AG7QmC;EAAW,iBAAA;CHgR9C;AG/QmC;EAAW,iBAAA;CHkR9C;AGjRmC;EAAW,iBAAA;CHoR9C;AGnRmC;EAAW,iBAAA;CHsR9C;AGrRmC;EAAW,iBAAA;CHwR9C;AGvRmC;EAAW,iBAAA;CH0R9C;AGzRmC;EAAW,iBAAA;CH4R9C;AG3RmC;EAAW,iBAAA;CH8R9C;AG7RmC;EAAW,iBAAA;CHgS9C;AG/RmC;EAAW,iBAAA;CHkS9C;AGjSmC;EAAW,iBAAA;CHoS9C;AGnSmC;EAAW,iBAAA;CHsS9C;AGrSmC;EAAW,iBAAA;CHwS9C;AGvSmC;EAAW,iBAAA;CH0S9C;AGzSmC;EAAW,iBAAA;CH4S9C;AG3SmC;EAAW,iBAAA;CH8S9C;AG7SmC;EAAW,iBAAA;CHgT9C;AG/SmC;EAAW,iBAAA;CHkT9C;AGjTmC;EAAW,iBAAA;CHoT9C;AGnTmC;EAAW,iBAAA;CHsT9C;AGrTmC;EAAW,iBAAA;CHwT9C;AGvTmC;EAAW,iBAAA;CH0T9C;AGzTmC;EAAW,iBAAA;CH4T9C;AG3TmC;EAAW,iBAAA;CH8T9C;AG7TmC;EAAW,iBAAA;CHgU9C;AG/TmC;EAAW,iBAAA;CHkU9C;AGjUmC;EAAW,iBAAA;CHoU9C;AGnUmC;EAAW,iBAAA;CHsU9C;AGrUmC;EAAW,iBAAA;CHwU9C;AGvUmC;EAAW,iBAAA;CH0U9C;AGzUmC;EAAW,iBAAA;CH4U9C;AG3UmC;EAAW,iBAAA;CH8U9C;AG7UmC;EAAW,iBAAA;CHgV9C;AG/UmC;EAAW,iBAAA;CHkV9C;AGjVmC;EAAW,iBAAA;CHoV9C;AGnVmC;EAAW,iBAAA;CHsV9C;AGrVmC;EAAW,iBAAA;CHwV9C;AGvVmC;EAAW,iBAAA;CH0V9C;AGzVmC;EAAW,iBAAA;CH4V9C;AG3VmC;EAAW,iBAAA;CH8V9C;AG7VmC;EAAW,iBAAA;CHgW9C;AG/VmC;EAAW,iBAAA;CHkW9C;AGjWmC;EAAW,iBAAA;CHoW9C;AGnWmC;EAAW,iBAAA;CHsW9C;AGrWmC;EAAW,iBAAA;CHwW9C;AGvWmC;EAAW,iBAAA;CH0W9C;AGzWmC;EAAW,iBAAA;CH4W9C;AG3WmC;EAAW,iBAAA;CH8W9C;AG7WmC;EAAW,iBAAA;CHgX9C;AG/WmC;EAAW,iBAAA;CHkX9C;AGjXmC;EAAW,iBAAA;CHoX9C;AGnXmC;EAAW,iBAAA;CHsX9C;AGrXmC;EAAW,iBAAA;CHwX9C;AGvXmC;EAAW,iBAAA;CH0X9C;AGzXmC;EAAW,iBAAA;CH4X9C;AG3XmC;EAAW,iBAAA;CH8X9C;AG7XmC;EAAW,iBAAA;CHgY9C;AG/XmC;EAAW,iBAAA;CHkY9C;AGjYmC;EAAW,iBAAA;CHoY9C;AGnYmC;EAAW,iBAAA;CHsY9C;AGrYmC;EAAW,iBAAA;CHwY9C;AGvYmC;EAAW,iBAAA;CH0Y9C;AGzYmC;EAAW,iBAAA;CH4Y9C;AG3YmC;EAAW,iBAAA;CH8Y9C;AG7YmC;EAAW,iBAAA;CHgZ9C;AG/YmC;EAAW,iBAAA;CHkZ9C;AGjZmC;EAAW,iBAAA;CHoZ9C;AGnZmC;EAAW,iBAAA;CHsZ9C;AGrZmC;EAAW,iBAAA;CHwZ9C;AGvZmC;EAAW,iBAAA;CH0Z9C;AGzZmC;EAAW,iBAAA;CH4Z9C;AG3ZmC;EAAW,iBAAA;CH8Z9C;AG7ZmC;EAAW,iBAAA;CHga9C;AG/ZmC;EAAW,iBAAA;CHka9C;AGjamC;EAAW,iBAAA;CHoa9C;AGnamC;EAAW,iBAAA;CHsa9C;AGramC;EAAW,iBAAA;CHwa9C;AGvamC;EAAW,iBAAA;CH0a9C;AGzamC;EAAW,iBAAA;CH4a9C;AG3amC;EAAW,iBAAA;CH8a9C;AG7amC;EAAW,iBAAA;CHgb9C;AG/amC;EAAW,iBAAA;CHkb9C;AGjbmC;EAAW,iBAAA;CHob9C;AGnbmC;EAAW,iBAAA;CHsb9C;AGrbmC;EAAW,iBAAA;CHwb9C;AGvbmC;EAAW,iBAAA;CH0b9C;AGzbmC;EAAW,iBAAA;CH4b9C;AG3bmC;EAAW,iBAAA;CH8b9C;AG7bmC;EAAW,iBAAA;CHgc9C;AG/bmC;EAAW,iBAAA;CHkc9C;AGjcmC;EAAW,iBAAA;CHoc9C;AGncmC;EAAW,iBAAA;CHsc9C;AGrcmC;EAAW,iBAAA;CHwc9C;AGvcmC;EAAW,iBAAA;CH0c9C;AGzcmC;EAAW,iBAAA;CH4c9C;AG3cmC;EAAW,iBAAA;CH8c9C;AG7cmC;EAAW,iBAAA;CHgd9C;AG/cmC;EAAW,iBAAA;CHkd9C;AGjdmC;EAAW,iBAAA;CHod9C;AGndmC;EAAW,iBAAA;CHsd9C;AGrdmC;EAAW,iBAAA;CHwd9C;AGvdmC;EAAW,iBAAA;CH0d9C;AGzdmC;EAAW,iBAAA;CH4d9C;AG3dmC;EAAW,iBAAA;CH8d9C;AG7dmC;EAAW,iBAAA;CHge9C;AG/dmC;EAAW,iBAAA;CHke9C;AGjemC;EAAW,iBAAA;CHoe9C;AGnemC;EAAW,iBAAA;CHse9C;AGremC;EAAW,iBAAA;CHwe9C;AGvemC;EAAW,iBAAA;CH0e9C;AGzemC;EAAW,iBAAA;CH4e9C;AG3emC;EAAW,iBAAA;CH8e9C;AG7emC;EAAW,iBAAA;CHgf9C;AG/emC;EAAW,iBAAA;CHkf9C;AGjfmC;EAAW,iBAAA;CHof9C;AGnfmC;EAAW,iBAAA;CHsf9C;AGrfmC;EAAW,iBAAA;CHwf9C;AGvfmC;EAAW,iBAAA;CH0f9C;AGzfmC;EAAW,iBAAA;CH4f9C;AG3fmC;EAAW,iBAAA;CH8f9C;AG7fmC;EAAW,iBAAA;CHggB9C;AG/fmC;EAAW,iBAAA;CHkgB9C;AGjgBmC;EAAW,iBAAA;CHogB9C;AGngBmC;EAAW,iBAAA;CHsgB9C;AGrgBmC;EAAW,iBAAA;CHwgB9C;AGvgBmC;EAAW,iBAAA;CH0gB9C;AGzgBmC;EAAW,iBAAA;CH4gB9C;AG3gBmC;EAAW,iBAAA;CH8gB9C;AG7gBmC;EAAW,iBAAA;CHghB9C;AG/gBmC;EAAW,iBAAA;CHkhB9C;AGjhBmC;EAAW,iBAAA;CHohB9C;AGnhBmC;EAAW,iBAAA;CHshB9C;AGrhBmC;EAAW,iBAAA;CHwhB9C;AGvhBmC;EAAW,iBAAA;CH0hB9C;AGzhBmC;EAAW,iBAAA;CH4hB9C;AG3hBmC;EAAW,iBAAA;CH8hB9C;AG7hBmC;EAAW,iBAAA;CHgiB9C;AG/hBmC;EAAW,iBAAA;CHkiB9C;AGjiBmC;EAAW,iBAAA;CHoiB9C;AGniBmC;EAAW,iBAAA;CHsiB9C;AGriBmC;EAAW,iBAAA;CHwiB9C;AGviBmC;EAAW,iBAAA;CH0iB9C;AGziBmC;EAAW,iBAAA;CH4iB9C;AG3iBmC;EAAW,iBAAA;CH8iB9C;AG7iBmC;EAAW,iBAAA;CHgjB9C;AG/iBmC;EAAW,iBAAA;CHkjB9C;AGjjBmC;EAAW,iBAAA;CHojB9C;AGnjBmC;EAAW,iBAAA;CHsjB9C;AGrjBmC;EAAW,iBAAA;CHwjB9C;AGvjBmC;EAAW,iBAAA;CH0jB9C;AGzjBmC;EAAW,iBAAA;CH4jB9C;AG3jBmC;EAAW,iBAAA;CH8jB9C;AG7jBmC;EAAW,iBAAA;CHgkB9C;AG/jBmC;EAAW,iBAAA;CHkkB9C;AGjkBmC;EAAW,iBAAA;CHokB9C;AGnkBmC;EAAW,iBAAA;CHskB9C;AGrkBmC;EAAW,iBAAA;CHwkB9C;AGvkBmC;EAAW,iBAAA;CH0kB9C;AGzkBmC;EAAW,iBAAA;CH4kB9C;AG3kBmC;EAAW,iBAAA;CH8kB9C;AG7kBmC;EAAW,iBAAA;CHglB9C;AG/kBmC;EAAW,iBAAA;CHklB9C;AGjlBmC;EAAW,iBAAA;CHolB9C;AGnlBmC;EAAW,iBAAA;CHslB9C;AGrlBmC;EAAW,iBAAA;CHwlB9C;AGvlBmC;EAAW,iBAAA;CH0lB9C;AGzlBmC;EAAW,iBAAA;CH4lB9C;AG3lBmC;EAAW,iBAAA;CH8lB9C;AG7lBmC;EAAW,iBAAA;CHgmB9C;AG/lBmC;EAAW,iBAAA;CHkmB9C;AGjmBmC;EAAW,iBAAA;CHomB9C;AGnmBmC;EAAW,iBAAA;CHsmB9C;AGrmBmC;EAAW,iBAAA;CHwmB9C;AGvmBmC;EAAW,iBAAA;CH0mB9C;AGzmBmC;EAAW,iBAAA;CH4mB9C;AG3mBmC;EAAW,iBAAA;CH8mB9C;AG7mBmC;EAAW,iBAAA;CHgnB9C;AG/mBmC;EAAW,iBAAA;CHknB9C;AGjnBmC;EAAW,iBAAA;CHonB9C;AGnnBmC;EAAW,iBAAA;CHsnB9C;AGrnBmC;EAAW,iBAAA;CHwnB9C;AGvnBmC;EAAW,iBAAA;CH0nB9C;AGznBmC;EAAW,iBAAA;CH4nB9C;AG3nBmC;EAAW,iBAAA;CH8nB9C;AG7nBmC;EAAW,iBAAA;CHgoB9C;AG/nBmC;EAAW,iBAAA;CHkoB9C;AGjoBmC;EAAW,iBAAA;CHooB9C;AGnoBmC;EAAW,iBAAA;CHsoB9C;AGroBmC;EAAW,iBAAA;CHwoB9C;AG/nBmC;EAAW,iBAAA;CHkoB9C;AGjoBmC;EAAW,iBAAA;CHooB9C;AGnoBmC;EAAW,iBAAA;CHsoB9C;AGroBmC;EAAW,iBAAA;CHwoB9C;AGvoBmC;EAAW,iBAAA;CH0oB9C;AGzoBmC;EAAW,iBAAA;CH4oB9C;AG3oBmC;EAAW,iBAAA;CH8oB9C;AG7oBmC;EAAW,iBAAA;CHgpB9C;AG/oBmC;EAAW,iBAAA;CHkpB9C;AGjpBmC;EAAW,iBAAA;CHopB9C;AGnpBmC;EAAW,iBAAA;CHspB9C;AGrpBmC;EAAW,iBAAA;CHwpB9C;AGvpBmC;EAAW,iBAAA;CH0pB9C;AGzpBmC;EAAW,iBAAA;CH4pB9C;AG3pBmC;EAAW,iBAAA;CH8pB9C;AG7pBmC;EAAW,iBAAA;CHgqB9C;AG/pBmC;EAAW,iBAAA;CHkqB9C;AGjqBmC;EAAW,iBAAA;CHoqB9C;AGnqBmC;EAAW,iBAAA;CHsqB9C;AGrqBmC;EAAW,iBAAA;CHwqB9C;AGvqBmC;EAAW,iBAAA;CH0qB9C;AGzqBmC;EAAW,iBAAA;CH4qB9C;AG3qBmC;EAAW,iBAAA;CH8qB9C;AG7qBmC;EAAW,iBAAA;CHgrB9C;AG/qBmC;EAAW,iBAAA;CHkrB9C;AGjrBmC;EAAW,iBAAA;CHorB9C;AGnrBmC;EAAW,iBAAA;CHsrB9C;AGrrBmC;EAAW,iBAAA;CHwrB9C;AGvrBmC;EAAW,iBAAA;CH0rB9C;AGzrBmC;EAAW,iBAAA;CH4rB9C;AG3rBmC;EAAW,iBAAA;CH8rB9C;AG7rBmC;EAAW,iBAAA;CHgsB9C;AG/rBmC;EAAW,iBAAA;CHksB9C;AGjsBmC;EAAW,iBAAA;CHosB9C;AGnsBmC;EAAW,iBAAA;CHssB9C;AGrsBmC;EAAW,iBAAA;CHwsB9C;AGvsBmC;EAAW,iBAAA;CH0sB9C;AGzsBmC;EAAW,iBAAA;CH4sB9C;AG3sBmC;EAAW,iBAAA;CH8sB9C;AG7sBmC;EAAW,iBAAA;CHgtB9C;AG/sBmC;EAAW,iBAAA;CHktB9C;AGjtBmC;EAAW,iBAAA;CHotB9C;AGntBmC;EAAW,iBAAA;CHstB9C;AGrtBmC;EAAW,iBAAA;CHwtB9C;AGvtBmC;EAAW,iBAAA;CH0tB9C;AGztBmC;EAAW,iBAAA;CH4tB9C;AG3tBmC;EAAW,iBAAA;CH8tB9C;AG7tBmC;EAAW,iBAAA;CHguB9C;AG/tBmC;EAAW,iBAAA;CHkuB9C;AGjuBmC;EAAW,iBAAA;CHouB9C;AGnuBmC;EAAW,iBAAA;CHsuB9C;AGruBmC;EAAW,iBAAA;CHwuB9C;AGvuBmC;EAAW,iBAAA;CH0uB9C;AGzuBmC;EAAW,iBAAA;CH4uB9C;AG3uBmC;EAAW,iBAAA;CH8uB9C;AG7uBmC;EAAW,iBAAA;CHgvB9C;AIthCD;ECgEE,+BAAA;EACG,4BAAA;EACK,uBAAA;CLy9BT;AIxhCD;;EC6DE,+BAAA;EACG,4BAAA;EACK,uBAAA;CL+9BT;AIthCD;EACE,gBAAA;EACA,8CAAA;CJwhCD;AIrhCD;EACE,4DAAA;EACA,gBAAA;EACA,wBAAA;EACA,eAAA;EACA,uBAAA;CJuhCD;AInhCD;;;;EAIE,qBAAA;EACA,mBAAA;EACA,qBAAA;CJqhCD;AI/gCD;EACE,eAAA;EACA,sBAAA;CJihCD;AI/gCC;;EAEE,eAAA;EACA,2BAAA;CJihCH;AI9gCC;EErDA,qBAAA;EAEA,2CAAA;EACA,qBAAA;CNqkCD;AIxgCD;EACE,UAAA;CJ0gCD;AIpgCD;EACE,uBAAA;CJsgCD;AIlgCD;;;;;EGvEE,eAAA;EACA,gBAAA;EACA,aAAA;CPglCD;AItgCD;EACE,mBAAA;CJwgCD;AIlgCD;EACE,aAAA;EACA,wBAAA;EACA,uBAAA;EACA,uBAAA;EACA,mBAAA;EC6FA,yCAAA;EACK,oCAAA;EACG,iCAAA;EEvLR,sBAAA;EACA,gBAAA;EACA,aAAA;CPgmCD;AIlgCD;EACE,mBAAA;CJogCD;AI9/BD;EACE,iBAAA;EACA,oBAAA;EACA,UAAA;EACA,8BAAA;CJggCD;AIx/BD;EACE,mBAAA;EACA,WAAA;EACA,YAAA;EACA,aAAA;EACA,WAAA;EACA,iBAAA;EACA,uBAAA;EACA,UAAA;CJ0/BD;AIl/BC;;EAEE,iBAAA;EACA,YAAA;EACA,aAAA;EACA,UAAA;EACA,kBAAA;EACA,WAAA;CJo/BH;AIz+BD;EACE,gBAAA;CJ2+BD;AQloCD;;;;;;;;;;;;EAEE,qBAAA;EACA,iBAAA;EACA,iBAAA;EACA,eAAA;CR8oCD;AQnpCD;;;;;;;;;;;;;;;;;;;;;;;;EASI,oBAAA;EACA,eAAA;EACA,eAAA;CRoqCH;AQhqCD;;;;;;EAGE,iBAAA;EACA,oBAAA;CRqqCD;AQzqCD;;;;;;;;;;;;EAQI,eAAA;CR+qCH;AQ5qCD;;;;;;EAGE,iBAAA;EACA,oBAAA;CRirCD;AQrrCD;;;;;;;;;;;;EAQI,eAAA;CR2rCH;AQvrCD;;EAAU,gBAAA;CR2rCT;AQ1rCD;;EAAU,gBAAA;CR8rCT;AQ7rCD;;EAAU,gBAAA;CRisCT;AQhsCD;;EAAU,gBAAA;CRosCT;AQnsCD;;EAAU,gBAAA;CRusCT;AQtsCD;;EAAU,gBAAA;CR0sCT;AQpsCD;EACE,iBAAA;CRssCD;AQnsCD;EACE,oBAAA;EACA,gBAAA;EACA,iBAAA;EACA,iBAAA;CRqsCD;AQhsCD;EAwOA;IA1OI,gBAAA;GRssCD;CACF;AQ9rCD;;EAEE,eAAA;CRgsCD;AQ7rCD;;EAEE,0BAAA;EACA,cAAA;CR+rCD;AQ3rCD;EAAuB,iBAAA;CR8rCtB;AQ7rCD;EAAuB,kBAAA;CRgsCtB;AQ/rCD;EAAuB,mBAAA;CRksCtB;AQjsCD;EAAuB,oBAAA;CRosCtB;AQnsCD;EAAuB,oBAAA;CRssCtB;AQnsCD;EAAuB,0BAAA;CRssCtB;AQrsCD;EAAuB,0BAAA;CRwsCtB;AQvsCD;EAAuB,2BAAA;CR0sCtB;AQvsCD;EACE,eAAA;CRysCD;AQvsCD;ECrGE,eAAA;CT+yCD;AS9yCC;;EAEE,eAAA;CTgzCH;AQ3sCD;ECxGE,eAAA;CTszCD;ASrzCC;;EAEE,eAAA;CTuzCH;AQ/sCD;EC3GE,eAAA;CT6zCD;AS5zCC;;EAEE,eAAA;CT8zCH;AQntCD;EC9GE,eAAA;CTo0CD;ASn0CC;;EAEE,eAAA;CTq0CH;AQvtCD;ECjHE,eAAA;CT20CD;AS10CC;;EAEE,eAAA;CT40CH;AQvtCD;EAGE,YAAA;EE3HA,0BAAA;CVm1CD;AUl1CC;;EAEE,0BAAA;CVo1CH;AQztCD;EE9HE,0BAAA;CV01CD;AUz1CC;;EAEE,0BAAA;CV21CH;AQ7tCD;EEjIE,0BAAA;CVi2CD;AUh2CC;;EAEE,0BAAA;CVk2CH;AQjuCD;EEpIE,0BAAA;CVw2CD;AUv2CC;;EAEE,0BAAA;CVy2CH;AQruCD;EEvIE,0BAAA;CV+2CD;AU92CC;;EAEE,0BAAA;CVg3CH;AQpuCD;EACE,oBAAA;EACA,oBAAA;EACA,iCAAA;CRsuCD;AQ9tCD;;EAEE,cAAA;EACA,oBAAA;CRguCD;AQnuCD;;;;EAMI,iBAAA;CRmuCH;AQ5tCD;EACE,gBAAA;EACA,iBAAA;CR8tCD;AQ1tCD;EALE,gBAAA;EACA,iBAAA;EAMA,kBAAA;CR6tCD;AQ/tCD;EAKI,sBAAA;EACA,kBAAA;EACA,mBAAA;CR6tCH;AQxtCD;EACE,cAAA;EACA,oBAAA;CR0tCD;AQxtCD;;EAEE,wBAAA;CR0tCD;AQxtCD;EACE,kBAAA;CR0tCD;AQxtCD;EACE,eAAA;CR0tCD;AQjsCD;EA6EA;IAvFM,YAAA;IACA,aAAA;IACA,YAAA;IACA,kBAAA;IGtNJ,iBAAA;IACA,wBAAA;IACA,oBAAA;GXs6CC;EQ9nCH;IAhFM,mBAAA;GRitCH;CACF;AQxsCD;;EAGE,aAAA;EACA,kCAAA;CRysCD;AQvsCD;EACE,eAAA;EA9IqB,0BAAA;CRw1CtB;AQrsCD;EACE,mBAAA;EACA,iBAAA;EACA,kBAAA;EACA,+BAAA;CRusCD;AQlsCG;;;EACE,iBAAA;CRssCL;AQhtCD;;;EAmBI,eAAA;EACA,eAAA;EACA,wBAAA;EACA,eAAA;CRksCH;AQhsCG;;;EACE,uBAAA;CRosCL;AQ5rCD;;EAEE,oBAAA;EACA,gBAAA;EACA,gCAAA;EACA,eAAA;EACA,kBAAA;CR8rCD;AQxrCG;;;;;;EAAW,YAAA;CRgsCd;AQ/rCG;;;;;;EACE,uBAAA;CRssCL;AQhsCD;EACE,oBAAA;EACA,mBAAA;EACA,wBAAA;CRksCD;AYx+CD;;;;EAIE,+DAAA;CZ0+CD;AYt+CD;EACE,iBAAA;EACA,eAAA;EACA,eAAA;EACA,0BAAA;EACA,mBAAA;CZw+CD;AYp+CD;EACE,iBAAA;EACA,eAAA;EACA,YAAA;EACA,uBAAA;EACA,mBAAA;EACA,uDAAA;UAAA,+CAAA;CZs+CD;AY5+CD;EASI,WAAA;EACA,gBAAA;EACA,kBAAA;EACA,yBAAA;UAAA,iBAAA;CZs+CH;AYj+CD;EACE,eAAA;EACA,eAAA;EACA,iBAAA;EACA,gBAAA;EACA,wBAAA;EACA,sBAAA;EACA,sBAAA;EACA,eAAA;EACA,0BAAA;EACA,uBAAA;EACA,mBAAA;CZm+CD;AY9+CD;EAeI,WAAA;EACA,mBAAA;EACA,eAAA;EACA,sBAAA;EACA,8BAAA;EACA,iBAAA;CZk+CH;AY79CD;EACE,kBAAA;EACA,mBAAA;CZ+9CD;AazhDD;ECHE,mBAAA;EACA,kBAAA;EACA,mBAAA;EACA,oBAAA;Cd+hDD;AazhDC;EAqEF;IAvEI,aAAA;Gb+hDD;CACF;Aa3hDC;EAkEF;IApEI,aAAA;GbiiDD;CACF;Aa7hDD;EA+DA;IAjEI,cAAA;GbmiDD;CACF;Aa1hDD;ECvBE,mBAAA;EACA,kBAAA;EACA,mBAAA;EACA,oBAAA;CdojDD;AavhDD;ECvBE,mBAAA;EACA,oBAAA;CdijDD;AejjDG;EACE,mBAAA;EAEA,gBAAA;EAEA,mBAAA;EACA,oBAAA;CfijDL;AejiDG;EACE,YAAA;CfmiDL;Ae5hDC;EACE,YAAA;Cf8hDH;Ae/hDC;EACE,oBAAA;CfiiDH;AeliDC;EACE,oBAAA;CfoiDH;AeriDC;EACE,WAAA;CfuiDH;AexiDC;EACE,oBAAA;Cf0iDH;Ae3iDC;EACE,oBAAA;Cf6iDH;Ae9iDC;EACE,WAAA;CfgjDH;AejjDC;EACE,oBAAA;CfmjDH;AepjDC;EACE,oBAAA;CfsjDH;AevjDC;EACE,WAAA;CfyjDH;Ae1jDC;EACE,oBAAA;Cf4jDH;Ae7jDC;EACE,mBAAA;Cf+jDH;AejjDC;EACE,YAAA;CfmjDH;AepjDC;EACE,oBAAA;CfsjDH;AevjDC;EACE,oBAAA;CfyjDH;Ae1jDC;EACE,WAAA;Cf4jDH;Ae7jDC;EACE,oBAAA;Cf+jDH;AehkDC;EACE,oBAAA;CfkkDH;AenkDC;EACE,WAAA;CfqkDH;AetkDC;EACE,oBAAA;CfwkDH;AezkDC;EACE,oBAAA;Cf2kDH;Ae5kDC;EACE,WAAA;Cf8kDH;Ae/kDC;EACE,oBAAA;CfilDH;AellDC;EACE,mBAAA;CfolDH;AehlDC;EACE,YAAA;CfklDH;AelmDC;EACE,WAAA;CfomDH;AermDC;EACE,mBAAA;CfumDH;AexmDC;EACE,mBAAA;Cf0mDH;Ae3mDC;EACE,UAAA;Cf6mDH;Ae9mDC;EACE,mBAAA;CfgnDH;AejnDC;EACE,mBAAA;CfmnDH;AepnDC;EACE,UAAA;CfsnDH;AevnDC;EACE,mBAAA;CfynDH;Ae1nDC;EACE,mBAAA;Cf4nDH;Ae7nDC;EACE,UAAA;Cf+nDH;AehoDC;EACE,mBAAA;CfkoDH;AenoDC;EACE,kBAAA;CfqoDH;AejoDC;EACE,WAAA;CfmoDH;AernDC;EACE,kBAAA;CfunDH;AexnDC;EACE,0BAAA;Cf0nDH;Ae3nDC;EACE,0BAAA;Cf6nDH;Ae9nDC;EACE,iBAAA;CfgoDH;AejoDC;EACE,0BAAA;CfmoDH;AepoDC;EACE,0BAAA;CfsoDH;AevoDC;EACE,iBAAA;CfyoDH;Ae1oDC;EACE,0BAAA;Cf4oDH;Ae7oDC;EACE,0BAAA;Cf+oDH;AehpDC;EACE,iBAAA;CfkpDH;AenpDC;EACE,0BAAA;CfqpDH;AetpDC;EACE,yBAAA;CfwpDH;AezpDC;EACE,gBAAA;Cf2pDH;Aa3pDD;EElCI;IACE,YAAA;GfgsDH;EezrDD;IACE,YAAA;Gf2rDD;Ee5rDD;IACE,oBAAA;Gf8rDD;Ee/rDD;IACE,oBAAA;GfisDD;EelsDD;IACE,WAAA;GfosDD;EersDD;IACE,oBAAA;GfusDD;EexsDD;IACE,oBAAA;Gf0sDD;Ee3sDD;IACE,WAAA;Gf6sDD;Ee9sDD;IACE,oBAAA;GfgtDD;EejtDD;IACE,oBAAA;GfmtDD;EeptDD;IACE,WAAA;GfstDD;EevtDD;IACE,oBAAA;GfytDD;Ee1tDD;IACE,mBAAA;Gf4tDD;Ee9sDD;IACE,YAAA;GfgtDD;EejtDD;IACE,oBAAA;GfmtDD;EeptDD;IACE,oBAAA;GfstDD;EevtDD;IACE,WAAA;GfytDD;Ee1tDD;IACE,oBAAA;Gf4tDD;Ee7tDD;IACE,oBAAA;Gf+tDD;EehuDD;IACE,WAAA;GfkuDD;EenuDD;IACE,oBAAA;GfquDD;EetuDD;IACE,oBAAA;GfwuDD;EezuDD;IACE,WAAA;Gf2uDD;Ee5uDD;IACE,oBAAA;Gf8uDD;Ee/uDD;IACE,mBAAA;GfivDD;Ee7uDD;IACE,YAAA;Gf+uDD;Ee/vDD;IACE,WAAA;GfiwDD;EelwDD;IACE,mBAAA;GfowDD;EerwDD;IACE,mBAAA;GfuwDD;EexwDD;IACE,UAAA;Gf0wDD;Ee3wDD;IACE,mBAAA;Gf6wDD;Ee9wDD;IACE,mBAAA;GfgxDD;EejxDD;IACE,UAAA;GfmxDD;EepxDD;IACE,mBAAA;GfsxDD;EevxDD;IACE,mBAAA;GfyxDD;Ee1xDD;IACE,UAAA;Gf4xDD;Ee7xDD;IACE,mBAAA;Gf+xDD;EehyDD;IACE,kBAAA;GfkyDD;Ee9xDD;IACE,WAAA;GfgyDD;EelxDD;IACE,kBAAA;GfoxDD;EerxDD;IACE,0BAAA;GfuxDD;EexxDD;IACE,0BAAA;Gf0xDD;Ee3xDD;IACE,iBAAA;Gf6xDD;Ee9xDD;IACE,0BAAA;GfgyDD;EejyDD;IACE,0BAAA;GfmyDD;EepyDD;IACE,iBAAA;GfsyDD;EevyDD;IACE,0BAAA;GfyyDD;Ee1yDD;IACE,0BAAA;Gf4yDD;Ee7yDD;IACE,iBAAA;Gf+yDD;EehzDD;IACE,0BAAA;GfkzDD;EenzDD;IACE,yBAAA;GfqzDD;EetzDD;IACE,gBAAA;GfwzDD;CACF;AahzDD;EE3CI;IACE,YAAA;Gf81DH;Eev1DD;IACE,YAAA;Gfy1DD;Ee11DD;IACE,oBAAA;Gf41DD;Ee71DD;IACE,oBAAA;Gf+1DD;Eeh2DD;IACE,WAAA;Gfk2DD;Een2DD;IACE,oBAAA;Gfq2DD;Eet2DD;IACE,oBAAA;Gfw2DD;Eez2DD;IACE,WAAA;Gf22DD;Ee52DD;IACE,oBAAA;Gf82DD;Ee/2DD;IACE,oBAAA;Gfi3DD;Eel3DD;IACE,WAAA;Gfo3DD;Eer3DD;IACE,oBAAA;Gfu3DD;Eex3DD;IACE,mBAAA;Gf03DD;Ee52DD;IACE,YAAA;Gf82DD;Ee/2DD;IACE,oBAAA;Gfi3DD;Eel3DD;IACE,oBAAA;Gfo3DD;Eer3DD;IACE,WAAA;Gfu3DD;Eex3DD;IACE,oBAAA;Gf03DD;Ee33DD;IACE,oBAAA;Gf63DD;Ee93DD;IACE,WAAA;Gfg4DD;Eej4DD;IACE,oBAAA;Gfm4DD;Eep4DD;IACE,oBAAA;Gfs4DD;Eev4DD;IACE,WAAA;Gfy4DD;Ee14DD;IACE,oBAAA;Gf44DD;Ee74DD;IACE,mBAAA;Gf+4DD;Ee34DD;IACE,YAAA;Gf64DD;Ee75DD;IACE,WAAA;Gf+5DD;Eeh6DD;IACE,mBAAA;Gfk6DD;Een6DD;IACE,mBAAA;Gfq6DD;Eet6DD;IACE,UAAA;Gfw6DD;Eez6DD;IACE,mBAAA;Gf26DD;Ee56DD;IACE,mBAAA;Gf86DD;Ee/6DD;IACE,UAAA;Gfi7DD;Eel7DD;IACE,mBAAA;Gfo7DD;Eer7DD;IACE,mBAAA;Gfu7DD;Eex7DD;IACE,UAAA;Gf07DD;Ee37DD;IACE,mBAAA;Gf67DD;Ee97DD;IACE,kBAAA;Gfg8DD;Ee57DD;IACE,WAAA;Gf87DD;Eeh7DD;IACE,kBAAA;Gfk7DD;Een7DD;IACE,0BAAA;Gfq7DD;Eet7DD;IACE,0BAAA;Gfw7DD;Eez7DD;IACE,iBAAA;Gf27DD;Ee57DD;IACE,0BAAA;Gf87DD;Ee/7DD;IACE,0BAAA;Gfi8DD;Eel8DD;IACE,iBAAA;Gfo8DD;Eer8DD;IACE,0BAAA;Gfu8DD;Eex8DD;IACE,0BAAA;Gf08DD;Ee38DD;IACE,iBAAA;Gf68DD;Ee98DD;IACE,0BAAA;Gfg9DD;Eej9DD;IACE,yBAAA;Gfm9DD;Eep9DD;IACE,gBAAA;Gfs9DD;CACF;Aa38DD;EE9CI;IACE,YAAA;Gf4/DH;Eer/DD;IACE,YAAA;Gfu/DD;Eex/DD;IACE,oBAAA;Gf0/DD;Ee3/DD;IACE,oBAAA;Gf6/DD;Ee9/DD;IACE,WAAA;GfggED;EejgED;IACE,oBAAA;GfmgED;EepgED;IACE,oBAAA;GfsgED;EevgED;IACE,WAAA;GfygED;Ee1gED;IACE,oBAAA;Gf4gED;Ee7gED;IACE,oBAAA;Gf+gED;EehhED;IACE,WAAA;GfkhED;EenhED;IACE,oBAAA;GfqhED;EethED;IACE,mBAAA;GfwhED;Ee1gED;IACE,YAAA;Gf4gED;Ee7gED;IACE,oBAAA;Gf+gED;EehhED;IACE,oBAAA;GfkhED;EenhED;IACE,WAAA;GfqhED;EethED;IACE,oBAAA;GfwhED;EezhED;IACE,oBAAA;Gf2hED;Ee5hED;IACE,WAAA;Gf8hED;Ee/hED;IACE,oBAAA;GfiiED;EeliED;IACE,oBAAA;GfoiED;EeriED;IACE,WAAA;GfuiED;EexiED;IACE,oBAAA;Gf0iED;Ee3iED;IACE,mBAAA;Gf6iED;EeziED;IACE,YAAA;Gf2iED;Ee3jED;IACE,WAAA;Gf6jED;Ee9jED;IACE,mBAAA;GfgkED;EejkED;IACE,mBAAA;GfmkED;EepkED;IACE,UAAA;GfskED;EevkED;IACE,mBAAA;GfykED;Ee1kED;IACE,mBAAA;Gf4kED;Ee7kED;IACE,UAAA;Gf+kED;EehlED;IACE,mBAAA;GfklED;EenlED;IACE,mBAAA;GfqlED;EetlED;IACE,UAAA;GfwlED;EezlED;IACE,mBAAA;Gf2lED;Ee5lED;IACE,kBAAA;Gf8lED;Ee1lED;IACE,WAAA;Gf4lED;Ee9kED;IACE,kBAAA;GfglED;EejlED;IACE,0BAAA;GfmlED;EeplED;IACE,0BAAA;GfslED;EevlED;IACE,iBAAA;GfylED;Ee1lED;IACE,0BAAA;Gf4lED;Ee7lED;IACE,0BAAA;Gf+lED;EehmED;IACE,iBAAA;GfkmED;EenmED;IACE,0BAAA;GfqmED;EetmED;IACE,0BAAA;GfwmED;EezmED;IACE,iBAAA;Gf2mED;Ee5mED;IACE,0BAAA;Gf8mED;Ee/mED;IACE,yBAAA;GfinED;EelnED;IACE,gBAAA;GfonED;CACF;AgBxrED;EACE,8BAAA;ChB0rED;AgBxrED;EACE,iBAAA;EACA,oBAAA;EACA,eAAA;EACA,iBAAA;ChB0rED;AgBxrED;EACE,iBAAA;ChB0rED;AgBprED;EACE,YAAA;EACA,gBAAA;EACA,oBAAA;ChBsrED;AgBzrED;;;;;;EAWQ,aAAA;EACA,wBAAA;EACA,oBAAA;EACA,2BAAA;ChBsrEP;AgBpsED;EAoBI,uBAAA;EACA,8BAAA;ChBmrEH;AgBxsED;;;;;;EA8BQ,cAAA;ChBkrEP;AgBhtED;EAoCI,2BAAA;ChB+qEH;AgBntED;EAyCI,uBAAA;ChB6qEH;AgBtqED;;;;;;EAOQ,aAAA;ChBuqEP;AgB5pED;EACE,uBAAA;ChB8pED;AgB/pED;;;;;;EAQQ,uBAAA;ChB+pEP;AgBvqED;;EAeM,yBAAA;ChB4pEL;AgBlpED;EAEI,0BAAA;ChBmpEH;AgB1oED;EAEI,0BAAA;ChB2oEH;AgBloED;EACE,iBAAA;EACA,YAAA;EACA,sBAAA;ChBooED;AgB/nEG;;EACE,iBAAA;EACA,YAAA;EACA,oBAAA;ChBkoEL;AiB9wEC;;;;;;;;;;;;EAOI,0BAAA;CjBqxEL;AiB/wEC;;;;;EAMI,0BAAA;CjBgxEL;AiBnyEC;;;;;;;;;;;;EAOI,0BAAA;CjB0yEL;AiBpyEC;;;;;EAMI,0BAAA;CjBqyEL;AiBxzEC;;;;;;;;;;;;EAOI,0BAAA;CjB+zEL;AiBzzEC;;;;;EAMI,0BAAA;CjB0zEL;AiB70EC;;;;;;;;;;;;EAOI,0BAAA;CjBo1EL;AiB90EC;;;;;EAMI,0BAAA;CjB+0EL;AiBl2EC;;;;;;;;;;;;EAOI,0BAAA;CjBy2EL;AiBn2EC;;;;;EAMI,0BAAA;CjBo2EL;AgBltED;EACE,iBAAA;EACA,kBAAA;ChBotED;AgBvpED;EACA;IA3DI,YAAA;IACA,oBAAA;IACA,mBAAA;IACA,6CAAA;IACA,uBAAA;GhBqtED;EgB9pEH;IAnDM,iBAAA;GhBotEH;EgBjqEH;;;;;;IA1CY,oBAAA;GhBmtET;EgBzqEH;IAlCM,UAAA;GhB8sEH;EgB5qEH;;;;;;IAzBY,eAAA;GhB6sET;EgBprEH;;;;;;IArBY,gBAAA;GhBitET;EgB5rEH;;;;IARY,iBAAA;GhB0sET;CACF;AkBp6ED;EACE,WAAA;EACA,UAAA;EACA,UAAA;EAIA,aAAA;ClBm6ED;AkBh6ED;EACE,eAAA;EACA,YAAA;EACA,WAAA;EACA,oBAAA;EACA,gBAAA;EACA,qBAAA;EACA,eAAA;EACA,UAAA;EACA,iCAAA;ClBk6ED;AkB/5ED;EACE,sBAAA;EACA,gBAAA;EACA,mBAAA;EACA,kBAAA;ClBi6ED;AkBt5ED;Eb4BE,+BAAA;EACG,4BAAA;EACK,uBAAA;CL63ET;AkBt5ED;;EAEE,gBAAA;EACA,mBAAA;EACA,oBAAA;ClBw5ED;AkBr5ED;EACE,eAAA;ClBu5ED;AkBn5ED;EACE,eAAA;EACA,YAAA;ClBq5ED;AkBj5ED;;EAEE,aAAA;ClBm5ED;AkB/4ED;;;EZvEE,qBAAA;EAEA,2CAAA;EACA,qBAAA;CN09ED;AkB/4ED;EACE,eAAA;EACA,iBAAA;EACA,gBAAA;EACA,wBAAA;EACA,eAAA;ClBi5ED;AkBv3ED;EACE,eAAA;EACA,YAAA;EACA,aAAA;EACA,kBAAA;EACA,gBAAA;EACA,wBAAA;EACA,eAAA;EACA,uBAAA;EACA,uBAAA;EACA,uBAAA;EACA,mBAAA;EbxDA,yDAAA;EACQ,iDAAA;EAyHR,uFAAA;EACK,0EAAA;EACG,uEAAA;CL0zET;AmBl8EC;EACE,sBAAA;EACA,WAAA;EdUF,uFAAA;EACQ,+EAAA;CL27ET;AK15EC;EACE,YAAA;EACA,WAAA;CL45EH;AK15EC;EAA0B,YAAA;CL65E3B;AK55EC;EAAgC,YAAA;CL+5EjC;AkBn4EC;EACE,UAAA;EACA,8BAAA;ClBq4EH;AkB73EC;;;EAGE,0BAAA;EACA,WAAA;ClB+3EH;AkB53EC;;EAEE,oBAAA;ClB83EH;AkB13EC;EACE,aAAA;ClB43EH;AkBh3ED;EACE,yBAAA;ClBk3ED;AkB10ED;EAtBI;;;;IACE,kBAAA;GlBs2EH;EkBn2EC;;;;;;;;IAEE,kBAAA;GlB22EH;EkBx2EC;;;;;;;;IAEE,kBAAA;GlBg3EH;CACF;AkBt2ED;EACE,oBAAA;ClBw2ED;AkBh2ED;;EAEE,mBAAA;EACA,eAAA;EACA,iBAAA;EACA,oBAAA;ClBk2ED;AkBv2ED;;EAQI,iBAAA;EACA,mBAAA;EACA,iBAAA;EACA,oBAAA;EACA,gBAAA;ClBm2EH;AkBh2ED;;;;EAIE,mBAAA;EACA,mBAAA;EACA,mBAAA;ClBk2ED;AkB/1ED;;EAEE,iBAAA;ClBi2ED;AkB71ED;;EAEE,mBAAA;EACA,sBAAA;EACA,mBAAA;EACA,iBAAA;EACA,uBAAA;EACA,oBAAA;EACA,gBAAA;ClB+1ED;AkB71ED;;EAEE,cAAA;EACA,kBAAA;ClB+1ED;AkBt1EC;;;;;;EAGE,oBAAA;ClB21EH;AkBr1EC;;;;EAEE,oBAAA;ClBy1EH;AkBn1EC;;;;EAGI,oBAAA;ClBs1EL;AkB30ED;EAEE,iBAAA;EACA,oBAAA;EAEA,iBAAA;EACA,iBAAA;ClB20ED;AkBz0EC;;EAEE,gBAAA;EACA,iBAAA;ClB20EH;AkB9zED;ECnQE,aAAA;EACA,kBAAA;EACA,gBAAA;EACA,iBAAA;EACA,mBAAA;CnBokFD;AmBlkFC;EACE,aAAA;EACA,kBAAA;CnBokFH;AmBjkFC;;EAEE,aAAA;CnBmkFH;AkB10ED;EAEI,aAAA;EACA,kBAAA;EACA,gBAAA;EACA,iBAAA;EACA,mBAAA;ClB20EH;AkBj1ED;EASI,aAAA;EACA,kBAAA;ClB20EH;AkBr1ED;;EAcI,aAAA;ClB20EH;AkBz1ED;EAiBI,aAAA;EACA,iBAAA;EACA,kBAAA;EACA,gBAAA;EACA,iBAAA;ClB20EH;AkBv0ED;EC/RE,aAAA;EACA,mBAAA;EACA,gBAAA;EACA,uBAAA;EACA,mBAAA;CnBymFD;AmBvmFC;EACE,aAAA;EACA,kBAAA;CnBymFH;AmBtmFC;;EAEE,aAAA;CnBwmFH;AkBn1ED;EAEI,aAAA;EACA,mBAAA;EACA,gBAAA;EACA,uBAAA;EACA,mBAAA;ClBo1EH;AkB11ED;EASI,aAAA;EACA,kBAAA;ClBo1EH;AkB91ED;;EAcI,aAAA;ClBo1EH;AkBl2ED;EAiBI,aAAA;EACA,iBAAA;EACA,mBAAA;EACA,gBAAA;EACA,uBAAA;ClBo1EH;AkB30ED;EAEE,mBAAA;ClB40ED;AkB90ED;EAMI,sBAAA;ClB20EH;AkBv0ED;EACE,mBAAA;EACA,OAAA;EACA,SAAA;EACA,WAAA;EACA,eAAA;EACA,YAAA;EACA,aAAA;EACA,kBAAA;EACA,mBAAA;EACA,qBAAA;ClBy0ED;AkBv0ED;;;EAGE,YAAA;EACA,aAAA;EACA,kBAAA;ClBy0ED;AkBv0ED;;;EAGE,YAAA;EACA,aAAA;EACA,kBAAA;ClBy0ED;AkBr0ED;;;;;;;;;;EC1ZI,eAAA;CnB2uFH;AkBj1ED;ECtZI,sBAAA;Ed+CF,yDAAA;EACQ,iDAAA;CL4rFT;AmB1uFG;EACE,sBAAA;Ed4CJ,0EAAA;EACQ,kEAAA;CLisFT;AkB31ED;EC5YI,eAAA;EACA,sBAAA;EACA,0BAAA;CnB0uFH;AkBh2ED;ECtYI,eAAA;CnByuFH;AkBh2ED;;;;;;;;;;EC7ZI,eAAA;CnBywFH;AkB52ED;ECzZI,sBAAA;Ed+CF,yDAAA;EACQ,iDAAA;CL0tFT;AmBxwFG;EACE,sBAAA;Ed4CJ,0EAAA;EACQ,kEAAA;CL+tFT;AkBt3ED;EC/YI,eAAA;EACA,sBAAA;EACA,0BAAA;CnBwwFH;AkB33ED;ECzYI,eAAA;CnBuwFH;AkB33ED;;;;;;;;;;EChaI,eAAA;CnBuyFH;AkBv4ED;EC5ZI,sBAAA;Ed+CF,yDAAA;EACQ,iDAAA;CLwvFT;AmBtyFG;EACE,sBAAA;Ed4CJ,0EAAA;EACQ,kEAAA;CL6vFT;AkBj5ED;EClZI,eAAA;EACA,sBAAA;EACA,0BAAA;CnBsyFH;AkBt5ED;EC5YI,eAAA;CnBqyFH;AkBl5EC;EACE,UAAA;ClBo5EH;AkBl5EC;EACE,OAAA;ClBo5EH;AkB14ED;EACE,eAAA;EACA,gBAAA;EACA,oBAAA;EACA,eAAA;ClB44ED;AkBzzED;EAwEA;IAtIM,sBAAA;IACA,iBAAA;IACA,uBAAA;GlB23EH;EkBvvEH;IA/HM,sBAAA;IACA,YAAA;IACA,uBAAA;GlBy3EH;EkB5vEH;IAxHM,sBAAA;GlBu3EH;EkB/vEH;IApHM,sBAAA;IACA,uBAAA;GlBs3EH;EkBnwEH;;;IA9GQ,YAAA;GlBs3EL;EkBxwEH;IAxGM,YAAA;GlBm3EH;EkB3wEH;IApGM,iBAAA;IACA,uBAAA;GlBk3EH;EkB/wEH;;IA5FM,sBAAA;IACA,cAAA;IACA,iBAAA;IACA,uBAAA;GlB+2EH;EkBtxEH;;IAtFQ,gBAAA;GlBg3EL;EkB1xEH;;IAjFM,mBAAA;IACA,eAAA;GlB+2EH;EkB/xEH;IA3EM,OAAA;GlB62EH;CACF;AkBn2ED;;;;EASI,cAAA;EACA,iBAAA;EACA,iBAAA;ClBg2EH;AkB32ED;;EAiBI,iBAAA;ClB81EH;AkB/2ED;EJthBE,mBAAA;EACA,oBAAA;Cdw4FD;AkB50EC;EAyBF;IAnCM,kBAAA;IACA,iBAAA;IACA,iBAAA;GlB01EH;CACF;AkB13ED;EAwCI,YAAA;ClBq1EH;AkBv0EC;EAUF;IAdQ,kBAAA;IACA,gBAAA;GlB+0EL;CACF;AkBr0EC;EAEF;IANQ,iBAAA;IACA,gBAAA;GlB60EL;CACF;AoBt6FD;EACE,sBAAA;EACA,iBAAA;EACA,oBAAA;EACA,mBAAA;EACA,uBAAA;EACA,+BAAA;MAAA,2BAAA;EACA,gBAAA;EACA,uBAAA;EACA,8BAAA;EACA,oBAAA;EC0CA,kBAAA;EACA,gBAAA;EACA,wBAAA;EACA,mBAAA;EhB+JA,0BAAA;EACG,uBAAA;EACC,sBAAA;EACI,kBAAA;CLiuFT;AoBz6FG;;;;;;EdrBF,qBAAA;EAEA,2CAAA;EACA,qBAAA;CNq8FD;AoB76FC;;;EAGE,YAAA;EACA,sBAAA;CpB+6FH;AoB56FC;;EAEE,WAAA;EACA,uBAAA;Ef2BF,yDAAA;EACQ,iDAAA;CLo5FT;AoB56FC;;;EAGE,oBAAA;EE7CF,cAAA;EAGA,0BAAA;EjB8DA,yBAAA;EACQ,iBAAA;CL65FT;AoB56FG;;EAEE,qBAAA;CpB86FL;AoBr6FD;EC3DE,YAAA;EACA,uBAAA;EACA,mBAAA;CrBm+FD;AqBj+FC;;EAEE,YAAA;EACA,0BAAA;EACI,sBAAA;CrBm+FP;AqBj+FC;EACE,YAAA;EACA,0BAAA;EACI,sBAAA;CrBm+FP;AqBj+FC;;;EAGE,YAAA;EACA,0BAAA;EACI,sBAAA;CrBm+FP;AqBj+FG;;;;;;;;;EAGE,YAAA;EACA,0BAAA;EACI,sBAAA;CrBy+FT;AqBt+FC;;;EAGE,uBAAA;CrBw+FH;AqBn+FG;;;;;;;;;EAGE,uBAAA;EACI,mBAAA;CrB2+FT;AoB19FD;ECZI,YAAA;EACA,uBAAA;CrBy+FH;AoB39FD;EC9DE,YAAA;EACA,0BAAA;EACA,sBAAA;CrB4hGD;AqB1hGC;;EAEE,YAAA;EACA,0BAAA;EACI,sBAAA;CrB4hGP;AqB1hGC;EACE,YAAA;EACA,0BAAA;EACI,sBAAA;CrB4hGP;AqB1hGC;;;EAGE,YAAA;EACA,0BAAA;EACI,sBAAA;CrB4hGP;AqB1hGG;;;;;;;;;EAGE,YAAA;EACA,0BAAA;EACI,sBAAA;CrBkiGT;AqB/hGC;;;EAGE,uBAAA;CrBiiGH;AqB5hGG;;;;;;;;;EAGE,0BAAA;EACI,sBAAA;CrBoiGT;AoBhhGD;ECfI,eAAA;EACA,uBAAA;CrBkiGH;AoBhhGD;EClEE,YAAA;EACA,0BAAA;EACA,sBAAA;CrBqlGD;AqBnlGC;;EAEE,YAAA;EACA,0BAAA;EACI,sBAAA;CrBqlGP;AqBnlGC;EACE,YAAA;EACA,0BAAA;EACI,sBAAA;CrBqlGP;AqBnlGC;;;EAGE,YAAA;EACA,0BAAA;EACI,sBAAA;CrBqlGP;AqBnlGG;;;;;;;;;EAGE,YAAA;EACA,0BAAA;EACI,sBAAA;CrB2lGT;AqBxlGC;;;EAGE,uBAAA;CrB0lGH;AqBrlGG;;;;;;;;;EAGE,0BAAA;EACI,sBAAA;CrB6lGT;AoBrkGD;ECnBI,eAAA;EACA,uBAAA;CrB2lGH;AoBrkGD;ECtEE,YAAA;EACA,0BAAA;EACA,sBAAA;CrB8oGD;AqB5oGC;;EAEE,YAAA;EACA,0BAAA;EACI,sBAAA;CrB8oGP;AqB5oGC;EACE,YAAA;EACA,0BAAA;EACI,sBAAA;CrB8oGP;AqB5oGC;;;EAGE,YAAA;EACA,0BAAA;EACI,sBAAA;CrB8oGP;AqB5oGG;;;;;;;;;EAGE,YAAA;EACA,0BAAA;EACI,sBAAA;CrBopGT;AqBjpGC;;;EAGE,uBAAA;CrBmpGH;AqB9oGG;;;;;;;;;EAGE,0BAAA;EACI,sBAAA;CrBspGT;AoB1nGD;ECvBI,eAAA;EACA,uBAAA;CrBopGH;AoB1nGD;EC1EE,YAAA;EACA,0BAAA;EACA,sBAAA;CrBusGD;AqBrsGC;;EAEE,YAAA;EACA,0BAAA;EACI,sBAAA;CrBusGP;AqBrsGC;EACE,YAAA;EACA,0BAAA;EACI,sBAAA;CrBusGP;AqBrsGC;;;EAGE,YAAA;EACA,0BAAA;EACI,sBAAA;CrBusGP;AqBrsGG;;;;;;;;;EAGE,YAAA;EACA,0BAAA;EACI,sBAAA;CrB6sGT;AqB1sGC;;;EAGE,uBAAA;CrB4sGH;AqBvsGG;;;;;;;;;EAGE,0BAAA;EACI,sBAAA;CrB+sGT;AoB/qGD;EC3BI,eAAA;EACA,uBAAA;CrB6sGH;AoB/qGD;EC9EE,YAAA;EACA,0BAAA;EACA,sBAAA;CrBgwGD;AqB9vGC;;EAEE,YAAA;EACA,0BAAA;EACI,sBAAA;CrBgwGP;AqB9vGC;EACE,YAAA;EACA,0BAAA;EACI,sBAAA;CrBgwGP;AqB9vGC;;;EAGE,YAAA;EACA,0BAAA;EACI,sBAAA;CrBgwGP;AqB9vGG;;;;;;;;;EAGE,YAAA;EACA,0BAAA;EACI,sBAAA;CrBswGT;AqBnwGC;;;EAGE,uBAAA;CrBqwGH;AqBhwGG;;;;;;;;;EAGE,0BAAA;EACI,sBAAA;CrBwwGT;AoBpuGD;EC/BI,eAAA;EACA,uBAAA;CrBswGH;AoB/tGD;EACE,eAAA;EACA,oBAAA;EACA,iBAAA;CpBiuGD;AoB/tGC;;;;;EAKE,8BAAA;EfnCF,yBAAA;EACQ,iBAAA;CLqwGT;AoBhuGC;;;;EAIE,0BAAA;CpBkuGH;AoBhuGC;;EAEE,eAAA;EACA,2BAAA;EACA,8BAAA;CpBkuGH;AoB9tGG;;;;EAEE,eAAA;EACA,sBAAA;CpBkuGL;AoBztGD;;ECxEE,mBAAA;EACA,gBAAA;EACA,uBAAA;EACA,mBAAA;CrBqyGD;AoB5tGD;;EC5EE,kBAAA;EACA,gBAAA;EACA,iBAAA;EACA,mBAAA;CrB4yGD;AoB/tGD;;EChFE,iBAAA;EACA,gBAAA;EACA,iBAAA;EACA,mBAAA;CrBmzGD;AoB9tGD;EACE,eAAA;EACA,YAAA;CpBguGD;AoB5tGD;EACE,gBAAA;CpB8tGD;AoBvtGC;;;EACE,YAAA;CpB2tGH;AuBr3GD;EACE,WAAA;ElBoLA,yCAAA;EACK,oCAAA;EACG,iCAAA;CLosGT;AuBx3GC;EACE,WAAA;CvB03GH;AuBt3GD;EACE,cAAA;CvBw3GD;AuBt3GC;EAAY,eAAA;CvBy3Gb;AuBx3GC;EAAY,mBAAA;CvB23Gb;AuB13GC;EAAY,yBAAA;CvB63Gb;AuB13GD;EACE,mBAAA;EACA,UAAA;EACA,iBAAA;ElBuKA,gDAAA;EACQ,2CAAA;KAAA,wCAAA;EAOR,mCAAA;EACQ,8BAAA;KAAA,2BAAA;EAGR,yCAAA;EACQ,oCAAA;KAAA,iCAAA;CL8sGT;AwBx5GD;EACE,sBAAA;EACA,SAAA;EACA,UAAA;EACA,iBAAA;EACA,uBAAA;EACA,uBAAA;EACA,yBAAA;EACA,oCAAA;EACA,mCAAA;CxB05GD;AwBt5GD;;EAEE,mBAAA;CxBw5GD;AwBp5GD;EACE,WAAA;CxBs5GD;AwBl5GD;EACE,mBAAA;EACA,UAAA;EACA,QAAA;EACA,cAAA;EACA,cAAA;EACA,YAAA;EACA,iBAAA;EACA,eAAA;EACA,gBAAA;EACA,iBAAA;EACA,gBAAA;EACA,iBAAA;EACA,uBAAA;EACA,uBAAA;EACA,sCAAA;EACA,mBAAA;EnBsBA,oDAAA;EACQ,4CAAA;EmBrBR,qCAAA;UAAA,6BAAA;CxBq5GD;AwBh5GC;EACE,SAAA;EACA,WAAA;CxBk5GH;AwB36GD;ECzBE,YAAA;EACA,cAAA;EACA,iBAAA;EACA,0BAAA;CzBu8GD;AwBj7GD;EAmCI,eAAA;EACA,kBAAA;EACA,YAAA;EACA,oBAAA;EACA,wBAAA;EACA,eAAA;EACA,oBAAA;CxBi5GH;AwB34GC;;EAEE,sBAAA;EACA,eAAA;EACA,0BAAA;CxB64GH;AwBv4GC;;;EAGE,YAAA;EACA,sBAAA;EACA,WAAA;EACA,0BAAA;CxBy4GH;AwBh4GC;;;EAGE,eAAA;CxBk4GH;AwB93GC;;EAEE,sBAAA;EACA,8BAAA;EACA,uBAAA;EE3GF,oEAAA;EF6GE,oBAAA;CxBg4GH;AwB33GD;EAGI,eAAA;CxB23GH;AwB93GD;EAQI,WAAA;CxBy3GH;AwBj3GD;EACE,WAAA;EACA,SAAA;CxBm3GD;AwB32GD;EACE,QAAA;EACA,YAAA;CxB62GD;AwBz2GD;EACE,eAAA;EACA,kBAAA;EACA,gBAAA;EACA,wBAAA;EACA,eAAA;EACA,oBAAA;CxB22GD;AwBv2GD;EACE,gBAAA;EACA,QAAA;EACA,SAAA;EACA,UAAA;EACA,OAAA;EACA,aAAA;CxBy2GD;AwBr2GD;EACE,SAAA;EACA,WAAA;CxBu2GD;AwB/1GD;;EAII,cAAA;EACA,0BAAA;EACA,4BAAA;EACA,YAAA;CxB+1GH;AwBt2GD;;EAWI,UAAA;EACA,aAAA;EACA,mBAAA;CxB+1GH;AwB10GD;EAXE;IApEA,WAAA;IACA,SAAA;GxB65GC;EwB11GD;IA1DA,QAAA;IACA,YAAA;GxBu5GC;CACF;A2BviHD;;EAEE,mBAAA;EACA,sBAAA;EACA,uBAAA;C3ByiHD;A2B7iHD;;EAMI,mBAAA;EACA,YAAA;C3B2iHH;A2BziHG;;;;;;;;EAIE,WAAA;C3B+iHL;A2BziHD;;;;EAKI,kBAAA;C3B0iHH;A2BriHD;EACE,kBAAA;C3BuiHD;A2BxiHD;;;EAOI,YAAA;C3BsiHH;A2B7iHD;;;EAYI,iBAAA;C3BsiHH;A2BliHD;EACE,iBAAA;C3BoiHD;A2BhiHD;EACE,eAAA;C3BkiHD;A2BjiHC;EClDA,8BAAA;EACG,2BAAA;C5BslHJ;A2BhiHD;;EC/CE,6BAAA;EACG,0BAAA;C5BmlHJ;A2B/hHD;EACE,YAAA;C3BiiHD;A2B/hHD;EACE,iBAAA;C3BiiHD;A2B/hHD;;ECnEE,8BAAA;EACG,2BAAA;C5BsmHJ;A2B9hHD;ECjEE,6BAAA;EACG,0BAAA;C5BkmHJ;A2B7hHD;;EAEE,WAAA;C3B+hHD;A2B9gHD;EACE,kBAAA;EACA,mBAAA;C3BghHD;A2B9gHD;EACE,mBAAA;EACA,oBAAA;C3BghHD;A2B3gHD;EtB/CE,yDAAA;EACQ,iDAAA;CL6jHT;A2B3gHC;EtBnDA,yBAAA;EACQ,iBAAA;CLikHT;A2BxgHD;EACE,eAAA;C3B0gHD;A2BvgHD;EACE,wBAAA;EACA,uBAAA;C3BygHD;A2BtgHD;EACE,wBAAA;C3BwgHD;A2BjgHD;;;EAII,eAAA;EACA,YAAA;EACA,YAAA;EACA,gBAAA;C3BkgHH;A2BzgHD;EAcM,YAAA;C3B8/GL;A2B5gHD;;;;EAsBI,iBAAA;EACA,eAAA;C3B4/GH;A2Bv/GC;EACE,iBAAA;C3By/GH;A2Bv/GC;EC3KA,6BAAA;EACC,4BAAA;EAOD,8BAAA;EACC,6BAAA;C5B+pHF;A2Bz/GC;EC/KA,2BAAA;EACC,0BAAA;EAOD,gCAAA;EACC,+BAAA;C5BqqHF;A2B1/GD;EACE,iBAAA;C3B4/GD;A2B1/GD;;EC/KE,8BAAA;EACC,6BAAA;C5B6qHF;A2Bz/GD;EC7LE,2BAAA;EACC,0BAAA;C5ByrHF;A2Br/GD;EACE,eAAA;EACA,YAAA;EACA,oBAAA;EACA,0BAAA;C3Bu/GD;A2B3/GD;;EAOI,YAAA;EACA,oBAAA;EACA,UAAA;C3Bw/GH;A2BjgHD;EAYI,YAAA;C3Bw/GH;A2BpgHD;EAgBI,WAAA;C3Bu/GH;A2Bt+GD;;;;EAKM,mBAAA;EACA,uBAAA;EACA,qBAAA;C3Bu+GL;A6BjtHD;EACE,mBAAA;EACA,eAAA;EACA,0BAAA;C7BmtHD;A6BhtHC;EACE,YAAA;EACA,gBAAA;EACA,iBAAA;C7BktHH;A6B3tHD;EAeI,mBAAA;EACA,WAAA;EAKA,YAAA;EAEA,YAAA;EACA,iBAAA;C7B0sHH;A6BxsHG;EACE,WAAA;C7B0sHL;A6BhsHD;;;EV0BE,aAAA;EACA,mBAAA;EACA,gBAAA;EACA,uBAAA;EACA,mBAAA;CnB2qHD;AmBzqHC;;;EACE,aAAA;EACA,kBAAA;CnB6qHH;AmB1qHC;;;;;;EAEE,aAAA;CnBgrHH;A6BltHD;;;EVqBE,aAAA;EACA,kBAAA;EACA,gBAAA;EACA,iBAAA;EACA,mBAAA;CnBksHD;AmBhsHC;;;EACE,aAAA;EACA,kBAAA;CnBosHH;AmBjsHC;;;;;;EAEE,aAAA;CnBusHH;A6BhuHD;;;EAGE,oBAAA;C7BkuHD;A6BhuHC;;;EACE,iBAAA;C7BouHH;A6BhuHD;;EAEE,UAAA;EACA,oBAAA;EACA,uBAAA;C7BkuHD;A6B7tHD;EACE,kBAAA;EACA,gBAAA;EACA,oBAAA;EACA,eAAA;EACA,eAAA;EACA,mBAAA;EACA,0BAAA;EACA,uBAAA;EACA,mBAAA;C7B+tHD;A6B5tHC;EACE,kBAAA;EACA,gBAAA;EACA,mBAAA;C7B8tHH;A6B5tHC;EACE,mBAAA;EACA,gBAAA;EACA,mBAAA;C7B8tHH;A6BlvHD;;EA0BI,cAAA;C7B4tHH;A6BvtHD;;;;;;;EDpGE,8BAAA;EACG,2BAAA;C5Bo0HJ;A6BxtHD;EACE,gBAAA;C7B0tHD;A6BxtHD;;;;;;;EDxGE,6BAAA;EACG,0BAAA;C5By0HJ;A6BztHD;EACE,eAAA;C7B2tHD;A6BttHD;EACE,mBAAA;EAGA,aAAA;EACA,oBAAA;C7BstHD;A6B3tHD;EAUI,mBAAA;C7BotHH;A6B9tHD;EAYM,kBAAA;C7BqtHL;A6BltHG;;;EAGE,WAAA;C7BotHL;A6B/sHC;;EAGI,mBAAA;C7BgtHL;A6B7sHC;;EAGI,WAAA;EACA,kBAAA;C7B8sHL;A8B72HD;EACE,iBAAA;EACA,gBAAA;EACA,iBAAA;C9B+2HD;A8Bl3HD;EAOI,mBAAA;EACA,eAAA;C9B82HH;A8Bt3HD;EAWM,mBAAA;EACA,eAAA;EACA,mBAAA;C9B82HL;A8B72HK;;EAEE,sBAAA;EACA,0BAAA;C9B+2HP;A8B12HG;EACE,eAAA;C9B42HL;A8B12HK;;EAEE,eAAA;EACA,sBAAA;EACA,8BAAA;EACA,oBAAA;C9B42HP;A8Br2HG;;;EAGE,0BAAA;EACA,sBAAA;C9Bu2HL;A8Bh5HD;ELHE,YAAA;EACA,cAAA;EACA,iBAAA;EACA,0BAAA;CzBs5HD;A8Bt5HD;EA0DI,gBAAA;C9B+1HH;A8Bt1HD;EACE,8BAAA;C9Bw1HD;A8Bz1HD;EAGI,YAAA;EAEA,oBAAA;C9Bw1HH;A8B71HD;EASM,kBAAA;EACA,wBAAA;EACA,8BAAA;EACA,2BAAA;C9Bu1HL;A8Bt1HK;EACE,mCAAA;C9Bw1HP;A8Bl1HK;;;EAGE,eAAA;EACA,uBAAA;EACA,uBAAA;EACA,iCAAA;EACA,gBAAA;C9Bo1HP;A8B/0HC;EAqDA,YAAA;EA8BA,iBAAA;C9BgwHD;A8Bn1HC;EAwDE,YAAA;C9B8xHH;A8Bt1HC;EA0DI,mBAAA;EACA,mBAAA;C9B+xHL;A8B11HC;EAgEE,UAAA;EACA,WAAA;C9B6xHH;A8BjxHD;EA0DA;IAjEM,oBAAA;IACA,UAAA;G9B4xHH;E8B5tHH;IA9DQ,iBAAA;G9B6xHL;CACF;A8Bv2HC;EAuFE,gBAAA;EACA,mBAAA;C9BmxHH;A8B32HC;;;EA8FE,uBAAA;C9BkxHH;A8BpwHD;EA2BA;IApCM,8BAAA;IACA,2BAAA;G9BixHH;E8B9uHH;;;IA9BM,0BAAA;G9BixHH;CACF;A8Bl3HD;EAEI,YAAA;C9Bm3HH;A8Br3HD;EAMM,mBAAA;C9Bk3HL;A8Bx3HD;EASM,iBAAA;C9Bk3HL;A8B72HK;;;EAGE,YAAA;EACA,0BAAA;C9B+2HP;A8Bv2HD;EAEI,YAAA;C9Bw2HH;A8B12HD;EAIM,gBAAA;EACA,eAAA;C9By2HL;A8B71HD;EACE,YAAA;C9B+1HD;A8Bh2HD;EAII,YAAA;C9B+1HH;A8Bn2HD;EAMM,mBAAA;EACA,mBAAA;C9Bg2HL;A8Bv2HD;EAYI,UAAA;EACA,WAAA;C9B81HH;A8Bl1HD;EA0DA;IAjEM,oBAAA;IACA,UAAA;G9B61HH;E8B7xHH;IA9DQ,iBAAA;G9B81HL;CACF;A8Bt1HD;EACE,iBAAA;C9Bw1HD;A8Bz1HD;EAKI,gBAAA;EACA,mBAAA;C9Bu1HH;A8B71HD;;;EAYI,uBAAA;C9Bs1HH;A8Bx0HD;EA2BA;IApCM,8BAAA;IACA,2BAAA;G9Bq1HH;E8BlzHH;;;IA9BM,0BAAA;G9Bq1HH;CACF;A8B50HD;EAEI,cAAA;C9B60HH;A8B/0HD;EAKI,eAAA;C9B60HH;A8Bp0HD;EAEE,iBAAA;EF3OA,2BAAA;EACC,0BAAA;C5BijIF;A+B3iID;EACE,mBAAA;EACA,iBAAA;EACA,oBAAA;EACA,8BAAA;C/B6iID;A+BriID;EA8nBA;IAhoBI,mBAAA;G/B2iID;CACF;A+B5hID;EAgnBA;IAlnBI,YAAA;G/BkiID;CACF;A+BphID;EACE,oBAAA;EACA,oBAAA;EACA,mBAAA;EACA,kCAAA;EACA,2DAAA;UAAA,mDAAA;EAEA,kCAAA;C/BqhID;A+BnhIC;EACE,iBAAA;C/BqhIH;A+Bz/HD;EA6jBA;IArlBI,YAAA;IACA,cAAA;IACA,yBAAA;YAAA,iBAAA;G/BqhID;E+BnhIC;IACE,0BAAA;IACA,wBAAA;IACA,kBAAA;IACA,6BAAA;G/BqhIH;E+BlhIC;IACE,oBAAA;G/BohIH;E+B/gIC;;;IAGE,gBAAA;IACA,iBAAA;G/BihIH;CACF;A+B7gID;;EAGI,kBAAA;C/B8gIH;A+BzgIC;EAmjBF;;IArjBM,kBAAA;G/BghIH;CACF;A+BvgID;;;;EAII,oBAAA;EACA,mBAAA;C/BygIH;A+BngIC;EAgiBF;;;;IAniBM,gBAAA;IACA,eAAA;G/B6gIH;CACF;A+BjgID;EACE,cAAA;EACA,sBAAA;C/BmgID;A+B9/HD;EA8gBA;IAhhBI,iBAAA;G/BogID;CACF;A+BhgID;;EAEE,gBAAA;EACA,SAAA;EACA,QAAA;EACA,cAAA;C/BkgID;A+B5/HD;EAggBA;;IAlgBI,iBAAA;G/BmgID;CACF;A+BjgID;EACE,OAAA;EACA,sBAAA;C/BmgID;A+BjgID;EACE,UAAA;EACA,iBAAA;EACA,sBAAA;C/BmgID;A+B7/HD;EACE,YAAA;EACA,mBAAA;EACA,gBAAA;EACA,kBAAA;EACA,aAAA;C/B+/HD;A+B7/HC;;EAEE,sBAAA;C/B+/HH;A+BxgID;EAaI,eAAA;C/B8/HH;A+Br/HD;EALI;;IAEE,mBAAA;G/B6/HH;CACF;A+Bn/HD;EACE,mBAAA;EACA,aAAA;EACA,mBAAA;EACA,kBAAA;EC9LA,gBAAA;EACA,mBAAA;ED+LA,8BAAA;EACA,uBAAA;EACA,8BAAA;EACA,mBAAA;C/Bs/HD;A+Bl/HC;EACE,WAAA;C/Bo/HH;A+BlgID;EAmBI,eAAA;EACA,YAAA;EACA,YAAA;EACA,mBAAA;C/Bk/HH;A+BxgID;EAyBI,gBAAA;C/Bk/HH;A+B5+HD;EAqbA;IAvbI,cAAA;G/Bk/HD;CACF;A+Bz+HD;EACE,oBAAA;C/B2+HD;A+B5+HD;EAII,kBAAA;EACA,qBAAA;EACA,kBAAA;C/B2+HH;A+B/8HC;EA2YF;IAjaM,iBAAA;IACA,YAAA;IACA,YAAA;IACA,cAAA;IACA,8BAAA;IACA,UAAA;IACA,yBAAA;YAAA,iBAAA;G/By+HH;E+B9kHH;;IAxZQ,2BAAA;G/B0+HL;E+BllHH;IArZQ,kBAAA;G/B0+HL;E+Bz+HK;;IAEE,uBAAA;G/B2+HP;CACF;A+Bz9HD;EA+XA;IA1YI,YAAA;IACA,UAAA;G/Bw+HD;E+B/lHH;IAtYM,YAAA;G/Bw+HH;E+BlmHH;IApYQ,kBAAA;IACA,qBAAA;G/By+HL;CACF;A+B99HD;EACE,mBAAA;EACA,oBAAA;EACA,mBAAA;EACA,kCAAA;EACA,qCAAA;E1B9NA,6FAAA;EACQ,qFAAA;E2B/DR,gBAAA;EACA,mBAAA;ChC+vID;AkBzuHD;EAwEA;IAtIM,sBAAA;IACA,iBAAA;IACA,uBAAA;GlB2yHH;EkBvqHH;IA/HM,sBAAA;IACA,YAAA;IACA,uBAAA;GlByyHH;EkB5qHH;IAxHM,sBAAA;GlBuyHH;EkB/qHH;IApHM,sBAAA;IACA,uBAAA;GlBsyHH;EkBnrHH;;;IA9GQ,YAAA;GlBsyHL;EkBxrHH;IAxGM,YAAA;GlBmyHH;EkB3rHH;IApGM,iBAAA;IACA,uBAAA;GlBkyHH;EkB/rHH;;IA5FM,sBAAA;IACA,cAAA;IACA,iBAAA;IACA,uBAAA;GlB+xHH;EkBtsHH;;IAtFQ,gBAAA;GlBgyHL;EkB1sHH;;IAjFM,mBAAA;IACA,eAAA;GlB+xHH;EkB/sHH;IA3EM,OAAA;GlB6xHH;CACF;A+BvgIC;EAmWF;IAzWM,mBAAA;G/BihIH;E+B/gIG;IACE,iBAAA;G/BihIL;CACF;A+BhgID;EAoVA;IA5VI,YAAA;IACA,UAAA;IACA,eAAA;IACA,gBAAA;IACA,eAAA;IACA,kBAAA;I1BzPF,yBAAA;IACQ,iBAAA;GLswIP;CACF;A+BtgID;EACE,cAAA;EHpUA,2BAAA;EACC,0BAAA;C5B60IF;A+BtgID;EACE,iBAAA;EHzUA,6BAAA;EACC,4BAAA;EAOD,8BAAA;EACC,6BAAA;C5B40IF;A+BlgID;EChVE,gBAAA;EACA,mBAAA;ChCq1ID;A+BngIC;ECnVA,iBAAA;EACA,oBAAA;ChCy1ID;A+BpgIC;ECtVA,iBAAA;EACA,oBAAA;ChC61ID;A+B9/HD;EChWE,iBAAA;EACA,oBAAA;ChCi2ID;A+B1/HD;EAsSA;IA1SI,YAAA;IACA,kBAAA;IACA,mBAAA;G/BkgID;CACF;A+Br+HD;EAhBE;IExWA,uBAAA;GjCi2IC;E+Bx/HD;IE5WA,wBAAA;IF8WE,oBAAA;G/B0/HD;E+B5/HD;IAKI,gBAAA;G/B0/HH;CACF;A+Bj/HD;EACE,0BAAA;EACA,sBAAA;C/Bm/HD;A+Br/HD;EAKI,YAAA;C/Bm/HH;A+Bl/HG;;EAEE,eAAA;EACA,8BAAA;C/Bo/HL;A+B7/HD;EAcI,YAAA;C/Bk/HH;A+BhgID;EAmBM,YAAA;C/Bg/HL;A+B9+HK;;EAEE,YAAA;EACA,8BAAA;C/Bg/HP;A+B5+HK;;;EAGE,YAAA;EACA,0BAAA;C/B8+HP;A+B1+HK;;;EAGE,YAAA;EACA,8BAAA;C/B4+HP;A+BphID;EA8CI,mBAAA;C/By+HH;A+Bx+HG;;EAEE,uBAAA;C/B0+HL;A+B3hID;EAoDM,uBAAA;C/B0+HL;A+B9hID;;EA0DI,sBAAA;C/Bw+HH;A+Bj+HK;;;EAGE,0BAAA;EACA,YAAA;C/Bm+HP;A+Bl8HC;EAoKF;IA7LU,YAAA;G/B+9HP;E+B99HO;;IAEE,YAAA;IACA,8BAAA;G/Bg+HT;E+B59HO;;;IAGE,YAAA;IACA,0BAAA;G/B89HT;E+B19HO;;;IAGE,YAAA;IACA,8BAAA;G/B49HT;CACF;A+B9jID;EA8GI,YAAA;C/Bm9HH;A+Bl9HG;EACE,YAAA;C/Bo9HL;A+BpkID;EAqHI,YAAA;C/Bk9HH;A+Bj9HG;;EAEE,YAAA;C/Bm9HL;A+B/8HK;;;;EAEE,YAAA;C/Bm9HP;A+B38HD;EACE,uBAAA;EACA,sBAAA;C/B68HD;A+B/8HD;EAKI,eAAA;C/B68HH;A+B58HG;;EAEE,YAAA;EACA,8BAAA;C/B88HL;A+Bv9HD;EAcI,eAAA;C/B48HH;A+B19HD;EAmBM,eAAA;C/B08HL;A+Bx8HK;;EAEE,YAAA;EACA,8BAAA;C/B08HP;A+Bt8HK;;;EAGE,YAAA;EACA,0BAAA;C/Bw8HP;A+Bp8HK;;;EAGE,YAAA;EACA,8BAAA;C/Bs8HP;A+B9+HD;EA+CI,mBAAA;C/Bk8HH;A+Bj8HG;;EAEE,uBAAA;C/Bm8HL;A+Br/HD;EAqDM,uBAAA;C/Bm8HL;A+Bx/HD;;EA2DI,sBAAA;C/Bi8HH;A+B37HK;;;EAGE,0BAAA;EACA,YAAA;C/B67HP;A+Bt5HC;EAwBF;IAvDU,sBAAA;G/By7HP;E+Bl4HH;IApDU,0BAAA;G/By7HP;E+Br4HH;IAjDU,eAAA;G/By7HP;E+Bx7HO;;IAEE,YAAA;IACA,8BAAA;G/B07HT;E+Bt7HO;;;IAGE,YAAA;IACA,0BAAA;G/Bw7HT;E+Bp7HO;;;IAGE,YAAA;IACA,8BAAA;G/Bs7HT;CACF;A+B9hID;EA+GI,eAAA;C/Bk7HH;A+Bj7HG;EACE,YAAA;C/Bm7HL;A+BpiID;EAsHI,eAAA;C/Bi7HH;A+Bh7HG;;EAEE,YAAA;C/Bk7HL;A+B96HK;;;;EAEE,YAAA;C/Bk7HP;AkC5jJD;EACE,kBAAA;EACA,oBAAA;EACA,iBAAA;EACA,0BAAA;EACA,mBAAA;ClC8jJD;AkCnkJD;EAQI,sBAAA;ClC8jJH;AkCtkJD;EAWM,kBAAA;EACA,eAAA;EACA,YAAA;ClC8jJL;AkC3kJD;EAkBI,eAAA;ClC4jJH;AmChlJD;EACE,sBAAA;EACA,gBAAA;EACA,eAAA;EACA,mBAAA;CnCklJD;AmCtlJD;EAOI,gBAAA;CnCklJH;AmCzlJD;;EAUM,mBAAA;EACA,YAAA;EACA,kBAAA;EACA,wBAAA;EACA,sBAAA;EACA,eAAA;EACA,uBAAA;EACA,uBAAA;EACA,kBAAA;CnCmlJL;AmCjlJG;;EAGI,eAAA;EPXN,+BAAA;EACG,4BAAA;C5B8lJJ;AmChlJG;;EPvBF,gCAAA;EACG,6BAAA;C5B2mJJ;AmC3kJG;;;;EAEE,WAAA;EACA,eAAA;EACA,0BAAA;EACA,mBAAA;CnC+kJL;AmCzkJG;;;;;;EAGE,WAAA;EACA,YAAA;EACA,0BAAA;EACA,sBAAA;EACA,gBAAA;CnC8kJL;AmCroJD;;;;;;EAkEM,eAAA;EACA,uBAAA;EACA,mBAAA;EACA,oBAAA;CnC2kJL;AmClkJD;;EC3EM,mBAAA;EACA,gBAAA;EACA,uBAAA;CpCipJL;AoC/oJG;;ERKF,+BAAA;EACG,4BAAA;C5B8oJJ;AoC9oJG;;ERTF,gCAAA;EACG,6BAAA;C5B2pJJ;AmC7kJD;;EChFM,kBAAA;EACA,gBAAA;EACA,iBAAA;CpCiqJL;AoC/pJG;;ERKF,+BAAA;EACG,4BAAA;C5B8pJJ;AoC9pJG;;ERTF,gCAAA;EACG,6BAAA;C5B2qJJ;AqC9qJD;EACE,gBAAA;EACA,eAAA;EACA,iBAAA;EACA,mBAAA;CrCgrJD;AqCprJD;EAOI,gBAAA;CrCgrJH;AqCvrJD;;EAUM,sBAAA;EACA,kBAAA;EACA,uBAAA;EACA,uBAAA;EACA,oBAAA;CrCirJL;AqC/rJD;;EAmBM,sBAAA;EACA,0BAAA;CrCgrJL;AqCpsJD;;EA2BM,aAAA;CrC6qJL;AqCxsJD;;EAkCM,YAAA;CrC0qJL;AqC5sJD;;;;EA2CM,eAAA;EACA,uBAAA;EACA,oBAAA;CrCuqJL;AsCrtJD;EACE,gBAAA;EACA,wBAAA;EACA,eAAA;EACA,kBAAA;EACA,eAAA;EACA,YAAA;EACA,mBAAA;EACA,oBAAA;EACA,yBAAA;EACA,qBAAA;CtCutJD;AsCntJG;;EAEE,YAAA;EACA,sBAAA;EACA,gBAAA;CtCqtJL;AsChtJC;EACE,cAAA;CtCktJH;AsC9sJC;EACE,mBAAA;EACA,UAAA;CtCgtJH;AsCzsJD;ECtCE,0BAAA;CvCkvJD;AuC/uJG;;EAEE,0BAAA;CvCivJL;AsC5sJD;EC1CE,0BAAA;CvCyvJD;AuCtvJG;;EAEE,0BAAA;CvCwvJL;AsC/sJD;EC9CE,0BAAA;CvCgwJD;AuC7vJG;;EAEE,0BAAA;CvC+vJL;AsCltJD;EClDE,0BAAA;CvCuwJD;AuCpwJG;;EAEE,0BAAA;CvCswJL;AsCrtJD;ECtDE,0BAAA;CvC8wJD;AuC3wJG;;EAEE,0BAAA;CvC6wJL;AsCxtJD;EC1DE,0BAAA;CvCqxJD;AuClxJG;;EAEE,0BAAA;CvCoxJL;AwCtxJD;EACE,sBAAA;EACA,gBAAA;EACA,iBAAA;EACA,gBAAA;EACA,kBAAA;EACA,YAAA;EACA,eAAA;EACA,uBAAA;EACA,oBAAA;EACA,mBAAA;EACA,0BAAA;EACA,oBAAA;CxCwxJD;AwCrxJC;EACE,cAAA;CxCuxJH;AwCnxJC;EACE,mBAAA;EACA,UAAA;CxCqxJH;AwClxJC;;EAEE,OAAA;EACA,iBAAA;CxCoxJH;AwC/wJG;;EAEE,YAAA;EACA,sBAAA;EACA,gBAAA;CxCixJL;AwC5wJC;;EAEE,eAAA;EACA,uBAAA;CxC8wJH;AwC3wJC;EACE,aAAA;CxC6wJH;AwC1wJC;EACE,kBAAA;CxC4wJH;AwCzwJC;EACE,iBAAA;CxC2wJH;AyCr0JD;EACE,kBAAA;EACA,qBAAA;EACA,oBAAA;EACA,eAAA;EACA,0BAAA;CzCu0JD;AyC50JD;;EASI,eAAA;CzCu0JH;AyCh1JD;EAaI,oBAAA;EACA,gBAAA;EACA,iBAAA;CzCs0JH;AyCr1JD;EAmBI,0BAAA;CzCq0JH;AyCl0JC;;EAEE,mBAAA;EACA,mBAAA;EACA,oBAAA;CzCo0JH;AyC91JD;EA8BI,gBAAA;CzCm0JH;AyCjzJD;EACA;IAfI,kBAAA;IACA,qBAAA;GzCm0JD;EyCj0JC;;IAEE,mBAAA;IACA,oBAAA;GzCm0JH;EyC1zJH;;IAJM,gBAAA;GzCk0JH;CACF;A0C/2JD;EACE,eAAA;EACA,aAAA;EACA,oBAAA;EACA,wBAAA;EACA,uBAAA;EACA,uBAAA;EACA,mBAAA;ErCiLA,4CAAA;EACK,uCAAA;EACG,oCAAA;CLisJT;A0C33JD;;EAaI,kBAAA;EACA,mBAAA;C1Ck3JH;A0C92JC;;;EAGE,sBAAA;C1Cg3JH;A0Cr4JD;EA0BI,aAAA;EACA,eAAA;C1C82JH;A2Cv4JD;EACE,cAAA;EACA,oBAAA;EACA,8BAAA;EACA,mBAAA;C3Cy4JD;A2C74JD;EAQI,cAAA;EAEA,eAAA;C3Cu4JH;A2Cj5JD;EAeI,kBAAA;C3Cq4JH;A2Cp5JD;;EAqBI,iBAAA;C3Cm4JH;A2Cx5JD;EAyBI,gBAAA;C3Ck4JH;A2C13JD;;EAEE,oBAAA;C3C43JD;A2C93JD;;EAMI,mBAAA;EACA,UAAA;EACA,aAAA;EACA,eAAA;C3C43JH;A2Cp3JD;ECvDE,0BAAA;EACA,sBAAA;EACA,eAAA;C5C86JD;A2Cz3JD;EClDI,0BAAA;C5C86JH;A2C53JD;EC/CI,eAAA;C5C86JH;A2C33JD;EC3DE,0BAAA;EACA,sBAAA;EACA,eAAA;C5Cy7JD;A2Ch4JD;ECtDI,0BAAA;C5Cy7JH;A2Cn4JD;ECnDI,eAAA;C5Cy7JH;A2Cl4JD;EC/DE,0BAAA;EACA,sBAAA;EACA,eAAA;C5Co8JD;A2Cv4JD;EC1DI,0BAAA;C5Co8JH;A2C14JD;ECvDI,eAAA;C5Co8JH;A2Cz4JD;ECnEE,0BAAA;EACA,sBAAA;EACA,eAAA;C5C+8JD;A2C94JD;EC9DI,0BAAA;C5C+8JH;A2Cj5JD;EC3DI,eAAA;C5C+8JH;A6Cj9JD;EACE;IAAQ,4BAAA;G7Co9JP;E6Cn9JD;IAAQ,yBAAA;G7Cs9JP;CACF;A6Cn9JD;EACE;IAAQ,4BAAA;G7Cs9JP;E6Cr9JD;IAAQ,yBAAA;G7Cw9JP;CACF;A6C39JD;EACE;IAAQ,4BAAA;G7Cs9JP;E6Cr9JD;IAAQ,yBAAA;G7Cw9JP;CACF;A6Cj9JD;EACE,iBAAA;EACA,aAAA;EACA,oBAAA;EACA,0BAAA;EACA,mBAAA;ExCsCA,uDAAA;EACQ,+CAAA;CL86JT;A6Ch9JD;EACE,YAAA;EACA,UAAA;EACA,aAAA;EACA,gBAAA;EACA,kBAAA;EACA,YAAA;EACA,mBAAA;EACA,0BAAA;ExCyBA,uDAAA;EACQ,+CAAA;EAyHR,oCAAA;EACK,+BAAA;EACG,4BAAA;CLk0JT;A6C78JD;;ECCI,8MAAA;EACA,yMAAA;EACA,sMAAA;EDAF,mCAAA;UAAA,2BAAA;C7Ci9JD;A6C18JD;;ExC5CE,2DAAA;EACK,sDAAA;EACG,mDAAA;CL0/JT;A6Cv8JD;EErEE,0BAAA;C/C+gKD;A+C5gKC;EDgDE,8MAAA;EACA,yMAAA;EACA,sMAAA;C9C+9JH;A6C38JD;EEzEE,0BAAA;C/CuhKD;A+CphKC;EDgDE,8MAAA;EACA,yMAAA;EACA,sMAAA;C9Cu+JH;A6C/8JD;EE7EE,0BAAA;C/C+hKD;A+C5hKC;EDgDE,8MAAA;EACA,yMAAA;EACA,sMAAA;C9C++JH;A6Cn9JD;EEjFE,0BAAA;C/CuiKD;A+CpiKC;EDgDE,8MAAA;EACA,yMAAA;EACA,sMAAA;C9Cu/JH;AgD/iKD;EAEE,iBAAA;ChDgjKD;AgD9iKC;EACE,cAAA;ChDgjKH;AgD5iKD;;EAEE,QAAA;EACA,iBAAA;ChD8iKD;AgD3iKD;EACE,eAAA;ChD6iKD;AgD1iKD;EACE,eAAA;ChD4iKD;AgDziKC;EACE,gBAAA;ChD2iKH;AgDviKD;;EAEE,mBAAA;ChDyiKD;AgDtiKD;;EAEE,oBAAA;ChDwiKD;AgDriKD;;;EAGE,oBAAA;EACA,oBAAA;ChDuiKD;AgDpiKD;EACE,uBAAA;ChDsiKD;AgDniKD;EACE,uBAAA;ChDqiKD;AgDjiKD;EACE,cAAA;EACA,mBAAA;ChDmiKD;AgD7hKD;EACE,gBAAA;EACA,iBAAA;ChD+hKD;AiDtlKD;EAEE,oBAAA;EACA,gBAAA;CjDulKD;AiD/kKD;EACE,mBAAA;EACA,eAAA;EACA,mBAAA;EAEA,oBAAA;EACA,uBAAA;EACA,uBAAA;CjDglKD;AiD7kKC;ErB3BA,6BAAA;EACC,4BAAA;C5B2mKF;AiD9kKC;EACE,iBAAA;ErBvBF,gCAAA;EACC,+BAAA;C5BwmKF;AiDvkKD;;EAEE,YAAA;CjDykKD;AiD3kKD;;EAKI,YAAA;CjD0kKH;AiDtkKC;;;;EAEE,sBAAA;EACA,YAAA;EACA,0BAAA;CjD0kKH;AiDtkKD;EACE,YAAA;EACA,iBAAA;CjDwkKD;AiDnkKC;;;EAGE,0BAAA;EACA,eAAA;EACA,oBAAA;CjDqkKH;AiD1kKC;;;EASI,eAAA;CjDskKL;AiD/kKC;;;EAYI,eAAA;CjDwkKL;AiDnkKC;;;EAGE,WAAA;EACA,YAAA;EACA,0BAAA;EACA,sBAAA;CjDqkKH;AiD3kKC;;;;;;;;;EAYI,eAAA;CjD0kKL;AiDtlKC;;;EAeI,eAAA;CjD4kKL;AkD9qKC;EACE,eAAA;EACA,0BAAA;ClDgrKH;AkD9qKG;;EAEE,eAAA;ClDgrKL;AkDlrKG;;EAKI,eAAA;ClDirKP;AkD9qKK;;;;EAEE,eAAA;EACA,0BAAA;ClDkrKP;AkDhrKK;;;;;;EAGE,YAAA;EACA,0BAAA;EACA,sBAAA;ClDqrKP;AkD3sKC;EACE,eAAA;EACA,0BAAA;ClD6sKH;AkD3sKG;;EAEE,eAAA;ClD6sKL;AkD/sKG;;EAKI,eAAA;ClD8sKP;AkD3sKK;;;;EAEE,eAAA;EACA,0BAAA;ClD+sKP;AkD7sKK;;;;;;EAGE,YAAA;EACA,0BAAA;EACA,sBAAA;ClDktKP;AkDxuKC;EACE,eAAA;EACA,0BAAA;ClD0uKH;AkDxuKG;;EAEE,eAAA;ClD0uKL;AkD5uKG;;EAKI,eAAA;ClD2uKP;AkDxuKK;;;;EAEE,eAAA;EACA,0BAAA;ClD4uKP;AkD1uKK;;;;;;EAGE,YAAA;EACA,0BAAA;EACA,sBAAA;ClD+uKP;AkDrwKC;EACE,eAAA;EACA,0BAAA;ClDuwKH;AkDrwKG;;EAEE,eAAA;ClDuwKL;AkDzwKG;;EAKI,eAAA;ClDwwKP;AkDrwKK;;;;EAEE,eAAA;EACA,0BAAA;ClDywKP;AkDvwKK;;;;;;EAGE,YAAA;EACA,0BAAA;EACA,sBAAA;ClD4wKP;AiD3qKD;EACE,cAAA;EACA,mBAAA;CjD6qKD;AiD3qKD;EACE,iBAAA;EACA,iBAAA;CjD6qKD;AmDvyKD;EACE,oBAAA;EACA,uBAAA;EACA,8BAAA;EACA,mBAAA;E9C0DA,kDAAA;EACQ,0CAAA;CLgvKT;AmDtyKD;EACE,cAAA;CnDwyKD;AmDnyKD;EACE,mBAAA;EACA,qCAAA;EvBpBA,6BAAA;EACC,4BAAA;C5B0zKF;AmDzyKD;EAMI,eAAA;CnDsyKH;AmDjyKD;EACE,cAAA;EACA,iBAAA;EACA,gBAAA;EACA,eAAA;CnDmyKD;AmDvyKD;;;;;EAWI,eAAA;CnDmyKH;AmD9xKD;EACE,mBAAA;EACA,0BAAA;EACA,2BAAA;EvBxCA,gCAAA;EACC,+BAAA;C5By0KF;AmDxxKD;;EAGI,iBAAA;CnDyxKH;AmD5xKD;;EAMM,oBAAA;EACA,iBAAA;CnD0xKL;AmDtxKG;;EAEI,cAAA;EvBvEN,6BAAA;EACC,4BAAA;C5Bg2KF;AmDpxKG;;EAEI,iBAAA;EvBvEN,gCAAA;EACC,+BAAA;C5B81KF;AmD7yKD;EvB1DE,2BAAA;EACC,0BAAA;C5B02KF;AmDhxKD;EAEI,oBAAA;CnDixKH;AmD9wKD;EACE,oBAAA;CnDgxKD;AmDxwKD;;;EAII,iBAAA;CnDywKH;AmD7wKD;;;EAOM,mBAAA;EACA,oBAAA;CnD2wKL;AmDnxKD;;EvBzGE,6BAAA;EACC,4BAAA;C5Bg4KF;AmDxxKD;;;;EAmBQ,4BAAA;EACA,6BAAA;CnD2wKP;AmD/xKD;;;;;;;;EAwBU,4BAAA;CnDixKT;AmDzyKD;;;;;;;;EA4BU,6BAAA;CnDuxKT;AmDnzKD;;EvBjGE,gCAAA;EACC,+BAAA;C5Bw5KF;AmDxzKD;;;;EAyCQ,+BAAA;EACA,gCAAA;CnDqxKP;AmD/zKD;;;;;;;;EA8CU,+BAAA;CnD2xKT;AmDz0KD;;;;;;;;EAkDU,gCAAA;CnDiyKT;AmDn1KD;;;;EA2DI,2BAAA;CnD8xKH;AmDz1KD;;EA+DI,cAAA;CnD8xKH;AmD71KD;;EAmEI,UAAA;CnD8xKH;AmDj2KD;;;;;;;;;;;;EA0EU,eAAA;CnDqyKT;AmD/2KD;;;;;;;;;;;;EA8EU,gBAAA;CnD+yKT;AmD73KD;;;;;;;;EAuFU,iBAAA;CnDgzKT;AmDv4KD;;;;;;;;EAgGU,iBAAA;CnDizKT;AmDj5KD;EAsGI,UAAA;EACA,iBAAA;CnD8yKH;AmDpyKD;EACE,oBAAA;CnDsyKD;AmDvyKD;EAKI,iBAAA;EACA,mBAAA;CnDqyKH;AmD3yKD;EASM,gBAAA;CnDqyKL;AmD9yKD;EAcI,iBAAA;CnDmyKH;AmDjzKD;;EAkBM,2BAAA;CnDmyKL;AmDrzKD;EAuBI,cAAA;CnDiyKH;AmDxzKD;EAyBM,8BAAA;CnDkyKL;AmD3xKD;EC1PE,mBAAA;CpDwhLD;AoDthLC;EACE,eAAA;EACA,0BAAA;EACA,mBAAA;CpDwhLH;AoD3hLC;EAMI,uBAAA;CpDwhLL;AoD9hLC;EASI,eAAA;EACA,0BAAA;CpDwhLL;AoDrhLC;EAEI,0BAAA;CpDshLL;AmD1yKD;EC7PE,sBAAA;CpD0iLD;AoDxiLC;EACE,YAAA;EACA,0BAAA;EACA,sBAAA;CpD0iLH;AoD7iLC;EAMI,0BAAA;CpD0iLL;AoDhjLC;EASI,eAAA;EACA,uBAAA;CpD0iLL;AoDviLC;EAEI,6BAAA;CpDwiLL;AmDzzKD;EChQE,sBAAA;CpD4jLD;AoD1jLC;EACE,eAAA;EACA,0BAAA;EACA,sBAAA;CpD4jLH;AoD/jLC;EAMI,0BAAA;CpD4jLL;AoDlkLC;EASI,eAAA;EACA,0BAAA;CpD4jLL;AoDzjLC;EAEI,6BAAA;CpD0jLL;AmDx0KD;ECnQE,sBAAA;CpD8kLD;AoD5kLC;EACE,eAAA;EACA,0BAAA;EACA,sBAAA;CpD8kLH;AoDjlLC;EAMI,0BAAA;CpD8kLL;AoDplLC;EASI,eAAA;EACA,0BAAA;CpD8kLL;AoD3kLC;EAEI,6BAAA;CpD4kLL;AmDv1KD;ECtQE,sBAAA;CpDgmLD;AoD9lLC;EACE,eAAA;EACA,0BAAA;EACA,sBAAA;CpDgmLH;AoDnmLC;EAMI,0BAAA;CpDgmLL;AoDtmLC;EASI,eAAA;EACA,0BAAA;CpDgmLL;AoD7lLC;EAEI,6BAAA;CpD8lLL;AmDt2KD;ECzQE,sBAAA;CpDknLD;AoDhnLC;EACE,eAAA;EACA,0BAAA;EACA,sBAAA;CpDknLH;AoDrnLC;EAMI,0BAAA;CpDknLL;AoDxnLC;EASI,eAAA;EACA,0BAAA;CpDknLL;AoD/mLC;EAEI,6BAAA;CpDgnLL;AqDhoLD;EACE,mBAAA;EACA,eAAA;EACA,UAAA;EACA,WAAA;EACA,iBAAA;CrDkoLD;AqDvoLD;;;;;EAYI,mBAAA;EACA,OAAA;EACA,QAAA;EACA,UAAA;EACA,aAAA;EACA,YAAA;EACA,UAAA;CrDkoLH;AqD7nLD;EACE,uBAAA;CrD+nLD;AqD3nLD;EACE,oBAAA;CrD6nLD;AsDxpLD;EACE,iBAAA;EACA,cAAA;EACA,oBAAA;EACA,0BAAA;EACA,0BAAA;EACA,mBAAA;EjDwDA,wDAAA;EACQ,gDAAA;CLmmLT;AsDlqLD;EASI,mBAAA;EACA,kCAAA;CtD4pLH;AsDvpLD;EACE,cAAA;EACA,mBAAA;CtDypLD;AsDvpLD;EACE,aAAA;EACA,mBAAA;CtDypLD;AuD/qLD;EACE,aAAA;EACA,gBAAA;EACA,kBAAA;EACA,eAAA;EACA,YAAA;EACA,0BAAA;EjCRA,aAAA;EAGA,0BAAA;CtBwrLD;AuDhrLC;;EAEE,YAAA;EACA,sBAAA;EACA,gBAAA;EjCfF,aAAA;EAGA,0BAAA;CtBgsLD;AuD5qLC;EACE,WAAA;EACA,gBAAA;EACA,wBAAA;EACA,UAAA;EACA,yBAAA;CvD8qLH;AwDnsLD;EACE,iBAAA;CxDqsLD;AwDjsLD;EACE,cAAA;EACA,iBAAA;EACA,gBAAA;EACA,OAAA;EACA,SAAA;EACA,UAAA;EACA,QAAA;EACA,cAAA;EACA,kCAAA;EAIA,WAAA;CxDgsLD;AwD7rLC;EnD+GA,sCAAA;EACI,kCAAA;EACC,iCAAA;EACG,8BAAA;EAkER,oDAAA;EAEK,0CAAA;EACG,oCAAA;CLghLT;AwDnsLC;EnD2GA,mCAAA;EACI,+BAAA;EACC,8BAAA;EACG,2BAAA;CL2lLT;AwDvsLD;EACE,mBAAA;EACA,iBAAA;CxDysLD;AwDrsLD;EACE,mBAAA;EACA,YAAA;EACA,aAAA;CxDusLD;AwDnsLD;EACE,mBAAA;EACA,uBAAA;EACA,uBAAA;EACA,qCAAA;EACA,mBAAA;EnDaA,iDAAA;EACQ,yCAAA;EmDZR,qCAAA;UAAA,6BAAA;EAEA,WAAA;CxDqsLD;AwDjsLD;EACE,gBAAA;EACA,OAAA;EACA,SAAA;EACA,UAAA;EACA,QAAA;EACA,cAAA;EACA,uBAAA;CxDmsLD;AwDjsLC;ElCrEA,WAAA;EAGA,yBAAA;CtBuwLD;AwDpsLC;ElCtEA,aAAA;EAGA,0BAAA;CtB2wLD;AwDnsLD;EACE,cAAA;EACA,iCAAA;CxDqsLD;AwDjsLD;EACE,iBAAA;CxDmsLD;AwD/rLD;EACE,UAAA;EACA,wBAAA;CxDisLD;AwD5rLD;EACE,mBAAA;EACA,cAAA;CxD8rLD;AwD1rLD;EACE,cAAA;EACA,kBAAA;EACA,8BAAA;CxD4rLD;AwD/rLD;EAQI,iBAAA;EACA,iBAAA;CxD0rLH;AwDnsLD;EAaI,kBAAA;CxDyrLH;AwDtsLD;EAiBI,eAAA;CxDwrLH;AwDnrLD;EACE,mBAAA;EACA,aAAA;EACA,YAAA;EACA,aAAA;EACA,iBAAA;CxDqrLD;AwDnqLD;EAZE;IACE,aAAA;IACA,kBAAA;GxDkrLD;EwDhrLD;InDvEA,kDAAA;IACQ,0CAAA;GL0vLP;EwD/qLD;IAAY,aAAA;GxDkrLX;CACF;AwD7qLD;EAFE;IAAY,aAAA;GxDmrLX;CACF;AyDl0LD;EACE,mBAAA;EACA,cAAA;EACA,eAAA;ECRA,4DAAA;EAEA,mBAAA;EACA,oBAAA;EACA,uBAAA;EACA,iBAAA;EACA,wBAAA;EACA,iBAAA;EACA,kBAAA;EACA,sBAAA;EACA,kBAAA;EACA,qBAAA;EACA,oBAAA;EACA,mBAAA;EACA,qBAAA;EACA,kBAAA;EDHA,gBAAA;EnCVA,WAAA;EAGA,yBAAA;CtBy1LD;AyD90LC;EnCdA,aAAA;EAGA,0BAAA;CtB61LD;AyDj1LC;EAAW,iBAAA;EAAmB,eAAA;CzDq1L/B;AyDp1LC;EAAW,iBAAA;EAAmB,eAAA;CzDw1L/B;AyDv1LC;EAAW,gBAAA;EAAmB,eAAA;CzD21L/B;AyD11LC;EAAW,kBAAA;EAAmB,eAAA;CzD81L/B;AyD11LD;EACE,iBAAA;EACA,iBAAA;EACA,YAAA;EACA,mBAAA;EACA,uBAAA;EACA,mBAAA;CzD41LD;AyDx1LD;EACE,mBAAA;EACA,SAAA;EACA,UAAA;EACA,0BAAA;EACA,oBAAA;CzD01LD;AyDt1LC;EACE,UAAA;EACA,UAAA;EACA,kBAAA;EACA,wBAAA;EACA,uBAAA;CzDw1LH;AyDt1LC;EACE,UAAA;EACA,WAAA;EACA,oBAAA;EACA,wBAAA;EACA,uBAAA;CzDw1LH;AyDt1LC;EACE,UAAA;EACA,UAAA;EACA,oBAAA;EACA,wBAAA;EACA,uBAAA;CzDw1LH;AyDt1LC;EACE,SAAA;EACA,QAAA;EACA,iBAAA;EACA,4BAAA;EACA,yBAAA;CzDw1LH;AyDt1LC;EACE,SAAA;EACA,SAAA;EACA,iBAAA;EACA,4BAAA;EACA,wBAAA;CzDw1LH;AyDt1LC;EACE,OAAA;EACA,UAAA;EACA,kBAAA;EACA,wBAAA;EACA,0BAAA;CzDw1LH;AyDt1LC;EACE,OAAA;EACA,WAAA;EACA,iBAAA;EACA,wBAAA;EACA,0BAAA;CzDw1LH;AyDt1LC;EACE,OAAA;EACA,UAAA;EACA,iBAAA;EACA,wBAAA;EACA,0BAAA;CzDw1LH;A2Dr7LD;EACE,mBAAA;EACA,OAAA;EACA,QAAA;EACA,cAAA;EACA,cAAA;EACA,iBAAA;EACA,aAAA;EDXA,4DAAA;EAEA,mBAAA;EACA,oBAAA;EACA,uBAAA;EACA,iBAAA;EACA,wBAAA;EACA,iBAAA;EACA,kBAAA;EACA,sBAAA;EACA,kBAAA;EACA,qBAAA;EACA,oBAAA;EACA,mBAAA;EACA,qBAAA;EACA,kBAAA;ECAA,gBAAA;EAEA,uBAAA;EACA,qCAAA;UAAA,6BAAA;EACA,uBAAA;EACA,qCAAA;EACA,mBAAA;EtD8CA,kDAAA;EACQ,0CAAA;CLq5LT;A2Dh8LC;EAAY,kBAAA;C3Dm8Lb;A2Dl8LC;EAAY,kBAAA;C3Dq8Lb;A2Dp8LC;EAAY,iBAAA;C3Du8Lb;A2Dt8LC;EAAY,mBAAA;C3Dy8Lb;A2Dt8LD;EACE,UAAA;EACA,kBAAA;EACA,gBAAA;EACA,0BAAA;EACA,iCAAA;EACA,2BAAA;C3Dw8LD;A2Dr8LD;EACE,kBAAA;C3Du8LD;A2D/7LC;;EAEE,mBAAA;EACA,eAAA;EACA,SAAA;EACA,UAAA;EACA,0BAAA;EACA,oBAAA;C3Di8LH;A2D97LD;EACE,mBAAA;C3Dg8LD;A2D97LD;EACE,mBAAA;EACA,YAAA;C3Dg8LD;A2D57LC;EACE,UAAA;EACA,mBAAA;EACA,uBAAA;EACA,0BAAA;EACA,sCAAA;EACA,cAAA;C3D87LH;A2D77LG;EACE,aAAA;EACA,YAAA;EACA,mBAAA;EACA,uBAAA;EACA,uBAAA;C3D+7LL;A2D57LC;EACE,SAAA;EACA,YAAA;EACA,kBAAA;EACA,qBAAA;EACA,4BAAA;EACA,wCAAA;C3D87LH;A2D77LG;EACE,aAAA;EACA,UAAA;EACA,cAAA;EACA,qBAAA;EACA,yBAAA;C3D+7LL;A2D57LC;EACE,UAAA;EACA,mBAAA;EACA,oBAAA;EACA,6BAAA;EACA,yCAAA;EACA,WAAA;C3D87LH;A2D77LG;EACE,aAAA;EACA,SAAA;EACA,mBAAA;EACA,oBAAA;EACA,0BAAA;C3D+7LL;A2D37LC;EACE,SAAA;EACA,aAAA;EACA,kBAAA;EACA,sBAAA;EACA,2BAAA;EACA,uCAAA;C3D67LH;A2D57LG;EACE,aAAA;EACA,WAAA;EACA,sBAAA;EACA,wBAAA;EACA,cAAA;C3D87LL;A4DvjMD;EACE,mBAAA;C5DyjMD;A4DtjMD;EACE,mBAAA;EACA,iBAAA;EACA,YAAA;C5DwjMD;A4D3jMD;EAMI,cAAA;EACA,mBAAA;EvD6KF,0CAAA;EACK,qCAAA;EACG,kCAAA;CL44LT;A4DlkMD;;EAcM,eAAA;C5DwjML;A4D9hMC;EA4NF;IvD3DE,uDAAA;IAEK,6CAAA;IACG,uCAAA;IA7JR,oCAAA;IAEQ,4BAAA;IA+GR,4BAAA;IAEQ,oBAAA;GLi7LP;E4D5jMG;;IvDmHJ,2CAAA;IACQ,mCAAA;IuDjHF,QAAA;G5D+jML;E4D7jMG;;IvD8GJ,4CAAA;IACQ,oCAAA;IuD5GF,QAAA;G5DgkML;E4D9jMG;;;IvDyGJ,wCAAA;IACQ,gCAAA;IuDtGF,QAAA;G5DikML;CACF;A4DvmMD;;;EA6CI,eAAA;C5D+jMH;A4D5mMD;EAiDI,QAAA;C5D8jMH;A4D/mMD;;EAsDI,mBAAA;EACA,OAAA;EACA,YAAA;C5D6jMH;A4DrnMD;EA4DI,WAAA;C5D4jMH;A4DxnMD;EA+DI,YAAA;C5D4jMH;A4D3nMD;;EAmEI,QAAA;C5D4jMH;A4D/nMD;EAuEI,YAAA;C5D2jMH;A4DloMD;EA0EI,WAAA;C5D2jMH;A4DnjMD;EACE,mBAAA;EACA,OAAA;EACA,QAAA;EACA,UAAA;EACA,WAAA;EtC9FA,aAAA;EAGA,0BAAA;EsC6FA,gBAAA;EACA,YAAA;EACA,mBAAA;EACA,0CAAA;EACA,mCAAA;C5DsjMD;A4DjjMC;EdnGE,mGAAA;EACA,8FAAA;EACA,qHAAA;EAAA,+FAAA;EACA,4BAAA;EACA,uHAAA;C9CupMH;A4DrjMC;EACE,WAAA;EACA,SAAA;EdxGA,mGAAA;EACA,8FAAA;EACA,qHAAA;EAAA,+FAAA;EACA,4BAAA;EACA,uHAAA;C9CgqMH;A4DvjMC;;EAEE,WAAA;EACA,YAAA;EACA,sBAAA;EtCvHF,aAAA;EAGA,0BAAA;CtB+qMD;A4DzlMD;;;;EAuCI,mBAAA;EACA,SAAA;EACA,kBAAA;EACA,WAAA;EACA,sBAAA;C5DwjMH;A4DnmMD;;EA+CI,UAAA;EACA,mBAAA;C5DwjMH;A4DxmMD;;EAoDI,WAAA;EACA,oBAAA;C5DwjMH;A4D7mMD;;EAyDI,YAAA;EACA,aAAA;EACA,eAAA;EACA,mBAAA;C5DwjMH;A4DnjMG;EACE,iBAAA;C5DqjML;A4DjjMG;EACE,iBAAA;C5DmjML;A4DziMD;EACE,mBAAA;EACA,aAAA;EACA,UAAA;EACA,YAAA;EACA,WAAA;EACA,kBAAA;EACA,gBAAA;EACA,iBAAA;EACA,mBAAA;C5D2iMD;A4DpjMD;EAYI,sBAAA;EACA,YAAA;EACA,aAAA;EACA,YAAA;EACA,oBAAA;EACA,uBAAA;EACA,oBAAA;EACA,gBAAA;EAWA,0BAAA;EACA,mCAAA;C5DiiMH;A4DhkMD;EAkCI,UAAA;EACA,YAAA;EACA,aAAA;EACA,uBAAA;C5DiiMH;A4D1hMD;EACE,mBAAA;EACA,UAAA;EACA,WAAA;EACA,aAAA;EACA,YAAA;EACA,kBAAA;EACA,qBAAA;EACA,YAAA;EACA,mBAAA;EACA,0CAAA;C5D4hMD;A4D3hMC;EACE,kBAAA;C5D6hMH;A4Dp/LD;EAhCE;;;;IAKI,YAAA;IACA,aAAA;IACA,kBAAA;IACA,gBAAA;G5DshMH;E4D9hMD;;IAYI,mBAAA;G5DshMH;E4DliMD;;IAgBI,oBAAA;G5DshMH;E4DjhMD;IACE,UAAA;IACA,WAAA;IACA,qBAAA;G5DmhMD;E4D/gMD;IACE,aAAA;G5DihMD;CACF;A6DhxMC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAEE,aAAA;EACA,eAAA;C7DgzMH;A6D9yMC;;;;;;;;;;;;;;;;EACE,YAAA;C7D+zMH;AiCv0MD;E6BRE,eAAA;EACA,kBAAA;EACA,mBAAA;C9Dk1MD;AiCz0MD;EACE,wBAAA;CjC20MD;AiCz0MD;EACE,uBAAA;CjC20MD;AiCn0MD;EACE,yBAAA;CjCq0MD;AiCn0MD;EACE,0BAAA;CjCq0MD;AiCn0MD;EACE,mBAAA;CjCq0MD;AiCn0MD;E8BzBE,YAAA;EACA,mBAAA;EACA,kBAAA;EACA,8BAAA;EACA,UAAA;C/D+1MD;AiCj0MD;EACE,yBAAA;CjCm0MD;AiC5zMD;EACE,gBAAA;CjC8zMD;AgE/1MD;EACE,oBAAA;ChEi2MD;AgE31MD;;;;ECdE,yBAAA;CjE+2MD;AgE11MD;;;;;;;;;;;;EAYE,yBAAA;ChE41MD;AgEr1MD;EA6IA;IC7LE,0BAAA;GjEy4MC;EiEx4MD;IAAU,0BAAA;GjE24MT;EiE14MD;IAAU,8BAAA;GjE64MT;EiE54MD;;IACU,+BAAA;GjE+4MT;CACF;AgE/1MD;EAwIA;IA1II,0BAAA;GhEq2MD;CACF;AgE/1MD;EAmIA;IArII,2BAAA;GhEq2MD;CACF;AgE/1MD;EA8HA;IAhII,iCAAA;GhEq2MD;CACF;AgE91MD;EAwHA;IC7LE,0BAAA;GjEu6MC;EiEt6MD;IAAU,0BAAA;GjEy6MT;EiEx6MD;IAAU,8BAAA;GjE26MT;EiE16MD;;IACU,+BAAA;GjE66MT;CACF;AgEx2MD;EAmHA;IArHI,0BAAA;GhE82MD;CACF;AgEx2MD;EA8GA;IAhHI,2BAAA;GhE82MD;CACF;AgEx2MD;EAyGA;IA3GI,iCAAA;GhE82MD;CACF;AgEv2MD;EAmGA;IC7LE,0BAAA;GjEq8MC;EiEp8MD;IAAU,0BAAA;GjEu8MT;EiEt8MD;IAAU,8BAAA;GjEy8MT;EiEx8MD;;IACU,+BAAA;GjE28MT;CACF;AgEj3MD;EA8FA;IAhGI,0BAAA;GhEu3MD;CACF;AgEj3MD;EAyFA;IA3FI,2BAAA;GhEu3MD;CACF;AgEj3MD;EAoFA;IAtFI,iCAAA;GhEu3MD;CACF;AgEh3MD;EA8EA;IC7LE,0BAAA;GjEm+MC;EiEl+MD;IAAU,0BAAA;GjEq+MT;EiEp+MD;IAAU,8BAAA;GjEu+MT;EiEt+MD;;IACU,+BAAA;GjEy+MT;CACF;AgE13MD;EAyEA;IA3EI,0BAAA;GhEg4MD;CACF;AgE13MD;EAoEA;IAtEI,2BAAA;GhEg4MD;CACF;AgE13MD;EA+DA;IAjEI,iCAAA;GhEg4MD;CACF;AgEz3MD;EAyDA;ICrLE,yBAAA;GjEy/MC;CACF;AgEz3MD;EAoDA;ICrLE,yBAAA;GjE8/MC;CACF;AgEz3MD;EA+CA;ICrLE,yBAAA;GjEmgNC;CACF;AgEz3MD;EA0CA;ICrLE,yBAAA;GjEwgNC;CACF;AgEt3MD;ECnJE,yBAAA;CjE4gND;AgEn3MD;EA4BA;IC7LE,0BAAA;GjEwhNC;EiEvhND;IAAU,0BAAA;GjE0hNT;EiEzhND;IAAU,8BAAA;GjE4hNT;EiE3hND;;IACU,+BAAA;GjE8hNT;CACF;AgEj4MD;EACE,yBAAA;ChEm4MD;AgE93MD;EAqBA;IAvBI,0BAAA;GhEo4MD;CACF;AgEl4MD;EACE,yBAAA;ChEo4MD;AgE/3MD;EAcA;IAhBI,2BAAA;GhEq4MD;CACF;AgEn4MD;EACE,yBAAA;ChEq4MD;AgEh4MD;EAOA;IATI,iCAAA;GhEs4MD;CACF;AgE/3MD;EACA;ICrLE,yBAAA;GjEujNC;CACF","file":"bootstrap.css","sourcesContent":["/*!\n * Bootstrap v3.3.6 (http://getbootstrap.com)\n * Copyright 2011-2015 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n */\n/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */\nhtml {\n font-family: sans-serif;\n -ms-text-size-adjust: 100%;\n -webkit-text-size-adjust: 100%;\n}\nbody {\n margin: 0;\n}\narticle,\naside,\ndetails,\nfigcaption,\nfigure,\nfooter,\nheader,\nhgroup,\nmain,\nmenu,\nnav,\nsection,\nsummary {\n display: block;\n}\naudio,\ncanvas,\nprogress,\nvideo {\n display: inline-block;\n vertical-align: baseline;\n}\naudio:not([controls]) {\n display: none;\n height: 0;\n}\n[hidden],\ntemplate {\n display: none;\n}\na {\n background-color: transparent;\n}\na:active,\na:hover {\n outline: 0;\n}\nabbr[title] {\n border-bottom: 1px dotted;\n}\nb,\nstrong {\n font-weight: bold;\n}\ndfn {\n font-style: italic;\n}\nh1 {\n font-size: 2em;\n margin: 0.67em 0;\n}\nmark {\n background: #ff0;\n color: #000;\n}\nsmall {\n font-size: 80%;\n}\nsub,\nsup {\n font-size: 75%;\n line-height: 0;\n position: relative;\n vertical-align: baseline;\n}\nsup {\n top: -0.5em;\n}\nsub {\n bottom: -0.25em;\n}\nimg {\n border: 0;\n}\nsvg:not(:root) {\n overflow: hidden;\n}\nfigure {\n margin: 1em 40px;\n}\nhr {\n box-sizing: content-box;\n height: 0;\n}\npre {\n overflow: auto;\n}\ncode,\nkbd,\npre,\nsamp {\n font-family: monospace, monospace;\n font-size: 1em;\n}\nbutton,\ninput,\noptgroup,\nselect,\ntextarea {\n color: inherit;\n font: inherit;\n margin: 0;\n}\nbutton {\n overflow: visible;\n}\nbutton,\nselect {\n text-transform: none;\n}\nbutton,\nhtml input[type=\"button\"],\ninput[type=\"reset\"],\ninput[type=\"submit\"] {\n -webkit-appearance: button;\n cursor: pointer;\n}\nbutton[disabled],\nhtml input[disabled] {\n cursor: default;\n}\nbutton::-moz-focus-inner,\ninput::-moz-focus-inner {\n border: 0;\n padding: 0;\n}\ninput {\n line-height: normal;\n}\ninput[type=\"checkbox\"],\ninput[type=\"radio\"] {\n box-sizing: border-box;\n padding: 0;\n}\ninput[type=\"number\"]::-webkit-inner-spin-button,\ninput[type=\"number\"]::-webkit-outer-spin-button {\n height: auto;\n}\ninput[type=\"search\"] {\n -webkit-appearance: textfield;\n box-sizing: content-box;\n}\ninput[type=\"search\"]::-webkit-search-cancel-button,\ninput[type=\"search\"]::-webkit-search-decoration {\n -webkit-appearance: none;\n}\nfieldset {\n border: 1px solid #c0c0c0;\n margin: 0 2px;\n padding: 0.35em 0.625em 0.75em;\n}\nlegend {\n border: 0;\n padding: 0;\n}\ntextarea {\n overflow: auto;\n}\noptgroup {\n font-weight: bold;\n}\ntable {\n border-collapse: collapse;\n border-spacing: 0;\n}\ntd,\nth {\n padding: 0;\n}\n/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */\n@media print {\n *,\n *:before,\n *:after {\n background: transparent !important;\n color: #000 !important;\n box-shadow: none !important;\n text-shadow: none !important;\n }\n a,\n a:visited {\n text-decoration: underline;\n }\n a[href]:after {\n content: \" (\" attr(href) \")\";\n }\n abbr[title]:after {\n content: \" (\" attr(title) \")\";\n }\n a[href^=\"#\"]:after,\n a[href^=\"javascript:\"]:after {\n content: \"\";\n }\n pre,\n blockquote {\n border: 1px solid #999;\n page-break-inside: avoid;\n }\n thead {\n display: table-header-group;\n }\n tr,\n img {\n page-break-inside: avoid;\n }\n img {\n max-width: 100% !important;\n }\n p,\n h2,\n h3 {\n orphans: 3;\n widows: 3;\n }\n h2,\n h3 {\n page-break-after: avoid;\n }\n .navbar {\n display: none;\n }\n .btn > .caret,\n .dropup > .btn > .caret {\n border-top-color: #000 !important;\n }\n .label {\n border: 1px solid #000;\n }\n .table {\n border-collapse: collapse !important;\n }\n .table td,\n .table th {\n background-color: #fff !important;\n }\n .table-bordered th,\n .table-bordered td {\n border: 1px solid #ddd !important;\n }\n}\n@font-face {\n font-family: 'Glyphicons Halflings';\n src: url('../fonts/glyphicons-halflings-regular.eot');\n src: url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), url('../fonts/glyphicons-halflings-regular.woff2') format('woff2'), url('../fonts/glyphicons-halflings-regular.woff') format('woff'), url('../fonts/glyphicons-halflings-regular.ttf') format('truetype'), url('../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg');\n}\n.glyphicon {\n position: relative;\n top: 1px;\n display: inline-block;\n font-family: 'Glyphicons Halflings';\n font-style: normal;\n font-weight: normal;\n line-height: 1;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n}\n.glyphicon-asterisk:before {\n content: \"\\002a\";\n}\n.glyphicon-plus:before {\n content: \"\\002b\";\n}\n.glyphicon-euro:before,\n.glyphicon-eur:before {\n content: \"\\20ac\";\n}\n.glyphicon-minus:before {\n content: \"\\2212\";\n}\n.glyphicon-cloud:before {\n content: \"\\2601\";\n}\n.glyphicon-envelope:before {\n content: \"\\2709\";\n}\n.glyphicon-pencil:before {\n content: \"\\270f\";\n}\n.glyphicon-glass:before {\n content: \"\\e001\";\n}\n.glyphicon-music:before {\n content: \"\\e002\";\n}\n.glyphicon-search:before {\n content: \"\\e003\";\n}\n.glyphicon-heart:before {\n content: \"\\e005\";\n}\n.glyphicon-star:before {\n content: \"\\e006\";\n}\n.glyphicon-star-empty:before {\n content: \"\\e007\";\n}\n.glyphicon-user:before {\n content: \"\\e008\";\n}\n.glyphicon-film:before {\n content: \"\\e009\";\n}\n.glyphicon-th-large:before {\n content: \"\\e010\";\n}\n.glyphicon-th:before {\n content: \"\\e011\";\n}\n.glyphicon-th-list:before {\n content: \"\\e012\";\n}\n.glyphicon-ok:before {\n content: \"\\e013\";\n}\n.glyphicon-remove:before {\n content: \"\\e014\";\n}\n.glyphicon-zoom-in:before {\n content: \"\\e015\";\n}\n.glyphicon-zoom-out:before {\n content: \"\\e016\";\n}\n.glyphicon-off:before {\n content: \"\\e017\";\n}\n.glyphicon-signal:before {\n content: \"\\e018\";\n}\n.glyphicon-cog:before {\n content: \"\\e019\";\n}\n.glyphicon-trash:before {\n content: \"\\e020\";\n}\n.glyphicon-home:before {\n content: \"\\e021\";\n}\n.glyphicon-file:before {\n content: \"\\e022\";\n}\n.glyphicon-time:before {\n content: \"\\e023\";\n}\n.glyphicon-road:before {\n content: \"\\e024\";\n}\n.glyphicon-download-alt:before {\n content: \"\\e025\";\n}\n.glyphicon-download:before {\n content: \"\\e026\";\n}\n.glyphicon-upload:before {\n content: \"\\e027\";\n}\n.glyphicon-inbox:before {\n content: \"\\e028\";\n}\n.glyphicon-play-circle:before {\n content: \"\\e029\";\n}\n.glyphicon-repeat:before {\n content: \"\\e030\";\n}\n.glyphicon-refresh:before {\n content: \"\\e031\";\n}\n.glyphicon-list-alt:before {\n content: \"\\e032\";\n}\n.glyphicon-lock:before {\n content: \"\\e033\";\n}\n.glyphicon-flag:before {\n content: \"\\e034\";\n}\n.glyphicon-headphones:before {\n content: \"\\e035\";\n}\n.glyphicon-volume-off:before {\n content: \"\\e036\";\n}\n.glyphicon-volume-down:before {\n content: \"\\e037\";\n}\n.glyphicon-volume-up:before {\n content: \"\\e038\";\n}\n.glyphicon-qrcode:before {\n content: \"\\e039\";\n}\n.glyphicon-barcode:before {\n content: \"\\e040\";\n}\n.glyphicon-tag:before {\n content: \"\\e041\";\n}\n.glyphicon-tags:before {\n content: \"\\e042\";\n}\n.glyphicon-book:before {\n content: \"\\e043\";\n}\n.glyphicon-bookmark:before {\n content: \"\\e044\";\n}\n.glyphicon-print:before {\n content: \"\\e045\";\n}\n.glyphicon-camera:before {\n content: \"\\e046\";\n}\n.glyphicon-font:before {\n content: \"\\e047\";\n}\n.glyphicon-bold:before {\n content: \"\\e048\";\n}\n.glyphicon-italic:before {\n content: \"\\e049\";\n}\n.glyphicon-text-height:before {\n content: \"\\e050\";\n}\n.glyphicon-text-width:before {\n content: \"\\e051\";\n}\n.glyphicon-align-left:before {\n content: \"\\e052\";\n}\n.glyphicon-align-center:before {\n content: \"\\e053\";\n}\n.glyphicon-align-right:before {\n content: \"\\e054\";\n}\n.glyphicon-align-justify:before {\n content: \"\\e055\";\n}\n.glyphicon-list:before {\n content: \"\\e056\";\n}\n.glyphicon-indent-left:before {\n content: \"\\e057\";\n}\n.glyphicon-indent-right:before {\n content: \"\\e058\";\n}\n.glyphicon-facetime-video:before {\n content: \"\\e059\";\n}\n.glyphicon-picture:before {\n content: \"\\e060\";\n}\n.glyphicon-map-marker:before {\n content: \"\\e062\";\n}\n.glyphicon-adjust:before {\n content: \"\\e063\";\n}\n.glyphicon-tint:before {\n content: \"\\e064\";\n}\n.glyphicon-edit:before {\n content: \"\\e065\";\n}\n.glyphicon-share:before {\n content: \"\\e066\";\n}\n.glyphicon-check:before {\n content: \"\\e067\";\n}\n.glyphicon-move:before {\n content: \"\\e068\";\n}\n.glyphicon-step-backward:before {\n content: \"\\e069\";\n}\n.glyphicon-fast-backward:before {\n content: \"\\e070\";\n}\n.glyphicon-backward:before {\n content: \"\\e071\";\n}\n.glyphicon-play:before {\n content: \"\\e072\";\n}\n.glyphicon-pause:before {\n content: \"\\e073\";\n}\n.glyphicon-stop:before {\n content: \"\\e074\";\n}\n.glyphicon-forward:before {\n content: \"\\e075\";\n}\n.glyphicon-fast-forward:before {\n content: \"\\e076\";\n}\n.glyphicon-step-forward:before {\n content: \"\\e077\";\n}\n.glyphicon-eject:before {\n content: \"\\e078\";\n}\n.glyphicon-chevron-left:before {\n content: \"\\e079\";\n}\n.glyphicon-chevron-right:before {\n content: \"\\e080\";\n}\n.glyphicon-plus-sign:before {\n content: \"\\e081\";\n}\n.glyphicon-minus-sign:before {\n content: \"\\e082\";\n}\n.glyphicon-remove-sign:before {\n content: \"\\e083\";\n}\n.glyphicon-ok-sign:before {\n content: \"\\e084\";\n}\n.glyphicon-question-sign:before {\n content: \"\\e085\";\n}\n.glyphicon-info-sign:before {\n content: \"\\e086\";\n}\n.glyphicon-screenshot:before {\n content: \"\\e087\";\n}\n.glyphicon-remove-circle:before {\n content: \"\\e088\";\n}\n.glyphicon-ok-circle:before {\n content: \"\\e089\";\n}\n.glyphicon-ban-circle:before {\n content: \"\\e090\";\n}\n.glyphicon-arrow-left:before {\n content: \"\\e091\";\n}\n.glyphicon-arrow-right:before {\n content: \"\\e092\";\n}\n.glyphicon-arrow-up:before {\n content: \"\\e093\";\n}\n.glyphicon-arrow-down:before {\n content: \"\\e094\";\n}\n.glyphicon-share-alt:before {\n content: \"\\e095\";\n}\n.glyphicon-resize-full:before {\n content: \"\\e096\";\n}\n.glyphicon-resize-small:before {\n content: \"\\e097\";\n}\n.glyphicon-exclamation-sign:before {\n content: \"\\e101\";\n}\n.glyphicon-gift:before {\n content: \"\\e102\";\n}\n.glyphicon-leaf:before {\n content: \"\\e103\";\n}\n.glyphicon-fire:before {\n content: \"\\e104\";\n}\n.glyphicon-eye-open:before {\n content: \"\\e105\";\n}\n.glyphicon-eye-close:before {\n content: \"\\e106\";\n}\n.glyphicon-warning-sign:before {\n content: \"\\e107\";\n}\n.glyphicon-plane:before {\n content: \"\\e108\";\n}\n.glyphicon-calendar:before {\n content: \"\\e109\";\n}\n.glyphicon-random:before {\n content: \"\\e110\";\n}\n.glyphicon-comment:before {\n content: \"\\e111\";\n}\n.glyphicon-magnet:before {\n content: \"\\e112\";\n}\n.glyphicon-chevron-up:before {\n content: \"\\e113\";\n}\n.glyphicon-chevron-down:before {\n content: \"\\e114\";\n}\n.glyphicon-retweet:before {\n content: \"\\e115\";\n}\n.glyphicon-shopping-cart:before {\n content: \"\\e116\";\n}\n.glyphicon-folder-close:before {\n content: \"\\e117\";\n}\n.glyphicon-folder-open:before {\n content: \"\\e118\";\n}\n.glyphicon-resize-vertical:before {\n content: \"\\e119\";\n}\n.glyphicon-resize-horizontal:before {\n content: \"\\e120\";\n}\n.glyphicon-hdd:before {\n content: \"\\e121\";\n}\n.glyphicon-bullhorn:before {\n content: \"\\e122\";\n}\n.glyphicon-bell:before {\n content: \"\\e123\";\n}\n.glyphicon-certificate:before {\n content: \"\\e124\";\n}\n.glyphicon-thumbs-up:before {\n content: \"\\e125\";\n}\n.glyphicon-thumbs-down:before {\n content: \"\\e126\";\n}\n.glyphicon-hand-right:before {\n content: \"\\e127\";\n}\n.glyphicon-hand-left:before {\n content: \"\\e128\";\n}\n.glyphicon-hand-up:before {\n content: \"\\e129\";\n}\n.glyphicon-hand-down:before {\n content: \"\\e130\";\n}\n.glyphicon-circle-arrow-right:before {\n content: \"\\e131\";\n}\n.glyphicon-circle-arrow-left:before {\n content: \"\\e132\";\n}\n.glyphicon-circle-arrow-up:before {\n content: \"\\e133\";\n}\n.glyphicon-circle-arrow-down:before {\n content: \"\\e134\";\n}\n.glyphicon-globe:before {\n content: \"\\e135\";\n}\n.glyphicon-wrench:before {\n content: \"\\e136\";\n}\n.glyphicon-tasks:before {\n content: \"\\e137\";\n}\n.glyphicon-filter:before {\n content: \"\\e138\";\n}\n.glyphicon-briefcase:before {\n content: \"\\e139\";\n}\n.glyphicon-fullscreen:before {\n content: \"\\e140\";\n}\n.glyphicon-dashboard:before {\n content: \"\\e141\";\n}\n.glyphicon-paperclip:before {\n content: \"\\e142\";\n}\n.glyphicon-heart-empty:before {\n content: \"\\e143\";\n}\n.glyphicon-link:before {\n content: \"\\e144\";\n}\n.glyphicon-phone:before {\n content: \"\\e145\";\n}\n.glyphicon-pushpin:before {\n content: \"\\e146\";\n}\n.glyphicon-usd:before {\n content: \"\\e148\";\n}\n.glyphicon-gbp:before {\n content: \"\\e149\";\n}\n.glyphicon-sort:before {\n content: \"\\e150\";\n}\n.glyphicon-sort-by-alphabet:before {\n content: \"\\e151\";\n}\n.glyphicon-sort-by-alphabet-alt:before {\n content: \"\\e152\";\n}\n.glyphicon-sort-by-order:before {\n content: \"\\e153\";\n}\n.glyphicon-sort-by-order-alt:before {\n content: \"\\e154\";\n}\n.glyphicon-sort-by-attributes:before {\n content: \"\\e155\";\n}\n.glyphicon-sort-by-attributes-alt:before {\n content: \"\\e156\";\n}\n.glyphicon-unchecked:before {\n content: \"\\e157\";\n}\n.glyphicon-expand:before {\n content: \"\\e158\";\n}\n.glyphicon-collapse-down:before {\n content: \"\\e159\";\n}\n.glyphicon-collapse-up:before {\n content: \"\\e160\";\n}\n.glyphicon-log-in:before {\n content: \"\\e161\";\n}\n.glyphicon-flash:before {\n content: \"\\e162\";\n}\n.glyphicon-log-out:before {\n content: \"\\e163\";\n}\n.glyphicon-new-window:before {\n content: \"\\e164\";\n}\n.glyphicon-record:before {\n content: \"\\e165\";\n}\n.glyphicon-save:before {\n content: \"\\e166\";\n}\n.glyphicon-open:before {\n content: \"\\e167\";\n}\n.glyphicon-saved:before {\n content: \"\\e168\";\n}\n.glyphicon-import:before {\n content: \"\\e169\";\n}\n.glyphicon-export:before {\n content: \"\\e170\";\n}\n.glyphicon-send:before {\n content: \"\\e171\";\n}\n.glyphicon-floppy-disk:before {\n content: \"\\e172\";\n}\n.glyphicon-floppy-saved:before {\n content: \"\\e173\";\n}\n.glyphicon-floppy-remove:before {\n content: \"\\e174\";\n}\n.glyphicon-floppy-save:before {\n content: \"\\e175\";\n}\n.glyphicon-floppy-open:before {\n content: \"\\e176\";\n}\n.glyphicon-credit-card:before {\n content: \"\\e177\";\n}\n.glyphicon-transfer:before {\n content: \"\\e178\";\n}\n.glyphicon-cutlery:before {\n content: \"\\e179\";\n}\n.glyphicon-header:before {\n content: \"\\e180\";\n}\n.glyphicon-compressed:before {\n content: \"\\e181\";\n}\n.glyphicon-earphone:before {\n content: \"\\e182\";\n}\n.glyphicon-phone-alt:before {\n content: \"\\e183\";\n}\n.glyphicon-tower:before {\n content: \"\\e184\";\n}\n.glyphicon-stats:before {\n content: \"\\e185\";\n}\n.glyphicon-sd-video:before {\n content: \"\\e186\";\n}\n.glyphicon-hd-video:before {\n content: \"\\e187\";\n}\n.glyphicon-subtitles:before {\n content: \"\\e188\";\n}\n.glyphicon-sound-stereo:before {\n content: \"\\e189\";\n}\n.glyphicon-sound-dolby:before {\n content: \"\\e190\";\n}\n.glyphicon-sound-5-1:before {\n content: \"\\e191\";\n}\n.glyphicon-sound-6-1:before {\n content: \"\\e192\";\n}\n.glyphicon-sound-7-1:before {\n content: \"\\e193\";\n}\n.glyphicon-copyright-mark:before {\n content: \"\\e194\";\n}\n.glyphicon-registration-mark:before {\n content: \"\\e195\";\n}\n.glyphicon-cloud-download:before {\n content: \"\\e197\";\n}\n.glyphicon-cloud-upload:before {\n content: \"\\e198\";\n}\n.glyphicon-tree-conifer:before {\n content: \"\\e199\";\n}\n.glyphicon-tree-deciduous:before {\n content: \"\\e200\";\n}\n.glyphicon-cd:before {\n content: \"\\e201\";\n}\n.glyphicon-save-file:before {\n content: \"\\e202\";\n}\n.glyphicon-open-file:before {\n content: \"\\e203\";\n}\n.glyphicon-level-up:before {\n content: \"\\e204\";\n}\n.glyphicon-copy:before {\n content: \"\\e205\";\n}\n.glyphicon-paste:before {\n content: \"\\e206\";\n}\n.glyphicon-alert:before {\n content: \"\\e209\";\n}\n.glyphicon-equalizer:before {\n content: \"\\e210\";\n}\n.glyphicon-king:before {\n content: \"\\e211\";\n}\n.glyphicon-queen:before {\n content: \"\\e212\";\n}\n.glyphicon-pawn:before {\n content: \"\\e213\";\n}\n.glyphicon-bishop:before {\n content: \"\\e214\";\n}\n.glyphicon-knight:before {\n content: \"\\e215\";\n}\n.glyphicon-baby-formula:before {\n content: \"\\e216\";\n}\n.glyphicon-tent:before {\n content: \"\\26fa\";\n}\n.glyphicon-blackboard:before {\n content: \"\\e218\";\n}\n.glyphicon-bed:before {\n content: \"\\e219\";\n}\n.glyphicon-apple:before {\n content: \"\\f8ff\";\n}\n.glyphicon-erase:before {\n content: \"\\e221\";\n}\n.glyphicon-hourglass:before {\n content: \"\\231b\";\n}\n.glyphicon-lamp:before {\n content: \"\\e223\";\n}\n.glyphicon-duplicate:before {\n content: \"\\e224\";\n}\n.glyphicon-piggy-bank:before {\n content: \"\\e225\";\n}\n.glyphicon-scissors:before {\n content: \"\\e226\";\n}\n.glyphicon-bitcoin:before {\n content: \"\\e227\";\n}\n.glyphicon-btc:before {\n content: \"\\e227\";\n}\n.glyphicon-xbt:before {\n content: \"\\e227\";\n}\n.glyphicon-yen:before {\n content: \"\\00a5\";\n}\n.glyphicon-jpy:before {\n content: \"\\00a5\";\n}\n.glyphicon-ruble:before {\n content: \"\\20bd\";\n}\n.glyphicon-rub:before {\n content: \"\\20bd\";\n}\n.glyphicon-scale:before {\n content: \"\\e230\";\n}\n.glyphicon-ice-lolly:before {\n content: \"\\e231\";\n}\n.glyphicon-ice-lolly-tasted:before {\n content: \"\\e232\";\n}\n.glyphicon-education:before {\n content: \"\\e233\";\n}\n.glyphicon-option-horizontal:before {\n content: \"\\e234\";\n}\n.glyphicon-option-vertical:before {\n content: \"\\e235\";\n}\n.glyphicon-menu-hamburger:before {\n content: \"\\e236\";\n}\n.glyphicon-modal-window:before {\n content: \"\\e237\";\n}\n.glyphicon-oil:before {\n content: \"\\e238\";\n}\n.glyphicon-grain:before {\n content: \"\\e239\";\n}\n.glyphicon-sunglasses:before {\n content: \"\\e240\";\n}\n.glyphicon-text-size:before {\n content: \"\\e241\";\n}\n.glyphicon-text-color:before {\n content: \"\\e242\";\n}\n.glyphicon-text-background:before {\n content: \"\\e243\";\n}\n.glyphicon-object-align-top:before {\n content: \"\\e244\";\n}\n.glyphicon-object-align-bottom:before {\n content: \"\\e245\";\n}\n.glyphicon-object-align-horizontal:before {\n content: \"\\e246\";\n}\n.glyphicon-object-align-left:before {\n content: \"\\e247\";\n}\n.glyphicon-object-align-vertical:before {\n content: \"\\e248\";\n}\n.glyphicon-object-align-right:before {\n content: \"\\e249\";\n}\n.glyphicon-triangle-right:before {\n content: \"\\e250\";\n}\n.glyphicon-triangle-left:before {\n content: \"\\e251\";\n}\n.glyphicon-triangle-bottom:before {\n content: \"\\e252\";\n}\n.glyphicon-triangle-top:before {\n content: \"\\e253\";\n}\n.glyphicon-console:before {\n content: \"\\e254\";\n}\n.glyphicon-superscript:before {\n content: \"\\e255\";\n}\n.glyphicon-subscript:before {\n content: \"\\e256\";\n}\n.glyphicon-menu-left:before {\n content: \"\\e257\";\n}\n.glyphicon-menu-right:before {\n content: \"\\e258\";\n}\n.glyphicon-menu-down:before {\n content: \"\\e259\";\n}\n.glyphicon-menu-up:before {\n content: \"\\e260\";\n}\n* {\n -webkit-box-sizing: border-box;\n -moz-box-sizing: border-box;\n box-sizing: border-box;\n}\n*:before,\n*:after {\n -webkit-box-sizing: border-box;\n -moz-box-sizing: border-box;\n box-sizing: border-box;\n}\nhtml {\n font-size: 10px;\n -webkit-tap-highlight-color: rgba(0, 0, 0, 0);\n}\nbody {\n font-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n font-size: 14px;\n line-height: 1.42857143;\n color: #333333;\n background-color: #fff;\n}\ninput,\nbutton,\nselect,\ntextarea {\n font-family: inherit;\n font-size: inherit;\n line-height: inherit;\n}\na {\n color: #337ab7;\n text-decoration: none;\n}\na:hover,\na:focus {\n color: #23527c;\n text-decoration: underline;\n}\na:focus {\n outline: thin dotted;\n outline: 5px auto -webkit-focus-ring-color;\n outline-offset: -2px;\n}\nfigure {\n margin: 0;\n}\nimg {\n vertical-align: middle;\n}\n.img-responsive,\n.thumbnail > img,\n.thumbnail a > img,\n.carousel-inner > .item > img,\n.carousel-inner > .item > a > img {\n display: block;\n max-width: 100%;\n height: auto;\n}\n.img-rounded {\n border-radius: 6px;\n}\n.img-thumbnail {\n padding: 4px;\n line-height: 1.42857143;\n background-color: #fff;\n border: 1px solid #ddd;\n border-radius: 4px;\n -webkit-transition: all 0.2s ease-in-out;\n -o-transition: all 0.2s ease-in-out;\n transition: all 0.2s ease-in-out;\n display: inline-block;\n max-width: 100%;\n height: auto;\n}\n.img-circle {\n border-radius: 50%;\n}\nhr {\n margin-top: 20px;\n margin-bottom: 20px;\n border: 0;\n border-top: 1px solid #eeeeee;\n}\n.sr-only {\n position: absolute;\n width: 1px;\n height: 1px;\n margin: -1px;\n padding: 0;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n border: 0;\n}\n.sr-only-focusable:active,\n.sr-only-focusable:focus {\n position: static;\n width: auto;\n height: auto;\n margin: 0;\n overflow: visible;\n clip: auto;\n}\n[role=\"button\"] {\n cursor: pointer;\n}\nh1,\nh2,\nh3,\nh4,\nh5,\nh6,\n.h1,\n.h2,\n.h3,\n.h4,\n.h5,\n.h6 {\n font-family: inherit;\n font-weight: 500;\n line-height: 1.1;\n color: inherit;\n}\nh1 small,\nh2 small,\nh3 small,\nh4 small,\nh5 small,\nh6 small,\n.h1 small,\n.h2 small,\n.h3 small,\n.h4 small,\n.h5 small,\n.h6 small,\nh1 .small,\nh2 .small,\nh3 .small,\nh4 .small,\nh5 .small,\nh6 .small,\n.h1 .small,\n.h2 .small,\n.h3 .small,\n.h4 .small,\n.h5 .small,\n.h6 .small {\n font-weight: normal;\n line-height: 1;\n color: #777777;\n}\nh1,\n.h1,\nh2,\n.h2,\nh3,\n.h3 {\n margin-top: 20px;\n margin-bottom: 10px;\n}\nh1 small,\n.h1 small,\nh2 small,\n.h2 small,\nh3 small,\n.h3 small,\nh1 .small,\n.h1 .small,\nh2 .small,\n.h2 .small,\nh3 .small,\n.h3 .small {\n font-size: 65%;\n}\nh4,\n.h4,\nh5,\n.h5,\nh6,\n.h6 {\n margin-top: 10px;\n margin-bottom: 10px;\n}\nh4 small,\n.h4 small,\nh5 small,\n.h5 small,\nh6 small,\n.h6 small,\nh4 .small,\n.h4 .small,\nh5 .small,\n.h5 .small,\nh6 .small,\n.h6 .small {\n font-size: 75%;\n}\nh1,\n.h1 {\n font-size: 36px;\n}\nh2,\n.h2 {\n font-size: 30px;\n}\nh3,\n.h3 {\n font-size: 24px;\n}\nh4,\n.h4 {\n font-size: 18px;\n}\nh5,\n.h5 {\n font-size: 14px;\n}\nh6,\n.h6 {\n font-size: 12px;\n}\np {\n margin: 0 0 10px;\n}\n.lead {\n margin-bottom: 20px;\n font-size: 16px;\n font-weight: 300;\n line-height: 1.4;\n}\n@media (min-width: 768px) {\n .lead {\n font-size: 21px;\n }\n}\nsmall,\n.small {\n font-size: 85%;\n}\nmark,\n.mark {\n background-color: #fcf8e3;\n padding: .2em;\n}\n.text-left {\n text-align: left;\n}\n.text-right {\n text-align: right;\n}\n.text-center {\n text-align: center;\n}\n.text-justify {\n text-align: justify;\n}\n.text-nowrap {\n white-space: nowrap;\n}\n.text-lowercase {\n text-transform: lowercase;\n}\n.text-uppercase {\n text-transform: uppercase;\n}\n.text-capitalize {\n text-transform: capitalize;\n}\n.text-muted {\n color: #777777;\n}\n.text-primary {\n color: #337ab7;\n}\na.text-primary:hover,\na.text-primary:focus {\n color: #286090;\n}\n.text-success {\n color: #3c763d;\n}\na.text-success:hover,\na.text-success:focus {\n color: #2b542c;\n}\n.text-info {\n color: #31708f;\n}\na.text-info:hover,\na.text-info:focus {\n color: #245269;\n}\n.text-warning {\n color: #8a6d3b;\n}\na.text-warning:hover,\na.text-warning:focus {\n color: #66512c;\n}\n.text-danger {\n color: #a94442;\n}\na.text-danger:hover,\na.text-danger:focus {\n color: #843534;\n}\n.bg-primary {\n color: #fff;\n background-color: #337ab7;\n}\na.bg-primary:hover,\na.bg-primary:focus {\n background-color: #286090;\n}\n.bg-success {\n background-color: #dff0d8;\n}\na.bg-success:hover,\na.bg-success:focus {\n background-color: #c1e2b3;\n}\n.bg-info {\n background-color: #d9edf7;\n}\na.bg-info:hover,\na.bg-info:focus {\n background-color: #afd9ee;\n}\n.bg-warning {\n background-color: #fcf8e3;\n}\na.bg-warning:hover,\na.bg-warning:focus {\n background-color: #f7ecb5;\n}\n.bg-danger {\n background-color: #f2dede;\n}\na.bg-danger:hover,\na.bg-danger:focus {\n background-color: #e4b9b9;\n}\n.page-header {\n padding-bottom: 9px;\n margin: 40px 0 20px;\n border-bottom: 1px solid #eeeeee;\n}\nul,\nol {\n margin-top: 0;\n margin-bottom: 10px;\n}\nul ul,\nol ul,\nul ol,\nol ol {\n margin-bottom: 0;\n}\n.list-unstyled {\n padding-left: 0;\n list-style: none;\n}\n.list-inline {\n padding-left: 0;\n list-style: none;\n margin-left: -5px;\n}\n.list-inline > li {\n display: inline-block;\n padding-left: 5px;\n padding-right: 5px;\n}\ndl {\n margin-top: 0;\n margin-bottom: 20px;\n}\ndt,\ndd {\n line-height: 1.42857143;\n}\ndt {\n font-weight: bold;\n}\ndd {\n margin-left: 0;\n}\n@media (min-width: 768px) {\n .dl-horizontal dt {\n float: left;\n width: 160px;\n clear: left;\n text-align: right;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n .dl-horizontal dd {\n margin-left: 180px;\n }\n}\nabbr[title],\nabbr[data-original-title] {\n cursor: help;\n border-bottom: 1px dotted #777777;\n}\n.initialism {\n font-size: 90%;\n text-transform: uppercase;\n}\nblockquote {\n padding: 10px 20px;\n margin: 0 0 20px;\n font-size: 17.5px;\n border-left: 5px solid #eeeeee;\n}\nblockquote p:last-child,\nblockquote ul:last-child,\nblockquote ol:last-child {\n margin-bottom: 0;\n}\nblockquote footer,\nblockquote small,\nblockquote .small {\n display: block;\n font-size: 80%;\n line-height: 1.42857143;\n color: #777777;\n}\nblockquote footer:before,\nblockquote small:before,\nblockquote .small:before {\n content: '\\2014 \\00A0';\n}\n.blockquote-reverse,\nblockquote.pull-right {\n padding-right: 15px;\n padding-left: 0;\n border-right: 5px solid #eeeeee;\n border-left: 0;\n text-align: right;\n}\n.blockquote-reverse footer:before,\nblockquote.pull-right footer:before,\n.blockquote-reverse small:before,\nblockquote.pull-right small:before,\n.blockquote-reverse .small:before,\nblockquote.pull-right .small:before {\n content: '';\n}\n.blockquote-reverse footer:after,\nblockquote.pull-right footer:after,\n.blockquote-reverse small:after,\nblockquote.pull-right small:after,\n.blockquote-reverse .small:after,\nblockquote.pull-right .small:after {\n content: '\\00A0 \\2014';\n}\naddress {\n margin-bottom: 20px;\n font-style: normal;\n line-height: 1.42857143;\n}\ncode,\nkbd,\npre,\nsamp {\n font-family: Menlo, Monaco, Consolas, \"Courier New\", monospace;\n}\ncode {\n padding: 2px 4px;\n font-size: 90%;\n color: #c7254e;\n background-color: #f9f2f4;\n border-radius: 4px;\n}\nkbd {\n padding: 2px 4px;\n font-size: 90%;\n color: #fff;\n background-color: #333;\n border-radius: 3px;\n box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.25);\n}\nkbd kbd {\n padding: 0;\n font-size: 100%;\n font-weight: bold;\n box-shadow: none;\n}\npre {\n display: block;\n padding: 9.5px;\n margin: 0 0 10px;\n font-size: 13px;\n line-height: 1.42857143;\n word-break: break-all;\n word-wrap: break-word;\n color: #333333;\n background-color: #f5f5f5;\n border: 1px solid #ccc;\n border-radius: 4px;\n}\npre code {\n padding: 0;\n font-size: inherit;\n color: inherit;\n white-space: pre-wrap;\n background-color: transparent;\n border-radius: 0;\n}\n.pre-scrollable {\n max-height: 340px;\n overflow-y: scroll;\n}\n.container {\n margin-right: auto;\n margin-left: auto;\n padding-left: 15px;\n padding-right: 15px;\n}\n@media (min-width: 768px) {\n .container {\n width: 750px;\n }\n}\n@media (min-width: 992px) {\n .container {\n width: 970px;\n }\n}\n@media (min-width: 1200px) {\n .container {\n width: 1170px;\n }\n}\n.container-fluid {\n margin-right: auto;\n margin-left: auto;\n padding-left: 15px;\n padding-right: 15px;\n}\n.row {\n margin-left: -15px;\n margin-right: -15px;\n}\n.col-xs-1, .col-sm-1, .col-md-1, .col-lg-1, .col-xs-2, .col-sm-2, .col-md-2, .col-lg-2, .col-xs-3, .col-sm-3, .col-md-3, .col-lg-3, .col-xs-4, .col-sm-4, .col-md-4, .col-lg-4, .col-xs-5, .col-sm-5, .col-md-5, .col-lg-5, .col-xs-6, .col-sm-6, .col-md-6, .col-lg-6, .col-xs-7, .col-sm-7, .col-md-7, .col-lg-7, .col-xs-8, .col-sm-8, .col-md-8, .col-lg-8, .col-xs-9, .col-sm-9, .col-md-9, .col-lg-9, .col-xs-10, .col-sm-10, .col-md-10, .col-lg-10, .col-xs-11, .col-sm-11, .col-md-11, .col-lg-11, .col-xs-12, .col-sm-12, .col-md-12, .col-lg-12 {\n position: relative;\n min-height: 1px;\n padding-left: 15px;\n padding-right: 15px;\n}\n.col-xs-1, .col-xs-2, .col-xs-3, .col-xs-4, .col-xs-5, .col-xs-6, .col-xs-7, .col-xs-8, .col-xs-9, .col-xs-10, .col-xs-11, .col-xs-12 {\n float: left;\n}\n.col-xs-12 {\n width: 100%;\n}\n.col-xs-11 {\n width: 91.66666667%;\n}\n.col-xs-10 {\n width: 83.33333333%;\n}\n.col-xs-9 {\n width: 75%;\n}\n.col-xs-8 {\n width: 66.66666667%;\n}\n.col-xs-7 {\n width: 58.33333333%;\n}\n.col-xs-6 {\n width: 50%;\n}\n.col-xs-5 {\n width: 41.66666667%;\n}\n.col-xs-4 {\n width: 33.33333333%;\n}\n.col-xs-3 {\n width: 25%;\n}\n.col-xs-2 {\n width: 16.66666667%;\n}\n.col-xs-1 {\n width: 8.33333333%;\n}\n.col-xs-pull-12 {\n right: 100%;\n}\n.col-xs-pull-11 {\n right: 91.66666667%;\n}\n.col-xs-pull-10 {\n right: 83.33333333%;\n}\n.col-xs-pull-9 {\n right: 75%;\n}\n.col-xs-pull-8 {\n right: 66.66666667%;\n}\n.col-xs-pull-7 {\n right: 58.33333333%;\n}\n.col-xs-pull-6 {\n right: 50%;\n}\n.col-xs-pull-5 {\n right: 41.66666667%;\n}\n.col-xs-pull-4 {\n right: 33.33333333%;\n}\n.col-xs-pull-3 {\n right: 25%;\n}\n.col-xs-pull-2 {\n right: 16.66666667%;\n}\n.col-xs-pull-1 {\n right: 8.33333333%;\n}\n.col-xs-pull-0 {\n right: auto;\n}\n.col-xs-push-12 {\n left: 100%;\n}\n.col-xs-push-11 {\n left: 91.66666667%;\n}\n.col-xs-push-10 {\n left: 83.33333333%;\n}\n.col-xs-push-9 {\n left: 75%;\n}\n.col-xs-push-8 {\n left: 66.66666667%;\n}\n.col-xs-push-7 {\n left: 58.33333333%;\n}\n.col-xs-push-6 {\n left: 50%;\n}\n.col-xs-push-5 {\n left: 41.66666667%;\n}\n.col-xs-push-4 {\n left: 33.33333333%;\n}\n.col-xs-push-3 {\n left: 25%;\n}\n.col-xs-push-2 {\n left: 16.66666667%;\n}\n.col-xs-push-1 {\n left: 8.33333333%;\n}\n.col-xs-push-0 {\n left: auto;\n}\n.col-xs-offset-12 {\n margin-left: 100%;\n}\n.col-xs-offset-11 {\n margin-left: 91.66666667%;\n}\n.col-xs-offset-10 {\n margin-left: 83.33333333%;\n}\n.col-xs-offset-9 {\n margin-left: 75%;\n}\n.col-xs-offset-8 {\n margin-left: 66.66666667%;\n}\n.col-xs-offset-7 {\n margin-left: 58.33333333%;\n}\n.col-xs-offset-6 {\n margin-left: 50%;\n}\n.col-xs-offset-5 {\n margin-left: 41.66666667%;\n}\n.col-xs-offset-4 {\n margin-left: 33.33333333%;\n}\n.col-xs-offset-3 {\n margin-left: 25%;\n}\n.col-xs-offset-2 {\n margin-left: 16.66666667%;\n}\n.col-xs-offset-1 {\n margin-left: 8.33333333%;\n}\n.col-xs-offset-0 {\n margin-left: 0%;\n}\n@media (min-width: 768px) {\n .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12 {\n float: left;\n }\n .col-sm-12 {\n width: 100%;\n }\n .col-sm-11 {\n width: 91.66666667%;\n }\n .col-sm-10 {\n width: 83.33333333%;\n }\n .col-sm-9 {\n width: 75%;\n }\n .col-sm-8 {\n width: 66.66666667%;\n }\n .col-sm-7 {\n width: 58.33333333%;\n }\n .col-sm-6 {\n width: 50%;\n }\n .col-sm-5 {\n width: 41.66666667%;\n }\n .col-sm-4 {\n width: 33.33333333%;\n }\n .col-sm-3 {\n width: 25%;\n }\n .col-sm-2 {\n width: 16.66666667%;\n }\n .col-sm-1 {\n width: 8.33333333%;\n }\n .col-sm-pull-12 {\n right: 100%;\n }\n .col-sm-pull-11 {\n right: 91.66666667%;\n }\n .col-sm-pull-10 {\n right: 83.33333333%;\n }\n .col-sm-pull-9 {\n right: 75%;\n }\n .col-sm-pull-8 {\n right: 66.66666667%;\n }\n .col-sm-pull-7 {\n right: 58.33333333%;\n }\n .col-sm-pull-6 {\n right: 50%;\n }\n .col-sm-pull-5 {\n right: 41.66666667%;\n }\n .col-sm-pull-4 {\n right: 33.33333333%;\n }\n .col-sm-pull-3 {\n right: 25%;\n }\n .col-sm-pull-2 {\n right: 16.66666667%;\n }\n .col-sm-pull-1 {\n right: 8.33333333%;\n }\n .col-sm-pull-0 {\n right: auto;\n }\n .col-sm-push-12 {\n left: 100%;\n }\n .col-sm-push-11 {\n left: 91.66666667%;\n }\n .col-sm-push-10 {\n left: 83.33333333%;\n }\n .col-sm-push-9 {\n left: 75%;\n }\n .col-sm-push-8 {\n left: 66.66666667%;\n }\n .col-sm-push-7 {\n left: 58.33333333%;\n }\n .col-sm-push-6 {\n left: 50%;\n }\n .col-sm-push-5 {\n left: 41.66666667%;\n }\n .col-sm-push-4 {\n left: 33.33333333%;\n }\n .col-sm-push-3 {\n left: 25%;\n }\n .col-sm-push-2 {\n left: 16.66666667%;\n }\n .col-sm-push-1 {\n left: 8.33333333%;\n }\n .col-sm-push-0 {\n left: auto;\n }\n .col-sm-offset-12 {\n margin-left: 100%;\n }\n .col-sm-offset-11 {\n margin-left: 91.66666667%;\n }\n .col-sm-offset-10 {\n margin-left: 83.33333333%;\n }\n .col-sm-offset-9 {\n margin-left: 75%;\n }\n .col-sm-offset-8 {\n margin-left: 66.66666667%;\n }\n .col-sm-offset-7 {\n margin-left: 58.33333333%;\n }\n .col-sm-offset-6 {\n margin-left: 50%;\n }\n .col-sm-offset-5 {\n margin-left: 41.66666667%;\n }\n .col-sm-offset-4 {\n margin-left: 33.33333333%;\n }\n .col-sm-offset-3 {\n margin-left: 25%;\n }\n .col-sm-offset-2 {\n margin-left: 16.66666667%;\n }\n .col-sm-offset-1 {\n margin-left: 8.33333333%;\n }\n .col-sm-offset-0 {\n margin-left: 0%;\n }\n}\n@media (min-width: 992px) {\n .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12 {\n float: left;\n }\n .col-md-12 {\n width: 100%;\n }\n .col-md-11 {\n width: 91.66666667%;\n }\n .col-md-10 {\n width: 83.33333333%;\n }\n .col-md-9 {\n width: 75%;\n }\n .col-md-8 {\n width: 66.66666667%;\n }\n .col-md-7 {\n width: 58.33333333%;\n }\n .col-md-6 {\n width: 50%;\n }\n .col-md-5 {\n width: 41.66666667%;\n }\n .col-md-4 {\n width: 33.33333333%;\n }\n .col-md-3 {\n width: 25%;\n }\n .col-md-2 {\n width: 16.66666667%;\n }\n .col-md-1 {\n width: 8.33333333%;\n }\n .col-md-pull-12 {\n right: 100%;\n }\n .col-md-pull-11 {\n right: 91.66666667%;\n }\n .col-md-pull-10 {\n right: 83.33333333%;\n }\n .col-md-pull-9 {\n right: 75%;\n }\n .col-md-pull-8 {\n right: 66.66666667%;\n }\n .col-md-pull-7 {\n right: 58.33333333%;\n }\n .col-md-pull-6 {\n right: 50%;\n }\n .col-md-pull-5 {\n right: 41.66666667%;\n }\n .col-md-pull-4 {\n right: 33.33333333%;\n }\n .col-md-pull-3 {\n right: 25%;\n }\n .col-md-pull-2 {\n right: 16.66666667%;\n }\n .col-md-pull-1 {\n right: 8.33333333%;\n }\n .col-md-pull-0 {\n right: auto;\n }\n .col-md-push-12 {\n left: 100%;\n }\n .col-md-push-11 {\n left: 91.66666667%;\n }\n .col-md-push-10 {\n left: 83.33333333%;\n }\n .col-md-push-9 {\n left: 75%;\n }\n .col-md-push-8 {\n left: 66.66666667%;\n }\n .col-md-push-7 {\n left: 58.33333333%;\n }\n .col-md-push-6 {\n left: 50%;\n }\n .col-md-push-5 {\n left: 41.66666667%;\n }\n .col-md-push-4 {\n left: 33.33333333%;\n }\n .col-md-push-3 {\n left: 25%;\n }\n .col-md-push-2 {\n left: 16.66666667%;\n }\n .col-md-push-1 {\n left: 8.33333333%;\n }\n .col-md-push-0 {\n left: auto;\n }\n .col-md-offset-12 {\n margin-left: 100%;\n }\n .col-md-offset-11 {\n margin-left: 91.66666667%;\n }\n .col-md-offset-10 {\n margin-left: 83.33333333%;\n }\n .col-md-offset-9 {\n margin-left: 75%;\n }\n .col-md-offset-8 {\n margin-left: 66.66666667%;\n }\n .col-md-offset-7 {\n margin-left: 58.33333333%;\n }\n .col-md-offset-6 {\n margin-left: 50%;\n }\n .col-md-offset-5 {\n margin-left: 41.66666667%;\n }\n .col-md-offset-4 {\n margin-left: 33.33333333%;\n }\n .col-md-offset-3 {\n margin-left: 25%;\n }\n .col-md-offset-2 {\n margin-left: 16.66666667%;\n }\n .col-md-offset-1 {\n margin-left: 8.33333333%;\n }\n .col-md-offset-0 {\n margin-left: 0%;\n }\n}\n@media (min-width: 1200px) {\n .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12 {\n float: left;\n }\n .col-lg-12 {\n width: 100%;\n }\n .col-lg-11 {\n width: 91.66666667%;\n }\n .col-lg-10 {\n width: 83.33333333%;\n }\n .col-lg-9 {\n width: 75%;\n }\n .col-lg-8 {\n width: 66.66666667%;\n }\n .col-lg-7 {\n width: 58.33333333%;\n }\n .col-lg-6 {\n width: 50%;\n }\n .col-lg-5 {\n width: 41.66666667%;\n }\n .col-lg-4 {\n width: 33.33333333%;\n }\n .col-lg-3 {\n width: 25%;\n }\n .col-lg-2 {\n width: 16.66666667%;\n }\n .col-lg-1 {\n width: 8.33333333%;\n }\n .col-lg-pull-12 {\n right: 100%;\n }\n .col-lg-pull-11 {\n right: 91.66666667%;\n }\n .col-lg-pull-10 {\n right: 83.33333333%;\n }\n .col-lg-pull-9 {\n right: 75%;\n }\n .col-lg-pull-8 {\n right: 66.66666667%;\n }\n .col-lg-pull-7 {\n right: 58.33333333%;\n }\n .col-lg-pull-6 {\n right: 50%;\n }\n .col-lg-pull-5 {\n right: 41.66666667%;\n }\n .col-lg-pull-4 {\n right: 33.33333333%;\n }\n .col-lg-pull-3 {\n right: 25%;\n }\n .col-lg-pull-2 {\n right: 16.66666667%;\n }\n .col-lg-pull-1 {\n right: 8.33333333%;\n }\n .col-lg-pull-0 {\n right: auto;\n }\n .col-lg-push-12 {\n left: 100%;\n }\n .col-lg-push-11 {\n left: 91.66666667%;\n }\n .col-lg-push-10 {\n left: 83.33333333%;\n }\n .col-lg-push-9 {\n left: 75%;\n }\n .col-lg-push-8 {\n left: 66.66666667%;\n }\n .col-lg-push-7 {\n left: 58.33333333%;\n }\n .col-lg-push-6 {\n left: 50%;\n }\n .col-lg-push-5 {\n left: 41.66666667%;\n }\n .col-lg-push-4 {\n left: 33.33333333%;\n }\n .col-lg-push-3 {\n left: 25%;\n }\n .col-lg-push-2 {\n left: 16.66666667%;\n }\n .col-lg-push-1 {\n left: 8.33333333%;\n }\n .col-lg-push-0 {\n left: auto;\n }\n .col-lg-offset-12 {\n margin-left: 100%;\n }\n .col-lg-offset-11 {\n margin-left: 91.66666667%;\n }\n .col-lg-offset-10 {\n margin-left: 83.33333333%;\n }\n .col-lg-offset-9 {\n margin-left: 75%;\n }\n .col-lg-offset-8 {\n margin-left: 66.66666667%;\n }\n .col-lg-offset-7 {\n margin-left: 58.33333333%;\n }\n .col-lg-offset-6 {\n margin-left: 50%;\n }\n .col-lg-offset-5 {\n margin-left: 41.66666667%;\n }\n .col-lg-offset-4 {\n margin-left: 33.33333333%;\n }\n .col-lg-offset-3 {\n margin-left: 25%;\n }\n .col-lg-offset-2 {\n margin-left: 16.66666667%;\n }\n .col-lg-offset-1 {\n margin-left: 8.33333333%;\n }\n .col-lg-offset-0 {\n margin-left: 0%;\n }\n}\ntable {\n background-color: transparent;\n}\ncaption {\n padding-top: 8px;\n padding-bottom: 8px;\n color: #777777;\n text-align: left;\n}\nth {\n text-align: left;\n}\n.table {\n width: 100%;\n max-width: 100%;\n margin-bottom: 20px;\n}\n.table > thead > tr > th,\n.table > tbody > tr > th,\n.table > tfoot > tr > th,\n.table > thead > tr > td,\n.table > tbody > tr > td,\n.table > tfoot > tr > td {\n padding: 8px;\n line-height: 1.42857143;\n vertical-align: top;\n border-top: 1px solid #ddd;\n}\n.table > thead > tr > th {\n vertical-align: bottom;\n border-bottom: 2px solid #ddd;\n}\n.table > caption + thead > tr:first-child > th,\n.table > colgroup + thead > tr:first-child > th,\n.table > thead:first-child > tr:first-child > th,\n.table > caption + thead > tr:first-child > td,\n.table > colgroup + thead > tr:first-child > td,\n.table > thead:first-child > tr:first-child > td {\n border-top: 0;\n}\n.table > tbody + tbody {\n border-top: 2px solid #ddd;\n}\n.table .table {\n background-color: #fff;\n}\n.table-condensed > thead > tr > th,\n.table-condensed > tbody > tr > th,\n.table-condensed > tfoot > tr > th,\n.table-condensed > thead > tr > td,\n.table-condensed > tbody > tr > td,\n.table-condensed > tfoot > tr > td {\n padding: 5px;\n}\n.table-bordered {\n border: 1px solid #ddd;\n}\n.table-bordered > thead > tr > th,\n.table-bordered > tbody > tr > th,\n.table-bordered > tfoot > tr > th,\n.table-bordered > thead > tr > td,\n.table-bordered > tbody > tr > td,\n.table-bordered > tfoot > tr > td {\n border: 1px solid #ddd;\n}\n.table-bordered > thead > tr > th,\n.table-bordered > thead > tr > td {\n border-bottom-width: 2px;\n}\n.table-striped > tbody > tr:nth-of-type(odd) {\n background-color: #f9f9f9;\n}\n.table-hover > tbody > tr:hover {\n background-color: #f5f5f5;\n}\ntable col[class*=\"col-\"] {\n position: static;\n float: none;\n display: table-column;\n}\ntable td[class*=\"col-\"],\ntable th[class*=\"col-\"] {\n position: static;\n float: none;\n display: table-cell;\n}\n.table > thead > tr > td.active,\n.table > tbody > tr > td.active,\n.table > tfoot > tr > td.active,\n.table > thead > tr > th.active,\n.table > tbody > tr > th.active,\n.table > tfoot > tr > th.active,\n.table > thead > tr.active > td,\n.table > tbody > tr.active > td,\n.table > tfoot > tr.active > td,\n.table > thead > tr.active > th,\n.table > tbody > tr.active > th,\n.table > tfoot > tr.active > th {\n background-color: #f5f5f5;\n}\n.table-hover > tbody > tr > td.active:hover,\n.table-hover > tbody > tr > th.active:hover,\n.table-hover > tbody > tr.active:hover > td,\n.table-hover > tbody > tr:hover > .active,\n.table-hover > tbody > tr.active:hover > th {\n background-color: #e8e8e8;\n}\n.table > thead > tr > td.success,\n.table > tbody > tr > td.success,\n.table > tfoot > tr > td.success,\n.table > thead > tr > th.success,\n.table > tbody > tr > th.success,\n.table > tfoot > tr > th.success,\n.table > thead > tr.success > td,\n.table > tbody > tr.success > td,\n.table > tfoot > tr.success > td,\n.table > thead > tr.success > th,\n.table > tbody > tr.success > th,\n.table > tfoot > tr.success > th {\n background-color: #dff0d8;\n}\n.table-hover > tbody > tr > td.success:hover,\n.table-hover > tbody > tr > th.success:hover,\n.table-hover > tbody > tr.success:hover > td,\n.table-hover > tbody > tr:hover > .success,\n.table-hover > tbody > tr.success:hover > th {\n background-color: #d0e9c6;\n}\n.table > thead > tr > td.info,\n.table > tbody > tr > td.info,\n.table > tfoot > tr > td.info,\n.table > thead > tr > th.info,\n.table > tbody > tr > th.info,\n.table > tfoot > tr > th.info,\n.table > thead > tr.info > td,\n.table > tbody > tr.info > td,\n.table > tfoot > tr.info > td,\n.table > thead > tr.info > th,\n.table > tbody > tr.info > th,\n.table > tfoot > tr.info > th {\n background-color: #d9edf7;\n}\n.table-hover > tbody > tr > td.info:hover,\n.table-hover > tbody > tr > th.info:hover,\n.table-hover > tbody > tr.info:hover > td,\n.table-hover > tbody > tr:hover > .info,\n.table-hover > tbody > tr.info:hover > th {\n background-color: #c4e3f3;\n}\n.table > thead > tr > td.warning,\n.table > tbody > tr > td.warning,\n.table > tfoot > tr > td.warning,\n.table > thead > tr > th.warning,\n.table > tbody > tr > th.warning,\n.table > tfoot > tr > th.warning,\n.table > thead > tr.warning > td,\n.table > tbody > tr.warning > td,\n.table > tfoot > tr.warning > td,\n.table > thead > tr.warning > th,\n.table > tbody > tr.warning > th,\n.table > tfoot > tr.warning > th {\n background-color: #fcf8e3;\n}\n.table-hover > tbody > tr > td.warning:hover,\n.table-hover > tbody > tr > th.warning:hover,\n.table-hover > tbody > tr.warning:hover > td,\n.table-hover > tbody > tr:hover > .warning,\n.table-hover > tbody > tr.warning:hover > th {\n background-color: #faf2cc;\n}\n.table > thead > tr > td.danger,\n.table > tbody > tr > td.danger,\n.table > tfoot > tr > td.danger,\n.table > thead > tr > th.danger,\n.table > tbody > tr > th.danger,\n.table > tfoot > tr > th.danger,\n.table > thead > tr.danger > td,\n.table > tbody > tr.danger > td,\n.table > tfoot > tr.danger > td,\n.table > thead > tr.danger > th,\n.table > tbody > tr.danger > th,\n.table > tfoot > tr.danger > th {\n background-color: #f2dede;\n}\n.table-hover > tbody > tr > td.danger:hover,\n.table-hover > tbody > tr > th.danger:hover,\n.table-hover > tbody > tr.danger:hover > td,\n.table-hover > tbody > tr:hover > .danger,\n.table-hover > tbody > tr.danger:hover > th {\n background-color: #ebcccc;\n}\n.table-responsive {\n overflow-x: auto;\n min-height: 0.01%;\n}\n@media screen and (max-width: 767px) {\n .table-responsive {\n width: 100%;\n margin-bottom: 15px;\n overflow-y: hidden;\n -ms-overflow-style: -ms-autohiding-scrollbar;\n border: 1px solid #ddd;\n }\n .table-responsive > .table {\n margin-bottom: 0;\n }\n .table-responsive > .table > thead > tr > th,\n .table-responsive > .table > tbody > tr > th,\n .table-responsive > .table > tfoot > tr > th,\n .table-responsive > .table > thead > tr > td,\n .table-responsive > .table > tbody > tr > td,\n .table-responsive > .table > tfoot > tr > td {\n white-space: nowrap;\n }\n .table-responsive > .table-bordered {\n border: 0;\n }\n .table-responsive > .table-bordered > thead > tr > th:first-child,\n .table-responsive > .table-bordered > tbody > tr > th:first-child,\n .table-responsive > .table-bordered > tfoot > tr > th:first-child,\n .table-responsive > .table-bordered > thead > tr > td:first-child,\n .table-responsive > .table-bordered > tbody > tr > td:first-child,\n .table-responsive > .table-bordered > tfoot > tr > td:first-child {\n border-left: 0;\n }\n .table-responsive > .table-bordered > thead > tr > th:last-child,\n .table-responsive > .table-bordered > tbody > tr > th:last-child,\n .table-responsive > .table-bordered > tfoot > tr > th:last-child,\n .table-responsive > .table-bordered > thead > tr > td:last-child,\n .table-responsive > .table-bordered > tbody > tr > td:last-child,\n .table-responsive > .table-bordered > tfoot > tr > td:last-child {\n border-right: 0;\n }\n .table-responsive > .table-bordered > tbody > tr:last-child > th,\n .table-responsive > .table-bordered > tfoot > tr:last-child > th,\n .table-responsive > .table-bordered > tbody > tr:last-child > td,\n .table-responsive > .table-bordered > tfoot > tr:last-child > td {\n border-bottom: 0;\n }\n}\nfieldset {\n padding: 0;\n margin: 0;\n border: 0;\n min-width: 0;\n}\nlegend {\n display: block;\n width: 100%;\n padding: 0;\n margin-bottom: 20px;\n font-size: 21px;\n line-height: inherit;\n color: #333333;\n border: 0;\n border-bottom: 1px solid #e5e5e5;\n}\nlabel {\n display: inline-block;\n max-width: 100%;\n margin-bottom: 5px;\n font-weight: bold;\n}\ninput[type=\"search\"] {\n -webkit-box-sizing: border-box;\n -moz-box-sizing: border-box;\n box-sizing: border-box;\n}\ninput[type=\"radio\"],\ninput[type=\"checkbox\"] {\n margin: 4px 0 0;\n margin-top: 1px \\9;\n line-height: normal;\n}\ninput[type=\"file\"] {\n display: block;\n}\ninput[type=\"range\"] {\n display: block;\n width: 100%;\n}\nselect[multiple],\nselect[size] {\n height: auto;\n}\ninput[type=\"file\"]:focus,\ninput[type=\"radio\"]:focus,\ninput[type=\"checkbox\"]:focus {\n outline: thin dotted;\n outline: 5px auto -webkit-focus-ring-color;\n outline-offset: -2px;\n}\noutput {\n display: block;\n padding-top: 7px;\n font-size: 14px;\n line-height: 1.42857143;\n color: #555555;\n}\n.form-control {\n display: block;\n width: 100%;\n height: 34px;\n padding: 6px 12px;\n font-size: 14px;\n line-height: 1.42857143;\n color: #555555;\n background-color: #fff;\n background-image: none;\n border: 1px solid #ccc;\n border-radius: 4px;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n -webkit-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;\n -o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;\n transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;\n}\n.form-control:focus {\n border-color: #66afe9;\n outline: 0;\n -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6);\n box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6);\n}\n.form-control::-moz-placeholder {\n color: #999;\n opacity: 1;\n}\n.form-control:-ms-input-placeholder {\n color: #999;\n}\n.form-control::-webkit-input-placeholder {\n color: #999;\n}\n.form-control::-ms-expand {\n border: 0;\n background-color: transparent;\n}\n.form-control[disabled],\n.form-control[readonly],\nfieldset[disabled] .form-control {\n background-color: #eeeeee;\n opacity: 1;\n}\n.form-control[disabled],\nfieldset[disabled] .form-control {\n cursor: not-allowed;\n}\ntextarea.form-control {\n height: auto;\n}\ninput[type=\"search\"] {\n -webkit-appearance: none;\n}\n@media screen and (-webkit-min-device-pixel-ratio: 0) {\n input[type=\"date\"].form-control,\n input[type=\"time\"].form-control,\n input[type=\"datetime-local\"].form-control,\n input[type=\"month\"].form-control {\n line-height: 34px;\n }\n input[type=\"date\"].input-sm,\n input[type=\"time\"].input-sm,\n input[type=\"datetime-local\"].input-sm,\n input[type=\"month\"].input-sm,\n .input-group-sm input[type=\"date\"],\n .input-group-sm input[type=\"time\"],\n .input-group-sm input[type=\"datetime-local\"],\n .input-group-sm input[type=\"month\"] {\n line-height: 30px;\n }\n input[type=\"date\"].input-lg,\n input[type=\"time\"].input-lg,\n input[type=\"datetime-local\"].input-lg,\n input[type=\"month\"].input-lg,\n .input-group-lg input[type=\"date\"],\n .input-group-lg input[type=\"time\"],\n .input-group-lg input[type=\"datetime-local\"],\n .input-group-lg input[type=\"month\"] {\n line-height: 46px;\n }\n}\n.form-group {\n margin-bottom: 15px;\n}\n.radio,\n.checkbox {\n position: relative;\n display: block;\n margin-top: 10px;\n margin-bottom: 10px;\n}\n.radio label,\n.checkbox label {\n min-height: 20px;\n padding-left: 20px;\n margin-bottom: 0;\n font-weight: normal;\n cursor: pointer;\n}\n.radio input[type=\"radio\"],\n.radio-inline input[type=\"radio\"],\n.checkbox input[type=\"checkbox\"],\n.checkbox-inline input[type=\"checkbox\"] {\n position: absolute;\n margin-left: -20px;\n margin-top: 4px \\9;\n}\n.radio + .radio,\n.checkbox + .checkbox {\n margin-top: -5px;\n}\n.radio-inline,\n.checkbox-inline {\n position: relative;\n display: inline-block;\n padding-left: 20px;\n margin-bottom: 0;\n vertical-align: middle;\n font-weight: normal;\n cursor: pointer;\n}\n.radio-inline + .radio-inline,\n.checkbox-inline + .checkbox-inline {\n margin-top: 0;\n margin-left: 10px;\n}\ninput[type=\"radio\"][disabled],\ninput[type=\"checkbox\"][disabled],\ninput[type=\"radio\"].disabled,\ninput[type=\"checkbox\"].disabled,\nfieldset[disabled] input[type=\"radio\"],\nfieldset[disabled] input[type=\"checkbox\"] {\n cursor: not-allowed;\n}\n.radio-inline.disabled,\n.checkbox-inline.disabled,\nfieldset[disabled] .radio-inline,\nfieldset[disabled] .checkbox-inline {\n cursor: not-allowed;\n}\n.radio.disabled label,\n.checkbox.disabled label,\nfieldset[disabled] .radio label,\nfieldset[disabled] .checkbox label {\n cursor: not-allowed;\n}\n.form-control-static {\n padding-top: 7px;\n padding-bottom: 7px;\n margin-bottom: 0;\n min-height: 34px;\n}\n.form-control-static.input-lg,\n.form-control-static.input-sm {\n padding-left: 0;\n padding-right: 0;\n}\n.input-sm {\n height: 30px;\n padding: 5px 10px;\n font-size: 12px;\n line-height: 1.5;\n border-radius: 3px;\n}\nselect.input-sm {\n height: 30px;\n line-height: 30px;\n}\ntextarea.input-sm,\nselect[multiple].input-sm {\n height: auto;\n}\n.form-group-sm .form-control {\n height: 30px;\n padding: 5px 10px;\n font-size: 12px;\n line-height: 1.5;\n border-radius: 3px;\n}\n.form-group-sm select.form-control {\n height: 30px;\n line-height: 30px;\n}\n.form-group-sm textarea.form-control,\n.form-group-sm select[multiple].form-control {\n height: auto;\n}\n.form-group-sm .form-control-static {\n height: 30px;\n min-height: 32px;\n padding: 6px 10px;\n font-size: 12px;\n line-height: 1.5;\n}\n.input-lg {\n height: 46px;\n padding: 10px 16px;\n font-size: 18px;\n line-height: 1.3333333;\n border-radius: 6px;\n}\nselect.input-lg {\n height: 46px;\n line-height: 46px;\n}\ntextarea.input-lg,\nselect[multiple].input-lg {\n height: auto;\n}\n.form-group-lg .form-control {\n height: 46px;\n padding: 10px 16px;\n font-size: 18px;\n line-height: 1.3333333;\n border-radius: 6px;\n}\n.form-group-lg select.form-control {\n height: 46px;\n line-height: 46px;\n}\n.form-group-lg textarea.form-control,\n.form-group-lg select[multiple].form-control {\n height: auto;\n}\n.form-group-lg .form-control-static {\n height: 46px;\n min-height: 38px;\n padding: 11px 16px;\n font-size: 18px;\n line-height: 1.3333333;\n}\n.has-feedback {\n position: relative;\n}\n.has-feedback .form-control {\n padding-right: 42.5px;\n}\n.form-control-feedback {\n position: absolute;\n top: 0;\n right: 0;\n z-index: 2;\n display: block;\n width: 34px;\n height: 34px;\n line-height: 34px;\n text-align: center;\n pointer-events: none;\n}\n.input-lg + .form-control-feedback,\n.input-group-lg + .form-control-feedback,\n.form-group-lg .form-control + .form-control-feedback {\n width: 46px;\n height: 46px;\n line-height: 46px;\n}\n.input-sm + .form-control-feedback,\n.input-group-sm + .form-control-feedback,\n.form-group-sm .form-control + .form-control-feedback {\n width: 30px;\n height: 30px;\n line-height: 30px;\n}\n.has-success .help-block,\n.has-success .control-label,\n.has-success .radio,\n.has-success .checkbox,\n.has-success .radio-inline,\n.has-success .checkbox-inline,\n.has-success.radio label,\n.has-success.checkbox label,\n.has-success.radio-inline label,\n.has-success.checkbox-inline label {\n color: #3c763d;\n}\n.has-success .form-control {\n border-color: #3c763d;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n}\n.has-success .form-control:focus {\n border-color: #2b542c;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #67b168;\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #67b168;\n}\n.has-success .input-group-addon {\n color: #3c763d;\n border-color: #3c763d;\n background-color: #dff0d8;\n}\n.has-success .form-control-feedback {\n color: #3c763d;\n}\n.has-warning .help-block,\n.has-warning .control-label,\n.has-warning .radio,\n.has-warning .checkbox,\n.has-warning .radio-inline,\n.has-warning .checkbox-inline,\n.has-warning.radio label,\n.has-warning.checkbox label,\n.has-warning.radio-inline label,\n.has-warning.checkbox-inline label {\n color: #8a6d3b;\n}\n.has-warning .form-control {\n border-color: #8a6d3b;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n}\n.has-warning .form-control:focus {\n border-color: #66512c;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #c0a16b;\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #c0a16b;\n}\n.has-warning .input-group-addon {\n color: #8a6d3b;\n border-color: #8a6d3b;\n background-color: #fcf8e3;\n}\n.has-warning .form-control-feedback {\n color: #8a6d3b;\n}\n.has-error .help-block,\n.has-error .control-label,\n.has-error .radio,\n.has-error .checkbox,\n.has-error .radio-inline,\n.has-error .checkbox-inline,\n.has-error.radio label,\n.has-error.checkbox label,\n.has-error.radio-inline label,\n.has-error.checkbox-inline label {\n color: #a94442;\n}\n.has-error .form-control {\n border-color: #a94442;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n}\n.has-error .form-control:focus {\n border-color: #843534;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ce8483;\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ce8483;\n}\n.has-error .input-group-addon {\n color: #a94442;\n border-color: #a94442;\n background-color: #f2dede;\n}\n.has-error .form-control-feedback {\n color: #a94442;\n}\n.has-feedback label ~ .form-control-feedback {\n top: 25px;\n}\n.has-feedback label.sr-only ~ .form-control-feedback {\n top: 0;\n}\n.help-block {\n display: block;\n margin-top: 5px;\n margin-bottom: 10px;\n color: #737373;\n}\n@media (min-width: 768px) {\n .form-inline .form-group {\n display: inline-block;\n margin-bottom: 0;\n vertical-align: middle;\n }\n .form-inline .form-control {\n display: inline-block;\n width: auto;\n vertical-align: middle;\n }\n .form-inline .form-control-static {\n display: inline-block;\n }\n .form-inline .input-group {\n display: inline-table;\n vertical-align: middle;\n }\n .form-inline .input-group .input-group-addon,\n .form-inline .input-group .input-group-btn,\n .form-inline .input-group .form-control {\n width: auto;\n }\n .form-inline .input-group > .form-control {\n width: 100%;\n }\n .form-inline .control-label {\n margin-bottom: 0;\n vertical-align: middle;\n }\n .form-inline .radio,\n .form-inline .checkbox {\n display: inline-block;\n margin-top: 0;\n margin-bottom: 0;\n vertical-align: middle;\n }\n .form-inline .radio label,\n .form-inline .checkbox label {\n padding-left: 0;\n }\n .form-inline .radio input[type=\"radio\"],\n .form-inline .checkbox input[type=\"checkbox\"] {\n position: relative;\n margin-left: 0;\n }\n .form-inline .has-feedback .form-control-feedback {\n top: 0;\n }\n}\n.form-horizontal .radio,\n.form-horizontal .checkbox,\n.form-horizontal .radio-inline,\n.form-horizontal .checkbox-inline {\n margin-top: 0;\n margin-bottom: 0;\n padding-top: 7px;\n}\n.form-horizontal .radio,\n.form-horizontal .checkbox {\n min-height: 27px;\n}\n.form-horizontal .form-group {\n margin-left: -15px;\n margin-right: -15px;\n}\n@media (min-width: 768px) {\n .form-horizontal .control-label {\n text-align: right;\n margin-bottom: 0;\n padding-top: 7px;\n }\n}\n.form-horizontal .has-feedback .form-control-feedback {\n right: 15px;\n}\n@media (min-width: 768px) {\n .form-horizontal .form-group-lg .control-label {\n padding-top: 11px;\n font-size: 18px;\n }\n}\n@media (min-width: 768px) {\n .form-horizontal .form-group-sm .control-label {\n padding-top: 6px;\n font-size: 12px;\n }\n}\n.btn {\n display: inline-block;\n margin-bottom: 0;\n font-weight: normal;\n text-align: center;\n vertical-align: middle;\n touch-action: manipulation;\n cursor: pointer;\n background-image: none;\n border: 1px solid transparent;\n white-space: nowrap;\n padding: 6px 12px;\n font-size: 14px;\n line-height: 1.42857143;\n border-radius: 4px;\n -webkit-user-select: none;\n -moz-user-select: none;\n -ms-user-select: none;\n user-select: none;\n}\n.btn:focus,\n.btn:active:focus,\n.btn.active:focus,\n.btn.focus,\n.btn:active.focus,\n.btn.active.focus {\n outline: thin dotted;\n outline: 5px auto -webkit-focus-ring-color;\n outline-offset: -2px;\n}\n.btn:hover,\n.btn:focus,\n.btn.focus {\n color: #333;\n text-decoration: none;\n}\n.btn:active,\n.btn.active {\n outline: 0;\n background-image: none;\n -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n}\n.btn.disabled,\n.btn[disabled],\nfieldset[disabled] .btn {\n cursor: not-allowed;\n opacity: 0.65;\n filter: alpha(opacity=65);\n -webkit-box-shadow: none;\n box-shadow: none;\n}\na.btn.disabled,\nfieldset[disabled] a.btn {\n pointer-events: none;\n}\n.btn-default {\n color: #333;\n background-color: #fff;\n border-color: #ccc;\n}\n.btn-default:focus,\n.btn-default.focus {\n color: #333;\n background-color: #e6e6e6;\n border-color: #8c8c8c;\n}\n.btn-default:hover {\n color: #333;\n background-color: #e6e6e6;\n border-color: #adadad;\n}\n.btn-default:active,\n.btn-default.active,\n.open > .dropdown-toggle.btn-default {\n color: #333;\n background-color: #e6e6e6;\n border-color: #adadad;\n}\n.btn-default:active:hover,\n.btn-default.active:hover,\n.open > .dropdown-toggle.btn-default:hover,\n.btn-default:active:focus,\n.btn-default.active:focus,\n.open > .dropdown-toggle.btn-default:focus,\n.btn-default:active.focus,\n.btn-default.active.focus,\n.open > .dropdown-toggle.btn-default.focus {\n color: #333;\n background-color: #d4d4d4;\n border-color: #8c8c8c;\n}\n.btn-default:active,\n.btn-default.active,\n.open > .dropdown-toggle.btn-default {\n background-image: none;\n}\n.btn-default.disabled:hover,\n.btn-default[disabled]:hover,\nfieldset[disabled] .btn-default:hover,\n.btn-default.disabled:focus,\n.btn-default[disabled]:focus,\nfieldset[disabled] .btn-default:focus,\n.btn-default.disabled.focus,\n.btn-default[disabled].focus,\nfieldset[disabled] .btn-default.focus {\n background-color: #fff;\n border-color: #ccc;\n}\n.btn-default .badge {\n color: #fff;\n background-color: #333;\n}\n.btn-primary {\n color: #fff;\n background-color: #337ab7;\n border-color: #2e6da4;\n}\n.btn-primary:focus,\n.btn-primary.focus {\n color: #fff;\n background-color: #286090;\n border-color: #122b40;\n}\n.btn-primary:hover {\n color: #fff;\n background-color: #286090;\n border-color: #204d74;\n}\n.btn-primary:active,\n.btn-primary.active,\n.open > .dropdown-toggle.btn-primary {\n color: #fff;\n background-color: #286090;\n border-color: #204d74;\n}\n.btn-primary:active:hover,\n.btn-primary.active:hover,\n.open > .dropdown-toggle.btn-primary:hover,\n.btn-primary:active:focus,\n.btn-primary.active:focus,\n.open > .dropdown-toggle.btn-primary:focus,\n.btn-primary:active.focus,\n.btn-primary.active.focus,\n.open > .dropdown-toggle.btn-primary.focus {\n color: #fff;\n background-color: #204d74;\n border-color: #122b40;\n}\n.btn-primary:active,\n.btn-primary.active,\n.open > .dropdown-toggle.btn-primary {\n background-image: none;\n}\n.btn-primary.disabled:hover,\n.btn-primary[disabled]:hover,\nfieldset[disabled] .btn-primary:hover,\n.btn-primary.disabled:focus,\n.btn-primary[disabled]:focus,\nfieldset[disabled] .btn-primary:focus,\n.btn-primary.disabled.focus,\n.btn-primary[disabled].focus,\nfieldset[disabled] .btn-primary.focus {\n background-color: #337ab7;\n border-color: #2e6da4;\n}\n.btn-primary .badge {\n color: #337ab7;\n background-color: #fff;\n}\n.btn-success {\n color: #fff;\n background-color: #5cb85c;\n border-color: #4cae4c;\n}\n.btn-success:focus,\n.btn-success.focus {\n color: #fff;\n background-color: #449d44;\n border-color: #255625;\n}\n.btn-success:hover {\n color: #fff;\n background-color: #449d44;\n border-color: #398439;\n}\n.btn-success:active,\n.btn-success.active,\n.open > .dropdown-toggle.btn-success {\n color: #fff;\n background-color: #449d44;\n border-color: #398439;\n}\n.btn-success:active:hover,\n.btn-success.active:hover,\n.open > .dropdown-toggle.btn-success:hover,\n.btn-success:active:focus,\n.btn-success.active:focus,\n.open > .dropdown-toggle.btn-success:focus,\n.btn-success:active.focus,\n.btn-success.active.focus,\n.open > .dropdown-toggle.btn-success.focus {\n color: #fff;\n background-color: #398439;\n border-color: #255625;\n}\n.btn-success:active,\n.btn-success.active,\n.open > .dropdown-toggle.btn-success {\n background-image: none;\n}\n.btn-success.disabled:hover,\n.btn-success[disabled]:hover,\nfieldset[disabled] .btn-success:hover,\n.btn-success.disabled:focus,\n.btn-success[disabled]:focus,\nfieldset[disabled] .btn-success:focus,\n.btn-success.disabled.focus,\n.btn-success[disabled].focus,\nfieldset[disabled] .btn-success.focus {\n background-color: #5cb85c;\n border-color: #4cae4c;\n}\n.btn-success .badge {\n color: #5cb85c;\n background-color: #fff;\n}\n.btn-info {\n color: #fff;\n background-color: #5bc0de;\n border-color: #46b8da;\n}\n.btn-info:focus,\n.btn-info.focus {\n color: #fff;\n background-color: #31b0d5;\n border-color: #1b6d85;\n}\n.btn-info:hover {\n color: #fff;\n background-color: #31b0d5;\n border-color: #269abc;\n}\n.btn-info:active,\n.btn-info.active,\n.open > .dropdown-toggle.btn-info {\n color: #fff;\n background-color: #31b0d5;\n border-color: #269abc;\n}\n.btn-info:active:hover,\n.btn-info.active:hover,\n.open > .dropdown-toggle.btn-info:hover,\n.btn-info:active:focus,\n.btn-info.active:focus,\n.open > .dropdown-toggle.btn-info:focus,\n.btn-info:active.focus,\n.btn-info.active.focus,\n.open > .dropdown-toggle.btn-info.focus {\n color: #fff;\n background-color: #269abc;\n border-color: #1b6d85;\n}\n.btn-info:active,\n.btn-info.active,\n.open > .dropdown-toggle.btn-info {\n background-image: none;\n}\n.btn-info.disabled:hover,\n.btn-info[disabled]:hover,\nfieldset[disabled] .btn-info:hover,\n.btn-info.disabled:focus,\n.btn-info[disabled]:focus,\nfieldset[disabled] .btn-info:focus,\n.btn-info.disabled.focus,\n.btn-info[disabled].focus,\nfieldset[disabled] .btn-info.focus {\n background-color: #5bc0de;\n border-color: #46b8da;\n}\n.btn-info .badge {\n color: #5bc0de;\n background-color: #fff;\n}\n.btn-warning {\n color: #fff;\n background-color: #f0ad4e;\n border-color: #eea236;\n}\n.btn-warning:focus,\n.btn-warning.focus {\n color: #fff;\n background-color: #ec971f;\n border-color: #985f0d;\n}\n.btn-warning:hover {\n color: #fff;\n background-color: #ec971f;\n border-color: #d58512;\n}\n.btn-warning:active,\n.btn-warning.active,\n.open > .dropdown-toggle.btn-warning {\n color: #fff;\n background-color: #ec971f;\n border-color: #d58512;\n}\n.btn-warning:active:hover,\n.btn-warning.active:hover,\n.open > .dropdown-toggle.btn-warning:hover,\n.btn-warning:active:focus,\n.btn-warning.active:focus,\n.open > .dropdown-toggle.btn-warning:focus,\n.btn-warning:active.focus,\n.btn-warning.active.focus,\n.open > .dropdown-toggle.btn-warning.focus {\n color: #fff;\n background-color: #d58512;\n border-color: #985f0d;\n}\n.btn-warning:active,\n.btn-warning.active,\n.open > .dropdown-toggle.btn-warning {\n background-image: none;\n}\n.btn-warning.disabled:hover,\n.btn-warning[disabled]:hover,\nfieldset[disabled] .btn-warning:hover,\n.btn-warning.disabled:focus,\n.btn-warning[disabled]:focus,\nfieldset[disabled] .btn-warning:focus,\n.btn-warning.disabled.focus,\n.btn-warning[disabled].focus,\nfieldset[disabled] .btn-warning.focus {\n background-color: #f0ad4e;\n border-color: #eea236;\n}\n.btn-warning .badge {\n color: #f0ad4e;\n background-color: #fff;\n}\n.btn-danger {\n color: #fff;\n background-color: #d9534f;\n border-color: #d43f3a;\n}\n.btn-danger:focus,\n.btn-danger.focus {\n color: #fff;\n background-color: #c9302c;\n border-color: #761c19;\n}\n.btn-danger:hover {\n color: #fff;\n background-color: #c9302c;\n border-color: #ac2925;\n}\n.btn-danger:active,\n.btn-danger.active,\n.open > .dropdown-toggle.btn-danger {\n color: #fff;\n background-color: #c9302c;\n border-color: #ac2925;\n}\n.btn-danger:active:hover,\n.btn-danger.active:hover,\n.open > .dropdown-toggle.btn-danger:hover,\n.btn-danger:active:focus,\n.btn-danger.active:focus,\n.open > .dropdown-toggle.btn-danger:focus,\n.btn-danger:active.focus,\n.btn-danger.active.focus,\n.open > .dropdown-toggle.btn-danger.focus {\n color: #fff;\n background-color: #ac2925;\n border-color: #761c19;\n}\n.btn-danger:active,\n.btn-danger.active,\n.open > .dropdown-toggle.btn-danger {\n background-image: none;\n}\n.btn-danger.disabled:hover,\n.btn-danger[disabled]:hover,\nfieldset[disabled] .btn-danger:hover,\n.btn-danger.disabled:focus,\n.btn-danger[disabled]:focus,\nfieldset[disabled] .btn-danger:focus,\n.btn-danger.disabled.focus,\n.btn-danger[disabled].focus,\nfieldset[disabled] .btn-danger.focus {\n background-color: #d9534f;\n border-color: #d43f3a;\n}\n.btn-danger .badge {\n color: #d9534f;\n background-color: #fff;\n}\n.btn-link {\n color: #337ab7;\n font-weight: normal;\n border-radius: 0;\n}\n.btn-link,\n.btn-link:active,\n.btn-link.active,\n.btn-link[disabled],\nfieldset[disabled] .btn-link {\n background-color: transparent;\n -webkit-box-shadow: none;\n box-shadow: none;\n}\n.btn-link,\n.btn-link:hover,\n.btn-link:focus,\n.btn-link:active {\n border-color: transparent;\n}\n.btn-link:hover,\n.btn-link:focus {\n color: #23527c;\n text-decoration: underline;\n background-color: transparent;\n}\n.btn-link[disabled]:hover,\nfieldset[disabled] .btn-link:hover,\n.btn-link[disabled]:focus,\nfieldset[disabled] .btn-link:focus {\n color: #777777;\n text-decoration: none;\n}\n.btn-lg,\n.btn-group-lg > .btn {\n padding: 10px 16px;\n font-size: 18px;\n line-height: 1.3333333;\n border-radius: 6px;\n}\n.btn-sm,\n.btn-group-sm > .btn {\n padding: 5px 10px;\n font-size: 12px;\n line-height: 1.5;\n border-radius: 3px;\n}\n.btn-xs,\n.btn-group-xs > .btn {\n padding: 1px 5px;\n font-size: 12px;\n line-height: 1.5;\n border-radius: 3px;\n}\n.btn-block {\n display: block;\n width: 100%;\n}\n.btn-block + .btn-block {\n margin-top: 5px;\n}\ninput[type=\"submit\"].btn-block,\ninput[type=\"reset\"].btn-block,\ninput[type=\"button\"].btn-block {\n width: 100%;\n}\n.fade {\n opacity: 0;\n -webkit-transition: opacity 0.15s linear;\n -o-transition: opacity 0.15s linear;\n transition: opacity 0.15s linear;\n}\n.fade.in {\n opacity: 1;\n}\n.collapse {\n display: none;\n}\n.collapse.in {\n display: block;\n}\ntr.collapse.in {\n display: table-row;\n}\ntbody.collapse.in {\n display: table-row-group;\n}\n.collapsing {\n position: relative;\n height: 0;\n overflow: hidden;\n -webkit-transition-property: height, visibility;\n transition-property: height, visibility;\n -webkit-transition-duration: 0.35s;\n transition-duration: 0.35s;\n -webkit-transition-timing-function: ease;\n transition-timing-function: ease;\n}\n.caret {\n display: inline-block;\n width: 0;\n height: 0;\n margin-left: 2px;\n vertical-align: middle;\n border-top: 4px dashed;\n border-top: 4px solid \\9;\n border-right: 4px solid transparent;\n border-left: 4px solid transparent;\n}\n.dropup,\n.dropdown {\n position: relative;\n}\n.dropdown-toggle:focus {\n outline: 0;\n}\n.dropdown-menu {\n position: absolute;\n top: 100%;\n left: 0;\n z-index: 1000;\n display: none;\n float: left;\n min-width: 160px;\n padding: 5px 0;\n margin: 2px 0 0;\n list-style: none;\n font-size: 14px;\n text-align: left;\n background-color: #fff;\n border: 1px solid #ccc;\n border: 1px solid rgba(0, 0, 0, 0.15);\n border-radius: 4px;\n -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);\n box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);\n background-clip: padding-box;\n}\n.dropdown-menu.pull-right {\n right: 0;\n left: auto;\n}\n.dropdown-menu .divider {\n height: 1px;\n margin: 9px 0;\n overflow: hidden;\n background-color: #e5e5e5;\n}\n.dropdown-menu > li > a {\n display: block;\n padding: 3px 20px;\n clear: both;\n font-weight: normal;\n line-height: 1.42857143;\n color: #333333;\n white-space: nowrap;\n}\n.dropdown-menu > li > a:hover,\n.dropdown-menu > li > a:focus {\n text-decoration: none;\n color: #262626;\n background-color: #f5f5f5;\n}\n.dropdown-menu > .active > a,\n.dropdown-menu > .active > a:hover,\n.dropdown-menu > .active > a:focus {\n color: #fff;\n text-decoration: none;\n outline: 0;\n background-color: #337ab7;\n}\n.dropdown-menu > .disabled > a,\n.dropdown-menu > .disabled > a:hover,\n.dropdown-menu > .disabled > a:focus {\n color: #777777;\n}\n.dropdown-menu > .disabled > a:hover,\n.dropdown-menu > .disabled > a:focus {\n text-decoration: none;\n background-color: transparent;\n background-image: none;\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n cursor: not-allowed;\n}\n.open > .dropdown-menu {\n display: block;\n}\n.open > a {\n outline: 0;\n}\n.dropdown-menu-right {\n left: auto;\n right: 0;\n}\n.dropdown-menu-left {\n left: 0;\n right: auto;\n}\n.dropdown-header {\n display: block;\n padding: 3px 20px;\n font-size: 12px;\n line-height: 1.42857143;\n color: #777777;\n white-space: nowrap;\n}\n.dropdown-backdrop {\n position: fixed;\n left: 0;\n right: 0;\n bottom: 0;\n top: 0;\n z-index: 990;\n}\n.pull-right > .dropdown-menu {\n right: 0;\n left: auto;\n}\n.dropup .caret,\n.navbar-fixed-bottom .dropdown .caret {\n border-top: 0;\n border-bottom: 4px dashed;\n border-bottom: 4px solid \\9;\n content: \"\";\n}\n.dropup .dropdown-menu,\n.navbar-fixed-bottom .dropdown .dropdown-menu {\n top: auto;\n bottom: 100%;\n margin-bottom: 2px;\n}\n@media (min-width: 768px) {\n .navbar-right .dropdown-menu {\n left: auto;\n right: 0;\n }\n .navbar-right .dropdown-menu-left {\n left: 0;\n right: auto;\n }\n}\n.btn-group,\n.btn-group-vertical {\n position: relative;\n display: inline-block;\n vertical-align: middle;\n}\n.btn-group > .btn,\n.btn-group-vertical > .btn {\n position: relative;\n float: left;\n}\n.btn-group > .btn:hover,\n.btn-group-vertical > .btn:hover,\n.btn-group > .btn:focus,\n.btn-group-vertical > .btn:focus,\n.btn-group > .btn:active,\n.btn-group-vertical > .btn:active,\n.btn-group > .btn.active,\n.btn-group-vertical > .btn.active {\n z-index: 2;\n}\n.btn-group .btn + .btn,\n.btn-group .btn + .btn-group,\n.btn-group .btn-group + .btn,\n.btn-group .btn-group + .btn-group {\n margin-left: -1px;\n}\n.btn-toolbar {\n margin-left: -5px;\n}\n.btn-toolbar .btn,\n.btn-toolbar .btn-group,\n.btn-toolbar .input-group {\n float: left;\n}\n.btn-toolbar > .btn,\n.btn-toolbar > .btn-group,\n.btn-toolbar > .input-group {\n margin-left: 5px;\n}\n.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) {\n border-radius: 0;\n}\n.btn-group > .btn:first-child {\n margin-left: 0;\n}\n.btn-group > .btn:first-child:not(:last-child):not(.dropdown-toggle) {\n border-bottom-right-radius: 0;\n border-top-right-radius: 0;\n}\n.btn-group > .btn:last-child:not(:first-child),\n.btn-group > .dropdown-toggle:not(:first-child) {\n border-bottom-left-radius: 0;\n border-top-left-radius: 0;\n}\n.btn-group > .btn-group {\n float: left;\n}\n.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn {\n border-radius: 0;\n}\n.btn-group > .btn-group:first-child:not(:last-child) > .btn:last-child,\n.btn-group > .btn-group:first-child:not(:last-child) > .dropdown-toggle {\n border-bottom-right-radius: 0;\n border-top-right-radius: 0;\n}\n.btn-group > .btn-group:last-child:not(:first-child) > .btn:first-child {\n border-bottom-left-radius: 0;\n border-top-left-radius: 0;\n}\n.btn-group .dropdown-toggle:active,\n.btn-group.open .dropdown-toggle {\n outline: 0;\n}\n.btn-group > .btn + .dropdown-toggle {\n padding-left: 8px;\n padding-right: 8px;\n}\n.btn-group > .btn-lg + .dropdown-toggle {\n padding-left: 12px;\n padding-right: 12px;\n}\n.btn-group.open .dropdown-toggle {\n -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n}\n.btn-group.open .dropdown-toggle.btn-link {\n -webkit-box-shadow: none;\n box-shadow: none;\n}\n.btn .caret {\n margin-left: 0;\n}\n.btn-lg .caret {\n border-width: 5px 5px 0;\n border-bottom-width: 0;\n}\n.dropup .btn-lg .caret {\n border-width: 0 5px 5px;\n}\n.btn-group-vertical > .btn,\n.btn-group-vertical > .btn-group,\n.btn-group-vertical > .btn-group > .btn {\n display: block;\n float: none;\n width: 100%;\n max-width: 100%;\n}\n.btn-group-vertical > .btn-group > .btn {\n float: none;\n}\n.btn-group-vertical > .btn + .btn,\n.btn-group-vertical > .btn + .btn-group,\n.btn-group-vertical > .btn-group + .btn,\n.btn-group-vertical > .btn-group + .btn-group {\n margin-top: -1px;\n margin-left: 0;\n}\n.btn-group-vertical > .btn:not(:first-child):not(:last-child) {\n border-radius: 0;\n}\n.btn-group-vertical > .btn:first-child:not(:last-child) {\n border-top-right-radius: 4px;\n border-top-left-radius: 4px;\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 0;\n}\n.btn-group-vertical > .btn:last-child:not(:first-child) {\n border-top-right-radius: 0;\n border-top-left-radius: 0;\n border-bottom-right-radius: 4px;\n border-bottom-left-radius: 4px;\n}\n.btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn {\n border-radius: 0;\n}\n.btn-group-vertical > .btn-group:first-child:not(:last-child) > .btn:last-child,\n.btn-group-vertical > .btn-group:first-child:not(:last-child) > .dropdown-toggle {\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 0;\n}\n.btn-group-vertical > .btn-group:last-child:not(:first-child) > .btn:first-child {\n border-top-right-radius: 0;\n border-top-left-radius: 0;\n}\n.btn-group-justified {\n display: table;\n width: 100%;\n table-layout: fixed;\n border-collapse: separate;\n}\n.btn-group-justified > .btn,\n.btn-group-justified > .btn-group {\n float: none;\n display: table-cell;\n width: 1%;\n}\n.btn-group-justified > .btn-group .btn {\n width: 100%;\n}\n.btn-group-justified > .btn-group .dropdown-menu {\n left: auto;\n}\n[data-toggle=\"buttons\"] > .btn input[type=\"radio\"],\n[data-toggle=\"buttons\"] > .btn-group > .btn input[type=\"radio\"],\n[data-toggle=\"buttons\"] > .btn input[type=\"checkbox\"],\n[data-toggle=\"buttons\"] > .btn-group > .btn input[type=\"checkbox\"] {\n position: absolute;\n clip: rect(0, 0, 0, 0);\n pointer-events: none;\n}\n.input-group {\n position: relative;\n display: table;\n border-collapse: separate;\n}\n.input-group[class*=\"col-\"] {\n float: none;\n padding-left: 0;\n padding-right: 0;\n}\n.input-group .form-control {\n position: relative;\n z-index: 2;\n float: left;\n width: 100%;\n margin-bottom: 0;\n}\n.input-group .form-control:focus {\n z-index: 3;\n}\n.input-group-lg > .form-control,\n.input-group-lg > .input-group-addon,\n.input-group-lg > .input-group-btn > .btn {\n height: 46px;\n padding: 10px 16px;\n font-size: 18px;\n line-height: 1.3333333;\n border-radius: 6px;\n}\nselect.input-group-lg > .form-control,\nselect.input-group-lg > .input-group-addon,\nselect.input-group-lg > .input-group-btn > .btn {\n height: 46px;\n line-height: 46px;\n}\ntextarea.input-group-lg > .form-control,\ntextarea.input-group-lg > .input-group-addon,\ntextarea.input-group-lg > .input-group-btn > .btn,\nselect[multiple].input-group-lg > .form-control,\nselect[multiple].input-group-lg > .input-group-addon,\nselect[multiple].input-group-lg > .input-group-btn > .btn {\n height: auto;\n}\n.input-group-sm > .form-control,\n.input-group-sm > .input-group-addon,\n.input-group-sm > .input-group-btn > .btn {\n height: 30px;\n padding: 5px 10px;\n font-size: 12px;\n line-height: 1.5;\n border-radius: 3px;\n}\nselect.input-group-sm > .form-control,\nselect.input-group-sm > .input-group-addon,\nselect.input-group-sm > .input-group-btn > .btn {\n height: 30px;\n line-height: 30px;\n}\ntextarea.input-group-sm > .form-control,\ntextarea.input-group-sm > .input-group-addon,\ntextarea.input-group-sm > .input-group-btn > .btn,\nselect[multiple].input-group-sm > .form-control,\nselect[multiple].input-group-sm > .input-group-addon,\nselect[multiple].input-group-sm > .input-group-btn > .btn {\n height: auto;\n}\n.input-group-addon,\n.input-group-btn,\n.input-group .form-control {\n display: table-cell;\n}\n.input-group-addon:not(:first-child):not(:last-child),\n.input-group-btn:not(:first-child):not(:last-child),\n.input-group .form-control:not(:first-child):not(:last-child) {\n border-radius: 0;\n}\n.input-group-addon,\n.input-group-btn {\n width: 1%;\n white-space: nowrap;\n vertical-align: middle;\n}\n.input-group-addon {\n padding: 6px 12px;\n font-size: 14px;\n font-weight: normal;\n line-height: 1;\n color: #555555;\n text-align: center;\n background-color: #eeeeee;\n border: 1px solid #ccc;\n border-radius: 4px;\n}\n.input-group-addon.input-sm {\n padding: 5px 10px;\n font-size: 12px;\n border-radius: 3px;\n}\n.input-group-addon.input-lg {\n padding: 10px 16px;\n font-size: 18px;\n border-radius: 6px;\n}\n.input-group-addon input[type=\"radio\"],\n.input-group-addon input[type=\"checkbox\"] {\n margin-top: 0;\n}\n.input-group .form-control:first-child,\n.input-group-addon:first-child,\n.input-group-btn:first-child > .btn,\n.input-group-btn:first-child > .btn-group > .btn,\n.input-group-btn:first-child > .dropdown-toggle,\n.input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle),\n.input-group-btn:last-child > .btn-group:not(:last-child) > .btn {\n border-bottom-right-radius: 0;\n border-top-right-radius: 0;\n}\n.input-group-addon:first-child {\n border-right: 0;\n}\n.input-group .form-control:last-child,\n.input-group-addon:last-child,\n.input-group-btn:last-child > .btn,\n.input-group-btn:last-child > .btn-group > .btn,\n.input-group-btn:last-child > .dropdown-toggle,\n.input-group-btn:first-child > .btn:not(:first-child),\n.input-group-btn:first-child > .btn-group:not(:first-child) > .btn {\n border-bottom-left-radius: 0;\n border-top-left-radius: 0;\n}\n.input-group-addon:last-child {\n border-left: 0;\n}\n.input-group-btn {\n position: relative;\n font-size: 0;\n white-space: nowrap;\n}\n.input-group-btn > .btn {\n position: relative;\n}\n.input-group-btn > .btn + .btn {\n margin-left: -1px;\n}\n.input-group-btn > .btn:hover,\n.input-group-btn > .btn:focus,\n.input-group-btn > .btn:active {\n z-index: 2;\n}\n.input-group-btn:first-child > .btn,\n.input-group-btn:first-child > .btn-group {\n margin-right: -1px;\n}\n.input-group-btn:last-child > .btn,\n.input-group-btn:last-child > .btn-group {\n z-index: 2;\n margin-left: -1px;\n}\n.nav {\n margin-bottom: 0;\n padding-left: 0;\n list-style: none;\n}\n.nav > li {\n position: relative;\n display: block;\n}\n.nav > li > a {\n position: relative;\n display: block;\n padding: 10px 15px;\n}\n.nav > li > a:hover,\n.nav > li > a:focus {\n text-decoration: none;\n background-color: #eeeeee;\n}\n.nav > li.disabled > a {\n color: #777777;\n}\n.nav > li.disabled > a:hover,\n.nav > li.disabled > a:focus {\n color: #777777;\n text-decoration: none;\n background-color: transparent;\n cursor: not-allowed;\n}\n.nav .open > a,\n.nav .open > a:hover,\n.nav .open > a:focus {\n background-color: #eeeeee;\n border-color: #337ab7;\n}\n.nav .nav-divider {\n height: 1px;\n margin: 9px 0;\n overflow: hidden;\n background-color: #e5e5e5;\n}\n.nav > li > a > img {\n max-width: none;\n}\n.nav-tabs {\n border-bottom: 1px solid #ddd;\n}\n.nav-tabs > li {\n float: left;\n margin-bottom: -1px;\n}\n.nav-tabs > li > a {\n margin-right: 2px;\n line-height: 1.42857143;\n border: 1px solid transparent;\n border-radius: 4px 4px 0 0;\n}\n.nav-tabs > li > a:hover {\n border-color: #eeeeee #eeeeee #ddd;\n}\n.nav-tabs > li.active > a,\n.nav-tabs > li.active > a:hover,\n.nav-tabs > li.active > a:focus {\n color: #555555;\n background-color: #fff;\n border: 1px solid #ddd;\n border-bottom-color: transparent;\n cursor: default;\n}\n.nav-tabs.nav-justified {\n width: 100%;\n border-bottom: 0;\n}\n.nav-tabs.nav-justified > li {\n float: none;\n}\n.nav-tabs.nav-justified > li > a {\n text-align: center;\n margin-bottom: 5px;\n}\n.nav-tabs.nav-justified > .dropdown .dropdown-menu {\n top: auto;\n left: auto;\n}\n@media (min-width: 768px) {\n .nav-tabs.nav-justified > li {\n display: table-cell;\n width: 1%;\n }\n .nav-tabs.nav-justified > li > a {\n margin-bottom: 0;\n }\n}\n.nav-tabs.nav-justified > li > a {\n margin-right: 0;\n border-radius: 4px;\n}\n.nav-tabs.nav-justified > .active > a,\n.nav-tabs.nav-justified > .active > a:hover,\n.nav-tabs.nav-justified > .active > a:focus {\n border: 1px solid #ddd;\n}\n@media (min-width: 768px) {\n .nav-tabs.nav-justified > li > a {\n border-bottom: 1px solid #ddd;\n border-radius: 4px 4px 0 0;\n }\n .nav-tabs.nav-justified > .active > a,\n .nav-tabs.nav-justified > .active > a:hover,\n .nav-tabs.nav-justified > .active > a:focus {\n border-bottom-color: #fff;\n }\n}\n.nav-pills > li {\n float: left;\n}\n.nav-pills > li > a {\n border-radius: 4px;\n}\n.nav-pills > li + li {\n margin-left: 2px;\n}\n.nav-pills > li.active > a,\n.nav-pills > li.active > a:hover,\n.nav-pills > li.active > a:focus {\n color: #fff;\n background-color: #337ab7;\n}\n.nav-stacked > li {\n float: none;\n}\n.nav-stacked > li + li {\n margin-top: 2px;\n margin-left: 0;\n}\n.nav-justified {\n width: 100%;\n}\n.nav-justified > li {\n float: none;\n}\n.nav-justified > li > a {\n text-align: center;\n margin-bottom: 5px;\n}\n.nav-justified > .dropdown .dropdown-menu {\n top: auto;\n left: auto;\n}\n@media (min-width: 768px) {\n .nav-justified > li {\n display: table-cell;\n width: 1%;\n }\n .nav-justified > li > a {\n margin-bottom: 0;\n }\n}\n.nav-tabs-justified {\n border-bottom: 0;\n}\n.nav-tabs-justified > li > a {\n margin-right: 0;\n border-radius: 4px;\n}\n.nav-tabs-justified > .active > a,\n.nav-tabs-justified > .active > a:hover,\n.nav-tabs-justified > .active > a:focus {\n border: 1px solid #ddd;\n}\n@media (min-width: 768px) {\n .nav-tabs-justified > li > a {\n border-bottom: 1px solid #ddd;\n border-radius: 4px 4px 0 0;\n }\n .nav-tabs-justified > .active > a,\n .nav-tabs-justified > .active > a:hover,\n .nav-tabs-justified > .active > a:focus {\n border-bottom-color: #fff;\n }\n}\n.tab-content > .tab-pane {\n display: none;\n}\n.tab-content > .active {\n display: block;\n}\n.nav-tabs .dropdown-menu {\n margin-top: -1px;\n border-top-right-radius: 0;\n border-top-left-radius: 0;\n}\n.navbar {\n position: relative;\n min-height: 50px;\n margin-bottom: 20px;\n border: 1px solid transparent;\n}\n@media (min-width: 768px) {\n .navbar {\n border-radius: 4px;\n }\n}\n@media (min-width: 768px) {\n .navbar-header {\n float: left;\n }\n}\n.navbar-collapse {\n overflow-x: visible;\n padding-right: 15px;\n padding-left: 15px;\n border-top: 1px solid transparent;\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1);\n -webkit-overflow-scrolling: touch;\n}\n.navbar-collapse.in {\n overflow-y: auto;\n}\n@media (min-width: 768px) {\n .navbar-collapse {\n width: auto;\n border-top: 0;\n box-shadow: none;\n }\n .navbar-collapse.collapse {\n display: block !important;\n height: auto !important;\n padding-bottom: 0;\n overflow: visible !important;\n }\n .navbar-collapse.in {\n overflow-y: visible;\n }\n .navbar-fixed-top .navbar-collapse,\n .navbar-static-top .navbar-collapse,\n .navbar-fixed-bottom .navbar-collapse {\n padding-left: 0;\n padding-right: 0;\n }\n}\n.navbar-fixed-top .navbar-collapse,\n.navbar-fixed-bottom .navbar-collapse {\n max-height: 340px;\n}\n@media (max-device-width: 480px) and (orientation: landscape) {\n .navbar-fixed-top .navbar-collapse,\n .navbar-fixed-bottom .navbar-collapse {\n max-height: 200px;\n }\n}\n.container > .navbar-header,\n.container-fluid > .navbar-header,\n.container > .navbar-collapse,\n.container-fluid > .navbar-collapse {\n margin-right: -15px;\n margin-left: -15px;\n}\n@media (min-width: 768px) {\n .container > .navbar-header,\n .container-fluid > .navbar-header,\n .container > .navbar-collapse,\n .container-fluid > .navbar-collapse {\n margin-right: 0;\n margin-left: 0;\n }\n}\n.navbar-static-top {\n z-index: 1000;\n border-width: 0 0 1px;\n}\n@media (min-width: 768px) {\n .navbar-static-top {\n border-radius: 0;\n }\n}\n.navbar-fixed-top,\n.navbar-fixed-bottom {\n position: fixed;\n right: 0;\n left: 0;\n z-index: 1030;\n}\n@media (min-width: 768px) {\n .navbar-fixed-top,\n .navbar-fixed-bottom {\n border-radius: 0;\n }\n}\n.navbar-fixed-top {\n top: 0;\n border-width: 0 0 1px;\n}\n.navbar-fixed-bottom {\n bottom: 0;\n margin-bottom: 0;\n border-width: 1px 0 0;\n}\n.navbar-brand {\n float: left;\n padding: 15px 15px;\n font-size: 18px;\n line-height: 20px;\n height: 50px;\n}\n.navbar-brand:hover,\n.navbar-brand:focus {\n text-decoration: none;\n}\n.navbar-brand > img {\n display: block;\n}\n@media (min-width: 768px) {\n .navbar > .container .navbar-brand,\n .navbar > .container-fluid .navbar-brand {\n margin-left: -15px;\n }\n}\n.navbar-toggle {\n position: relative;\n float: right;\n margin-right: 15px;\n padding: 9px 10px;\n margin-top: 8px;\n margin-bottom: 8px;\n background-color: transparent;\n background-image: none;\n border: 1px solid transparent;\n border-radius: 4px;\n}\n.navbar-toggle:focus {\n outline: 0;\n}\n.navbar-toggle .icon-bar {\n display: block;\n width: 22px;\n height: 2px;\n border-radius: 1px;\n}\n.navbar-toggle .icon-bar + .icon-bar {\n margin-top: 4px;\n}\n@media (min-width: 768px) {\n .navbar-toggle {\n display: none;\n }\n}\n.navbar-nav {\n margin: 7.5px -15px;\n}\n.navbar-nav > li > a {\n padding-top: 10px;\n padding-bottom: 10px;\n line-height: 20px;\n}\n@media (max-width: 767px) {\n .navbar-nav .open .dropdown-menu {\n position: static;\n float: none;\n width: auto;\n margin-top: 0;\n background-color: transparent;\n border: 0;\n box-shadow: none;\n }\n .navbar-nav .open .dropdown-menu > li > a,\n .navbar-nav .open .dropdown-menu .dropdown-header {\n padding: 5px 15px 5px 25px;\n }\n .navbar-nav .open .dropdown-menu > li > a {\n line-height: 20px;\n }\n .navbar-nav .open .dropdown-menu > li > a:hover,\n .navbar-nav .open .dropdown-menu > li > a:focus {\n background-image: none;\n }\n}\n@media (min-width: 768px) {\n .navbar-nav {\n float: left;\n margin: 0;\n }\n .navbar-nav > li {\n float: left;\n }\n .navbar-nav > li > a {\n padding-top: 15px;\n padding-bottom: 15px;\n }\n}\n.navbar-form {\n margin-left: -15px;\n margin-right: -15px;\n padding: 10px 15px;\n border-top: 1px solid transparent;\n border-bottom: 1px solid transparent;\n -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);\n margin-top: 8px;\n margin-bottom: 8px;\n}\n@media (min-width: 768px) {\n .navbar-form .form-group {\n display: inline-block;\n margin-bottom: 0;\n vertical-align: middle;\n }\n .navbar-form .form-control {\n display: inline-block;\n width: auto;\n vertical-align: middle;\n }\n .navbar-form .form-control-static {\n display: inline-block;\n }\n .navbar-form .input-group {\n display: inline-table;\n vertical-align: middle;\n }\n .navbar-form .input-group .input-group-addon,\n .navbar-form .input-group .input-group-btn,\n .navbar-form .input-group .form-control {\n width: auto;\n }\n .navbar-form .input-group > .form-control {\n width: 100%;\n }\n .navbar-form .control-label {\n margin-bottom: 0;\n vertical-align: middle;\n }\n .navbar-form .radio,\n .navbar-form .checkbox {\n display: inline-block;\n margin-top: 0;\n margin-bottom: 0;\n vertical-align: middle;\n }\n .navbar-form .radio label,\n .navbar-form .checkbox label {\n padding-left: 0;\n }\n .navbar-form .radio input[type=\"radio\"],\n .navbar-form .checkbox input[type=\"checkbox\"] {\n position: relative;\n margin-left: 0;\n }\n .navbar-form .has-feedback .form-control-feedback {\n top: 0;\n }\n}\n@media (max-width: 767px) {\n .navbar-form .form-group {\n margin-bottom: 5px;\n }\n .navbar-form .form-group:last-child {\n margin-bottom: 0;\n }\n}\n@media (min-width: 768px) {\n .navbar-form {\n width: auto;\n border: 0;\n margin-left: 0;\n margin-right: 0;\n padding-top: 0;\n padding-bottom: 0;\n -webkit-box-shadow: none;\n box-shadow: none;\n }\n}\n.navbar-nav > li > .dropdown-menu {\n margin-top: 0;\n border-top-right-radius: 0;\n border-top-left-radius: 0;\n}\n.navbar-fixed-bottom .navbar-nav > li > .dropdown-menu {\n margin-bottom: 0;\n border-top-right-radius: 4px;\n border-top-left-radius: 4px;\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 0;\n}\n.navbar-btn {\n margin-top: 8px;\n margin-bottom: 8px;\n}\n.navbar-btn.btn-sm {\n margin-top: 10px;\n margin-bottom: 10px;\n}\n.navbar-btn.btn-xs {\n margin-top: 14px;\n margin-bottom: 14px;\n}\n.navbar-text {\n margin-top: 15px;\n margin-bottom: 15px;\n}\n@media (min-width: 768px) {\n .navbar-text {\n float: left;\n margin-left: 15px;\n margin-right: 15px;\n }\n}\n@media (min-width: 768px) {\n .navbar-left {\n float: left !important;\n }\n .navbar-right {\n float: right !important;\n margin-right: -15px;\n }\n .navbar-right ~ .navbar-right {\n margin-right: 0;\n }\n}\n.navbar-default {\n background-color: #f8f8f8;\n border-color: #e7e7e7;\n}\n.navbar-default .navbar-brand {\n color: #777;\n}\n.navbar-default .navbar-brand:hover,\n.navbar-default .navbar-brand:focus {\n color: #5e5e5e;\n background-color: transparent;\n}\n.navbar-default .navbar-text {\n color: #777;\n}\n.navbar-default .navbar-nav > li > a {\n color: #777;\n}\n.navbar-default .navbar-nav > li > a:hover,\n.navbar-default .navbar-nav > li > a:focus {\n color: #333;\n background-color: transparent;\n}\n.navbar-default .navbar-nav > .active > a,\n.navbar-default .navbar-nav > .active > a:hover,\n.navbar-default .navbar-nav > .active > a:focus {\n color: #555;\n background-color: #e7e7e7;\n}\n.navbar-default .navbar-nav > .disabled > a,\n.navbar-default .navbar-nav > .disabled > a:hover,\n.navbar-default .navbar-nav > .disabled > a:focus {\n color: #ccc;\n background-color: transparent;\n}\n.navbar-default .navbar-toggle {\n border-color: #ddd;\n}\n.navbar-default .navbar-toggle:hover,\n.navbar-default .navbar-toggle:focus {\n background-color: #ddd;\n}\n.navbar-default .navbar-toggle .icon-bar {\n background-color: #888;\n}\n.navbar-default .navbar-collapse,\n.navbar-default .navbar-form {\n border-color: #e7e7e7;\n}\n.navbar-default .navbar-nav > .open > a,\n.navbar-default .navbar-nav > .open > a:hover,\n.navbar-default .navbar-nav > .open > a:focus {\n background-color: #e7e7e7;\n color: #555;\n}\n@media (max-width: 767px) {\n .navbar-default .navbar-nav .open .dropdown-menu > li > a {\n color: #777;\n }\n .navbar-default .navbar-nav .open .dropdown-menu > li > a:hover,\n .navbar-default .navbar-nav .open .dropdown-menu > li > a:focus {\n color: #333;\n background-color: transparent;\n }\n .navbar-default .navbar-nav .open .dropdown-menu > .active > a,\n .navbar-default .navbar-nav .open .dropdown-menu > .active > a:hover,\n .navbar-default .navbar-nav .open .dropdown-menu > .active > a:focus {\n color: #555;\n background-color: #e7e7e7;\n }\n .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a,\n .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:hover,\n .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:focus {\n color: #ccc;\n background-color: transparent;\n }\n}\n.navbar-default .navbar-link {\n color: #777;\n}\n.navbar-default .navbar-link:hover {\n color: #333;\n}\n.navbar-default .btn-link {\n color: #777;\n}\n.navbar-default .btn-link:hover,\n.navbar-default .btn-link:focus {\n color: #333;\n}\n.navbar-default .btn-link[disabled]:hover,\nfieldset[disabled] .navbar-default .btn-link:hover,\n.navbar-default .btn-link[disabled]:focus,\nfieldset[disabled] .navbar-default .btn-link:focus {\n color: #ccc;\n}\n.navbar-inverse {\n background-color: #222;\n border-color: #080808;\n}\n.navbar-inverse .navbar-brand {\n color: #9d9d9d;\n}\n.navbar-inverse .navbar-brand:hover,\n.navbar-inverse .navbar-brand:focus {\n color: #fff;\n background-color: transparent;\n}\n.navbar-inverse .navbar-text {\n color: #9d9d9d;\n}\n.navbar-inverse .navbar-nav > li > a {\n color: #9d9d9d;\n}\n.navbar-inverse .navbar-nav > li > a:hover,\n.navbar-inverse .navbar-nav > li > a:focus {\n color: #fff;\n background-color: transparent;\n}\n.navbar-inverse .navbar-nav > .active > a,\n.navbar-inverse .navbar-nav > .active > a:hover,\n.navbar-inverse .navbar-nav > .active > a:focus {\n color: #fff;\n background-color: #080808;\n}\n.navbar-inverse .navbar-nav > .disabled > a,\n.navbar-inverse .navbar-nav > .disabled > a:hover,\n.navbar-inverse .navbar-nav > .disabled > a:focus {\n color: #444;\n background-color: transparent;\n}\n.navbar-inverse .navbar-toggle {\n border-color: #333;\n}\n.navbar-inverse .navbar-toggle:hover,\n.navbar-inverse .navbar-toggle:focus {\n background-color: #333;\n}\n.navbar-inverse .navbar-toggle .icon-bar {\n background-color: #fff;\n}\n.navbar-inverse .navbar-collapse,\n.navbar-inverse .navbar-form {\n border-color: #101010;\n}\n.navbar-inverse .navbar-nav > .open > a,\n.navbar-inverse .navbar-nav > .open > a:hover,\n.navbar-inverse .navbar-nav > .open > a:focus {\n background-color: #080808;\n color: #fff;\n}\n@media (max-width: 767px) {\n .navbar-inverse .navbar-nav .open .dropdown-menu > .dropdown-header {\n border-color: #080808;\n }\n .navbar-inverse .navbar-nav .open .dropdown-menu .divider {\n background-color: #080808;\n }\n .navbar-inverse .navbar-nav .open .dropdown-menu > li > a {\n color: #9d9d9d;\n }\n .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:hover,\n .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:focus {\n color: #fff;\n background-color: transparent;\n }\n .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a,\n .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:hover,\n .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:focus {\n color: #fff;\n background-color: #080808;\n }\n .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a,\n .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:hover,\n .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:focus {\n color: #444;\n background-color: transparent;\n }\n}\n.navbar-inverse .navbar-link {\n color: #9d9d9d;\n}\n.navbar-inverse .navbar-link:hover {\n color: #fff;\n}\n.navbar-inverse .btn-link {\n color: #9d9d9d;\n}\n.navbar-inverse .btn-link:hover,\n.navbar-inverse .btn-link:focus {\n color: #fff;\n}\n.navbar-inverse .btn-link[disabled]:hover,\nfieldset[disabled] .navbar-inverse .btn-link:hover,\n.navbar-inverse .btn-link[disabled]:focus,\nfieldset[disabled] .navbar-inverse .btn-link:focus {\n color: #444;\n}\n.breadcrumb {\n padding: 8px 15px;\n margin-bottom: 20px;\n list-style: none;\n background-color: #f5f5f5;\n border-radius: 4px;\n}\n.breadcrumb > li {\n display: inline-block;\n}\n.breadcrumb > li + li:before {\n content: \"/\\00a0\";\n padding: 0 5px;\n color: #ccc;\n}\n.breadcrumb > .active {\n color: #777777;\n}\n.pagination {\n display: inline-block;\n padding-left: 0;\n margin: 20px 0;\n border-radius: 4px;\n}\n.pagination > li {\n display: inline;\n}\n.pagination > li > a,\n.pagination > li > span {\n position: relative;\n float: left;\n padding: 6px 12px;\n line-height: 1.42857143;\n text-decoration: none;\n color: #337ab7;\n background-color: #fff;\n border: 1px solid #ddd;\n margin-left: -1px;\n}\n.pagination > li:first-child > a,\n.pagination > li:first-child > span {\n margin-left: 0;\n border-bottom-left-radius: 4px;\n border-top-left-radius: 4px;\n}\n.pagination > li:last-child > a,\n.pagination > li:last-child > span {\n border-bottom-right-radius: 4px;\n border-top-right-radius: 4px;\n}\n.pagination > li > a:hover,\n.pagination > li > span:hover,\n.pagination > li > a:focus,\n.pagination > li > span:focus {\n z-index: 2;\n color: #23527c;\n background-color: #eeeeee;\n border-color: #ddd;\n}\n.pagination > .active > a,\n.pagination > .active > span,\n.pagination > .active > a:hover,\n.pagination > .active > span:hover,\n.pagination > .active > a:focus,\n.pagination > .active > span:focus {\n z-index: 3;\n color: #fff;\n background-color: #337ab7;\n border-color: #337ab7;\n cursor: default;\n}\n.pagination > .disabled > span,\n.pagination > .disabled > span:hover,\n.pagination > .disabled > span:focus,\n.pagination > .disabled > a,\n.pagination > .disabled > a:hover,\n.pagination > .disabled > a:focus {\n color: #777777;\n background-color: #fff;\n border-color: #ddd;\n cursor: not-allowed;\n}\n.pagination-lg > li > a,\n.pagination-lg > li > span {\n padding: 10px 16px;\n font-size: 18px;\n line-height: 1.3333333;\n}\n.pagination-lg > li:first-child > a,\n.pagination-lg > li:first-child > span {\n border-bottom-left-radius: 6px;\n border-top-left-radius: 6px;\n}\n.pagination-lg > li:last-child > a,\n.pagination-lg > li:last-child > span {\n border-bottom-right-radius: 6px;\n border-top-right-radius: 6px;\n}\n.pagination-sm > li > a,\n.pagination-sm > li > span {\n padding: 5px 10px;\n font-size: 12px;\n line-height: 1.5;\n}\n.pagination-sm > li:first-child > a,\n.pagination-sm > li:first-child > span {\n border-bottom-left-radius: 3px;\n border-top-left-radius: 3px;\n}\n.pagination-sm > li:last-child > a,\n.pagination-sm > li:last-child > span {\n border-bottom-right-radius: 3px;\n border-top-right-radius: 3px;\n}\n.pager {\n padding-left: 0;\n margin: 20px 0;\n list-style: none;\n text-align: center;\n}\n.pager li {\n display: inline;\n}\n.pager li > a,\n.pager li > span {\n display: inline-block;\n padding: 5px 14px;\n background-color: #fff;\n border: 1px solid #ddd;\n border-radius: 15px;\n}\n.pager li > a:hover,\n.pager li > a:focus {\n text-decoration: none;\n background-color: #eeeeee;\n}\n.pager .next > a,\n.pager .next > span {\n float: right;\n}\n.pager .previous > a,\n.pager .previous > span {\n float: left;\n}\n.pager .disabled > a,\n.pager .disabled > a:hover,\n.pager .disabled > a:focus,\n.pager .disabled > span {\n color: #777777;\n background-color: #fff;\n cursor: not-allowed;\n}\n.label {\n display: inline;\n padding: .2em .6em .3em;\n font-size: 75%;\n font-weight: bold;\n line-height: 1;\n color: #fff;\n text-align: center;\n white-space: nowrap;\n vertical-align: baseline;\n border-radius: .25em;\n}\na.label:hover,\na.label:focus {\n color: #fff;\n text-decoration: none;\n cursor: pointer;\n}\n.label:empty {\n display: none;\n}\n.btn .label {\n position: relative;\n top: -1px;\n}\n.label-default {\n background-color: #777777;\n}\n.label-default[href]:hover,\n.label-default[href]:focus {\n background-color: #5e5e5e;\n}\n.label-primary {\n background-color: #337ab7;\n}\n.label-primary[href]:hover,\n.label-primary[href]:focus {\n background-color: #286090;\n}\n.label-success {\n background-color: #5cb85c;\n}\n.label-success[href]:hover,\n.label-success[href]:focus {\n background-color: #449d44;\n}\n.label-info {\n background-color: #5bc0de;\n}\n.label-info[href]:hover,\n.label-info[href]:focus {\n background-color: #31b0d5;\n}\n.label-warning {\n background-color: #f0ad4e;\n}\n.label-warning[href]:hover,\n.label-warning[href]:focus {\n background-color: #ec971f;\n}\n.label-danger {\n background-color: #d9534f;\n}\n.label-danger[href]:hover,\n.label-danger[href]:focus {\n background-color: #c9302c;\n}\n.badge {\n display: inline-block;\n min-width: 10px;\n padding: 3px 7px;\n font-size: 12px;\n font-weight: bold;\n color: #fff;\n line-height: 1;\n vertical-align: middle;\n white-space: nowrap;\n text-align: center;\n background-color: #777777;\n border-radius: 10px;\n}\n.badge:empty {\n display: none;\n}\n.btn .badge {\n position: relative;\n top: -1px;\n}\n.btn-xs .badge,\n.btn-group-xs > .btn .badge {\n top: 0;\n padding: 1px 5px;\n}\na.badge:hover,\na.badge:focus {\n color: #fff;\n text-decoration: none;\n cursor: pointer;\n}\n.list-group-item.active > .badge,\n.nav-pills > .active > a > .badge {\n color: #337ab7;\n background-color: #fff;\n}\n.list-group-item > .badge {\n float: right;\n}\n.list-group-item > .badge + .badge {\n margin-right: 5px;\n}\n.nav-pills > li > a > .badge {\n margin-left: 3px;\n}\n.jumbotron {\n padding-top: 30px;\n padding-bottom: 30px;\n margin-bottom: 30px;\n color: inherit;\n background-color: #eeeeee;\n}\n.jumbotron h1,\n.jumbotron .h1 {\n color: inherit;\n}\n.jumbotron p {\n margin-bottom: 15px;\n font-size: 21px;\n font-weight: 200;\n}\n.jumbotron > hr {\n border-top-color: #d5d5d5;\n}\n.container .jumbotron,\n.container-fluid .jumbotron {\n border-radius: 6px;\n padding-left: 15px;\n padding-right: 15px;\n}\n.jumbotron .container {\n max-width: 100%;\n}\n@media screen and (min-width: 768px) {\n .jumbotron {\n padding-top: 48px;\n padding-bottom: 48px;\n }\n .container .jumbotron,\n .container-fluid .jumbotron {\n padding-left: 60px;\n padding-right: 60px;\n }\n .jumbotron h1,\n .jumbotron .h1 {\n font-size: 63px;\n }\n}\n.thumbnail {\n display: block;\n padding: 4px;\n margin-bottom: 20px;\n line-height: 1.42857143;\n background-color: #fff;\n border: 1px solid #ddd;\n border-radius: 4px;\n -webkit-transition: border 0.2s ease-in-out;\n -o-transition: border 0.2s ease-in-out;\n transition: border 0.2s ease-in-out;\n}\n.thumbnail > img,\n.thumbnail a > img {\n margin-left: auto;\n margin-right: auto;\n}\na.thumbnail:hover,\na.thumbnail:focus,\na.thumbnail.active {\n border-color: #337ab7;\n}\n.thumbnail .caption {\n padding: 9px;\n color: #333333;\n}\n.alert {\n padding: 15px;\n margin-bottom: 20px;\n border: 1px solid transparent;\n border-radius: 4px;\n}\n.alert h4 {\n margin-top: 0;\n color: inherit;\n}\n.alert .alert-link {\n font-weight: bold;\n}\n.alert > p,\n.alert > ul {\n margin-bottom: 0;\n}\n.alert > p + p {\n margin-top: 5px;\n}\n.alert-dismissable,\n.alert-dismissible {\n padding-right: 35px;\n}\n.alert-dismissable .close,\n.alert-dismissible .close {\n position: relative;\n top: -2px;\n right: -21px;\n color: inherit;\n}\n.alert-success {\n background-color: #dff0d8;\n border-color: #d6e9c6;\n color: #3c763d;\n}\n.alert-success hr {\n border-top-color: #c9e2b3;\n}\n.alert-success .alert-link {\n color: #2b542c;\n}\n.alert-info {\n background-color: #d9edf7;\n border-color: #bce8f1;\n color: #31708f;\n}\n.alert-info hr {\n border-top-color: #a6e1ec;\n}\n.alert-info .alert-link {\n color: #245269;\n}\n.alert-warning {\n background-color: #fcf8e3;\n border-color: #faebcc;\n color: #8a6d3b;\n}\n.alert-warning hr {\n border-top-color: #f7e1b5;\n}\n.alert-warning .alert-link {\n color: #66512c;\n}\n.alert-danger {\n background-color: #f2dede;\n border-color: #ebccd1;\n color: #a94442;\n}\n.alert-danger hr {\n border-top-color: #e4b9c0;\n}\n.alert-danger .alert-link {\n color: #843534;\n}\n@-webkit-keyframes progress-bar-stripes {\n from {\n background-position: 40px 0;\n }\n to {\n background-position: 0 0;\n }\n}\n@keyframes progress-bar-stripes {\n from {\n background-position: 40px 0;\n }\n to {\n background-position: 0 0;\n }\n}\n.progress {\n overflow: hidden;\n height: 20px;\n margin-bottom: 20px;\n background-color: #f5f5f5;\n border-radius: 4px;\n -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);\n box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);\n}\n.progress-bar {\n float: left;\n width: 0%;\n height: 100%;\n font-size: 12px;\n line-height: 20px;\n color: #fff;\n text-align: center;\n background-color: #337ab7;\n -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);\n box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);\n -webkit-transition: width 0.6s ease;\n -o-transition: width 0.6s ease;\n transition: width 0.6s ease;\n}\n.progress-striped .progress-bar,\n.progress-bar-striped {\n background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-size: 40px 40px;\n}\n.progress.active .progress-bar,\n.progress-bar.active {\n -webkit-animation: progress-bar-stripes 2s linear infinite;\n -o-animation: progress-bar-stripes 2s linear infinite;\n animation: progress-bar-stripes 2s linear infinite;\n}\n.progress-bar-success {\n background-color: #5cb85c;\n}\n.progress-striped .progress-bar-success {\n background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n}\n.progress-bar-info {\n background-color: #5bc0de;\n}\n.progress-striped .progress-bar-info {\n background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n}\n.progress-bar-warning {\n background-color: #f0ad4e;\n}\n.progress-striped .progress-bar-warning {\n background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n}\n.progress-bar-danger {\n background-color: #d9534f;\n}\n.progress-striped .progress-bar-danger {\n background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n}\n.media {\n margin-top: 15px;\n}\n.media:first-child {\n margin-top: 0;\n}\n.media,\n.media-body {\n zoom: 1;\n overflow: hidden;\n}\n.media-body {\n width: 10000px;\n}\n.media-object {\n display: block;\n}\n.media-object.img-thumbnail {\n max-width: none;\n}\n.media-right,\n.media > .pull-right {\n padding-left: 10px;\n}\n.media-left,\n.media > .pull-left {\n padding-right: 10px;\n}\n.media-left,\n.media-right,\n.media-body {\n display: table-cell;\n vertical-align: top;\n}\n.media-middle {\n vertical-align: middle;\n}\n.media-bottom {\n vertical-align: bottom;\n}\n.media-heading {\n margin-top: 0;\n margin-bottom: 5px;\n}\n.media-list {\n padding-left: 0;\n list-style: none;\n}\n.list-group {\n margin-bottom: 20px;\n padding-left: 0;\n}\n.list-group-item {\n position: relative;\n display: block;\n padding: 10px 15px;\n margin-bottom: -1px;\n background-color: #fff;\n border: 1px solid #ddd;\n}\n.list-group-item:first-child {\n border-top-right-radius: 4px;\n border-top-left-radius: 4px;\n}\n.list-group-item:last-child {\n margin-bottom: 0;\n border-bottom-right-radius: 4px;\n border-bottom-left-radius: 4px;\n}\na.list-group-item,\nbutton.list-group-item {\n color: #555;\n}\na.list-group-item .list-group-item-heading,\nbutton.list-group-item .list-group-item-heading {\n color: #333;\n}\na.list-group-item:hover,\nbutton.list-group-item:hover,\na.list-group-item:focus,\nbutton.list-group-item:focus {\n text-decoration: none;\n color: #555;\n background-color: #f5f5f5;\n}\nbutton.list-group-item {\n width: 100%;\n text-align: left;\n}\n.list-group-item.disabled,\n.list-group-item.disabled:hover,\n.list-group-item.disabled:focus {\n background-color: #eeeeee;\n color: #777777;\n cursor: not-allowed;\n}\n.list-group-item.disabled .list-group-item-heading,\n.list-group-item.disabled:hover .list-group-item-heading,\n.list-group-item.disabled:focus .list-group-item-heading {\n color: inherit;\n}\n.list-group-item.disabled .list-group-item-text,\n.list-group-item.disabled:hover .list-group-item-text,\n.list-group-item.disabled:focus .list-group-item-text {\n color: #777777;\n}\n.list-group-item.active,\n.list-group-item.active:hover,\n.list-group-item.active:focus {\n z-index: 2;\n color: #fff;\n background-color: #337ab7;\n border-color: #337ab7;\n}\n.list-group-item.active .list-group-item-heading,\n.list-group-item.active:hover .list-group-item-heading,\n.list-group-item.active:focus .list-group-item-heading,\n.list-group-item.active .list-group-item-heading > small,\n.list-group-item.active:hover .list-group-item-heading > small,\n.list-group-item.active:focus .list-group-item-heading > small,\n.list-group-item.active .list-group-item-heading > .small,\n.list-group-item.active:hover .list-group-item-heading > .small,\n.list-group-item.active:focus .list-group-item-heading > .small {\n color: inherit;\n}\n.list-group-item.active .list-group-item-text,\n.list-group-item.active:hover .list-group-item-text,\n.list-group-item.active:focus .list-group-item-text {\n color: #c7ddef;\n}\n.list-group-item-success {\n color: #3c763d;\n background-color: #dff0d8;\n}\na.list-group-item-success,\nbutton.list-group-item-success {\n color: #3c763d;\n}\na.list-group-item-success .list-group-item-heading,\nbutton.list-group-item-success .list-group-item-heading {\n color: inherit;\n}\na.list-group-item-success:hover,\nbutton.list-group-item-success:hover,\na.list-group-item-success:focus,\nbutton.list-group-item-success:focus {\n color: #3c763d;\n background-color: #d0e9c6;\n}\na.list-group-item-success.active,\nbutton.list-group-item-success.active,\na.list-group-item-success.active:hover,\nbutton.list-group-item-success.active:hover,\na.list-group-item-success.active:focus,\nbutton.list-group-item-success.active:focus {\n color: #fff;\n background-color: #3c763d;\n border-color: #3c763d;\n}\n.list-group-item-info {\n color: #31708f;\n background-color: #d9edf7;\n}\na.list-group-item-info,\nbutton.list-group-item-info {\n color: #31708f;\n}\na.list-group-item-info .list-group-item-heading,\nbutton.list-group-item-info .list-group-item-heading {\n color: inherit;\n}\na.list-group-item-info:hover,\nbutton.list-group-item-info:hover,\na.list-group-item-info:focus,\nbutton.list-group-item-info:focus {\n color: #31708f;\n background-color: #c4e3f3;\n}\na.list-group-item-info.active,\nbutton.list-group-item-info.active,\na.list-group-item-info.active:hover,\nbutton.list-group-item-info.active:hover,\na.list-group-item-info.active:focus,\nbutton.list-group-item-info.active:focus {\n color: #fff;\n background-color: #31708f;\n border-color: #31708f;\n}\n.list-group-item-warning {\n color: #8a6d3b;\n background-color: #fcf8e3;\n}\na.list-group-item-warning,\nbutton.list-group-item-warning {\n color: #8a6d3b;\n}\na.list-group-item-warning .list-group-item-heading,\nbutton.list-group-item-warning .list-group-item-heading {\n color: inherit;\n}\na.list-group-item-warning:hover,\nbutton.list-group-item-warning:hover,\na.list-group-item-warning:focus,\nbutton.list-group-item-warning:focus {\n color: #8a6d3b;\n background-color: #faf2cc;\n}\na.list-group-item-warning.active,\nbutton.list-group-item-warning.active,\na.list-group-item-warning.active:hover,\nbutton.list-group-item-warning.active:hover,\na.list-group-item-warning.active:focus,\nbutton.list-group-item-warning.active:focus {\n color: #fff;\n background-color: #8a6d3b;\n border-color: #8a6d3b;\n}\n.list-group-item-danger {\n color: #a94442;\n background-color: #f2dede;\n}\na.list-group-item-danger,\nbutton.list-group-item-danger {\n color: #a94442;\n}\na.list-group-item-danger .list-group-item-heading,\nbutton.list-group-item-danger .list-group-item-heading {\n color: inherit;\n}\na.list-group-item-danger:hover,\nbutton.list-group-item-danger:hover,\na.list-group-item-danger:focus,\nbutton.list-group-item-danger:focus {\n color: #a94442;\n background-color: #ebcccc;\n}\na.list-group-item-danger.active,\nbutton.list-group-item-danger.active,\na.list-group-item-danger.active:hover,\nbutton.list-group-item-danger.active:hover,\na.list-group-item-danger.active:focus,\nbutton.list-group-item-danger.active:focus {\n color: #fff;\n background-color: #a94442;\n border-color: #a94442;\n}\n.list-group-item-heading {\n margin-top: 0;\n margin-bottom: 5px;\n}\n.list-group-item-text {\n margin-bottom: 0;\n line-height: 1.3;\n}\n.panel {\n margin-bottom: 20px;\n background-color: #fff;\n border: 1px solid transparent;\n border-radius: 4px;\n -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05);\n box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05);\n}\n.panel-body {\n padding: 15px;\n}\n.panel-heading {\n padding: 10px 15px;\n border-bottom: 1px solid transparent;\n border-top-right-radius: 3px;\n border-top-left-radius: 3px;\n}\n.panel-heading > .dropdown .dropdown-toggle {\n color: inherit;\n}\n.panel-title {\n margin-top: 0;\n margin-bottom: 0;\n font-size: 16px;\n color: inherit;\n}\n.panel-title > a,\n.panel-title > small,\n.panel-title > .small,\n.panel-title > small > a,\n.panel-title > .small > a {\n color: inherit;\n}\n.panel-footer {\n padding: 10px 15px;\n background-color: #f5f5f5;\n border-top: 1px solid #ddd;\n border-bottom-right-radius: 3px;\n border-bottom-left-radius: 3px;\n}\n.panel > .list-group,\n.panel > .panel-collapse > .list-group {\n margin-bottom: 0;\n}\n.panel > .list-group .list-group-item,\n.panel > .panel-collapse > .list-group .list-group-item {\n border-width: 1px 0;\n border-radius: 0;\n}\n.panel > .list-group:first-child .list-group-item:first-child,\n.panel > .panel-collapse > .list-group:first-child .list-group-item:first-child {\n border-top: 0;\n border-top-right-radius: 3px;\n border-top-left-radius: 3px;\n}\n.panel > .list-group:last-child .list-group-item:last-child,\n.panel > .panel-collapse > .list-group:last-child .list-group-item:last-child {\n border-bottom: 0;\n border-bottom-right-radius: 3px;\n border-bottom-left-radius: 3px;\n}\n.panel > .panel-heading + .panel-collapse > .list-group .list-group-item:first-child {\n border-top-right-radius: 0;\n border-top-left-radius: 0;\n}\n.panel-heading + .list-group .list-group-item:first-child {\n border-top-width: 0;\n}\n.list-group + .panel-footer {\n border-top-width: 0;\n}\n.panel > .table,\n.panel > .table-responsive > .table,\n.panel > .panel-collapse > .table {\n margin-bottom: 0;\n}\n.panel > .table caption,\n.panel > .table-responsive > .table caption,\n.panel > .panel-collapse > .table caption {\n padding-left: 15px;\n padding-right: 15px;\n}\n.panel > .table:first-child,\n.panel > .table-responsive:first-child > .table:first-child {\n border-top-right-radius: 3px;\n border-top-left-radius: 3px;\n}\n.panel > .table:first-child > thead:first-child > tr:first-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child {\n border-top-left-radius: 3px;\n border-top-right-radius: 3px;\n}\n.panel > .table:first-child > thead:first-child > tr:first-child td:first-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:first-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child td:first-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:first-child,\n.panel > .table:first-child > thead:first-child > tr:first-child th:first-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:first-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child th:first-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:first-child {\n border-top-left-radius: 3px;\n}\n.panel > .table:first-child > thead:first-child > tr:first-child td:last-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:last-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child td:last-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:last-child,\n.panel > .table:first-child > thead:first-child > tr:first-child th:last-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:last-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child th:last-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:last-child {\n border-top-right-radius: 3px;\n}\n.panel > .table:last-child,\n.panel > .table-responsive:last-child > .table:last-child {\n border-bottom-right-radius: 3px;\n border-bottom-left-radius: 3px;\n}\n.panel > .table:last-child > tbody:last-child > tr:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child {\n border-bottom-left-radius: 3px;\n border-bottom-right-radius: 3px;\n}\n.panel > .table:last-child > tbody:last-child > tr:last-child td:first-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:first-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child td:first-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:first-child,\n.panel > .table:last-child > tbody:last-child > tr:last-child th:first-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:first-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child th:first-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:first-child {\n border-bottom-left-radius: 3px;\n}\n.panel > .table:last-child > tbody:last-child > tr:last-child td:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:last-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child td:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:last-child,\n.panel > .table:last-child > tbody:last-child > tr:last-child th:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:last-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child th:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:last-child {\n border-bottom-right-radius: 3px;\n}\n.panel > .panel-body + .table,\n.panel > .panel-body + .table-responsive,\n.panel > .table + .panel-body,\n.panel > .table-responsive + .panel-body {\n border-top: 1px solid #ddd;\n}\n.panel > .table > tbody:first-child > tr:first-child th,\n.panel > .table > tbody:first-child > tr:first-child td {\n border-top: 0;\n}\n.panel > .table-bordered,\n.panel > .table-responsive > .table-bordered {\n border: 0;\n}\n.panel > .table-bordered > thead > tr > th:first-child,\n.panel > .table-responsive > .table-bordered > thead > tr > th:first-child,\n.panel > .table-bordered > tbody > tr > th:first-child,\n.panel > .table-responsive > .table-bordered > tbody > tr > th:first-child,\n.panel > .table-bordered > tfoot > tr > th:first-child,\n.panel > .table-responsive > .table-bordered > tfoot > tr > th:first-child,\n.panel > .table-bordered > thead > tr > td:first-child,\n.panel > .table-responsive > .table-bordered > thead > tr > td:first-child,\n.panel > .table-bordered > tbody > tr > td:first-child,\n.panel > .table-responsive > .table-bordered > tbody > tr > td:first-child,\n.panel > .table-bordered > tfoot > tr > td:first-child,\n.panel > .table-responsive > .table-bordered > tfoot > tr > td:first-child {\n border-left: 0;\n}\n.panel > .table-bordered > thead > tr > th:last-child,\n.panel > .table-responsive > .table-bordered > thead > tr > th:last-child,\n.panel > .table-bordered > tbody > tr > th:last-child,\n.panel > .table-responsive > .table-bordered > tbody > tr > th:last-child,\n.panel > .table-bordered > tfoot > tr > th:last-child,\n.panel > .table-responsive > .table-bordered > tfoot > tr > th:last-child,\n.panel > .table-bordered > thead > tr > td:last-child,\n.panel > .table-responsive > .table-bordered > thead > tr > td:last-child,\n.panel > .table-bordered > tbody > tr > td:last-child,\n.panel > .table-responsive > .table-bordered > tbody > tr > td:last-child,\n.panel > .table-bordered > tfoot > tr > td:last-child,\n.panel > .table-responsive > .table-bordered > tfoot > tr > td:last-child {\n border-right: 0;\n}\n.panel > .table-bordered > thead > tr:first-child > td,\n.panel > .table-responsive > .table-bordered > thead > tr:first-child > td,\n.panel > .table-bordered > tbody > tr:first-child > td,\n.panel > .table-responsive > .table-bordered > tbody > tr:first-child > td,\n.panel > .table-bordered > thead > tr:first-child > th,\n.panel > .table-responsive > .table-bordered > thead > tr:first-child > th,\n.panel > .table-bordered > tbody > tr:first-child > th,\n.panel > .table-responsive > .table-bordered > tbody > tr:first-child > th {\n border-bottom: 0;\n}\n.panel > .table-bordered > tbody > tr:last-child > td,\n.panel > .table-responsive > .table-bordered > tbody > tr:last-child > td,\n.panel > .table-bordered > tfoot > tr:last-child > td,\n.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > td,\n.panel > .table-bordered > tbody > tr:last-child > th,\n.panel > .table-responsive > .table-bordered > tbody > tr:last-child > th,\n.panel > .table-bordered > tfoot > tr:last-child > th,\n.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > th {\n border-bottom: 0;\n}\n.panel > .table-responsive {\n border: 0;\n margin-bottom: 0;\n}\n.panel-group {\n margin-bottom: 20px;\n}\n.panel-group .panel {\n margin-bottom: 0;\n border-radius: 4px;\n}\n.panel-group .panel + .panel {\n margin-top: 5px;\n}\n.panel-group .panel-heading {\n border-bottom: 0;\n}\n.panel-group .panel-heading + .panel-collapse > .panel-body,\n.panel-group .panel-heading + .panel-collapse > .list-group {\n border-top: 1px solid #ddd;\n}\n.panel-group .panel-footer {\n border-top: 0;\n}\n.panel-group .panel-footer + .panel-collapse .panel-body {\n border-bottom: 1px solid #ddd;\n}\n.panel-default {\n border-color: #ddd;\n}\n.panel-default > .panel-heading {\n color: #333333;\n background-color: #f5f5f5;\n border-color: #ddd;\n}\n.panel-default > .panel-heading + .panel-collapse > .panel-body {\n border-top-color: #ddd;\n}\n.panel-default > .panel-heading .badge {\n color: #f5f5f5;\n background-color: #333333;\n}\n.panel-default > .panel-footer + .panel-collapse > .panel-body {\n border-bottom-color: #ddd;\n}\n.panel-primary {\n border-color: #337ab7;\n}\n.panel-primary > .panel-heading {\n color: #fff;\n background-color: #337ab7;\n border-color: #337ab7;\n}\n.panel-primary > .panel-heading + .panel-collapse > .panel-body {\n border-top-color: #337ab7;\n}\n.panel-primary > .panel-heading .badge {\n color: #337ab7;\n background-color: #fff;\n}\n.panel-primary > .panel-footer + .panel-collapse > .panel-body {\n border-bottom-color: #337ab7;\n}\n.panel-success {\n border-color: #d6e9c6;\n}\n.panel-success > .panel-heading {\n color: #3c763d;\n background-color: #dff0d8;\n border-color: #d6e9c6;\n}\n.panel-success > .panel-heading + .panel-collapse > .panel-body {\n border-top-color: #d6e9c6;\n}\n.panel-success > .panel-heading .badge {\n color: #dff0d8;\n background-color: #3c763d;\n}\n.panel-success > .panel-footer + .panel-collapse > .panel-body {\n border-bottom-color: #d6e9c6;\n}\n.panel-info {\n border-color: #bce8f1;\n}\n.panel-info > .panel-heading {\n color: #31708f;\n background-color: #d9edf7;\n border-color: #bce8f1;\n}\n.panel-info > .panel-heading + .panel-collapse > .panel-body {\n border-top-color: #bce8f1;\n}\n.panel-info > .panel-heading .badge {\n color: #d9edf7;\n background-color: #31708f;\n}\n.panel-info > .panel-footer + .panel-collapse > .panel-body {\n border-bottom-color: #bce8f1;\n}\n.panel-warning {\n border-color: #faebcc;\n}\n.panel-warning > .panel-heading {\n color: #8a6d3b;\n background-color: #fcf8e3;\n border-color: #faebcc;\n}\n.panel-warning > .panel-heading + .panel-collapse > .panel-body {\n border-top-color: #faebcc;\n}\n.panel-warning > .panel-heading .badge {\n color: #fcf8e3;\n background-color: #8a6d3b;\n}\n.panel-warning > .panel-footer + .panel-collapse > .panel-body {\n border-bottom-color: #faebcc;\n}\n.panel-danger {\n border-color: #ebccd1;\n}\n.panel-danger > .panel-heading {\n color: #a94442;\n background-color: #f2dede;\n border-color: #ebccd1;\n}\n.panel-danger > .panel-heading + .panel-collapse > .panel-body {\n border-top-color: #ebccd1;\n}\n.panel-danger > .panel-heading .badge {\n color: #f2dede;\n background-color: #a94442;\n}\n.panel-danger > .panel-footer + .panel-collapse > .panel-body {\n border-bottom-color: #ebccd1;\n}\n.embed-responsive {\n position: relative;\n display: block;\n height: 0;\n padding: 0;\n overflow: hidden;\n}\n.embed-responsive .embed-responsive-item,\n.embed-responsive iframe,\n.embed-responsive embed,\n.embed-responsive object,\n.embed-responsive video {\n position: absolute;\n top: 0;\n left: 0;\n bottom: 0;\n height: 100%;\n width: 100%;\n border: 0;\n}\n.embed-responsive-16by9 {\n padding-bottom: 56.25%;\n}\n.embed-responsive-4by3 {\n padding-bottom: 75%;\n}\n.well {\n min-height: 20px;\n padding: 19px;\n margin-bottom: 20px;\n background-color: #f5f5f5;\n border: 1px solid #e3e3e3;\n border-radius: 4px;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);\n}\n.well blockquote {\n border-color: #ddd;\n border-color: rgba(0, 0, 0, 0.15);\n}\n.well-lg {\n padding: 24px;\n border-radius: 6px;\n}\n.well-sm {\n padding: 9px;\n border-radius: 3px;\n}\n.close {\n float: right;\n font-size: 21px;\n font-weight: bold;\n line-height: 1;\n color: #000;\n text-shadow: 0 1px 0 #fff;\n opacity: 0.2;\n filter: alpha(opacity=20);\n}\n.close:hover,\n.close:focus {\n color: #000;\n text-decoration: none;\n cursor: pointer;\n opacity: 0.5;\n filter: alpha(opacity=50);\n}\nbutton.close {\n padding: 0;\n cursor: pointer;\n background: transparent;\n border: 0;\n -webkit-appearance: none;\n}\n.modal-open {\n overflow: hidden;\n}\n.modal {\n display: none;\n overflow: hidden;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: 1050;\n -webkit-overflow-scrolling: touch;\n outline: 0;\n}\n.modal.fade .modal-dialog {\n -webkit-transform: translate(0, -25%);\n -ms-transform: translate(0, -25%);\n -o-transform: translate(0, -25%);\n transform: translate(0, -25%);\n -webkit-transition: -webkit-transform 0.3s ease-out;\n -moz-transition: -moz-transform 0.3s ease-out;\n -o-transition: -o-transform 0.3s ease-out;\n transition: transform 0.3s ease-out;\n}\n.modal.in .modal-dialog {\n -webkit-transform: translate(0, 0);\n -ms-transform: translate(0, 0);\n -o-transform: translate(0, 0);\n transform: translate(0, 0);\n}\n.modal-open .modal {\n overflow-x: hidden;\n overflow-y: auto;\n}\n.modal-dialog {\n position: relative;\n width: auto;\n margin: 10px;\n}\n.modal-content {\n position: relative;\n background-color: #fff;\n border: 1px solid #999;\n border: 1px solid rgba(0, 0, 0, 0.2);\n border-radius: 6px;\n -webkit-box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5);\n box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5);\n background-clip: padding-box;\n outline: 0;\n}\n.modal-backdrop {\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: 1040;\n background-color: #000;\n}\n.modal-backdrop.fade {\n opacity: 0;\n filter: alpha(opacity=0);\n}\n.modal-backdrop.in {\n opacity: 0.5;\n filter: alpha(opacity=50);\n}\n.modal-header {\n padding: 15px;\n border-bottom: 1px solid #e5e5e5;\n}\n.modal-header .close {\n margin-top: -2px;\n}\n.modal-title {\n margin: 0;\n line-height: 1.42857143;\n}\n.modal-body {\n position: relative;\n padding: 15px;\n}\n.modal-footer {\n padding: 15px;\n text-align: right;\n border-top: 1px solid #e5e5e5;\n}\n.modal-footer .btn + .btn {\n margin-left: 5px;\n margin-bottom: 0;\n}\n.modal-footer .btn-group .btn + .btn {\n margin-left: -1px;\n}\n.modal-footer .btn-block + .btn-block {\n margin-left: 0;\n}\n.modal-scrollbar-measure {\n position: absolute;\n top: -9999px;\n width: 50px;\n height: 50px;\n overflow: scroll;\n}\n@media (min-width: 768px) {\n .modal-dialog {\n width: 600px;\n margin: 30px auto;\n }\n .modal-content {\n -webkit-box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5);\n box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5);\n }\n .modal-sm {\n width: 300px;\n }\n}\n@media (min-width: 992px) {\n .modal-lg {\n width: 900px;\n }\n}\n.tooltip {\n position: absolute;\n z-index: 1070;\n display: block;\n font-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n font-style: normal;\n font-weight: normal;\n letter-spacing: normal;\n line-break: auto;\n line-height: 1.42857143;\n text-align: left;\n text-align: start;\n text-decoration: none;\n text-shadow: none;\n text-transform: none;\n white-space: normal;\n word-break: normal;\n word-spacing: normal;\n word-wrap: normal;\n font-size: 12px;\n opacity: 0;\n filter: alpha(opacity=0);\n}\n.tooltip.in {\n opacity: 0.9;\n filter: alpha(opacity=90);\n}\n.tooltip.top {\n margin-top: -3px;\n padding: 5px 0;\n}\n.tooltip.right {\n margin-left: 3px;\n padding: 0 5px;\n}\n.tooltip.bottom {\n margin-top: 3px;\n padding: 5px 0;\n}\n.tooltip.left {\n margin-left: -3px;\n padding: 0 5px;\n}\n.tooltip-inner {\n max-width: 200px;\n padding: 3px 8px;\n color: #fff;\n text-align: center;\n background-color: #000;\n border-radius: 4px;\n}\n.tooltip-arrow {\n position: absolute;\n width: 0;\n height: 0;\n border-color: transparent;\n border-style: solid;\n}\n.tooltip.top .tooltip-arrow {\n bottom: 0;\n left: 50%;\n margin-left: -5px;\n border-width: 5px 5px 0;\n border-top-color: #000;\n}\n.tooltip.top-left .tooltip-arrow {\n bottom: 0;\n right: 5px;\n margin-bottom: -5px;\n border-width: 5px 5px 0;\n border-top-color: #000;\n}\n.tooltip.top-right .tooltip-arrow {\n bottom: 0;\n left: 5px;\n margin-bottom: -5px;\n border-width: 5px 5px 0;\n border-top-color: #000;\n}\n.tooltip.right .tooltip-arrow {\n top: 50%;\n left: 0;\n margin-top: -5px;\n border-width: 5px 5px 5px 0;\n border-right-color: #000;\n}\n.tooltip.left .tooltip-arrow {\n top: 50%;\n right: 0;\n margin-top: -5px;\n border-width: 5px 0 5px 5px;\n border-left-color: #000;\n}\n.tooltip.bottom .tooltip-arrow {\n top: 0;\n left: 50%;\n margin-left: -5px;\n border-width: 0 5px 5px;\n border-bottom-color: #000;\n}\n.tooltip.bottom-left .tooltip-arrow {\n top: 0;\n right: 5px;\n margin-top: -5px;\n border-width: 0 5px 5px;\n border-bottom-color: #000;\n}\n.tooltip.bottom-right .tooltip-arrow {\n top: 0;\n left: 5px;\n margin-top: -5px;\n border-width: 0 5px 5px;\n border-bottom-color: #000;\n}\n.popover {\n position: absolute;\n top: 0;\n left: 0;\n z-index: 1060;\n display: none;\n max-width: 276px;\n padding: 1px;\n font-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n font-style: normal;\n font-weight: normal;\n letter-spacing: normal;\n line-break: auto;\n line-height: 1.42857143;\n text-align: left;\n text-align: start;\n text-decoration: none;\n text-shadow: none;\n text-transform: none;\n white-space: normal;\n word-break: normal;\n word-spacing: normal;\n word-wrap: normal;\n font-size: 14px;\n background-color: #fff;\n background-clip: padding-box;\n border: 1px solid #ccc;\n border: 1px solid rgba(0, 0, 0, 0.2);\n border-radius: 6px;\n -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);\n box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);\n}\n.popover.top {\n margin-top: -10px;\n}\n.popover.right {\n margin-left: 10px;\n}\n.popover.bottom {\n margin-top: 10px;\n}\n.popover.left {\n margin-left: -10px;\n}\n.popover-title {\n margin: 0;\n padding: 8px 14px;\n font-size: 14px;\n background-color: #f7f7f7;\n border-bottom: 1px solid #ebebeb;\n border-radius: 5px 5px 0 0;\n}\n.popover-content {\n padding: 9px 14px;\n}\n.popover > .arrow,\n.popover > .arrow:after {\n position: absolute;\n display: block;\n width: 0;\n height: 0;\n border-color: transparent;\n border-style: solid;\n}\n.popover > .arrow {\n border-width: 11px;\n}\n.popover > .arrow:after {\n border-width: 10px;\n content: \"\";\n}\n.popover.top > .arrow {\n left: 50%;\n margin-left: -11px;\n border-bottom-width: 0;\n border-top-color: #999999;\n border-top-color: rgba(0, 0, 0, 0.25);\n bottom: -11px;\n}\n.popover.top > .arrow:after {\n content: \" \";\n bottom: 1px;\n margin-left: -10px;\n border-bottom-width: 0;\n border-top-color: #fff;\n}\n.popover.right > .arrow {\n top: 50%;\n left: -11px;\n margin-top: -11px;\n border-left-width: 0;\n border-right-color: #999999;\n border-right-color: rgba(0, 0, 0, 0.25);\n}\n.popover.right > .arrow:after {\n content: \" \";\n left: 1px;\n bottom: -10px;\n border-left-width: 0;\n border-right-color: #fff;\n}\n.popover.bottom > .arrow {\n left: 50%;\n margin-left: -11px;\n border-top-width: 0;\n border-bottom-color: #999999;\n border-bottom-color: rgba(0, 0, 0, 0.25);\n top: -11px;\n}\n.popover.bottom > .arrow:after {\n content: \" \";\n top: 1px;\n margin-left: -10px;\n border-top-width: 0;\n border-bottom-color: #fff;\n}\n.popover.left > .arrow {\n top: 50%;\n right: -11px;\n margin-top: -11px;\n border-right-width: 0;\n border-left-color: #999999;\n border-left-color: rgba(0, 0, 0, 0.25);\n}\n.popover.left > .arrow:after {\n content: \" \";\n right: 1px;\n border-right-width: 0;\n border-left-color: #fff;\n bottom: -10px;\n}\n.carousel {\n position: relative;\n}\n.carousel-inner {\n position: relative;\n overflow: hidden;\n width: 100%;\n}\n.carousel-inner > .item {\n display: none;\n position: relative;\n -webkit-transition: 0.6s ease-in-out left;\n -o-transition: 0.6s ease-in-out left;\n transition: 0.6s ease-in-out left;\n}\n.carousel-inner > .item > img,\n.carousel-inner > .item > a > img {\n line-height: 1;\n}\n@media all and (transform-3d), (-webkit-transform-3d) {\n .carousel-inner > .item {\n -webkit-transition: -webkit-transform 0.6s ease-in-out;\n -moz-transition: -moz-transform 0.6s ease-in-out;\n -o-transition: -o-transform 0.6s ease-in-out;\n transition: transform 0.6s ease-in-out;\n -webkit-backface-visibility: hidden;\n -moz-backface-visibility: hidden;\n backface-visibility: hidden;\n -webkit-perspective: 1000px;\n -moz-perspective: 1000px;\n perspective: 1000px;\n }\n .carousel-inner > .item.next,\n .carousel-inner > .item.active.right {\n -webkit-transform: translate3d(100%, 0, 0);\n transform: translate3d(100%, 0, 0);\n left: 0;\n }\n .carousel-inner > .item.prev,\n .carousel-inner > .item.active.left {\n -webkit-transform: translate3d(-100%, 0, 0);\n transform: translate3d(-100%, 0, 0);\n left: 0;\n }\n .carousel-inner > .item.next.left,\n .carousel-inner > .item.prev.right,\n .carousel-inner > .item.active {\n -webkit-transform: translate3d(0, 0, 0);\n transform: translate3d(0, 0, 0);\n left: 0;\n }\n}\n.carousel-inner > .active,\n.carousel-inner > .next,\n.carousel-inner > .prev {\n display: block;\n}\n.carousel-inner > .active {\n left: 0;\n}\n.carousel-inner > .next,\n.carousel-inner > .prev {\n position: absolute;\n top: 0;\n width: 100%;\n}\n.carousel-inner > .next {\n left: 100%;\n}\n.carousel-inner > .prev {\n left: -100%;\n}\n.carousel-inner > .next.left,\n.carousel-inner > .prev.right {\n left: 0;\n}\n.carousel-inner > .active.left {\n left: -100%;\n}\n.carousel-inner > .active.right {\n left: 100%;\n}\n.carousel-control {\n position: absolute;\n top: 0;\n left: 0;\n bottom: 0;\n width: 15%;\n opacity: 0.5;\n filter: alpha(opacity=50);\n font-size: 20px;\n color: #fff;\n text-align: center;\n text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);\n background-color: rgba(0, 0, 0, 0);\n}\n.carousel-control.left {\n background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.0001) 100%);\n background-image: -o-linear-gradient(left, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.0001) 100%);\n background-image: linear-gradient(to right, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.0001) 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);\n}\n.carousel-control.right {\n left: auto;\n right: 0;\n background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, 0.0001) 0%, rgba(0, 0, 0, 0.5) 100%);\n background-image: -o-linear-gradient(left, rgba(0, 0, 0, 0.0001) 0%, rgba(0, 0, 0, 0.5) 100%);\n background-image: linear-gradient(to right, rgba(0, 0, 0, 0.0001) 0%, rgba(0, 0, 0, 0.5) 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);\n}\n.carousel-control:hover,\n.carousel-control:focus {\n outline: 0;\n color: #fff;\n text-decoration: none;\n opacity: 0.9;\n filter: alpha(opacity=90);\n}\n.carousel-control .icon-prev,\n.carousel-control .icon-next,\n.carousel-control .glyphicon-chevron-left,\n.carousel-control .glyphicon-chevron-right {\n position: absolute;\n top: 50%;\n margin-top: -10px;\n z-index: 5;\n display: inline-block;\n}\n.carousel-control .icon-prev,\n.carousel-control .glyphicon-chevron-left {\n left: 50%;\n margin-left: -10px;\n}\n.carousel-control .icon-next,\n.carousel-control .glyphicon-chevron-right {\n right: 50%;\n margin-right: -10px;\n}\n.carousel-control .icon-prev,\n.carousel-control .icon-next {\n width: 20px;\n height: 20px;\n line-height: 1;\n font-family: serif;\n}\n.carousel-control .icon-prev:before {\n content: '\\2039';\n}\n.carousel-control .icon-next:before {\n content: '\\203a';\n}\n.carousel-indicators {\n position: absolute;\n bottom: 10px;\n left: 50%;\n z-index: 15;\n width: 60%;\n margin-left: -30%;\n padding-left: 0;\n list-style: none;\n text-align: center;\n}\n.carousel-indicators li {\n display: inline-block;\n width: 10px;\n height: 10px;\n margin: 1px;\n text-indent: -999px;\n border: 1px solid #fff;\n border-radius: 10px;\n cursor: pointer;\n background-color: #000 \\9;\n background-color: rgba(0, 0, 0, 0);\n}\n.carousel-indicators .active {\n margin: 0;\n width: 12px;\n height: 12px;\n background-color: #fff;\n}\n.carousel-caption {\n position: absolute;\n left: 15%;\n right: 15%;\n bottom: 20px;\n z-index: 10;\n padding-top: 20px;\n padding-bottom: 20px;\n color: #fff;\n text-align: center;\n text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);\n}\n.carousel-caption .btn {\n text-shadow: none;\n}\n@media screen and (min-width: 768px) {\n .carousel-control .glyphicon-chevron-left,\n .carousel-control .glyphicon-chevron-right,\n .carousel-control .icon-prev,\n .carousel-control .icon-next {\n width: 30px;\n height: 30px;\n margin-top: -10px;\n font-size: 30px;\n }\n .carousel-control .glyphicon-chevron-left,\n .carousel-control .icon-prev {\n margin-left: -10px;\n }\n .carousel-control .glyphicon-chevron-right,\n .carousel-control .icon-next {\n margin-right: -10px;\n }\n .carousel-caption {\n left: 20%;\n right: 20%;\n padding-bottom: 30px;\n }\n .carousel-indicators {\n bottom: 20px;\n }\n}\n.clearfix:before,\n.clearfix:after,\n.dl-horizontal dd:before,\n.dl-horizontal dd:after,\n.container:before,\n.container:after,\n.container-fluid:before,\n.container-fluid:after,\n.row:before,\n.row:after,\n.form-horizontal .form-group:before,\n.form-horizontal .form-group:after,\n.btn-toolbar:before,\n.btn-toolbar:after,\n.btn-group-vertical > .btn-group:before,\n.btn-group-vertical > .btn-group:after,\n.nav:before,\n.nav:after,\n.navbar:before,\n.navbar:after,\n.navbar-header:before,\n.navbar-header:after,\n.navbar-collapse:before,\n.navbar-collapse:after,\n.pager:before,\n.pager:after,\n.panel-body:before,\n.panel-body:after,\n.modal-header:before,\n.modal-header:after,\n.modal-footer:before,\n.modal-footer:after {\n content: \" \";\n display: table;\n}\n.clearfix:after,\n.dl-horizontal dd:after,\n.container:after,\n.container-fluid:after,\n.row:after,\n.form-horizontal .form-group:after,\n.btn-toolbar:after,\n.btn-group-vertical > .btn-group:after,\n.nav:after,\n.navbar:after,\n.navbar-header:after,\n.navbar-collapse:after,\n.pager:after,\n.panel-body:after,\n.modal-header:after,\n.modal-footer:after {\n clear: both;\n}\n.center-block {\n display: block;\n margin-left: auto;\n margin-right: auto;\n}\n.pull-right {\n float: right !important;\n}\n.pull-left {\n float: left !important;\n}\n.hide {\n display: none !important;\n}\n.show {\n display: block !important;\n}\n.invisible {\n visibility: hidden;\n}\n.text-hide {\n font: 0/0 a;\n color: transparent;\n text-shadow: none;\n background-color: transparent;\n border: 0;\n}\n.hidden {\n display: none !important;\n}\n.affix {\n position: fixed;\n}\n@-ms-viewport {\n width: device-width;\n}\n.visible-xs,\n.visible-sm,\n.visible-md,\n.visible-lg {\n display: none !important;\n}\n.visible-xs-block,\n.visible-xs-inline,\n.visible-xs-inline-block,\n.visible-sm-block,\n.visible-sm-inline,\n.visible-sm-inline-block,\n.visible-md-block,\n.visible-md-inline,\n.visible-md-inline-block,\n.visible-lg-block,\n.visible-lg-inline,\n.visible-lg-inline-block {\n display: none !important;\n}\n@media (max-width: 767px) {\n .visible-xs {\n display: block !important;\n }\n table.visible-xs {\n display: table !important;\n }\n tr.visible-xs {\n display: table-row !important;\n }\n th.visible-xs,\n td.visible-xs {\n display: table-cell !important;\n }\n}\n@media (max-width: 767px) {\n .visible-xs-block {\n display: block !important;\n }\n}\n@media (max-width: 767px) {\n .visible-xs-inline {\n display: inline !important;\n }\n}\n@media (max-width: 767px) {\n .visible-xs-inline-block {\n display: inline-block !important;\n }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n .visible-sm {\n display: block !important;\n }\n table.visible-sm {\n display: table !important;\n }\n tr.visible-sm {\n display: table-row !important;\n }\n th.visible-sm,\n td.visible-sm {\n display: table-cell !important;\n }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n .visible-sm-block {\n display: block !important;\n }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n .visible-sm-inline {\n display: inline !important;\n }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n .visible-sm-inline-block {\n display: inline-block !important;\n }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n .visible-md {\n display: block !important;\n }\n table.visible-md {\n display: table !important;\n }\n tr.visible-md {\n display: table-row !important;\n }\n th.visible-md,\n td.visible-md {\n display: table-cell !important;\n }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n .visible-md-block {\n display: block !important;\n }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n .visible-md-inline {\n display: inline !important;\n }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n .visible-md-inline-block {\n display: inline-block !important;\n }\n}\n@media (min-width: 1200px) {\n .visible-lg {\n display: block !important;\n }\n table.visible-lg {\n display: table !important;\n }\n tr.visible-lg {\n display: table-row !important;\n }\n th.visible-lg,\n td.visible-lg {\n display: table-cell !important;\n }\n}\n@media (min-width: 1200px) {\n .visible-lg-block {\n display: block !important;\n }\n}\n@media (min-width: 1200px) {\n .visible-lg-inline {\n display: inline !important;\n }\n}\n@media (min-width: 1200px) {\n .visible-lg-inline-block {\n display: inline-block !important;\n }\n}\n@media (max-width: 767px) {\n .hidden-xs {\n display: none !important;\n }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n .hidden-sm {\n display: none !important;\n }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n .hidden-md {\n display: none !important;\n }\n}\n@media (min-width: 1200px) {\n .hidden-lg {\n display: none !important;\n }\n}\n.visible-print {\n display: none !important;\n}\n@media print {\n .visible-print {\n display: block !important;\n }\n table.visible-print {\n display: table !important;\n }\n tr.visible-print {\n display: table-row !important;\n }\n th.visible-print,\n td.visible-print {\n display: table-cell !important;\n }\n}\n.visible-print-block {\n display: none !important;\n}\n@media print {\n .visible-print-block {\n display: block !important;\n }\n}\n.visible-print-inline {\n display: none !important;\n}\n@media print {\n .visible-print-inline {\n display: inline !important;\n }\n}\n.visible-print-inline-block {\n display: none !important;\n}\n@media print {\n .visible-print-inline-block {\n display: inline-block !important;\n }\n}\n@media print {\n .hidden-print {\n display: none !important;\n }\n}\n/*# sourceMappingURL=bootstrap.css.map */","/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */\n\n//\n// 1. Set default font family to sans-serif.\n// 2. Prevent iOS and IE text size adjust after device orientation change,\n// without disabling user zoom.\n//\n\nhtml {\n font-family: sans-serif; // 1\n -ms-text-size-adjust: 100%; // 2\n -webkit-text-size-adjust: 100%; // 2\n}\n\n//\n// Remove default margin.\n//\n\nbody {\n margin: 0;\n}\n\n// HTML5 display definitions\n// ==========================================================================\n\n//\n// Correct `block` display not defined for any HTML5 element in IE 8/9.\n// Correct `block` display not defined for `details` or `summary` in IE 10/11\n// and Firefox.\n// Correct `block` display not defined for `main` in IE 11.\n//\n\narticle,\naside,\ndetails,\nfigcaption,\nfigure,\nfooter,\nheader,\nhgroup,\nmain,\nmenu,\nnav,\nsection,\nsummary {\n display: block;\n}\n\n//\n// 1. Correct `inline-block` display not defined in IE 8/9.\n// 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.\n//\n\naudio,\ncanvas,\nprogress,\nvideo {\n display: inline-block; // 1\n vertical-align: baseline; // 2\n}\n\n//\n// Prevent modern browsers from displaying `audio` without controls.\n// Remove excess height in iOS 5 devices.\n//\n\naudio:not([controls]) {\n display: none;\n height: 0;\n}\n\n//\n// Address `[hidden]` styling not present in IE 8/9/10.\n// Hide the `template` element in IE 8/9/10/11, Safari, and Firefox < 22.\n//\n\n[hidden],\ntemplate {\n display: none;\n}\n\n// Links\n// ==========================================================================\n\n//\n// Remove the gray background color from active links in IE 10.\n//\n\na {\n background-color: transparent;\n}\n\n//\n// Improve readability of focused elements when they are also in an\n// active/hover state.\n//\n\na:active,\na:hover {\n outline: 0;\n}\n\n// Text-level semantics\n// ==========================================================================\n\n//\n// Address styling not present in IE 8/9/10/11, Safari, and Chrome.\n//\n\nabbr[title] {\n border-bottom: 1px dotted;\n}\n\n//\n// Address style set to `bolder` in Firefox 4+, Safari, and Chrome.\n//\n\nb,\nstrong {\n font-weight: bold;\n}\n\n//\n// Address styling not present in Safari and Chrome.\n//\n\ndfn {\n font-style: italic;\n}\n\n//\n// Address variable `h1` font-size and margin within `section` and `article`\n// contexts in Firefox 4+, Safari, and Chrome.\n//\n\nh1 {\n font-size: 2em;\n margin: 0.67em 0;\n}\n\n//\n// Address styling not present in IE 8/9.\n//\n\nmark {\n background: #ff0;\n color: #000;\n}\n\n//\n// Address inconsistent and variable font size in all browsers.\n//\n\nsmall {\n font-size: 80%;\n}\n\n//\n// Prevent `sub` and `sup` affecting `line-height` in all browsers.\n//\n\nsub,\nsup {\n font-size: 75%;\n line-height: 0;\n position: relative;\n vertical-align: baseline;\n}\n\nsup {\n top: -0.5em;\n}\n\nsub {\n bottom: -0.25em;\n}\n\n// Embedded content\n// ==========================================================================\n\n//\n// Remove border when inside `a` element in IE 8/9/10.\n//\n\nimg {\n border: 0;\n}\n\n//\n// Correct overflow not hidden in IE 9/10/11.\n//\n\nsvg:not(:root) {\n overflow: hidden;\n}\n\n// Grouping content\n// ==========================================================================\n\n//\n// Address margin not present in IE 8/9 and Safari.\n//\n\nfigure {\n margin: 1em 40px;\n}\n\n//\n// Address differences between Firefox and other browsers.\n//\n\nhr {\n box-sizing: content-box;\n height: 0;\n}\n\n//\n// Contain overflow in all browsers.\n//\n\npre {\n overflow: auto;\n}\n\n//\n// Address odd `em`-unit font size rendering in all browsers.\n//\n\ncode,\nkbd,\npre,\nsamp {\n font-family: monospace, monospace;\n font-size: 1em;\n}\n\n// Forms\n// ==========================================================================\n\n//\n// Known limitation: by default, Chrome and Safari on OS X allow very limited\n// styling of `select`, unless a `border` property is set.\n//\n\n//\n// 1. Correct color not being inherited.\n// Known issue: affects color of disabled elements.\n// 2. Correct font properties not being inherited.\n// 3. Address margins set differently in Firefox 4+, Safari, and Chrome.\n//\n\nbutton,\ninput,\noptgroup,\nselect,\ntextarea {\n color: inherit; // 1\n font: inherit; // 2\n margin: 0; // 3\n}\n\n//\n// Address `overflow` set to `hidden` in IE 8/9/10/11.\n//\n\nbutton {\n overflow: visible;\n}\n\n//\n// Address inconsistent `text-transform` inheritance for `button` and `select`.\n// All other form control elements do not inherit `text-transform` values.\n// Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera.\n// Correct `select` style inheritance in Firefox.\n//\n\nbutton,\nselect {\n text-transform: none;\n}\n\n//\n// 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`\n// and `video` controls.\n// 2. Correct inability to style clickable `input` types in iOS.\n// 3. Improve usability and consistency of cursor style between image-type\n// `input` and others.\n//\n\nbutton,\nhtml input[type=\"button\"], // 1\ninput[type=\"reset\"],\ninput[type=\"submit\"] {\n -webkit-appearance: button; // 2\n cursor: pointer; // 3\n}\n\n//\n// Re-set default cursor for disabled elements.\n//\n\nbutton[disabled],\nhtml input[disabled] {\n cursor: default;\n}\n\n//\n// Remove inner padding and border in Firefox 4+.\n//\n\nbutton::-moz-focus-inner,\ninput::-moz-focus-inner {\n border: 0;\n padding: 0;\n}\n\n//\n// Address Firefox 4+ setting `line-height` on `input` using `!important` in\n// the UA stylesheet.\n//\n\ninput {\n line-height: normal;\n}\n\n//\n// It's recommended that you don't attempt to style these elements.\n// Firefox's implementation doesn't respect box-sizing, padding, or width.\n//\n// 1. Address box sizing set to `content-box` in IE 8/9/10.\n// 2. Remove excess padding in IE 8/9/10.\n//\n\ninput[type=\"checkbox\"],\ninput[type=\"radio\"] {\n box-sizing: border-box; // 1\n padding: 0; // 2\n}\n\n//\n// Fix the cursor style for Chrome's increment/decrement buttons. For certain\n// `font-size` values of the `input`, it causes the cursor style of the\n// decrement button to change from `default` to `text`.\n//\n\ninput[type=\"number\"]::-webkit-inner-spin-button,\ninput[type=\"number\"]::-webkit-outer-spin-button {\n height: auto;\n}\n\n//\n// 1. Address `appearance` set to `searchfield` in Safari and Chrome.\n// 2. Address `box-sizing` set to `border-box` in Safari and Chrome.\n//\n\ninput[type=\"search\"] {\n -webkit-appearance: textfield; // 1\n box-sizing: content-box; //2\n}\n\n//\n// Remove inner padding and search cancel button in Safari and Chrome on OS X.\n// Safari (but not Chrome) clips the cancel button when the search input has\n// padding (and `textfield` appearance).\n//\n\ninput[type=\"search\"]::-webkit-search-cancel-button,\ninput[type=\"search\"]::-webkit-search-decoration {\n -webkit-appearance: none;\n}\n\n//\n// Define consistent border, margin, and padding.\n//\n\nfieldset {\n border: 1px solid #c0c0c0;\n margin: 0 2px;\n padding: 0.35em 0.625em 0.75em;\n}\n\n//\n// 1. Correct `color` not being inherited in IE 8/9/10/11.\n// 2. Remove padding so people aren't caught out if they zero out fieldsets.\n//\n\nlegend {\n border: 0; // 1\n padding: 0; // 2\n}\n\n//\n// Remove default vertical scrollbar in IE 8/9/10/11.\n//\n\ntextarea {\n overflow: auto;\n}\n\n//\n// Don't inherit the `font-weight` (applied by a rule above).\n// NOTE: the default cannot safely be changed in Chrome and Safari on OS X.\n//\n\noptgroup {\n font-weight: bold;\n}\n\n// Tables\n// ==========================================================================\n\n//\n// Remove most spacing between table cells.\n//\n\ntable {\n border-collapse: collapse;\n border-spacing: 0;\n}\n\ntd,\nth {\n padding: 0;\n}\n","/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */\n\n// ==========================================================================\n// Print styles.\n// Inlined to avoid the additional HTTP request: h5bp.com/r\n// ==========================================================================\n\n@media print {\n *,\n *:before,\n *:after {\n background: transparent !important;\n color: #000 !important; // Black prints faster: h5bp.com/s\n box-shadow: none !important;\n text-shadow: none !important;\n }\n\n a,\n a:visited {\n text-decoration: underline;\n }\n\n a[href]:after {\n content: \" (\" attr(href) \")\";\n }\n\n abbr[title]:after {\n content: \" (\" attr(title) \")\";\n }\n\n // Don't show links that are fragment identifiers,\n // or use the `javascript:` pseudo protocol\n a[href^=\"#\"]:after,\n a[href^=\"javascript:\"]:after {\n content: \"\";\n }\n\n pre,\n blockquote {\n border: 1px solid #999;\n page-break-inside: avoid;\n }\n\n thead {\n display: table-header-group; // h5bp.com/t\n }\n\n tr,\n img {\n page-break-inside: avoid;\n }\n\n img {\n max-width: 100% !important;\n }\n\n p,\n h2,\n h3 {\n orphans: 3;\n widows: 3;\n }\n\n h2,\n h3 {\n page-break-after: avoid;\n }\n\n // Bootstrap specific changes start\n\n // Bootstrap components\n .navbar {\n display: none;\n }\n .btn,\n .dropup > .btn {\n > .caret {\n border-top-color: #000 !important;\n }\n }\n .label {\n border: 1px solid #000;\n }\n\n .table {\n border-collapse: collapse !important;\n\n td,\n th {\n background-color: #fff !important;\n }\n }\n .table-bordered {\n th,\n td {\n border: 1px solid #ddd !important;\n }\n }\n\n // Bootstrap specific changes end\n}\n","//\n// Glyphicons for Bootstrap\n//\n// Since icons are fonts, they can be placed anywhere text is placed and are\n// thus automatically sized to match the surrounding child. To use, create an\n// inline element with the appropriate classes, like so:\n//\n// <a href=\"#\"><span class=\"glyphicon glyphicon-star\"></span> Star</a>\n\n// Import the fonts\n@font-face {\n font-family: 'Glyphicons Halflings';\n src: url('@{icon-font-path}@{icon-font-name}.eot');\n src: url('@{icon-font-path}@{icon-font-name}.eot?#iefix') format('embedded-opentype'),\n url('@{icon-font-path}@{icon-font-name}.woff2') format('woff2'),\n url('@{icon-font-path}@{icon-font-name}.woff') format('woff'),\n url('@{icon-font-path}@{icon-font-name}.ttf') format('truetype'),\n url('@{icon-font-path}@{icon-font-name}.svg#@{icon-font-svg-id}') format('svg');\n}\n\n// Catchall baseclass\n.glyphicon {\n position: relative;\n top: 1px;\n display: inline-block;\n font-family: 'Glyphicons Halflings';\n font-style: normal;\n font-weight: normal;\n line-height: 1;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n}\n\n// Individual icons\n.glyphicon-asterisk { &:before { content: \"\\002a\"; } }\n.glyphicon-plus { &:before { content: \"\\002b\"; } }\n.glyphicon-euro,\n.glyphicon-eur { &:before { content: \"\\20ac\"; } }\n.glyphicon-minus { &:before { content: \"\\2212\"; } }\n.glyphicon-cloud { &:before { content: \"\\2601\"; } }\n.glyphicon-envelope { &:before { content: \"\\2709\"; } }\n.glyphicon-pencil { &:before { content: \"\\270f\"; } }\n.glyphicon-glass { &:before { content: \"\\e001\"; } }\n.glyphicon-music { &:before { content: \"\\e002\"; } }\n.glyphicon-search { &:before { content: \"\\e003\"; } }\n.glyphicon-heart { &:before { content: \"\\e005\"; } }\n.glyphicon-star { &:before { content: \"\\e006\"; } }\n.glyphicon-star-empty { &:before { content: \"\\e007\"; } }\n.glyphicon-user { &:before { content: \"\\e008\"; } }\n.glyphicon-film { &:before { content: \"\\e009\"; } }\n.glyphicon-th-large { &:before { content: \"\\e010\"; } }\n.glyphicon-th { &:before { content: \"\\e011\"; } }\n.glyphicon-th-list { &:before { content: \"\\e012\"; } }\n.glyphicon-ok { &:before { content: \"\\e013\"; } }\n.glyphicon-remove { &:before { content: \"\\e014\"; } }\n.glyphicon-zoom-in { &:before { content: \"\\e015\"; } }\n.glyphicon-zoom-out { &:before { content: \"\\e016\"; } }\n.glyphicon-off { &:before { content: \"\\e017\"; } }\n.glyphicon-signal { &:before { content: \"\\e018\"; } }\n.glyphicon-cog { &:before { content: \"\\e019\"; } }\n.glyphicon-trash { &:before { content: \"\\e020\"; } }\n.glyphicon-home { &:before { content: \"\\e021\"; } }\n.glyphicon-file { &:before { content: \"\\e022\"; } }\n.glyphicon-time { &:before { content: \"\\e023\"; } }\n.glyphicon-road { &:before { content: \"\\e024\"; } }\n.glyphicon-download-alt { &:before { content: \"\\e025\"; } }\n.glyphicon-download { &:before { content: \"\\e026\"; } }\n.glyphicon-upload { &:before { content: \"\\e027\"; } }\n.glyphicon-inbox { &:before { content: \"\\e028\"; } }\n.glyphicon-play-circle { &:before { content: \"\\e029\"; } }\n.glyphicon-repeat { &:before { content: \"\\e030\"; } }\n.glyphicon-refresh { &:before { content: \"\\e031\"; } }\n.glyphicon-list-alt { &:before { content: \"\\e032\"; } }\n.glyphicon-lock { &:before { content: \"\\e033\"; } }\n.glyphicon-flag { &:before { content: \"\\e034\"; } }\n.glyphicon-headphones { &:before { content: \"\\e035\"; } }\n.glyphicon-volume-off { &:before { content: \"\\e036\"; } }\n.glyphicon-volume-down { &:before { content: \"\\e037\"; } }\n.glyphicon-volume-up { &:before { content: \"\\e038\"; } }\n.glyphicon-qrcode { &:before { content: \"\\e039\"; } }\n.glyphicon-barcode { &:before { content: \"\\e040\"; } }\n.glyphicon-tag { &:before { content: \"\\e041\"; } }\n.glyphicon-tags { &:before { content: \"\\e042\"; } }\n.glyphicon-book { &:before { content: \"\\e043\"; } }\n.glyphicon-bookmark { &:before { content: \"\\e044\"; } }\n.glyphicon-print { &:before { content: \"\\e045\"; } }\n.glyphicon-camera { &:before { content: \"\\e046\"; } }\n.glyphicon-font { &:before { content: \"\\e047\"; } }\n.glyphicon-bold { &:before { content: \"\\e048\"; } }\n.glyphicon-italic { &:before { content: \"\\e049\"; } }\n.glyphicon-text-height { &:before { content: \"\\e050\"; } }\n.glyphicon-text-width { &:before { content: \"\\e051\"; } }\n.glyphicon-align-left { &:before { content: \"\\e052\"; } }\n.glyphicon-align-center { &:before { content: \"\\e053\"; } }\n.glyphicon-align-right { &:before { content: \"\\e054\"; } }\n.glyphicon-align-justify { &:before { content: \"\\e055\"; } }\n.glyphicon-list { &:before { content: \"\\e056\"; } }\n.glyphicon-indent-left { &:before { content: \"\\e057\"; } }\n.glyphicon-indent-right { &:before { content: \"\\e058\"; } }\n.glyphicon-facetime-video { &:before { content: \"\\e059\"; } }\n.glyphicon-picture { &:before { content: \"\\e060\"; } }\n.glyphicon-map-marker { &:before { content: \"\\e062\"; } }\n.glyphicon-adjust { &:before { content: \"\\e063\"; } }\n.glyphicon-tint { &:before { content: \"\\e064\"; } }\n.glyphicon-edit { &:before { content: \"\\e065\"; } }\n.glyphicon-share { &:before { content: \"\\e066\"; } }\n.glyphicon-check { &:before { content: \"\\e067\"; } }\n.glyphicon-move { &:before { content: \"\\e068\"; } }\n.glyphicon-step-backward { &:before { content: \"\\e069\"; } }\n.glyphicon-fast-backward { &:before { content: \"\\e070\"; } }\n.glyphicon-backward { &:before { content: \"\\e071\"; } }\n.glyphicon-play { &:before { content: \"\\e072\"; } }\n.glyphicon-pause { &:before { content: \"\\e073\"; } }\n.glyphicon-stop { &:before { content: \"\\e074\"; } }\n.glyphicon-forward { &:before { content: \"\\e075\"; } }\n.glyphicon-fast-forward { &:before { content: \"\\e076\"; } }\n.glyphicon-step-forward { &:before { content: \"\\e077\"; } }\n.glyphicon-eject { &:before { content: \"\\e078\"; } }\n.glyphicon-chevron-left { &:before { content: \"\\e079\"; } }\n.glyphicon-chevron-right { &:before { content: \"\\e080\"; } }\n.glyphicon-plus-sign { &:before { content: \"\\e081\"; } }\n.glyphicon-minus-sign { &:before { content: \"\\e082\"; } }\n.glyphicon-remove-sign { &:before { content: \"\\e083\"; } }\n.glyphicon-ok-sign { &:before { content: \"\\e084\"; } }\n.glyphicon-question-sign { &:before { content: \"\\e085\"; } }\n.glyphicon-info-sign { &:before { content: \"\\e086\"; } }\n.glyphicon-screenshot { &:before { content: \"\\e087\"; } }\n.glyphicon-remove-circle { &:before { content: \"\\e088\"; } }\n.glyphicon-ok-circle { &:before { content: \"\\e089\"; } }\n.glyphicon-ban-circle { &:before { content: \"\\e090\"; } }\n.glyphicon-arrow-left { &:before { content: \"\\e091\"; } }\n.glyphicon-arrow-right { &:before { content: \"\\e092\"; } }\n.glyphicon-arrow-up { &:before { content: \"\\e093\"; } }\n.glyphicon-arrow-down { &:before { content: \"\\e094\"; } }\n.glyphicon-share-alt { &:before { content: \"\\e095\"; } }\n.glyphicon-resize-full { &:before { content: \"\\e096\"; } }\n.glyphicon-resize-small { &:before { content: \"\\e097\"; } }\n.glyphicon-exclamation-sign { &:before { content: \"\\e101\"; } }\n.glyphicon-gift { &:before { content: \"\\e102\"; } }\n.glyphicon-leaf { &:before { content: \"\\e103\"; } }\n.glyphicon-fire { &:before { content: \"\\e104\"; } }\n.glyphicon-eye-open { &:before { content: \"\\e105\"; } }\n.glyphicon-eye-close { &:before { content: \"\\e106\"; } }\n.glyphicon-warning-sign { &:before { content: \"\\e107\"; } }\n.glyphicon-plane { &:before { content: \"\\e108\"; } }\n.glyphicon-calendar { &:before { content: \"\\e109\"; } }\n.glyphicon-random { &:before { content: \"\\e110\"; } }\n.glyphicon-comment { &:before { content: \"\\e111\"; } }\n.glyphicon-magnet { &:before { content: \"\\e112\"; } }\n.glyphicon-chevron-up { &:before { content: \"\\e113\"; } }\n.glyphicon-chevron-down { &:before { content: \"\\e114\"; } }\n.glyphicon-retweet { &:before { content: \"\\e115\"; } }\n.glyphicon-shopping-cart { &:before { content: \"\\e116\"; } }\n.glyphicon-folder-close { &:before { content: \"\\e117\"; } }\n.glyphicon-folder-open { &:before { content: \"\\e118\"; } }\n.glyphicon-resize-vertical { &:before { content: \"\\e119\"; } }\n.glyphicon-resize-horizontal { &:before { content: \"\\e120\"; } }\n.glyphicon-hdd { &:before { content: \"\\e121\"; } }\n.glyphicon-bullhorn { &:before { content: \"\\e122\"; } }\n.glyphicon-bell { &:before { content: \"\\e123\"; } }\n.glyphicon-certificate { &:before { content: \"\\e124\"; } }\n.glyphicon-thumbs-up { &:before { content: \"\\e125\"; } }\n.glyphicon-thumbs-down { &:before { content: \"\\e126\"; } }\n.glyphicon-hand-right { &:before { content: \"\\e127\"; } }\n.glyphicon-hand-left { &:before { content: \"\\e128\"; } }\n.glyphicon-hand-up { &:before { content: \"\\e129\"; } }\n.glyphicon-hand-down { &:before { content: \"\\e130\"; } }\n.glyphicon-circle-arrow-right { &:before { content: \"\\e131\"; } }\n.glyphicon-circle-arrow-left { &:before { content: \"\\e132\"; } }\n.glyphicon-circle-arrow-up { &:before { content: \"\\e133\"; } }\n.glyphicon-circle-arrow-down { &:before { content: \"\\e134\"; } }\n.glyphicon-globe { &:before { content: \"\\e135\"; } }\n.glyphicon-wrench { &:before { content: \"\\e136\"; } }\n.glyphicon-tasks { &:before { content: \"\\e137\"; } }\n.glyphicon-filter { &:before { content: \"\\e138\"; } }\n.glyphicon-briefcase { &:before { content: \"\\e139\"; } }\n.glyphicon-fullscreen { &:before { content: \"\\e140\"; } }\n.glyphicon-dashboard { &:before { content: \"\\e141\"; } }\n.glyphicon-paperclip { &:before { content: \"\\e142\"; } }\n.glyphicon-heart-empty { &:before { content: \"\\e143\"; } }\n.glyphicon-link { &:before { content: \"\\e144\"; } }\n.glyphicon-phone { &:before { content: \"\\e145\"; } }\n.glyphicon-pushpin { &:before { content: \"\\e146\"; } }\n.glyphicon-usd { &:before { content: \"\\e148\"; } }\n.glyphicon-gbp { &:before { content: \"\\e149\"; } }\n.glyphicon-sort { &:before { content: \"\\e150\"; } }\n.glyphicon-sort-by-alphabet { &:before { content: \"\\e151\"; } }\n.glyphicon-sort-by-alphabet-alt { &:before { content: \"\\e152\"; } }\n.glyphicon-sort-by-order { &:before { content: \"\\e153\"; } }\n.glyphicon-sort-by-order-alt { &:before { content: \"\\e154\"; } }\n.glyphicon-sort-by-attributes { &:before { content: \"\\e155\"; } }\n.glyphicon-sort-by-attributes-alt { &:before { content: \"\\e156\"; } }\n.glyphicon-unchecked { &:before { content: \"\\e157\"; } }\n.glyphicon-expand { &:before { content: \"\\e158\"; } }\n.glyphicon-collapse-down { &:before { content: \"\\e159\"; } }\n.glyphicon-collapse-up { &:before { content: \"\\e160\"; } }\n.glyphicon-log-in { &:before { content: \"\\e161\"; } }\n.glyphicon-flash { &:before { content: \"\\e162\"; } }\n.glyphicon-log-out { &:before { content: \"\\e163\"; } }\n.glyphicon-new-window { &:before { content: \"\\e164\"; } }\n.glyphicon-record { &:before { content: \"\\e165\"; } }\n.glyphicon-save { &:before { content: \"\\e166\"; } }\n.glyphicon-open { &:before { content: \"\\e167\"; } }\n.glyphicon-saved { &:before { content: \"\\e168\"; } }\n.glyphicon-import { &:before { content: \"\\e169\"; } }\n.glyphicon-export { &:before { content: \"\\e170\"; } }\n.glyphicon-send { &:before { content: \"\\e171\"; } }\n.glyphicon-floppy-disk { &:before { content: \"\\e172\"; } }\n.glyphicon-floppy-saved { &:before { content: \"\\e173\"; } }\n.glyphicon-floppy-remove { &:before { content: \"\\e174\"; } }\n.glyphicon-floppy-save { &:before { content: \"\\e175\"; } }\n.glyphicon-floppy-open { &:before { content: \"\\e176\"; } }\n.glyphicon-credit-card { &:before { content: \"\\e177\"; } }\n.glyphicon-transfer { &:before { content: \"\\e178\"; } }\n.glyphicon-cutlery { &:before { content: \"\\e179\"; } }\n.glyphicon-header { &:before { content: \"\\e180\"; } }\n.glyphicon-compressed { &:before { content: \"\\e181\"; } }\n.glyphicon-earphone { &:before { content: \"\\e182\"; } }\n.glyphicon-phone-alt { &:before { content: \"\\e183\"; } }\n.glyphicon-tower { &:before { content: \"\\e184\"; } }\n.glyphicon-stats { &:before { content: \"\\e185\"; } }\n.glyphicon-sd-video { &:before { content: \"\\e186\"; } }\n.glyphicon-hd-video { &:before { content: \"\\e187\"; } }\n.glyphicon-subtitles { &:before { content: \"\\e188\"; } }\n.glyphicon-sound-stereo { &:before { content: \"\\e189\"; } }\n.glyphicon-sound-dolby { &:before { content: \"\\e190\"; } }\n.glyphicon-sound-5-1 { &:before { content: \"\\e191\"; } }\n.glyphicon-sound-6-1 { &:before { content: \"\\e192\"; } }\n.glyphicon-sound-7-1 { &:before { content: \"\\e193\"; } }\n.glyphicon-copyright-mark { &:before { content: \"\\e194\"; } }\n.glyphicon-registration-mark { &:before { content: \"\\e195\"; } }\n.glyphicon-cloud-download { &:before { content: \"\\e197\"; } }\n.glyphicon-cloud-upload { &:before { content: \"\\e198\"; } }\n.glyphicon-tree-conifer { &:before { content: \"\\e199\"; } }\n.glyphicon-tree-deciduous { &:before { content: \"\\e200\"; } }\n.glyphicon-cd { &:before { content: \"\\e201\"; } }\n.glyphicon-save-file { &:before { content: \"\\e202\"; } }\n.glyphicon-open-file { &:before { content: \"\\e203\"; } }\n.glyphicon-level-up { &:before { content: \"\\e204\"; } }\n.glyphicon-copy { &:before { content: \"\\e205\"; } }\n.glyphicon-paste { &:before { content: \"\\e206\"; } }\n// The following 2 Glyphicons are omitted for the time being because\n// they currently use Unicode codepoints that are outside the\n// Basic Multilingual Plane (BMP). Older buggy versions of WebKit can't handle\n// non-BMP codepoints in CSS string escapes, and thus can't display these two icons.\n// Notably, the bug affects some older versions of the Android Browser.\n// More info: https://github.com/twbs/bootstrap/issues/10106\n// .glyphicon-door { &:before { content: \"\\1f6aa\"; } }\n// .glyphicon-key { &:before { content: \"\\1f511\"; } }\n.glyphicon-alert { &:before { content: \"\\e209\"; } }\n.glyphicon-equalizer { &:before { content: \"\\e210\"; } }\n.glyphicon-king { &:before { content: \"\\e211\"; } }\n.glyphicon-queen { &:before { content: \"\\e212\"; } }\n.glyphicon-pawn { &:before { content: \"\\e213\"; } }\n.glyphicon-bishop { &:before { content: \"\\e214\"; } }\n.glyphicon-knight { &:before { content: \"\\e215\"; } }\n.glyphicon-baby-formula { &:before { content: \"\\e216\"; } }\n.glyphicon-tent { &:before { content: \"\\26fa\"; } }\n.glyphicon-blackboard { &:before { content: \"\\e218\"; } }\n.glyphicon-bed { &:before { content: \"\\e219\"; } }\n.glyphicon-apple { &:before { content: \"\\f8ff\"; } }\n.glyphicon-erase { &:before { content: \"\\e221\"; } }\n.glyphicon-hourglass { &:before { content: \"\\231b\"; } }\n.glyphicon-lamp { &:before { content: \"\\e223\"; } }\n.glyphicon-duplicate { &:before { content: \"\\e224\"; } }\n.glyphicon-piggy-bank { &:before { content: \"\\e225\"; } }\n.glyphicon-scissors { &:before { content: \"\\e226\"; } }\n.glyphicon-bitcoin { &:before { content: \"\\e227\"; } }\n.glyphicon-btc { &:before { content: \"\\e227\"; } }\n.glyphicon-xbt { &:before { content: \"\\e227\"; } }\n.glyphicon-yen { &:before { content: \"\\00a5\"; } }\n.glyphicon-jpy { &:before { content: \"\\00a5\"; } }\n.glyphicon-ruble { &:before { content: \"\\20bd\"; } }\n.glyphicon-rub { &:before { content: \"\\20bd\"; } }\n.glyphicon-scale { &:before { content: \"\\e230\"; } }\n.glyphicon-ice-lolly { &:before { content: \"\\e231\"; } }\n.glyphicon-ice-lolly-tasted { &:before { content: \"\\e232\"; } }\n.glyphicon-education { &:before { content: \"\\e233\"; } }\n.glyphicon-option-horizontal { &:before { content: \"\\e234\"; } }\n.glyphicon-option-vertical { &:before { content: \"\\e235\"; } }\n.glyphicon-menu-hamburger { &:before { content: \"\\e236\"; } }\n.glyphicon-modal-window { &:before { content: \"\\e237\"; } }\n.glyphicon-oil { &:before { content: \"\\e238\"; } }\n.glyphicon-grain { &:before { content: \"\\e239\"; } }\n.glyphicon-sunglasses { &:before { content: \"\\e240\"; } }\n.glyphicon-text-size { &:before { content: \"\\e241\"; } }\n.glyphicon-text-color { &:before { content: \"\\e242\"; } }\n.glyphicon-text-background { &:before { content: \"\\e243\"; } }\n.glyphicon-object-align-top { &:before { content: \"\\e244\"; } }\n.glyphicon-object-align-bottom { &:before { content: \"\\e245\"; } }\n.glyphicon-object-align-horizontal{ &:before { content: \"\\e246\"; } }\n.glyphicon-object-align-left { &:before { content: \"\\e247\"; } }\n.glyphicon-object-align-vertical { &:before { content: \"\\e248\"; } }\n.glyphicon-object-align-right { &:before { content: \"\\e249\"; } }\n.glyphicon-triangle-right { &:before { content: \"\\e250\"; } }\n.glyphicon-triangle-left { &:before { content: \"\\e251\"; } }\n.glyphicon-triangle-bottom { &:before { content: \"\\e252\"; } }\n.glyphicon-triangle-top { &:before { content: \"\\e253\"; } }\n.glyphicon-console { &:before { content: \"\\e254\"; } }\n.glyphicon-superscript { &:before { content: \"\\e255\"; } }\n.glyphicon-subscript { &:before { content: \"\\e256\"; } }\n.glyphicon-menu-left { &:before { content: \"\\e257\"; } }\n.glyphicon-menu-right { &:before { content: \"\\e258\"; } }\n.glyphicon-menu-down { &:before { content: \"\\e259\"; } }\n.glyphicon-menu-up { &:before { content: \"\\e260\"; } }\n","//\n// Scaffolding\n// --------------------------------------------------\n\n\n// Reset the box-sizing\n//\n// Heads up! This reset may cause conflicts with some third-party widgets.\n// For recommendations on resolving such conflicts, see\n// http://getbootstrap.com/getting-started/#third-box-sizing\n* {\n .box-sizing(border-box);\n}\n*:before,\n*:after {\n .box-sizing(border-box);\n}\n\n\n// Body reset\n\nhtml {\n font-size: 10px;\n -webkit-tap-highlight-color: rgba(0,0,0,0);\n}\n\nbody {\n font-family: @font-family-base;\n font-size: @font-size-base;\n line-height: @line-height-base;\n color: @text-color;\n background-color: @body-bg;\n}\n\n// Reset fonts for relevant elements\ninput,\nbutton,\nselect,\ntextarea {\n font-family: inherit;\n font-size: inherit;\n line-height: inherit;\n}\n\n\n// Links\n\na {\n color: @link-color;\n text-decoration: none;\n\n &:hover,\n &:focus {\n color: @link-hover-color;\n text-decoration: @link-hover-decoration;\n }\n\n &:focus {\n .tab-focus();\n }\n}\n\n\n// Figures\n//\n// We reset this here because previously Normalize had no `figure` margins. This\n// ensures we don't break anyone's use of the element.\n\nfigure {\n margin: 0;\n}\n\n\n// Images\n\nimg {\n vertical-align: middle;\n}\n\n// Responsive images (ensure images don't scale beyond their parents)\n.img-responsive {\n .img-responsive();\n}\n\n// Rounded corners\n.img-rounded {\n border-radius: @border-radius-large;\n}\n\n// Image thumbnails\n//\n// Heads up! This is mixin-ed into thumbnails.less for `.thumbnail`.\n.img-thumbnail {\n padding: @thumbnail-padding;\n line-height: @line-height-base;\n background-color: @thumbnail-bg;\n border: 1px solid @thumbnail-border;\n border-radius: @thumbnail-border-radius;\n .transition(all .2s ease-in-out);\n\n // Keep them at most 100% wide\n .img-responsive(inline-block);\n}\n\n// Perfect circle\n.img-circle {\n border-radius: 50%; // set radius in percents\n}\n\n\n// Horizontal rules\n\nhr {\n margin-top: @line-height-computed;\n margin-bottom: @line-height-computed;\n border: 0;\n border-top: 1px solid @hr-border;\n}\n\n\n// Only display content to screen readers\n//\n// See: http://a11yproject.com/posts/how-to-hide-content/\n\n.sr-only {\n position: absolute;\n width: 1px;\n height: 1px;\n margin: -1px;\n padding: 0;\n overflow: hidden;\n clip: rect(0,0,0,0);\n border: 0;\n}\n\n// Use in conjunction with .sr-only to only display content when it's focused.\n// Useful for \"Skip to main content\" links; see http://www.w3.org/TR/2013/NOTE-WCAG20-TECHS-20130905/G1\n// Credit: HTML5 Boilerplate\n\n.sr-only-focusable {\n &:active,\n &:focus {\n position: static;\n width: auto;\n height: auto;\n margin: 0;\n overflow: visible;\n clip: auto;\n }\n}\n\n\n// iOS \"clickable elements\" fix for role=\"button\"\n//\n// Fixes \"clickability\" issue (and more generally, the firing of events such as focus as well)\n// for traditionally non-focusable elements with role=\"button\"\n// see https://developer.mozilla.org/en-US/docs/Web/Events/click#Safari_Mobile\n\n[role=\"button\"] {\n cursor: pointer;\n}\n","// Vendor Prefixes\n//\n// All vendor mixins are deprecated as of v3.2.0 due to the introduction of\n// Autoprefixer in our Gruntfile. They have been removed in v4.\n\n// - Animations\n// - Backface visibility\n// - Box shadow\n// - Box sizing\n// - Content columns\n// - Hyphens\n// - Placeholder text\n// - Transformations\n// - Transitions\n// - User Select\n\n\n// Animations\n.animation(@animation) {\n -webkit-animation: @animation;\n -o-animation: @animation;\n animation: @animation;\n}\n.animation-name(@name) {\n -webkit-animation-name: @name;\n animation-name: @name;\n}\n.animation-duration(@duration) {\n -webkit-animation-duration: @duration;\n animation-duration: @duration;\n}\n.animation-timing-function(@timing-function) {\n -webkit-animation-timing-function: @timing-function;\n animation-timing-function: @timing-function;\n}\n.animation-delay(@delay) {\n -webkit-animation-delay: @delay;\n animation-delay: @delay;\n}\n.animation-iteration-count(@iteration-count) {\n -webkit-animation-iteration-count: @iteration-count;\n animation-iteration-count: @iteration-count;\n}\n.animation-direction(@direction) {\n -webkit-animation-direction: @direction;\n animation-direction: @direction;\n}\n.animation-fill-mode(@fill-mode) {\n -webkit-animation-fill-mode: @fill-mode;\n animation-fill-mode: @fill-mode;\n}\n\n// Backface visibility\n// Prevent browsers from flickering when using CSS 3D transforms.\n// Default value is `visible`, but can be changed to `hidden`\n\n.backface-visibility(@visibility) {\n -webkit-backface-visibility: @visibility;\n -moz-backface-visibility: @visibility;\n backface-visibility: @visibility;\n}\n\n// Drop shadows\n//\n// Note: Deprecated `.box-shadow()` as of v3.1.0 since all of Bootstrap's\n// supported browsers that have box shadow capabilities now support it.\n\n.box-shadow(@shadow) {\n -webkit-box-shadow: @shadow; // iOS <4.3 & Android <4.1\n box-shadow: @shadow;\n}\n\n// Box sizing\n.box-sizing(@boxmodel) {\n -webkit-box-sizing: @boxmodel;\n -moz-box-sizing: @boxmodel;\n box-sizing: @boxmodel;\n}\n\n// CSS3 Content Columns\n.content-columns(@column-count; @column-gap: @grid-gutter-width) {\n -webkit-column-count: @column-count;\n -moz-column-count: @column-count;\n column-count: @column-count;\n -webkit-column-gap: @column-gap;\n -moz-column-gap: @column-gap;\n column-gap: @column-gap;\n}\n\n// Optional hyphenation\n.hyphens(@mode: auto) {\n word-wrap: break-word;\n -webkit-hyphens: @mode;\n -moz-hyphens: @mode;\n -ms-hyphens: @mode; // IE10+\n -o-hyphens: @mode;\n hyphens: @mode;\n}\n\n// Placeholder text\n.placeholder(@color: @input-color-placeholder) {\n // Firefox\n &::-moz-placeholder {\n color: @color;\n opacity: 1; // Override Firefox's unusual default opacity; see https://github.com/twbs/bootstrap/pull/11526\n }\n &:-ms-input-placeholder { color: @color; } // Internet Explorer 10+\n &::-webkit-input-placeholder { color: @color; } // Safari and Chrome\n}\n\n// Transformations\n.scale(@ratio) {\n -webkit-transform: scale(@ratio);\n -ms-transform: scale(@ratio); // IE9 only\n -o-transform: scale(@ratio);\n transform: scale(@ratio);\n}\n.scale(@ratioX; @ratioY) {\n -webkit-transform: scale(@ratioX, @ratioY);\n -ms-transform: scale(@ratioX, @ratioY); // IE9 only\n -o-transform: scale(@ratioX, @ratioY);\n transform: scale(@ratioX, @ratioY);\n}\n.scaleX(@ratio) {\n -webkit-transform: scaleX(@ratio);\n -ms-transform: scaleX(@ratio); // IE9 only\n -o-transform: scaleX(@ratio);\n transform: scaleX(@ratio);\n}\n.scaleY(@ratio) {\n -webkit-transform: scaleY(@ratio);\n -ms-transform: scaleY(@ratio); // IE9 only\n -o-transform: scaleY(@ratio);\n transform: scaleY(@ratio);\n}\n.skew(@x; @y) {\n -webkit-transform: skewX(@x) skewY(@y);\n -ms-transform: skewX(@x) skewY(@y); // See https://github.com/twbs/bootstrap/issues/4885; IE9+\n -o-transform: skewX(@x) skewY(@y);\n transform: skewX(@x) skewY(@y);\n}\n.translate(@x; @y) {\n -webkit-transform: translate(@x, @y);\n -ms-transform: translate(@x, @y); // IE9 only\n -o-transform: translate(@x, @y);\n transform: translate(@x, @y);\n}\n.translate3d(@x; @y; @z) {\n -webkit-transform: translate3d(@x, @y, @z);\n transform: translate3d(@x, @y, @z);\n}\n.rotate(@degrees) {\n -webkit-transform: rotate(@degrees);\n -ms-transform: rotate(@degrees); // IE9 only\n -o-transform: rotate(@degrees);\n transform: rotate(@degrees);\n}\n.rotateX(@degrees) {\n -webkit-transform: rotateX(@degrees);\n -ms-transform: rotateX(@degrees); // IE9 only\n -o-transform: rotateX(@degrees);\n transform: rotateX(@degrees);\n}\n.rotateY(@degrees) {\n -webkit-transform: rotateY(@degrees);\n -ms-transform: rotateY(@degrees); // IE9 only\n -o-transform: rotateY(@degrees);\n transform: rotateY(@degrees);\n}\n.perspective(@perspective) {\n -webkit-perspective: @perspective;\n -moz-perspective: @perspective;\n perspective: @perspective;\n}\n.perspective-origin(@perspective) {\n -webkit-perspective-origin: @perspective;\n -moz-perspective-origin: @perspective;\n perspective-origin: @perspective;\n}\n.transform-origin(@origin) {\n -webkit-transform-origin: @origin;\n -moz-transform-origin: @origin;\n -ms-transform-origin: @origin; // IE9 only\n transform-origin: @origin;\n}\n\n\n// Transitions\n\n.transition(@transition) {\n -webkit-transition: @transition;\n -o-transition: @transition;\n transition: @transition;\n}\n.transition-property(@transition-property) {\n -webkit-transition-property: @transition-property;\n transition-property: @transition-property;\n}\n.transition-delay(@transition-delay) {\n -webkit-transition-delay: @transition-delay;\n transition-delay: @transition-delay;\n}\n.transition-duration(@transition-duration) {\n -webkit-transition-duration: @transition-duration;\n transition-duration: @transition-duration;\n}\n.transition-timing-function(@timing-function) {\n -webkit-transition-timing-function: @timing-function;\n transition-timing-function: @timing-function;\n}\n.transition-transform(@transition) {\n -webkit-transition: -webkit-transform @transition;\n -moz-transition: -moz-transform @transition;\n -o-transition: -o-transform @transition;\n transition: transform @transition;\n}\n\n\n// User select\n// For selecting text on the page\n\n.user-select(@select) {\n -webkit-user-select: @select;\n -moz-user-select: @select;\n -ms-user-select: @select; // IE10+\n user-select: @select;\n}\n","// WebKit-style focus\n\n.tab-focus() {\n // Default\n outline: thin dotted;\n // WebKit\n outline: 5px auto -webkit-focus-ring-color;\n outline-offset: -2px;\n}\n","// Image Mixins\n// - Responsive image\n// - Retina image\n\n\n// Responsive image\n//\n// Keep images from scaling beyond the width of their parents.\n.img-responsive(@display: block) {\n display: @display;\n max-width: 100%; // Part 1: Set a maximum relative to the parent\n height: auto; // Part 2: Scale the height according to the width, otherwise you get stretching\n}\n\n\n// Retina image\n//\n// Short retina mixin for setting background-image and -size. Note that the\n// spelling of `min--moz-device-pixel-ratio` is intentional.\n.img-retina(@file-1x; @file-2x; @width-1x; @height-1x) {\n background-image: url(\"@{file-1x}\");\n\n @media\n only screen and (-webkit-min-device-pixel-ratio: 2),\n only screen and ( min--moz-device-pixel-ratio: 2),\n only screen and ( -o-min-device-pixel-ratio: 2/1),\n only screen and ( min-device-pixel-ratio: 2),\n only screen and ( min-resolution: 192dpi),\n only screen and ( min-resolution: 2dppx) {\n background-image: url(\"@{file-2x}\");\n background-size: @width-1x @height-1x;\n }\n}\n","//\n// Typography\n// --------------------------------------------------\n\n\n// Headings\n// -------------------------\n\nh1, h2, h3, h4, h5, h6,\n.h1, .h2, .h3, .h4, .h5, .h6 {\n font-family: @headings-font-family;\n font-weight: @headings-font-weight;\n line-height: @headings-line-height;\n color: @headings-color;\n\n small,\n .small {\n font-weight: normal;\n line-height: 1;\n color: @headings-small-color;\n }\n}\n\nh1, .h1,\nh2, .h2,\nh3, .h3 {\n margin-top: @line-height-computed;\n margin-bottom: (@line-height-computed / 2);\n\n small,\n .small {\n font-size: 65%;\n }\n}\nh4, .h4,\nh5, .h5,\nh6, .h6 {\n margin-top: (@line-height-computed / 2);\n margin-bottom: (@line-height-computed / 2);\n\n small,\n .small {\n font-size: 75%;\n }\n}\n\nh1, .h1 { font-size: @font-size-h1; }\nh2, .h2 { font-size: @font-size-h2; }\nh3, .h3 { font-size: @font-size-h3; }\nh4, .h4 { font-size: @font-size-h4; }\nh5, .h5 { font-size: @font-size-h5; }\nh6, .h6 { font-size: @font-size-h6; }\n\n\n// Body text\n// -------------------------\n\np {\n margin: 0 0 (@line-height-computed / 2);\n}\n\n.lead {\n margin-bottom: @line-height-computed;\n font-size: floor((@font-size-base * 1.15));\n font-weight: 300;\n line-height: 1.4;\n\n @media (min-width: @screen-sm-min) {\n font-size: (@font-size-base * 1.5);\n }\n}\n\n\n// Emphasis & misc\n// -------------------------\n\n// Ex: (12px small font / 14px base font) * 100% = about 85%\nsmall,\n.small {\n font-size: floor((100% * @font-size-small / @font-size-base));\n}\n\nmark,\n.mark {\n background-color: @state-warning-bg;\n padding: .2em;\n}\n\n// Alignment\n.text-left { text-align: left; }\n.text-right { text-align: right; }\n.text-center { text-align: center; }\n.text-justify { text-align: justify; }\n.text-nowrap { white-space: nowrap; }\n\n// Transformation\n.text-lowercase { text-transform: lowercase; }\n.text-uppercase { text-transform: uppercase; }\n.text-capitalize { text-transform: capitalize; }\n\n// Contextual colors\n.text-muted {\n color: @text-muted;\n}\n.text-primary {\n .text-emphasis-variant(@brand-primary);\n}\n.text-success {\n .text-emphasis-variant(@state-success-text);\n}\n.text-info {\n .text-emphasis-variant(@state-info-text);\n}\n.text-warning {\n .text-emphasis-variant(@state-warning-text);\n}\n.text-danger {\n .text-emphasis-variant(@state-danger-text);\n}\n\n// Contextual backgrounds\n// For now we'll leave these alongside the text classes until v4 when we can\n// safely shift things around (per SemVer rules).\n.bg-primary {\n // Given the contrast here, this is the only class to have its color inverted\n // automatically.\n color: #fff;\n .bg-variant(@brand-primary);\n}\n.bg-success {\n .bg-variant(@state-success-bg);\n}\n.bg-info {\n .bg-variant(@state-info-bg);\n}\n.bg-warning {\n .bg-variant(@state-warning-bg);\n}\n.bg-danger {\n .bg-variant(@state-danger-bg);\n}\n\n\n// Page header\n// -------------------------\n\n.page-header {\n padding-bottom: ((@line-height-computed / 2) - 1);\n margin: (@line-height-computed * 2) 0 @line-height-computed;\n border-bottom: 1px solid @page-header-border-color;\n}\n\n\n// Lists\n// -------------------------\n\n// Unordered and Ordered lists\nul,\nol {\n margin-top: 0;\n margin-bottom: (@line-height-computed / 2);\n ul,\n ol {\n margin-bottom: 0;\n }\n}\n\n// List options\n\n// Unstyled keeps list items block level, just removes default browser padding and list-style\n.list-unstyled {\n padding-left: 0;\n list-style: none;\n}\n\n// Inline turns list items into inline-block\n.list-inline {\n .list-unstyled();\n margin-left: -5px;\n\n > li {\n display: inline-block;\n padding-left: 5px;\n padding-right: 5px;\n }\n}\n\n// Description Lists\ndl {\n margin-top: 0; // Remove browser default\n margin-bottom: @line-height-computed;\n}\ndt,\ndd {\n line-height: @line-height-base;\n}\ndt {\n font-weight: bold;\n}\ndd {\n margin-left: 0; // Undo browser default\n}\n\n// Horizontal description lists\n//\n// Defaults to being stacked without any of the below styles applied, until the\n// grid breakpoint is reached (default of ~768px).\n\n.dl-horizontal {\n dd {\n &:extend(.clearfix all); // Clear the floated `dt` if an empty `dd` is present\n }\n\n @media (min-width: @dl-horizontal-breakpoint) {\n dt {\n float: left;\n width: (@dl-horizontal-offset - 20);\n clear: left;\n text-align: right;\n .text-overflow();\n }\n dd {\n margin-left: @dl-horizontal-offset;\n }\n }\n}\n\n\n// Misc\n// -------------------------\n\n// Abbreviations and acronyms\nabbr[title],\n// Add data-* attribute to help out our tooltip plugin, per https://github.com/twbs/bootstrap/issues/5257\nabbr[data-original-title] {\n cursor: help;\n border-bottom: 1px dotted @abbr-border-color;\n}\n.initialism {\n font-size: 90%;\n .text-uppercase();\n}\n\n// Blockquotes\nblockquote {\n padding: (@line-height-computed / 2) @line-height-computed;\n margin: 0 0 @line-height-computed;\n font-size: @blockquote-font-size;\n border-left: 5px solid @blockquote-border-color;\n\n p,\n ul,\n ol {\n &:last-child {\n margin-bottom: 0;\n }\n }\n\n // Note: Deprecated small and .small as of v3.1.0\n // Context: https://github.com/twbs/bootstrap/issues/11660\n footer,\n small,\n .small {\n display: block;\n font-size: 80%; // back to default font-size\n line-height: @line-height-base;\n color: @blockquote-small-color;\n\n &:before {\n content: '\\2014 \\00A0'; // em dash, nbsp\n }\n }\n}\n\n// Opposite alignment of blockquote\n//\n// Heads up: `blockquote.pull-right` has been deprecated as of v3.1.0.\n.blockquote-reverse,\nblockquote.pull-right {\n padding-right: 15px;\n padding-left: 0;\n border-right: 5px solid @blockquote-border-color;\n border-left: 0;\n text-align: right;\n\n // Account for citation\n footer,\n small,\n .small {\n &:before { content: ''; }\n &:after {\n content: '\\00A0 \\2014'; // nbsp, em dash\n }\n }\n}\n\n// Addresses\naddress {\n margin-bottom: @line-height-computed;\n font-style: normal;\n line-height: @line-height-base;\n}\n","// Typography\n\n.text-emphasis-variant(@color) {\n color: @color;\n a&:hover,\n a&:focus {\n color: darken(@color, 10%);\n }\n}\n","// Contextual backgrounds\n\n.bg-variant(@color) {\n background-color: @color;\n a&:hover,\n a&:focus {\n background-color: darken(@color, 10%);\n }\n}\n","// Text overflow\n// Requires inline-block or block for proper styling\n\n.text-overflow() {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n","//\n// Code (inline and block)\n// --------------------------------------------------\n\n\n// Inline and block code styles\ncode,\nkbd,\npre,\nsamp {\n font-family: @font-family-monospace;\n}\n\n// Inline code\ncode {\n padding: 2px 4px;\n font-size: 90%;\n color: @code-color;\n background-color: @code-bg;\n border-radius: @border-radius-base;\n}\n\n// User input typically entered via keyboard\nkbd {\n padding: 2px 4px;\n font-size: 90%;\n color: @kbd-color;\n background-color: @kbd-bg;\n border-radius: @border-radius-small;\n box-shadow: inset 0 -1px 0 rgba(0,0,0,.25);\n\n kbd {\n padding: 0;\n font-size: 100%;\n font-weight: bold;\n box-shadow: none;\n }\n}\n\n// Blocks of code\npre {\n display: block;\n padding: ((@line-height-computed - 1) / 2);\n margin: 0 0 (@line-height-computed / 2);\n font-size: (@font-size-base - 1); // 14px to 13px\n line-height: @line-height-base;\n word-break: break-all;\n word-wrap: break-word;\n color: @pre-color;\n background-color: @pre-bg;\n border: 1px solid @pre-border-color;\n border-radius: @border-radius-base;\n\n // Account for some code outputs that place code tags in pre tags\n code {\n padding: 0;\n font-size: inherit;\n color: inherit;\n white-space: pre-wrap;\n background-color: transparent;\n border-radius: 0;\n }\n}\n\n// Enable scrollable blocks of code\n.pre-scrollable {\n max-height: @pre-scrollable-max-height;\n overflow-y: scroll;\n}\n","//\n// Grid system\n// --------------------------------------------------\n\n\n// Container widths\n//\n// Set the container width, and override it for fixed navbars in media queries.\n\n.container {\n .container-fixed();\n\n @media (min-width: @screen-sm-min) {\n width: @container-sm;\n }\n @media (min-width: @screen-md-min) {\n width: @container-md;\n }\n @media (min-width: @screen-lg-min) {\n width: @container-lg;\n }\n}\n\n\n// Fluid container\n//\n// Utilizes the mixin meant for fixed width containers, but without any defined\n// width for fluid, full width layouts.\n\n.container-fluid {\n .container-fixed();\n}\n\n\n// Row\n//\n// Rows contain and clear the floats of your columns.\n\n.row {\n .make-row();\n}\n\n\n// Columns\n//\n// Common styles for small and large grid columns\n\n.make-grid-columns();\n\n\n// Extra small grid\n//\n// Columns, offsets, pushes, and pulls for extra small devices like\n// smartphones.\n\n.make-grid(xs);\n\n\n// Small grid\n//\n// Columns, offsets, pushes, and pulls for the small device range, from phones\n// to tablets.\n\n@media (min-width: @screen-sm-min) {\n .make-grid(sm);\n}\n\n\n// Medium grid\n//\n// Columns, offsets, pushes, and pulls for the desktop device range.\n\n@media (min-width: @screen-md-min) {\n .make-grid(md);\n}\n\n\n// Large grid\n//\n// Columns, offsets, pushes, and pulls for the large desktop device range.\n\n@media (min-width: @screen-lg-min) {\n .make-grid(lg);\n}\n","// Grid system\n//\n// Generate semantic grid columns with these mixins.\n\n// Centered container element\n.container-fixed(@gutter: @grid-gutter-width) {\n margin-right: auto;\n margin-left: auto;\n padding-left: floor((@gutter / 2));\n padding-right: ceil((@gutter / 2));\n &:extend(.clearfix all);\n}\n\n// Creates a wrapper for a series of columns\n.make-row(@gutter: @grid-gutter-width) {\n margin-left: ceil((@gutter / -2));\n margin-right: floor((@gutter / -2));\n &:extend(.clearfix all);\n}\n\n// Generate the extra small columns\n.make-xs-column(@columns; @gutter: @grid-gutter-width) {\n position: relative;\n float: left;\n width: percentage((@columns / @grid-columns));\n min-height: 1px;\n padding-left: (@gutter / 2);\n padding-right: (@gutter / 2);\n}\n.make-xs-column-offset(@columns) {\n margin-left: percentage((@columns / @grid-columns));\n}\n.make-xs-column-push(@columns) {\n left: percentage((@columns / @grid-columns));\n}\n.make-xs-column-pull(@columns) {\n right: percentage((@columns / @grid-columns));\n}\n\n// Generate the small columns\n.make-sm-column(@columns; @gutter: @grid-gutter-width) {\n position: relative;\n min-height: 1px;\n padding-left: (@gutter / 2);\n padding-right: (@gutter / 2);\n\n @media (min-width: @screen-sm-min) {\n float: left;\n width: percentage((@columns / @grid-columns));\n }\n}\n.make-sm-column-offset(@columns) {\n @media (min-width: @screen-sm-min) {\n margin-left: percentage((@columns / @grid-columns));\n }\n}\n.make-sm-column-push(@columns) {\n @media (min-width: @screen-sm-min) {\n left: percentage((@columns / @grid-columns));\n }\n}\n.make-sm-column-pull(@columns) {\n @media (min-width: @screen-sm-min) {\n right: percentage((@columns / @grid-columns));\n }\n}\n\n// Generate the medium columns\n.make-md-column(@columns; @gutter: @grid-gutter-width) {\n position: relative;\n min-height: 1px;\n padding-left: (@gutter / 2);\n padding-right: (@gutter / 2);\n\n @media (min-width: @screen-md-min) {\n float: left;\n width: percentage((@columns / @grid-columns));\n }\n}\n.make-md-column-offset(@columns) {\n @media (min-width: @screen-md-min) {\n margin-left: percentage((@columns / @grid-columns));\n }\n}\n.make-md-column-push(@columns) {\n @media (min-width: @screen-md-min) {\n left: percentage((@columns / @grid-columns));\n }\n}\n.make-md-column-pull(@columns) {\n @media (min-width: @screen-md-min) {\n right: percentage((@columns / @grid-columns));\n }\n}\n\n// Generate the large columns\n.make-lg-column(@columns; @gutter: @grid-gutter-width) {\n position: relative;\n min-height: 1px;\n padding-left: (@gutter / 2);\n padding-right: (@gutter / 2);\n\n @media (min-width: @screen-lg-min) {\n float: left;\n width: percentage((@columns / @grid-columns));\n }\n}\n.make-lg-column-offset(@columns) {\n @media (min-width: @screen-lg-min) {\n margin-left: percentage((@columns / @grid-columns));\n }\n}\n.make-lg-column-push(@columns) {\n @media (min-width: @screen-lg-min) {\n left: percentage((@columns / @grid-columns));\n }\n}\n.make-lg-column-pull(@columns) {\n @media (min-width: @screen-lg-min) {\n right: percentage((@columns / @grid-columns));\n }\n}\n","// Framework grid generation\n//\n// Used only by Bootstrap to generate the correct number of grid classes given\n// any value of `@grid-columns`.\n\n.make-grid-columns() {\n // Common styles for all sizes of grid columns, widths 1-12\n .col(@index) { // initial\n @item: ~\".col-xs-@{index}, .col-sm-@{index}, .col-md-@{index}, .col-lg-@{index}\";\n .col((@index + 1), @item);\n }\n .col(@index, @list) when (@index =< @grid-columns) { // general; \"=<\" isn't a typo\n @item: ~\".col-xs-@{index}, .col-sm-@{index}, .col-md-@{index}, .col-lg-@{index}\";\n .col((@index + 1), ~\"@{list}, @{item}\");\n }\n .col(@index, @list) when (@index > @grid-columns) { // terminal\n @{list} {\n position: relative;\n // Prevent columns from collapsing when empty\n min-height: 1px;\n // Inner gutter via padding\n padding-left: ceil((@grid-gutter-width / 2));\n padding-right: floor((@grid-gutter-width / 2));\n }\n }\n .col(1); // kickstart it\n}\n\n.float-grid-columns(@class) {\n .col(@index) { // initial\n @item: ~\".col-@{class}-@{index}\";\n .col((@index + 1), @item);\n }\n .col(@index, @list) when (@index =< @grid-columns) { // general\n @item: ~\".col-@{class}-@{index}\";\n .col((@index + 1), ~\"@{list}, @{item}\");\n }\n .col(@index, @list) when (@index > @grid-columns) { // terminal\n @{list} {\n float: left;\n }\n }\n .col(1); // kickstart it\n}\n\n.calc-grid-column(@index, @class, @type) when (@type = width) and (@index > 0) {\n .col-@{class}-@{index} {\n width: percentage((@index / @grid-columns));\n }\n}\n.calc-grid-column(@index, @class, @type) when (@type = push) and (@index > 0) {\n .col-@{class}-push-@{index} {\n left: percentage((@index / @grid-columns));\n }\n}\n.calc-grid-column(@index, @class, @type) when (@type = push) and (@index = 0) {\n .col-@{class}-push-0 {\n left: auto;\n }\n}\n.calc-grid-column(@index, @class, @type) when (@type = pull) and (@index > 0) {\n .col-@{class}-pull-@{index} {\n right: percentage((@index / @grid-columns));\n }\n}\n.calc-grid-column(@index, @class, @type) when (@type = pull) and (@index = 0) {\n .col-@{class}-pull-0 {\n right: auto;\n }\n}\n.calc-grid-column(@index, @class, @type) when (@type = offset) {\n .col-@{class}-offset-@{index} {\n margin-left: percentage((@index / @grid-columns));\n }\n}\n\n// Basic looping in LESS\n.loop-grid-columns(@index, @class, @type) when (@index >= 0) {\n .calc-grid-column(@index, @class, @type);\n // next iteration\n .loop-grid-columns((@index - 1), @class, @type);\n}\n\n// Create grid for specific class\n.make-grid(@class) {\n .float-grid-columns(@class);\n .loop-grid-columns(@grid-columns, @class, width);\n .loop-grid-columns(@grid-columns, @class, pull);\n .loop-grid-columns(@grid-columns, @class, push);\n .loop-grid-columns(@grid-columns, @class, offset);\n}\n","//\n// Tables\n// --------------------------------------------------\n\n\ntable {\n background-color: @table-bg;\n}\ncaption {\n padding-top: @table-cell-padding;\n padding-bottom: @table-cell-padding;\n color: @text-muted;\n text-align: left;\n}\nth {\n text-align: left;\n}\n\n\n// Baseline styles\n\n.table {\n width: 100%;\n max-width: 100%;\n margin-bottom: @line-height-computed;\n // Cells\n > thead,\n > tbody,\n > tfoot {\n > tr {\n > th,\n > td {\n padding: @table-cell-padding;\n line-height: @line-height-base;\n vertical-align: top;\n border-top: 1px solid @table-border-color;\n }\n }\n }\n // Bottom align for column headings\n > thead > tr > th {\n vertical-align: bottom;\n border-bottom: 2px solid @table-border-color;\n }\n // Remove top border from thead by default\n > caption + thead,\n > colgroup + thead,\n > thead:first-child {\n > tr:first-child {\n > th,\n > td {\n border-top: 0;\n }\n }\n }\n // Account for multiple tbody instances\n > tbody + tbody {\n border-top: 2px solid @table-border-color;\n }\n\n // Nesting\n .table {\n background-color: @body-bg;\n }\n}\n\n\n// Condensed table w/ half padding\n\n.table-condensed {\n > thead,\n > tbody,\n > tfoot {\n > tr {\n > th,\n > td {\n padding: @table-condensed-cell-padding;\n }\n }\n }\n}\n\n\n// Bordered version\n//\n// Add borders all around the table and between all the columns.\n\n.table-bordered {\n border: 1px solid @table-border-color;\n > thead,\n > tbody,\n > tfoot {\n > tr {\n > th,\n > td {\n border: 1px solid @table-border-color;\n }\n }\n }\n > thead > tr {\n > th,\n > td {\n border-bottom-width: 2px;\n }\n }\n}\n\n\n// Zebra-striping\n//\n// Default zebra-stripe styles (alternating gray and transparent backgrounds)\n\n.table-striped {\n > tbody > tr:nth-of-type(odd) {\n background-color: @table-bg-accent;\n }\n}\n\n\n// Hover effect\n//\n// Placed here since it has to come after the potential zebra striping\n\n.table-hover {\n > tbody > tr:hover {\n background-color: @table-bg-hover;\n }\n}\n\n\n// Table cell sizing\n//\n// Reset default table behavior\n\ntable col[class*=\"col-\"] {\n position: static; // Prevent border hiding in Firefox and IE9-11 (see https://github.com/twbs/bootstrap/issues/11623)\n float: none;\n display: table-column;\n}\ntable {\n td,\n th {\n &[class*=\"col-\"] {\n position: static; // Prevent border hiding in Firefox and IE9-11 (see https://github.com/twbs/bootstrap/issues/11623)\n float: none;\n display: table-cell;\n }\n }\n}\n\n\n// Table backgrounds\n//\n// Exact selectors below required to override `.table-striped` and prevent\n// inheritance to nested tables.\n\n// Generate the contextual variants\n.table-row-variant(active; @table-bg-active);\n.table-row-variant(success; @state-success-bg);\n.table-row-variant(info; @state-info-bg);\n.table-row-variant(warning; @state-warning-bg);\n.table-row-variant(danger; @state-danger-bg);\n\n\n// Responsive tables\n//\n// Wrap your tables in `.table-responsive` and we'll make them mobile friendly\n// by enabling horizontal scrolling. Only applies <768px. Everything above that\n// will display normally.\n\n.table-responsive {\n overflow-x: auto;\n min-height: 0.01%; // Workaround for IE9 bug (see https://github.com/twbs/bootstrap/issues/14837)\n\n @media screen and (max-width: @screen-xs-max) {\n width: 100%;\n margin-bottom: (@line-height-computed * 0.75);\n overflow-y: hidden;\n -ms-overflow-style: -ms-autohiding-scrollbar;\n border: 1px solid @table-border-color;\n\n // Tighten up spacing\n > .table {\n margin-bottom: 0;\n\n // Ensure the content doesn't wrap\n > thead,\n > tbody,\n > tfoot {\n > tr {\n > th,\n > td {\n white-space: nowrap;\n }\n }\n }\n }\n\n // Special overrides for the bordered tables\n > .table-bordered {\n border: 0;\n\n // Nuke the appropriate borders so that the parent can handle them\n > thead,\n > tbody,\n > tfoot {\n > tr {\n > th:first-child,\n > td:first-child {\n border-left: 0;\n }\n > th:last-child,\n > td:last-child {\n border-right: 0;\n }\n }\n }\n\n // Only nuke the last row's bottom-border in `tbody` and `tfoot` since\n // chances are there will be only one `tr` in a `thead` and that would\n // remove the border altogether.\n > tbody,\n > tfoot {\n > tr:last-child {\n > th,\n > td {\n border-bottom: 0;\n }\n }\n }\n\n }\n }\n}\n","// Tables\n\n.table-row-variant(@state; @background) {\n // Exact selectors below required to override `.table-striped` and prevent\n // inheritance to nested tables.\n .table > thead > tr,\n .table > tbody > tr,\n .table > tfoot > tr {\n > td.@{state},\n > th.@{state},\n &.@{state} > td,\n &.@{state} > th {\n background-color: @background;\n }\n }\n\n // Hover states for `.table-hover`\n // Note: this is not available for cells or rows within `thead` or `tfoot`.\n .table-hover > tbody > tr {\n > td.@{state}:hover,\n > th.@{state}:hover,\n &.@{state}:hover > td,\n &:hover > .@{state},\n &.@{state}:hover > th {\n background-color: darken(@background, 5%);\n }\n }\n}\n","//\n// Forms\n// --------------------------------------------------\n\n\n// Normalize non-controls\n//\n// Restyle and baseline non-control form elements.\n\nfieldset {\n padding: 0;\n margin: 0;\n border: 0;\n // Chrome and Firefox set a `min-width: min-content;` on fieldsets,\n // so we reset that to ensure it behaves more like a standard block element.\n // See https://github.com/twbs/bootstrap/issues/12359.\n min-width: 0;\n}\n\nlegend {\n display: block;\n width: 100%;\n padding: 0;\n margin-bottom: @line-height-computed;\n font-size: (@font-size-base * 1.5);\n line-height: inherit;\n color: @legend-color;\n border: 0;\n border-bottom: 1px solid @legend-border-color;\n}\n\nlabel {\n display: inline-block;\n max-width: 100%; // Force IE8 to wrap long content (see https://github.com/twbs/bootstrap/issues/13141)\n margin-bottom: 5px;\n font-weight: bold;\n}\n\n\n// Normalize form controls\n//\n// While most of our form styles require extra classes, some basic normalization\n// is required to ensure optimum display with or without those classes to better\n// address browser inconsistencies.\n\n// Override content-box in Normalize (* isn't specific enough)\ninput[type=\"search\"] {\n .box-sizing(border-box);\n}\n\n// Position radios and checkboxes better\ninput[type=\"radio\"],\ninput[type=\"checkbox\"] {\n margin: 4px 0 0;\n margin-top: 1px \\9; // IE8-9\n line-height: normal;\n}\n\ninput[type=\"file\"] {\n display: block;\n}\n\n// Make range inputs behave like textual form controls\ninput[type=\"range\"] {\n display: block;\n width: 100%;\n}\n\n// Make multiple select elements height not fixed\nselect[multiple],\nselect[size] {\n height: auto;\n}\n\n// Focus for file, radio, and checkbox\ninput[type=\"file\"]:focus,\ninput[type=\"radio\"]:focus,\ninput[type=\"checkbox\"]:focus {\n .tab-focus();\n}\n\n// Adjust output element\noutput {\n display: block;\n padding-top: (@padding-base-vertical + 1);\n font-size: @font-size-base;\n line-height: @line-height-base;\n color: @input-color;\n}\n\n\n// Common form controls\n//\n// Shared size and type resets for form controls. Apply `.form-control` to any\n// of the following form controls:\n//\n// select\n// textarea\n// input[type=\"text\"]\n// input[type=\"password\"]\n// input[type=\"datetime\"]\n// input[type=\"datetime-local\"]\n// input[type=\"date\"]\n// input[type=\"month\"]\n// input[type=\"time\"]\n// input[type=\"week\"]\n// input[type=\"number\"]\n// input[type=\"email\"]\n// input[type=\"url\"]\n// input[type=\"search\"]\n// input[type=\"tel\"]\n// input[type=\"color\"]\n\n.form-control {\n display: block;\n width: 100%;\n height: @input-height-base; // Make inputs at least the height of their button counterpart (base line-height + padding + border)\n padding: @padding-base-vertical @padding-base-horizontal;\n font-size: @font-size-base;\n line-height: @line-height-base;\n color: @input-color;\n background-color: @input-bg;\n background-image: none; // Reset unusual Firefox-on-Android default style; see https://github.com/necolas/normalize.css/issues/214\n border: 1px solid @input-border;\n border-radius: @input-border-radius; // Note: This has no effect on <select>s in some browsers, due to the limited stylability of <select>s in CSS.\n .box-shadow(inset 0 1px 1px rgba(0,0,0,.075));\n .transition(~\"border-color ease-in-out .15s, box-shadow ease-in-out .15s\");\n\n // Customize the `:focus` state to imitate native WebKit styles.\n .form-control-focus();\n\n // Placeholder\n .placeholder();\n\n // Unstyle the caret on `<select>`s in IE10+.\n &::-ms-expand {\n border: 0;\n background-color: transparent;\n }\n\n // Disabled and read-only inputs\n //\n // HTML5 says that controls under a fieldset > legend:first-child won't be\n // disabled if the fieldset is disabled. Due to implementation difficulty, we\n // don't honor that edge case; we style them as disabled anyway.\n &[disabled],\n &[readonly],\n fieldset[disabled] & {\n background-color: @input-bg-disabled;\n opacity: 1; // iOS fix for unreadable disabled content; see https://github.com/twbs/bootstrap/issues/11655\n }\n\n &[disabled],\n fieldset[disabled] & {\n cursor: @cursor-disabled;\n }\n\n // Reset height for `textarea`s\n textarea& {\n height: auto;\n }\n}\n\n\n// Search inputs in iOS\n//\n// This overrides the extra rounded corners on search inputs in iOS so that our\n// `.form-control` class can properly style them. Note that this cannot simply\n// be added to `.form-control` as it's not specific enough. For details, see\n// https://github.com/twbs/bootstrap/issues/11586.\n\ninput[type=\"search\"] {\n -webkit-appearance: none;\n}\n\n\n// Special styles for iOS temporal inputs\n//\n// In Mobile Safari, setting `display: block` on temporal inputs causes the\n// text within the input to become vertically misaligned. As a workaround, we\n// set a pixel line-height that matches the given height of the input, but only\n// for Safari. See https://bugs.webkit.org/show_bug.cgi?id=139848\n//\n// Note that as of 8.3, iOS doesn't support `datetime` or `week`.\n\n@media screen and (-webkit-min-device-pixel-ratio: 0) {\n input[type=\"date\"],\n input[type=\"time\"],\n input[type=\"datetime-local\"],\n input[type=\"month\"] {\n &.form-control {\n line-height: @input-height-base;\n }\n\n &.input-sm,\n .input-group-sm & {\n line-height: @input-height-small;\n }\n\n &.input-lg,\n .input-group-lg & {\n line-height: @input-height-large;\n }\n }\n}\n\n\n// Form groups\n//\n// Designed to help with the organization and spacing of vertical forms. For\n// horizontal forms, use the predefined grid classes.\n\n.form-group {\n margin-bottom: @form-group-margin-bottom;\n}\n\n\n// Checkboxes and radios\n//\n// Indent the labels to position radios/checkboxes as hanging controls.\n\n.radio,\n.checkbox {\n position: relative;\n display: block;\n margin-top: 10px;\n margin-bottom: 10px;\n\n label {\n min-height: @line-height-computed; // Ensure the input doesn't jump when there is no text\n padding-left: 20px;\n margin-bottom: 0;\n font-weight: normal;\n cursor: pointer;\n }\n}\n.radio input[type=\"radio\"],\n.radio-inline input[type=\"radio\"],\n.checkbox input[type=\"checkbox\"],\n.checkbox-inline input[type=\"checkbox\"] {\n position: absolute;\n margin-left: -20px;\n margin-top: 4px \\9;\n}\n\n.radio + .radio,\n.checkbox + .checkbox {\n margin-top: -5px; // Move up sibling radios or checkboxes for tighter spacing\n}\n\n// Radios and checkboxes on same line\n.radio-inline,\n.checkbox-inline {\n position: relative;\n display: inline-block;\n padding-left: 20px;\n margin-bottom: 0;\n vertical-align: middle;\n font-weight: normal;\n cursor: pointer;\n}\n.radio-inline + .radio-inline,\n.checkbox-inline + .checkbox-inline {\n margin-top: 0;\n margin-left: 10px; // space out consecutive inline controls\n}\n\n// Apply same disabled cursor tweak as for inputs\n// Some special care is needed because <label>s don't inherit their parent's `cursor`.\n//\n// Note: Neither radios nor checkboxes can be readonly.\ninput[type=\"radio\"],\ninput[type=\"checkbox\"] {\n &[disabled],\n &.disabled,\n fieldset[disabled] & {\n cursor: @cursor-disabled;\n }\n}\n// These classes are used directly on <label>s\n.radio-inline,\n.checkbox-inline {\n &.disabled,\n fieldset[disabled] & {\n cursor: @cursor-disabled;\n }\n}\n// These classes are used on elements with <label> descendants\n.radio,\n.checkbox {\n &.disabled,\n fieldset[disabled] & {\n label {\n cursor: @cursor-disabled;\n }\n }\n}\n\n\n// Static form control text\n//\n// Apply class to a `p` element to make any string of text align with labels in\n// a horizontal form layout.\n\n.form-control-static {\n // Size it appropriately next to real form controls\n padding-top: (@padding-base-vertical + 1);\n padding-bottom: (@padding-base-vertical + 1);\n // Remove default margin from `p`\n margin-bottom: 0;\n min-height: (@line-height-computed + @font-size-base);\n\n &.input-lg,\n &.input-sm {\n padding-left: 0;\n padding-right: 0;\n }\n}\n\n\n// Form control sizing\n//\n// Build on `.form-control` with modifier classes to decrease or increase the\n// height and font-size of form controls.\n//\n// The `.form-group-* form-control` variations are sadly duplicated to avoid the\n// issue documented in https://github.com/twbs/bootstrap/issues/15074.\n\n.input-sm {\n .input-size(@input-height-small; @padding-small-vertical; @padding-small-horizontal; @font-size-small; @line-height-small; @input-border-radius-small);\n}\n.form-group-sm {\n .form-control {\n height: @input-height-small;\n padding: @padding-small-vertical @padding-small-horizontal;\n font-size: @font-size-small;\n line-height: @line-height-small;\n border-radius: @input-border-radius-small;\n }\n select.form-control {\n height: @input-height-small;\n line-height: @input-height-small;\n }\n textarea.form-control,\n select[multiple].form-control {\n height: auto;\n }\n .form-control-static {\n height: @input-height-small;\n min-height: (@line-height-computed + @font-size-small);\n padding: (@padding-small-vertical + 1) @padding-small-horizontal;\n font-size: @font-size-small;\n line-height: @line-height-small;\n }\n}\n\n.input-lg {\n .input-size(@input-height-large; @padding-large-vertical; @padding-large-horizontal; @font-size-large; @line-height-large; @input-border-radius-large);\n}\n.form-group-lg {\n .form-control {\n height: @input-height-large;\n padding: @padding-large-vertical @padding-large-horizontal;\n font-size: @font-size-large;\n line-height: @line-height-large;\n border-radius: @input-border-radius-large;\n }\n select.form-control {\n height: @input-height-large;\n line-height: @input-height-large;\n }\n textarea.form-control,\n select[multiple].form-control {\n height: auto;\n }\n .form-control-static {\n height: @input-height-large;\n min-height: (@line-height-computed + @font-size-large);\n padding: (@padding-large-vertical + 1) @padding-large-horizontal;\n font-size: @font-size-large;\n line-height: @line-height-large;\n }\n}\n\n\n// Form control feedback states\n//\n// Apply contextual and semantic states to individual form controls.\n\n.has-feedback {\n // Enable absolute positioning\n position: relative;\n\n // Ensure icons don't overlap text\n .form-control {\n padding-right: (@input-height-base * 1.25);\n }\n}\n// Feedback icon (requires .glyphicon classes)\n.form-control-feedback {\n position: absolute;\n top: 0;\n right: 0;\n z-index: 2; // Ensure icon is above input groups\n display: block;\n width: @input-height-base;\n height: @input-height-base;\n line-height: @input-height-base;\n text-align: center;\n pointer-events: none;\n}\n.input-lg + .form-control-feedback,\n.input-group-lg + .form-control-feedback,\n.form-group-lg .form-control + .form-control-feedback {\n width: @input-height-large;\n height: @input-height-large;\n line-height: @input-height-large;\n}\n.input-sm + .form-control-feedback,\n.input-group-sm + .form-control-feedback,\n.form-group-sm .form-control + .form-control-feedback {\n width: @input-height-small;\n height: @input-height-small;\n line-height: @input-height-small;\n}\n\n// Feedback states\n.has-success {\n .form-control-validation(@state-success-text; @state-success-text; @state-success-bg);\n}\n.has-warning {\n .form-control-validation(@state-warning-text; @state-warning-text; @state-warning-bg);\n}\n.has-error {\n .form-control-validation(@state-danger-text; @state-danger-text; @state-danger-bg);\n}\n\n// Reposition feedback icon if input has visible label above\n.has-feedback label {\n\n & ~ .form-control-feedback {\n top: (@line-height-computed + 5); // Height of the `label` and its margin\n }\n &.sr-only ~ .form-control-feedback {\n top: 0;\n }\n}\n\n\n// Help text\n//\n// Apply to any element you wish to create light text for placement immediately\n// below a form control. Use for general help, formatting, or instructional text.\n\n.help-block {\n display: block; // account for any element using help-block\n margin-top: 5px;\n margin-bottom: 10px;\n color: lighten(@text-color, 25%); // lighten the text some for contrast\n}\n\n\n// Inline forms\n//\n// Make forms appear inline(-block) by adding the `.form-inline` class. Inline\n// forms begin stacked on extra small (mobile) devices and then go inline when\n// viewports reach <768px.\n//\n// Requires wrapping inputs and labels with `.form-group` for proper display of\n// default HTML form controls and our custom form controls (e.g., input groups).\n//\n// Heads up! This is mixin-ed into `.navbar-form` in navbars.less.\n\n.form-inline {\n\n // Kick in the inline\n @media (min-width: @screen-sm-min) {\n // Inline-block all the things for \"inline\"\n .form-group {\n display: inline-block;\n margin-bottom: 0;\n vertical-align: middle;\n }\n\n // In navbar-form, allow folks to *not* use `.form-group`\n .form-control {\n display: inline-block;\n width: auto; // Prevent labels from stacking above inputs in `.form-group`\n vertical-align: middle;\n }\n\n // Make static controls behave like regular ones\n .form-control-static {\n display: inline-block;\n }\n\n .input-group {\n display: inline-table;\n vertical-align: middle;\n\n .input-group-addon,\n .input-group-btn,\n .form-control {\n width: auto;\n }\n }\n\n // Input groups need that 100% width though\n .input-group > .form-control {\n width: 100%;\n }\n\n .control-label {\n margin-bottom: 0;\n vertical-align: middle;\n }\n\n // Remove default margin on radios/checkboxes that were used for stacking, and\n // then undo the floating of radios and checkboxes to match.\n .radio,\n .checkbox {\n display: inline-block;\n margin-top: 0;\n margin-bottom: 0;\n vertical-align: middle;\n\n label {\n padding-left: 0;\n }\n }\n .radio input[type=\"radio\"],\n .checkbox input[type=\"checkbox\"] {\n position: relative;\n margin-left: 0;\n }\n\n // Re-override the feedback icon.\n .has-feedback .form-control-feedback {\n top: 0;\n }\n }\n}\n\n\n// Horizontal forms\n//\n// Horizontal forms are built on grid classes and allow you to create forms with\n// labels on the left and inputs on the right.\n\n.form-horizontal {\n\n // Consistent vertical alignment of radios and checkboxes\n //\n // Labels also get some reset styles, but that is scoped to a media query below.\n .radio,\n .checkbox,\n .radio-inline,\n .checkbox-inline {\n margin-top: 0;\n margin-bottom: 0;\n padding-top: (@padding-base-vertical + 1); // Default padding plus a border\n }\n // Account for padding we're adding to ensure the alignment and of help text\n // and other content below items\n .radio,\n .checkbox {\n min-height: (@line-height-computed + (@padding-base-vertical + 1));\n }\n\n // Make form groups behave like rows\n .form-group {\n .make-row();\n }\n\n // Reset spacing and right align labels, but scope to media queries so that\n // labels on narrow viewports stack the same as a default form example.\n @media (min-width: @screen-sm-min) {\n .control-label {\n text-align: right;\n margin-bottom: 0;\n padding-top: (@padding-base-vertical + 1); // Default padding plus a border\n }\n }\n\n // Validation states\n //\n // Reposition the icon because it's now within a grid column and columns have\n // `position: relative;` on them. Also accounts for the grid gutter padding.\n .has-feedback .form-control-feedback {\n right: floor((@grid-gutter-width / 2));\n }\n\n // Form group sizes\n //\n // Quick utility class for applying `.input-lg` and `.input-sm` styles to the\n // inputs and labels within a `.form-group`.\n .form-group-lg {\n @media (min-width: @screen-sm-min) {\n .control-label {\n padding-top: (@padding-large-vertical + 1);\n font-size: @font-size-large;\n }\n }\n }\n .form-group-sm {\n @media (min-width: @screen-sm-min) {\n .control-label {\n padding-top: (@padding-small-vertical + 1);\n font-size: @font-size-small;\n }\n }\n }\n}\n","// Form validation states\n//\n// Used in forms.less to generate the form validation CSS for warnings, errors,\n// and successes.\n\n.form-control-validation(@text-color: #555; @border-color: #ccc; @background-color: #f5f5f5) {\n // Color the label and help text\n .help-block,\n .control-label,\n .radio,\n .checkbox,\n .radio-inline,\n .checkbox-inline,\n &.radio label,\n &.checkbox label,\n &.radio-inline label,\n &.checkbox-inline label {\n color: @text-color;\n }\n // Set the border and box shadow on specific inputs to match\n .form-control {\n border-color: @border-color;\n .box-shadow(inset 0 1px 1px rgba(0,0,0,.075)); // Redeclare so transitions work\n &:focus {\n border-color: darken(@border-color, 10%);\n @shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 6px lighten(@border-color, 20%);\n .box-shadow(@shadow);\n }\n }\n // Set validation states also for addons\n .input-group-addon {\n color: @text-color;\n border-color: @border-color;\n background-color: @background-color;\n }\n // Optional feedback icon\n .form-control-feedback {\n color: @text-color;\n }\n}\n\n\n// Form control focus state\n//\n// Generate a customized focus state and for any input with the specified color,\n// which defaults to the `@input-border-focus` variable.\n//\n// We highly encourage you to not customize the default value, but instead use\n// this to tweak colors on an as-needed basis. This aesthetic change is based on\n// WebKit's default styles, but applicable to a wider range of browsers. Its\n// usability and accessibility should be taken into account with any change.\n//\n// Example usage: change the default blue border and shadow to white for better\n// contrast against a dark gray background.\n.form-control-focus(@color: @input-border-focus) {\n @color-rgba: rgba(red(@color), green(@color), blue(@color), .6);\n &:focus {\n border-color: @color;\n outline: 0;\n .box-shadow(~\"inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px @{color-rgba}\");\n }\n}\n\n// Form control sizing\n//\n// Relative text size, padding, and border-radii changes for form controls. For\n// horizontal sizing, wrap controls in the predefined grid classes. `<select>`\n// element gets special love because it's special, and that's a fact!\n.input-size(@input-height; @padding-vertical; @padding-horizontal; @font-size; @line-height; @border-radius) {\n height: @input-height;\n padding: @padding-vertical @padding-horizontal;\n font-size: @font-size;\n line-height: @line-height;\n border-radius: @border-radius;\n\n select& {\n height: @input-height;\n line-height: @input-height;\n }\n\n textarea&,\n select[multiple]& {\n height: auto;\n }\n}\n","//\n// Buttons\n// --------------------------------------------------\n\n\n// Base styles\n// --------------------------------------------------\n\n.btn {\n display: inline-block;\n margin-bottom: 0; // For input.btn\n font-weight: @btn-font-weight;\n text-align: center;\n vertical-align: middle;\n touch-action: manipulation;\n cursor: pointer;\n background-image: none; // Reset unusual Firefox-on-Android default style; see https://github.com/necolas/normalize.css/issues/214\n border: 1px solid transparent;\n white-space: nowrap;\n .button-size(@padding-base-vertical; @padding-base-horizontal; @font-size-base; @line-height-base; @btn-border-radius-base);\n .user-select(none);\n\n &,\n &:active,\n &.active {\n &:focus,\n &.focus {\n .tab-focus();\n }\n }\n\n &:hover,\n &:focus,\n &.focus {\n color: @btn-default-color;\n text-decoration: none;\n }\n\n &:active,\n &.active {\n outline: 0;\n background-image: none;\n .box-shadow(inset 0 3px 5px rgba(0,0,0,.125));\n }\n\n &.disabled,\n &[disabled],\n fieldset[disabled] & {\n cursor: @cursor-disabled;\n .opacity(.65);\n .box-shadow(none);\n }\n\n a& {\n &.disabled,\n fieldset[disabled] & {\n pointer-events: none; // Future-proof disabling of clicks on `<a>` elements\n }\n }\n}\n\n\n// Alternate buttons\n// --------------------------------------------------\n\n.btn-default {\n .button-variant(@btn-default-color; @btn-default-bg; @btn-default-border);\n}\n.btn-primary {\n .button-variant(@btn-primary-color; @btn-primary-bg; @btn-primary-border);\n}\n// Success appears as green\n.btn-success {\n .button-variant(@btn-success-color; @btn-success-bg; @btn-success-border);\n}\n// Info appears as blue-green\n.btn-info {\n .button-variant(@btn-info-color; @btn-info-bg; @btn-info-border);\n}\n// Warning appears as orange\n.btn-warning {\n .button-variant(@btn-warning-color; @btn-warning-bg; @btn-warning-border);\n}\n// Danger and error appear as red\n.btn-danger {\n .button-variant(@btn-danger-color; @btn-danger-bg; @btn-danger-border);\n}\n\n\n// Link buttons\n// -------------------------\n\n// Make a button look and behave like a link\n.btn-link {\n color: @link-color;\n font-weight: normal;\n border-radius: 0;\n\n &,\n &:active,\n &.active,\n &[disabled],\n fieldset[disabled] & {\n background-color: transparent;\n .box-shadow(none);\n }\n &,\n &:hover,\n &:focus,\n &:active {\n border-color: transparent;\n }\n &:hover,\n &:focus {\n color: @link-hover-color;\n text-decoration: @link-hover-decoration;\n background-color: transparent;\n }\n &[disabled],\n fieldset[disabled] & {\n &:hover,\n &:focus {\n color: @btn-link-disabled-color;\n text-decoration: none;\n }\n }\n}\n\n\n// Button Sizes\n// --------------------------------------------------\n\n.btn-lg {\n // line-height: ensure even-numbered height of button next to large input\n .button-size(@padding-large-vertical; @padding-large-horizontal; @font-size-large; @line-height-large; @btn-border-radius-large);\n}\n.btn-sm {\n // line-height: ensure proper height of button next to small input\n .button-size(@padding-small-vertical; @padding-small-horizontal; @font-size-small; @line-height-small; @btn-border-radius-small);\n}\n.btn-xs {\n .button-size(@padding-xs-vertical; @padding-xs-horizontal; @font-size-small; @line-height-small; @btn-border-radius-small);\n}\n\n\n// Block button\n// --------------------------------------------------\n\n.btn-block {\n display: block;\n width: 100%;\n}\n\n// Vertically space out multiple block buttons\n.btn-block + .btn-block {\n margin-top: 5px;\n}\n\n// Specificity overrides\ninput[type=\"submit\"],\ninput[type=\"reset\"],\ninput[type=\"button\"] {\n &.btn-block {\n width: 100%;\n }\n}\n","// Button variants\n//\n// Easily pump out default styles, as well as :hover, :focus, :active,\n// and disabled options for all buttons\n\n.button-variant(@color; @background; @border) {\n color: @color;\n background-color: @background;\n border-color: @border;\n\n &:focus,\n &.focus {\n color: @color;\n background-color: darken(@background, 10%);\n border-color: darken(@border, 25%);\n }\n &:hover {\n color: @color;\n background-color: darken(@background, 10%);\n border-color: darken(@border, 12%);\n }\n &:active,\n &.active,\n .open > .dropdown-toggle& {\n color: @color;\n background-color: darken(@background, 10%);\n border-color: darken(@border, 12%);\n\n &:hover,\n &:focus,\n &.focus {\n color: @color;\n background-color: darken(@background, 17%);\n border-color: darken(@border, 25%);\n }\n }\n &:active,\n &.active,\n .open > .dropdown-toggle& {\n background-image: none;\n }\n &.disabled,\n &[disabled],\n fieldset[disabled] & {\n &:hover,\n &:focus,\n &.focus {\n background-color: @background;\n border-color: @border;\n }\n }\n\n .badge {\n color: @background;\n background-color: @color;\n }\n}\n\n// Button sizes\n.button-size(@padding-vertical; @padding-horizontal; @font-size; @line-height; @border-radius) {\n padding: @padding-vertical @padding-horizontal;\n font-size: @font-size;\n line-height: @line-height;\n border-radius: @border-radius;\n}\n","// Opacity\n\n.opacity(@opacity) {\n opacity: @opacity;\n // IE8 filter\n @opacity-ie: (@opacity * 100);\n filter: ~\"alpha(opacity=@{opacity-ie})\";\n}\n","//\n// Component animations\n// --------------------------------------------------\n\n// Heads up!\n//\n// We don't use the `.opacity()` mixin here since it causes a bug with text\n// fields in IE7-8. Source: https://github.com/twbs/bootstrap/pull/3552.\n\n.fade {\n opacity: 0;\n .transition(opacity .15s linear);\n &.in {\n opacity: 1;\n }\n}\n\n.collapse {\n display: none;\n\n &.in { display: block; }\n tr&.in { display: table-row; }\n tbody&.in { display: table-row-group; }\n}\n\n.collapsing {\n position: relative;\n height: 0;\n overflow: hidden;\n .transition-property(~\"height, visibility\");\n .transition-duration(.35s);\n .transition-timing-function(ease);\n}\n","//\n// Dropdown menus\n// --------------------------------------------------\n\n\n// Dropdown arrow/caret\n.caret {\n display: inline-block;\n width: 0;\n height: 0;\n margin-left: 2px;\n vertical-align: middle;\n border-top: @caret-width-base dashed;\n border-top: @caret-width-base solid ~\"\\9\"; // IE8\n border-right: @caret-width-base solid transparent;\n border-left: @caret-width-base solid transparent;\n}\n\n// The dropdown wrapper (div)\n.dropup,\n.dropdown {\n position: relative;\n}\n\n// Prevent the focus on the dropdown toggle when closing dropdowns\n.dropdown-toggle:focus {\n outline: 0;\n}\n\n// The dropdown menu (ul)\n.dropdown-menu {\n position: absolute;\n top: 100%;\n left: 0;\n z-index: @zindex-dropdown;\n display: none; // none by default, but block on \"open\" of the menu\n float: left;\n min-width: 160px;\n padding: 5px 0;\n margin: 2px 0 0; // override default ul\n list-style: none;\n font-size: @font-size-base;\n text-align: left; // Ensures proper alignment if parent has it changed (e.g., modal footer)\n background-color: @dropdown-bg;\n border: 1px solid @dropdown-fallback-border; // IE8 fallback\n border: 1px solid @dropdown-border;\n border-radius: @border-radius-base;\n .box-shadow(0 6px 12px rgba(0,0,0,.175));\n background-clip: padding-box;\n\n // Aligns the dropdown menu to right\n //\n // Deprecated as of 3.1.0 in favor of `.dropdown-menu-[dir]`\n &.pull-right {\n right: 0;\n left: auto;\n }\n\n // Dividers (basically an hr) within the dropdown\n .divider {\n .nav-divider(@dropdown-divider-bg);\n }\n\n // Links within the dropdown menu\n > li > a {\n display: block;\n padding: 3px 20px;\n clear: both;\n font-weight: normal;\n line-height: @line-height-base;\n color: @dropdown-link-color;\n white-space: nowrap; // prevent links from randomly breaking onto new lines\n }\n}\n\n// Hover/Focus state\n.dropdown-menu > li > a {\n &:hover,\n &:focus {\n text-decoration: none;\n color: @dropdown-link-hover-color;\n background-color: @dropdown-link-hover-bg;\n }\n}\n\n// Active state\n.dropdown-menu > .active > a {\n &,\n &:hover,\n &:focus {\n color: @dropdown-link-active-color;\n text-decoration: none;\n outline: 0;\n background-color: @dropdown-link-active-bg;\n }\n}\n\n// Disabled state\n//\n// Gray out text and ensure the hover/focus state remains gray\n\n.dropdown-menu > .disabled > a {\n &,\n &:hover,\n &:focus {\n color: @dropdown-link-disabled-color;\n }\n\n // Nuke hover/focus effects\n &:hover,\n &:focus {\n text-decoration: none;\n background-color: transparent;\n background-image: none; // Remove CSS gradient\n .reset-filter();\n cursor: @cursor-disabled;\n }\n}\n\n// Open state for the dropdown\n.open {\n // Show the menu\n > .dropdown-menu {\n display: block;\n }\n\n // Remove the outline when :focus is triggered\n > a {\n outline: 0;\n }\n}\n\n// Menu positioning\n//\n// Add extra class to `.dropdown-menu` to flip the alignment of the dropdown\n// menu with the parent.\n.dropdown-menu-right {\n left: auto; // Reset the default from `.dropdown-menu`\n right: 0;\n}\n// With v3, we enabled auto-flipping if you have a dropdown within a right\n// aligned nav component. To enable the undoing of that, we provide an override\n// to restore the default dropdown menu alignment.\n//\n// This is only for left-aligning a dropdown menu within a `.navbar-right` or\n// `.pull-right` nav component.\n.dropdown-menu-left {\n left: 0;\n right: auto;\n}\n\n// Dropdown section headers\n.dropdown-header {\n display: block;\n padding: 3px 20px;\n font-size: @font-size-small;\n line-height: @line-height-base;\n color: @dropdown-header-color;\n white-space: nowrap; // as with > li > a\n}\n\n// Backdrop to catch body clicks on mobile, etc.\n.dropdown-backdrop {\n position: fixed;\n left: 0;\n right: 0;\n bottom: 0;\n top: 0;\n z-index: (@zindex-dropdown - 10);\n}\n\n// Right aligned dropdowns\n.pull-right > .dropdown-menu {\n right: 0;\n left: auto;\n}\n\n// Allow for dropdowns to go bottom up (aka, dropup-menu)\n//\n// Just add .dropup after the standard .dropdown class and you're set, bro.\n// TODO: abstract this so that the navbar fixed styles are not placed here?\n\n.dropup,\n.navbar-fixed-bottom .dropdown {\n // Reverse the caret\n .caret {\n border-top: 0;\n border-bottom: @caret-width-base dashed;\n border-bottom: @caret-width-base solid ~\"\\9\"; // IE8\n content: \"\";\n }\n // Different positioning for bottom up menu\n .dropdown-menu {\n top: auto;\n bottom: 100%;\n margin-bottom: 2px;\n }\n}\n\n\n// Component alignment\n//\n// Reiterate per navbar.less and the modified component alignment there.\n\n@media (min-width: @grid-float-breakpoint) {\n .navbar-right {\n .dropdown-menu {\n .dropdown-menu-right();\n }\n // Necessary for overrides of the default right aligned menu.\n // Will remove come v4 in all likelihood.\n .dropdown-menu-left {\n .dropdown-menu-left();\n }\n }\n}\n","// Horizontal dividers\n//\n// Dividers (basically an hr) within dropdowns and nav lists\n\n.nav-divider(@color: #e5e5e5) {\n height: 1px;\n margin: ((@line-height-computed / 2) - 1) 0;\n overflow: hidden;\n background-color: @color;\n}\n","// Reset filters for IE\n//\n// When you need to remove a gradient background, do not forget to use this to reset\n// the IE filter for IE9 and below.\n\n.reset-filter() {\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(enabled = false)\"));\n}\n","//\n// Button groups\n// --------------------------------------------------\n\n// Make the div behave like a button\n.btn-group,\n.btn-group-vertical {\n position: relative;\n display: inline-block;\n vertical-align: middle; // match .btn alignment given font-size hack above\n > .btn {\n position: relative;\n float: left;\n // Bring the \"active\" button to the front\n &:hover,\n &:focus,\n &:active,\n &.active {\n z-index: 2;\n }\n }\n}\n\n// Prevent double borders when buttons are next to each other\n.btn-group {\n .btn + .btn,\n .btn + .btn-group,\n .btn-group + .btn,\n .btn-group + .btn-group {\n margin-left: -1px;\n }\n}\n\n// Optional: Group multiple button groups together for a toolbar\n.btn-toolbar {\n margin-left: -5px; // Offset the first child's margin\n &:extend(.clearfix all);\n\n .btn,\n .btn-group,\n .input-group {\n float: left;\n }\n > .btn,\n > .btn-group,\n > .input-group {\n margin-left: 5px;\n }\n}\n\n.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) {\n border-radius: 0;\n}\n\n// Set corners individual because sometimes a single button can be in a .btn-group and we need :first-child and :last-child to both match\n.btn-group > .btn:first-child {\n margin-left: 0;\n &:not(:last-child):not(.dropdown-toggle) {\n .border-right-radius(0);\n }\n}\n// Need .dropdown-toggle since :last-child doesn't apply given a .dropdown-menu immediately after it\n.btn-group > .btn:last-child:not(:first-child),\n.btn-group > .dropdown-toggle:not(:first-child) {\n .border-left-radius(0);\n}\n\n// Custom edits for including btn-groups within btn-groups (useful for including dropdown buttons within a btn-group)\n.btn-group > .btn-group {\n float: left;\n}\n.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn {\n border-radius: 0;\n}\n.btn-group > .btn-group:first-child:not(:last-child) {\n > .btn:last-child,\n > .dropdown-toggle {\n .border-right-radius(0);\n }\n}\n.btn-group > .btn-group:last-child:not(:first-child) > .btn:first-child {\n .border-left-radius(0);\n}\n\n// On active and open, don't show outline\n.btn-group .dropdown-toggle:active,\n.btn-group.open .dropdown-toggle {\n outline: 0;\n}\n\n\n// Sizing\n//\n// Remix the default button sizing classes into new ones for easier manipulation.\n\n.btn-group-xs > .btn { &:extend(.btn-xs); }\n.btn-group-sm > .btn { &:extend(.btn-sm); }\n.btn-group-lg > .btn { &:extend(.btn-lg); }\n\n\n// Split button dropdowns\n// ----------------------\n\n// Give the line between buttons some depth\n.btn-group > .btn + .dropdown-toggle {\n padding-left: 8px;\n padding-right: 8px;\n}\n.btn-group > .btn-lg + .dropdown-toggle {\n padding-left: 12px;\n padding-right: 12px;\n}\n\n// The clickable button for toggling the menu\n// Remove the gradient and set the same inset shadow as the :active state\n.btn-group.open .dropdown-toggle {\n .box-shadow(inset 0 3px 5px rgba(0,0,0,.125));\n\n // Show no shadow for `.btn-link` since it has no other button styles.\n &.btn-link {\n .box-shadow(none);\n }\n}\n\n\n// Reposition the caret\n.btn .caret {\n margin-left: 0;\n}\n// Carets in other button sizes\n.btn-lg .caret {\n border-width: @caret-width-large @caret-width-large 0;\n border-bottom-width: 0;\n}\n// Upside down carets for .dropup\n.dropup .btn-lg .caret {\n border-width: 0 @caret-width-large @caret-width-large;\n}\n\n\n// Vertical button groups\n// ----------------------\n\n.btn-group-vertical {\n > .btn,\n > .btn-group,\n > .btn-group > .btn {\n display: block;\n float: none;\n width: 100%;\n max-width: 100%;\n }\n\n // Clear floats so dropdown menus can be properly placed\n > .btn-group {\n &:extend(.clearfix all);\n > .btn {\n float: none;\n }\n }\n\n > .btn + .btn,\n > .btn + .btn-group,\n > .btn-group + .btn,\n > .btn-group + .btn-group {\n margin-top: -1px;\n margin-left: 0;\n }\n}\n\n.btn-group-vertical > .btn {\n &:not(:first-child):not(:last-child) {\n border-radius: 0;\n }\n &:first-child:not(:last-child) {\n .border-top-radius(@btn-border-radius-base);\n .border-bottom-radius(0);\n }\n &:last-child:not(:first-child) {\n .border-top-radius(0);\n .border-bottom-radius(@btn-border-radius-base);\n }\n}\n.btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn {\n border-radius: 0;\n}\n.btn-group-vertical > .btn-group:first-child:not(:last-child) {\n > .btn:last-child,\n > .dropdown-toggle {\n .border-bottom-radius(0);\n }\n}\n.btn-group-vertical > .btn-group:last-child:not(:first-child) > .btn:first-child {\n .border-top-radius(0);\n}\n\n\n// Justified button groups\n// ----------------------\n\n.btn-group-justified {\n display: table;\n width: 100%;\n table-layout: fixed;\n border-collapse: separate;\n > .btn,\n > .btn-group {\n float: none;\n display: table-cell;\n width: 1%;\n }\n > .btn-group .btn {\n width: 100%;\n }\n\n > .btn-group .dropdown-menu {\n left: auto;\n }\n}\n\n\n// Checkbox and radio options\n//\n// In order to support the browser's form validation feedback, powered by the\n// `required` attribute, we have to \"hide\" the inputs via `clip`. We cannot use\n// `display: none;` or `visibility: hidden;` as that also hides the popover.\n// Simply visually hiding the inputs via `opacity` would leave them clickable in\n// certain cases which is prevented by using `clip` and `pointer-events`.\n// This way, we ensure a DOM element is visible to position the popover from.\n//\n// See https://github.com/twbs/bootstrap/pull/12794 and\n// https://github.com/twbs/bootstrap/pull/14559 for more information.\n\n[data-toggle=\"buttons\"] {\n > .btn,\n > .btn-group > .btn {\n input[type=\"radio\"],\n input[type=\"checkbox\"] {\n position: absolute;\n clip: rect(0,0,0,0);\n pointer-events: none;\n }\n }\n}\n","// Single side border-radius\n\n.border-top-radius(@radius) {\n border-top-right-radius: @radius;\n border-top-left-radius: @radius;\n}\n.border-right-radius(@radius) {\n border-bottom-right-radius: @radius;\n border-top-right-radius: @radius;\n}\n.border-bottom-radius(@radius) {\n border-bottom-right-radius: @radius;\n border-bottom-left-radius: @radius;\n}\n.border-left-radius(@radius) {\n border-bottom-left-radius: @radius;\n border-top-left-radius: @radius;\n}\n","//\n// Input groups\n// --------------------------------------------------\n\n// Base styles\n// -------------------------\n.input-group {\n position: relative; // For dropdowns\n display: table;\n border-collapse: separate; // prevent input groups from inheriting border styles from table cells when placed within a table\n\n // Undo padding and float of grid classes\n &[class*=\"col-\"] {\n float: none;\n padding-left: 0;\n padding-right: 0;\n }\n\n .form-control {\n // Ensure that the input is always above the *appended* addon button for\n // proper border colors.\n position: relative;\n z-index: 2;\n\n // IE9 fubars the placeholder attribute in text inputs and the arrows on\n // select elements in input groups. To fix it, we float the input. Details:\n // https://github.com/twbs/bootstrap/issues/11561#issuecomment-28936855\n float: left;\n\n width: 100%;\n margin-bottom: 0;\n \n &:focus {\n z-index: 3;\n }\n }\n}\n\n// Sizing options\n//\n// Remix the default form control sizing classes into new ones for easier\n// manipulation.\n\n.input-group-lg > .form-control,\n.input-group-lg > .input-group-addon,\n.input-group-lg > .input-group-btn > .btn {\n .input-lg();\n}\n.input-group-sm > .form-control,\n.input-group-sm > .input-group-addon,\n.input-group-sm > .input-group-btn > .btn {\n .input-sm();\n}\n\n\n// Display as table-cell\n// -------------------------\n.input-group-addon,\n.input-group-btn,\n.input-group .form-control {\n display: table-cell;\n\n &:not(:first-child):not(:last-child) {\n border-radius: 0;\n }\n}\n// Addon and addon wrapper for buttons\n.input-group-addon,\n.input-group-btn {\n width: 1%;\n white-space: nowrap;\n vertical-align: middle; // Match the inputs\n}\n\n// Text input groups\n// -------------------------\n.input-group-addon {\n padding: @padding-base-vertical @padding-base-horizontal;\n font-size: @font-size-base;\n font-weight: normal;\n line-height: 1;\n color: @input-color;\n text-align: center;\n background-color: @input-group-addon-bg;\n border: 1px solid @input-group-addon-border-color;\n border-radius: @input-border-radius;\n\n // Sizing\n &.input-sm {\n padding: @padding-small-vertical @padding-small-horizontal;\n font-size: @font-size-small;\n border-radius: @input-border-radius-small;\n }\n &.input-lg {\n padding: @padding-large-vertical @padding-large-horizontal;\n font-size: @font-size-large;\n border-radius: @input-border-radius-large;\n }\n\n // Nuke default margins from checkboxes and radios to vertically center within.\n input[type=\"radio\"],\n input[type=\"checkbox\"] {\n margin-top: 0;\n }\n}\n\n// Reset rounded corners\n.input-group .form-control:first-child,\n.input-group-addon:first-child,\n.input-group-btn:first-child > .btn,\n.input-group-btn:first-child > .btn-group > .btn,\n.input-group-btn:first-child > .dropdown-toggle,\n.input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle),\n.input-group-btn:last-child > .btn-group:not(:last-child) > .btn {\n .border-right-radius(0);\n}\n.input-group-addon:first-child {\n border-right: 0;\n}\n.input-group .form-control:last-child,\n.input-group-addon:last-child,\n.input-group-btn:last-child > .btn,\n.input-group-btn:last-child > .btn-group > .btn,\n.input-group-btn:last-child > .dropdown-toggle,\n.input-group-btn:first-child > .btn:not(:first-child),\n.input-group-btn:first-child > .btn-group:not(:first-child) > .btn {\n .border-left-radius(0);\n}\n.input-group-addon:last-child {\n border-left: 0;\n}\n\n// Button input groups\n// -------------------------\n.input-group-btn {\n position: relative;\n // Jankily prevent input button groups from wrapping with `white-space` and\n // `font-size` in combination with `inline-block` on buttons.\n font-size: 0;\n white-space: nowrap;\n\n // Negative margin for spacing, position for bringing hovered/focused/actived\n // element above the siblings.\n > .btn {\n position: relative;\n + .btn {\n margin-left: -1px;\n }\n // Bring the \"active\" button to the front\n &:hover,\n &:focus,\n &:active {\n z-index: 2;\n }\n }\n\n // Negative margin to only have a 1px border between the two\n &:first-child {\n > .btn,\n > .btn-group {\n margin-right: -1px;\n }\n }\n &:last-child {\n > .btn,\n > .btn-group {\n z-index: 2;\n margin-left: -1px;\n }\n }\n}\n","//\n// Navs\n// --------------------------------------------------\n\n\n// Base class\n// --------------------------------------------------\n\n.nav {\n margin-bottom: 0;\n padding-left: 0; // Override default ul/ol\n list-style: none;\n &:extend(.clearfix all);\n\n > li {\n position: relative;\n display: block;\n\n > a {\n position: relative;\n display: block;\n padding: @nav-link-padding;\n &:hover,\n &:focus {\n text-decoration: none;\n background-color: @nav-link-hover-bg;\n }\n }\n\n // Disabled state sets text to gray and nukes hover/tab effects\n &.disabled > a {\n color: @nav-disabled-link-color;\n\n &:hover,\n &:focus {\n color: @nav-disabled-link-hover-color;\n text-decoration: none;\n background-color: transparent;\n cursor: @cursor-disabled;\n }\n }\n }\n\n // Open dropdowns\n .open > a {\n &,\n &:hover,\n &:focus {\n background-color: @nav-link-hover-bg;\n border-color: @link-color;\n }\n }\n\n // Nav dividers (deprecated with v3.0.1)\n //\n // This should have been removed in v3 with the dropping of `.nav-list`, but\n // we missed it. We don't currently support this anywhere, but in the interest\n // of maintaining backward compatibility in case you use it, it's deprecated.\n .nav-divider {\n .nav-divider();\n }\n\n // Prevent IE8 from misplacing imgs\n //\n // See https://github.com/h5bp/html5-boilerplate/issues/984#issuecomment-3985989\n > li > a > img {\n max-width: none;\n }\n}\n\n\n// Tabs\n// -------------------------\n\n// Give the tabs something to sit on\n.nav-tabs {\n border-bottom: 1px solid @nav-tabs-border-color;\n > li {\n float: left;\n // Make the list-items overlay the bottom border\n margin-bottom: -1px;\n\n // Actual tabs (as links)\n > a {\n margin-right: 2px;\n line-height: @line-height-base;\n border: 1px solid transparent;\n border-radius: @border-radius-base @border-radius-base 0 0;\n &:hover {\n border-color: @nav-tabs-link-hover-border-color @nav-tabs-link-hover-border-color @nav-tabs-border-color;\n }\n }\n\n // Active state, and its :hover to override normal :hover\n &.active > a {\n &,\n &:hover,\n &:focus {\n color: @nav-tabs-active-link-hover-color;\n background-color: @nav-tabs-active-link-hover-bg;\n border: 1px solid @nav-tabs-active-link-hover-border-color;\n border-bottom-color: transparent;\n cursor: default;\n }\n }\n }\n // pulling this in mainly for less shorthand\n &.nav-justified {\n .nav-justified();\n .nav-tabs-justified();\n }\n}\n\n\n// Pills\n// -------------------------\n.nav-pills {\n > li {\n float: left;\n\n // Links rendered as pills\n > a {\n border-radius: @nav-pills-border-radius;\n }\n + li {\n margin-left: 2px;\n }\n\n // Active state\n &.active > a {\n &,\n &:hover,\n &:focus {\n color: @nav-pills-active-link-hover-color;\n background-color: @nav-pills-active-link-hover-bg;\n }\n }\n }\n}\n\n\n// Stacked pills\n.nav-stacked {\n > li {\n float: none;\n + li {\n margin-top: 2px;\n margin-left: 0; // no need for this gap between nav items\n }\n }\n}\n\n\n// Nav variations\n// --------------------------------------------------\n\n// Justified nav links\n// -------------------------\n\n.nav-justified {\n width: 100%;\n\n > li {\n float: none;\n > a {\n text-align: center;\n margin-bottom: 5px;\n }\n }\n\n > .dropdown .dropdown-menu {\n top: auto;\n left: auto;\n }\n\n @media (min-width: @screen-sm-min) {\n > li {\n display: table-cell;\n width: 1%;\n > a {\n margin-bottom: 0;\n }\n }\n }\n}\n\n// Move borders to anchors instead of bottom of list\n//\n// Mixin for adding on top the shared `.nav-justified` styles for our tabs\n.nav-tabs-justified {\n border-bottom: 0;\n\n > li > a {\n // Override margin from .nav-tabs\n margin-right: 0;\n border-radius: @border-radius-base;\n }\n\n > .active > a,\n > .active > a:hover,\n > .active > a:focus {\n border: 1px solid @nav-tabs-justified-link-border-color;\n }\n\n @media (min-width: @screen-sm-min) {\n > li > a {\n border-bottom: 1px solid @nav-tabs-justified-link-border-color;\n border-radius: @border-radius-base @border-radius-base 0 0;\n }\n > .active > a,\n > .active > a:hover,\n > .active > a:focus {\n border-bottom-color: @nav-tabs-justified-active-link-border-color;\n }\n }\n}\n\n\n// Tabbable tabs\n// -------------------------\n\n// Hide tabbable panes to start, show them when `.active`\n.tab-content {\n > .tab-pane {\n display: none;\n }\n > .active {\n display: block;\n }\n}\n\n\n// Dropdowns\n// -------------------------\n\n// Specific dropdowns\n.nav-tabs .dropdown-menu {\n // make dropdown border overlap tab border\n margin-top: -1px;\n // Remove the top rounded corners here since there is a hard edge above the menu\n .border-top-radius(0);\n}\n","//\n// Navbars\n// --------------------------------------------------\n\n\n// Wrapper and base class\n//\n// Provide a static navbar from which we expand to create full-width, fixed, and\n// other navbar variations.\n\n.navbar {\n position: relative;\n min-height: @navbar-height; // Ensure a navbar always shows (e.g., without a .navbar-brand in collapsed mode)\n margin-bottom: @navbar-margin-bottom;\n border: 1px solid transparent;\n\n // Prevent floats from breaking the navbar\n &:extend(.clearfix all);\n\n @media (min-width: @grid-float-breakpoint) {\n border-radius: @navbar-border-radius;\n }\n}\n\n\n// Navbar heading\n//\n// Groups `.navbar-brand` and `.navbar-toggle` into a single component for easy\n// styling of responsive aspects.\n\n.navbar-header {\n &:extend(.clearfix all);\n\n @media (min-width: @grid-float-breakpoint) {\n float: left;\n }\n}\n\n\n// Navbar collapse (body)\n//\n// Group your navbar content into this for easy collapsing and expanding across\n// various device sizes. By default, this content is collapsed when <768px, but\n// will expand past that for a horizontal display.\n//\n// To start (on mobile devices) the navbar links, forms, and buttons are stacked\n// vertically and include a `max-height` to overflow in case you have too much\n// content for the user's viewport.\n\n.navbar-collapse {\n overflow-x: visible;\n padding-right: @navbar-padding-horizontal;\n padding-left: @navbar-padding-horizontal;\n border-top: 1px solid transparent;\n box-shadow: inset 0 1px 0 rgba(255,255,255,.1);\n &:extend(.clearfix all);\n -webkit-overflow-scrolling: touch;\n\n &.in {\n overflow-y: auto;\n }\n\n @media (min-width: @grid-float-breakpoint) {\n width: auto;\n border-top: 0;\n box-shadow: none;\n\n &.collapse {\n display: block !important;\n height: auto !important;\n padding-bottom: 0; // Override default setting\n overflow: visible !important;\n }\n\n &.in {\n overflow-y: visible;\n }\n\n // Undo the collapse side padding for navbars with containers to ensure\n // alignment of right-aligned contents.\n .navbar-fixed-top &,\n .navbar-static-top &,\n .navbar-fixed-bottom & {\n padding-left: 0;\n padding-right: 0;\n }\n }\n}\n\n.navbar-fixed-top,\n.navbar-fixed-bottom {\n .navbar-collapse {\n max-height: @navbar-collapse-max-height;\n\n @media (max-device-width: @screen-xs-min) and (orientation: landscape) {\n max-height: 200px;\n }\n }\n}\n\n\n// Both navbar header and collapse\n//\n// When a container is present, change the behavior of the header and collapse.\n\n.container,\n.container-fluid {\n > .navbar-header,\n > .navbar-collapse {\n margin-right: -@navbar-padding-horizontal;\n margin-left: -@navbar-padding-horizontal;\n\n @media (min-width: @grid-float-breakpoint) {\n margin-right: 0;\n margin-left: 0;\n }\n }\n}\n\n\n//\n// Navbar alignment options\n//\n// Display the navbar across the entirety of the page or fixed it to the top or\n// bottom of the page.\n\n// Static top (unfixed, but 100% wide) navbar\n.navbar-static-top {\n z-index: @zindex-navbar;\n border-width: 0 0 1px;\n\n @media (min-width: @grid-float-breakpoint) {\n border-radius: 0;\n }\n}\n\n// Fix the top/bottom navbars when screen real estate supports it\n.navbar-fixed-top,\n.navbar-fixed-bottom {\n position: fixed;\n right: 0;\n left: 0;\n z-index: @zindex-navbar-fixed;\n\n // Undo the rounded corners\n @media (min-width: @grid-float-breakpoint) {\n border-radius: 0;\n }\n}\n.navbar-fixed-top {\n top: 0;\n border-width: 0 0 1px;\n}\n.navbar-fixed-bottom {\n bottom: 0;\n margin-bottom: 0; // override .navbar defaults\n border-width: 1px 0 0;\n}\n\n\n// Brand/project name\n\n.navbar-brand {\n float: left;\n padding: @navbar-padding-vertical @navbar-padding-horizontal;\n font-size: @font-size-large;\n line-height: @line-height-computed;\n height: @navbar-height;\n\n &:hover,\n &:focus {\n text-decoration: none;\n }\n\n > img {\n display: block;\n }\n\n @media (min-width: @grid-float-breakpoint) {\n .navbar > .container &,\n .navbar > .container-fluid & {\n margin-left: -@navbar-padding-horizontal;\n }\n }\n}\n\n\n// Navbar toggle\n//\n// Custom button for toggling the `.navbar-collapse`, powered by the collapse\n// JavaScript plugin.\n\n.navbar-toggle {\n position: relative;\n float: right;\n margin-right: @navbar-padding-horizontal;\n padding: 9px 10px;\n .navbar-vertical-align(34px);\n background-color: transparent;\n background-image: none; // Reset unusual Firefox-on-Android default style; see https://github.com/necolas/normalize.css/issues/214\n border: 1px solid transparent;\n border-radius: @border-radius-base;\n\n // We remove the `outline` here, but later compensate by attaching `:hover`\n // styles to `:focus`.\n &:focus {\n outline: 0;\n }\n\n // Bars\n .icon-bar {\n display: block;\n width: 22px;\n height: 2px;\n border-radius: 1px;\n }\n .icon-bar + .icon-bar {\n margin-top: 4px;\n }\n\n @media (min-width: @grid-float-breakpoint) {\n display: none;\n }\n}\n\n\n// Navbar nav links\n//\n// Builds on top of the `.nav` components with its own modifier class to make\n// the nav the full height of the horizontal nav (above 768px).\n\n.navbar-nav {\n margin: (@navbar-padding-vertical / 2) -@navbar-padding-horizontal;\n\n > li > a {\n padding-top: 10px;\n padding-bottom: 10px;\n line-height: @line-height-computed;\n }\n\n @media (max-width: @grid-float-breakpoint-max) {\n // Dropdowns get custom display when collapsed\n .open .dropdown-menu {\n position: static;\n float: none;\n width: auto;\n margin-top: 0;\n background-color: transparent;\n border: 0;\n box-shadow: none;\n > li > a,\n .dropdown-header {\n padding: 5px 15px 5px 25px;\n }\n > li > a {\n line-height: @line-height-computed;\n &:hover,\n &:focus {\n background-image: none;\n }\n }\n }\n }\n\n // Uncollapse the nav\n @media (min-width: @grid-float-breakpoint) {\n float: left;\n margin: 0;\n\n > li {\n float: left;\n > a {\n padding-top: @navbar-padding-vertical;\n padding-bottom: @navbar-padding-vertical;\n }\n }\n }\n}\n\n\n// Navbar form\n//\n// Extension of the `.form-inline` with some extra flavor for optimum display in\n// our navbars.\n\n.navbar-form {\n margin-left: -@navbar-padding-horizontal;\n margin-right: -@navbar-padding-horizontal;\n padding: 10px @navbar-padding-horizontal;\n border-top: 1px solid transparent;\n border-bottom: 1px solid transparent;\n @shadow: inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.1);\n .box-shadow(@shadow);\n\n // Mixin behavior for optimum display\n .form-inline();\n\n .form-group {\n @media (max-width: @grid-float-breakpoint-max) {\n margin-bottom: 5px;\n\n &:last-child {\n margin-bottom: 0;\n }\n }\n }\n\n // Vertically center in expanded, horizontal navbar\n .navbar-vertical-align(@input-height-base);\n\n // Undo 100% width for pull classes\n @media (min-width: @grid-float-breakpoint) {\n width: auto;\n border: 0;\n margin-left: 0;\n margin-right: 0;\n padding-top: 0;\n padding-bottom: 0;\n .box-shadow(none);\n }\n}\n\n\n// Dropdown menus\n\n// Menu position and menu carets\n.navbar-nav > li > .dropdown-menu {\n margin-top: 0;\n .border-top-radius(0);\n}\n// Menu position and menu caret support for dropups via extra dropup class\n.navbar-fixed-bottom .navbar-nav > li > .dropdown-menu {\n margin-bottom: 0;\n .border-top-radius(@navbar-border-radius);\n .border-bottom-radius(0);\n}\n\n\n// Buttons in navbars\n//\n// Vertically center a button within a navbar (when *not* in a form).\n\n.navbar-btn {\n .navbar-vertical-align(@input-height-base);\n\n &.btn-sm {\n .navbar-vertical-align(@input-height-small);\n }\n &.btn-xs {\n .navbar-vertical-align(22);\n }\n}\n\n\n// Text in navbars\n//\n// Add a class to make any element properly align itself vertically within the navbars.\n\n.navbar-text {\n .navbar-vertical-align(@line-height-computed);\n\n @media (min-width: @grid-float-breakpoint) {\n float: left;\n margin-left: @navbar-padding-horizontal;\n margin-right: @navbar-padding-horizontal;\n }\n}\n\n\n// Component alignment\n//\n// Repurpose the pull utilities as their own navbar utilities to avoid specificity\n// issues with parents and chaining. Only do this when the navbar is uncollapsed\n// though so that navbar contents properly stack and align in mobile.\n//\n// Declared after the navbar components to ensure more specificity on the margins.\n\n@media (min-width: @grid-float-breakpoint) {\n .navbar-left { .pull-left(); }\n .navbar-right {\n .pull-right();\n margin-right: -@navbar-padding-horizontal;\n\n ~ .navbar-right {\n margin-right: 0;\n }\n }\n}\n\n\n// Alternate navbars\n// --------------------------------------------------\n\n// Default navbar\n.navbar-default {\n background-color: @navbar-default-bg;\n border-color: @navbar-default-border;\n\n .navbar-brand {\n color: @navbar-default-brand-color;\n &:hover,\n &:focus {\n color: @navbar-default-brand-hover-color;\n background-color: @navbar-default-brand-hover-bg;\n }\n }\n\n .navbar-text {\n color: @navbar-default-color;\n }\n\n .navbar-nav {\n > li > a {\n color: @navbar-default-link-color;\n\n &:hover,\n &:focus {\n color: @navbar-default-link-hover-color;\n background-color: @navbar-default-link-hover-bg;\n }\n }\n > .active > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-default-link-active-color;\n background-color: @navbar-default-link-active-bg;\n }\n }\n > .disabled > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-default-link-disabled-color;\n background-color: @navbar-default-link-disabled-bg;\n }\n }\n }\n\n .navbar-toggle {\n border-color: @navbar-default-toggle-border-color;\n &:hover,\n &:focus {\n background-color: @navbar-default-toggle-hover-bg;\n }\n .icon-bar {\n background-color: @navbar-default-toggle-icon-bar-bg;\n }\n }\n\n .navbar-collapse,\n .navbar-form {\n border-color: @navbar-default-border;\n }\n\n // Dropdown menu items\n .navbar-nav {\n // Remove background color from open dropdown\n > .open > a {\n &,\n &:hover,\n &:focus {\n background-color: @navbar-default-link-active-bg;\n color: @navbar-default-link-active-color;\n }\n }\n\n @media (max-width: @grid-float-breakpoint-max) {\n // Dropdowns get custom display when collapsed\n .open .dropdown-menu {\n > li > a {\n color: @navbar-default-link-color;\n &:hover,\n &:focus {\n color: @navbar-default-link-hover-color;\n background-color: @navbar-default-link-hover-bg;\n }\n }\n > .active > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-default-link-active-color;\n background-color: @navbar-default-link-active-bg;\n }\n }\n > .disabled > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-default-link-disabled-color;\n background-color: @navbar-default-link-disabled-bg;\n }\n }\n }\n }\n }\n\n\n // Links in navbars\n //\n // Add a class to ensure links outside the navbar nav are colored correctly.\n\n .navbar-link {\n color: @navbar-default-link-color;\n &:hover {\n color: @navbar-default-link-hover-color;\n }\n }\n\n .btn-link {\n color: @navbar-default-link-color;\n &:hover,\n &:focus {\n color: @navbar-default-link-hover-color;\n }\n &[disabled],\n fieldset[disabled] & {\n &:hover,\n &:focus {\n color: @navbar-default-link-disabled-color;\n }\n }\n }\n}\n\n// Inverse navbar\n\n.navbar-inverse {\n background-color: @navbar-inverse-bg;\n border-color: @navbar-inverse-border;\n\n .navbar-brand {\n color: @navbar-inverse-brand-color;\n &:hover,\n &:focus {\n color: @navbar-inverse-brand-hover-color;\n background-color: @navbar-inverse-brand-hover-bg;\n }\n }\n\n .navbar-text {\n color: @navbar-inverse-color;\n }\n\n .navbar-nav {\n > li > a {\n color: @navbar-inverse-link-color;\n\n &:hover,\n &:focus {\n color: @navbar-inverse-link-hover-color;\n background-color: @navbar-inverse-link-hover-bg;\n }\n }\n > .active > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-inverse-link-active-color;\n background-color: @navbar-inverse-link-active-bg;\n }\n }\n > .disabled > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-inverse-link-disabled-color;\n background-color: @navbar-inverse-link-disabled-bg;\n }\n }\n }\n\n // Darken the responsive nav toggle\n .navbar-toggle {\n border-color: @navbar-inverse-toggle-border-color;\n &:hover,\n &:focus {\n background-color: @navbar-inverse-toggle-hover-bg;\n }\n .icon-bar {\n background-color: @navbar-inverse-toggle-icon-bar-bg;\n }\n }\n\n .navbar-collapse,\n .navbar-form {\n border-color: darken(@navbar-inverse-bg, 7%);\n }\n\n // Dropdowns\n .navbar-nav {\n > .open > a {\n &,\n &:hover,\n &:focus {\n background-color: @navbar-inverse-link-active-bg;\n color: @navbar-inverse-link-active-color;\n }\n }\n\n @media (max-width: @grid-float-breakpoint-max) {\n // Dropdowns get custom display\n .open .dropdown-menu {\n > .dropdown-header {\n border-color: @navbar-inverse-border;\n }\n .divider {\n background-color: @navbar-inverse-border;\n }\n > li > a {\n color: @navbar-inverse-link-color;\n &:hover,\n &:focus {\n color: @navbar-inverse-link-hover-color;\n background-color: @navbar-inverse-link-hover-bg;\n }\n }\n > .active > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-inverse-link-active-color;\n background-color: @navbar-inverse-link-active-bg;\n }\n }\n > .disabled > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-inverse-link-disabled-color;\n background-color: @navbar-inverse-link-disabled-bg;\n }\n }\n }\n }\n }\n\n .navbar-link {\n color: @navbar-inverse-link-color;\n &:hover {\n color: @navbar-inverse-link-hover-color;\n }\n }\n\n .btn-link {\n color: @navbar-inverse-link-color;\n &:hover,\n &:focus {\n color: @navbar-inverse-link-hover-color;\n }\n &[disabled],\n fieldset[disabled] & {\n &:hover,\n &:focus {\n color: @navbar-inverse-link-disabled-color;\n }\n }\n }\n}\n","// Navbar vertical align\n//\n// Vertically center elements in the navbar.\n// Example: an element has a height of 30px, so write out `.navbar-vertical-align(30px);` to calculate the appropriate top margin.\n\n.navbar-vertical-align(@element-height) {\n margin-top: ((@navbar-height - @element-height) / 2);\n margin-bottom: ((@navbar-height - @element-height) / 2);\n}\n","//\n// Utility classes\n// --------------------------------------------------\n\n\n// Floats\n// -------------------------\n\n.clearfix {\n .clearfix();\n}\n.center-block {\n .center-block();\n}\n.pull-right {\n float: right !important;\n}\n.pull-left {\n float: left !important;\n}\n\n\n// Toggling content\n// -------------------------\n\n// Note: Deprecated .hide in favor of .hidden or .sr-only (as appropriate) in v3.0.1\n.hide {\n display: none !important;\n}\n.show {\n display: block !important;\n}\n.invisible {\n visibility: hidden;\n}\n.text-hide {\n .text-hide();\n}\n\n\n// Hide from screenreaders and browsers\n//\n// Credit: HTML5 Boilerplate\n\n.hidden {\n display: none !important;\n}\n\n\n// For Affix plugin\n// -------------------------\n\n.affix {\n position: fixed;\n}\n","//\n// Breadcrumbs\n// --------------------------------------------------\n\n\n.breadcrumb {\n padding: @breadcrumb-padding-vertical @breadcrumb-padding-horizontal;\n margin-bottom: @line-height-computed;\n list-style: none;\n background-color: @breadcrumb-bg;\n border-radius: @border-radius-base;\n\n > li {\n display: inline-block;\n\n + li:before {\n content: \"@{breadcrumb-separator}\\00a0\"; // Unicode space added since inline-block means non-collapsing white-space\n padding: 0 5px;\n color: @breadcrumb-color;\n }\n }\n\n > .active {\n color: @breadcrumb-active-color;\n }\n}\n","//\n// Pagination (multiple pages)\n// --------------------------------------------------\n.pagination {\n display: inline-block;\n padding-left: 0;\n margin: @line-height-computed 0;\n border-radius: @border-radius-base;\n\n > li {\n display: inline; // Remove list-style and block-level defaults\n > a,\n > span {\n position: relative;\n float: left; // Collapse white-space\n padding: @padding-base-vertical @padding-base-horizontal;\n line-height: @line-height-base;\n text-decoration: none;\n color: @pagination-color;\n background-color: @pagination-bg;\n border: 1px solid @pagination-border;\n margin-left: -1px;\n }\n &:first-child {\n > a,\n > span {\n margin-left: 0;\n .border-left-radius(@border-radius-base);\n }\n }\n &:last-child {\n > a,\n > span {\n .border-right-radius(@border-radius-base);\n }\n }\n }\n\n > li > a,\n > li > span {\n &:hover,\n &:focus {\n z-index: 2;\n color: @pagination-hover-color;\n background-color: @pagination-hover-bg;\n border-color: @pagination-hover-border;\n }\n }\n\n > .active > a,\n > .active > span {\n &,\n &:hover,\n &:focus {\n z-index: 3;\n color: @pagination-active-color;\n background-color: @pagination-active-bg;\n border-color: @pagination-active-border;\n cursor: default;\n }\n }\n\n > .disabled {\n > span,\n > span:hover,\n > span:focus,\n > a,\n > a:hover,\n > a:focus {\n color: @pagination-disabled-color;\n background-color: @pagination-disabled-bg;\n border-color: @pagination-disabled-border;\n cursor: @cursor-disabled;\n }\n }\n}\n\n// Sizing\n// --------------------------------------------------\n\n// Large\n.pagination-lg {\n .pagination-size(@padding-large-vertical; @padding-large-horizontal; @font-size-large; @line-height-large; @border-radius-large);\n}\n\n// Small\n.pagination-sm {\n .pagination-size(@padding-small-vertical; @padding-small-horizontal; @font-size-small; @line-height-small; @border-radius-small);\n}\n","// Pagination\n\n.pagination-size(@padding-vertical; @padding-horizontal; @font-size; @line-height; @border-radius) {\n > li {\n > a,\n > span {\n padding: @padding-vertical @padding-horizontal;\n font-size: @font-size;\n line-height: @line-height;\n }\n &:first-child {\n > a,\n > span {\n .border-left-radius(@border-radius);\n }\n }\n &:last-child {\n > a,\n > span {\n .border-right-radius(@border-radius);\n }\n }\n }\n}\n","//\n// Pager pagination\n// --------------------------------------------------\n\n\n.pager {\n padding-left: 0;\n margin: @line-height-computed 0;\n list-style: none;\n text-align: center;\n &:extend(.clearfix all);\n li {\n display: inline;\n > a,\n > span {\n display: inline-block;\n padding: 5px 14px;\n background-color: @pager-bg;\n border: 1px solid @pager-border;\n border-radius: @pager-border-radius;\n }\n\n > a:hover,\n > a:focus {\n text-decoration: none;\n background-color: @pager-hover-bg;\n }\n }\n\n .next {\n > a,\n > span {\n float: right;\n }\n }\n\n .previous {\n > a,\n > span {\n float: left;\n }\n }\n\n .disabled {\n > a,\n > a:hover,\n > a:focus,\n > span {\n color: @pager-disabled-color;\n background-color: @pager-bg;\n cursor: @cursor-disabled;\n }\n }\n}\n","//\n// Labels\n// --------------------------------------------------\n\n.label {\n display: inline;\n padding: .2em .6em .3em;\n font-size: 75%;\n font-weight: bold;\n line-height: 1;\n color: @label-color;\n text-align: center;\n white-space: nowrap;\n vertical-align: baseline;\n border-radius: .25em;\n\n // Add hover effects, but only for links\n a& {\n &:hover,\n &:focus {\n color: @label-link-hover-color;\n text-decoration: none;\n cursor: pointer;\n }\n }\n\n // Empty labels collapse automatically (not available in IE8)\n &:empty {\n display: none;\n }\n\n // Quick fix for labels in buttons\n .btn & {\n position: relative;\n top: -1px;\n }\n}\n\n// Colors\n// Contextual variations (linked labels get darker on :hover)\n\n.label-default {\n .label-variant(@label-default-bg);\n}\n\n.label-primary {\n .label-variant(@label-primary-bg);\n}\n\n.label-success {\n .label-variant(@label-success-bg);\n}\n\n.label-info {\n .label-variant(@label-info-bg);\n}\n\n.label-warning {\n .label-variant(@label-warning-bg);\n}\n\n.label-danger {\n .label-variant(@label-danger-bg);\n}\n","// Labels\n\n.label-variant(@color) {\n background-color: @color;\n\n &[href] {\n &:hover,\n &:focus {\n background-color: darken(@color, 10%);\n }\n }\n}\n","//\n// Badges\n// --------------------------------------------------\n\n\n// Base class\n.badge {\n display: inline-block;\n min-width: 10px;\n padding: 3px 7px;\n font-size: @font-size-small;\n font-weight: @badge-font-weight;\n color: @badge-color;\n line-height: @badge-line-height;\n vertical-align: middle;\n white-space: nowrap;\n text-align: center;\n background-color: @badge-bg;\n border-radius: @badge-border-radius;\n\n // Empty badges collapse automatically (not available in IE8)\n &:empty {\n display: none;\n }\n\n // Quick fix for badges in buttons\n .btn & {\n position: relative;\n top: -1px;\n }\n\n .btn-xs &,\n .btn-group-xs > .btn & {\n top: 0;\n padding: 1px 5px;\n }\n\n // Hover state, but only for links\n a& {\n &:hover,\n &:focus {\n color: @badge-link-hover-color;\n text-decoration: none;\n cursor: pointer;\n }\n }\n\n // Account for badges in navs\n .list-group-item.active > &,\n .nav-pills > .active > a > & {\n color: @badge-active-color;\n background-color: @badge-active-bg;\n }\n\n .list-group-item > & {\n float: right;\n }\n\n .list-group-item > & + & {\n margin-right: 5px;\n }\n\n .nav-pills > li > a > & {\n margin-left: 3px;\n }\n}\n","//\n// Jumbotron\n// --------------------------------------------------\n\n\n.jumbotron {\n padding-top: @jumbotron-padding;\n padding-bottom: @jumbotron-padding;\n margin-bottom: @jumbotron-padding;\n color: @jumbotron-color;\n background-color: @jumbotron-bg;\n\n h1,\n .h1 {\n color: @jumbotron-heading-color;\n }\n\n p {\n margin-bottom: (@jumbotron-padding / 2);\n font-size: @jumbotron-font-size;\n font-weight: 200;\n }\n\n > hr {\n border-top-color: darken(@jumbotron-bg, 10%);\n }\n\n .container &,\n .container-fluid & {\n border-radius: @border-radius-large; // Only round corners at higher resolutions if contained in a container\n padding-left: (@grid-gutter-width / 2);\n padding-right: (@grid-gutter-width / 2);\n }\n\n .container {\n max-width: 100%;\n }\n\n @media screen and (min-width: @screen-sm-min) {\n padding-top: (@jumbotron-padding * 1.6);\n padding-bottom: (@jumbotron-padding * 1.6);\n\n .container &,\n .container-fluid & {\n padding-left: (@jumbotron-padding * 2);\n padding-right: (@jumbotron-padding * 2);\n }\n\n h1,\n .h1 {\n font-size: @jumbotron-heading-font-size;\n }\n }\n}\n","//\n// Thumbnails\n// --------------------------------------------------\n\n\n// Mixin and adjust the regular image class\n.thumbnail {\n display: block;\n padding: @thumbnail-padding;\n margin-bottom: @line-height-computed;\n line-height: @line-height-base;\n background-color: @thumbnail-bg;\n border: 1px solid @thumbnail-border;\n border-radius: @thumbnail-border-radius;\n .transition(border .2s ease-in-out);\n\n > img,\n a > img {\n &:extend(.img-responsive);\n margin-left: auto;\n margin-right: auto;\n }\n\n // Add a hover state for linked versions only\n a&:hover,\n a&:focus,\n a&.active {\n border-color: @link-color;\n }\n\n // Image captions\n .caption {\n padding: @thumbnail-caption-padding;\n color: @thumbnail-caption-color;\n }\n}\n","//\n// Alerts\n// --------------------------------------------------\n\n\n// Base styles\n// -------------------------\n\n.alert {\n padding: @alert-padding;\n margin-bottom: @line-height-computed;\n border: 1px solid transparent;\n border-radius: @alert-border-radius;\n\n // Headings for larger alerts\n h4 {\n margin-top: 0;\n // Specified for the h4 to prevent conflicts of changing @headings-color\n color: inherit;\n }\n\n // Provide class for links that match alerts\n .alert-link {\n font-weight: @alert-link-font-weight;\n }\n\n // Improve alignment and spacing of inner content\n > p,\n > ul {\n margin-bottom: 0;\n }\n\n > p + p {\n margin-top: 5px;\n }\n}\n\n// Dismissible alerts\n//\n// Expand the right padding and account for the close button's positioning.\n\n.alert-dismissable, // The misspelled .alert-dismissable was deprecated in 3.2.0.\n.alert-dismissible {\n padding-right: (@alert-padding + 20);\n\n // Adjust close link position\n .close {\n position: relative;\n top: -2px;\n right: -21px;\n color: inherit;\n }\n}\n\n// Alternate styles\n//\n// Generate contextual modifier classes for colorizing the alert.\n\n.alert-success {\n .alert-variant(@alert-success-bg; @alert-success-border; @alert-success-text);\n}\n\n.alert-info {\n .alert-variant(@alert-info-bg; @alert-info-border; @alert-info-text);\n}\n\n.alert-warning {\n .alert-variant(@alert-warning-bg; @alert-warning-border; @alert-warning-text);\n}\n\n.alert-danger {\n .alert-variant(@alert-danger-bg; @alert-danger-border; @alert-danger-text);\n}\n","// Alerts\n\n.alert-variant(@background; @border; @text-color) {\n background-color: @background;\n border-color: @border;\n color: @text-color;\n\n hr {\n border-top-color: darken(@border, 5%);\n }\n .alert-link {\n color: darken(@text-color, 10%);\n }\n}\n","//\n// Progress bars\n// --------------------------------------------------\n\n\n// Bar animations\n// -------------------------\n\n// WebKit\n@-webkit-keyframes progress-bar-stripes {\n from { background-position: 40px 0; }\n to { background-position: 0 0; }\n}\n\n// Spec and IE10+\n@keyframes progress-bar-stripes {\n from { background-position: 40px 0; }\n to { background-position: 0 0; }\n}\n\n\n// Bar itself\n// -------------------------\n\n// Outer container\n.progress {\n overflow: hidden;\n height: @line-height-computed;\n margin-bottom: @line-height-computed;\n background-color: @progress-bg;\n border-radius: @progress-border-radius;\n .box-shadow(inset 0 1px 2px rgba(0,0,0,.1));\n}\n\n// Bar of progress\n.progress-bar {\n float: left;\n width: 0%;\n height: 100%;\n font-size: @font-size-small;\n line-height: @line-height-computed;\n color: @progress-bar-color;\n text-align: center;\n background-color: @progress-bar-bg;\n .box-shadow(inset 0 -1px 0 rgba(0,0,0,.15));\n .transition(width .6s ease);\n}\n\n// Striped bars\n//\n// `.progress-striped .progress-bar` is deprecated as of v3.2.0 in favor of the\n// `.progress-bar-striped` class, which you just add to an existing\n// `.progress-bar`.\n.progress-striped .progress-bar,\n.progress-bar-striped {\n #gradient > .striped();\n background-size: 40px 40px;\n}\n\n// Call animation for the active one\n//\n// `.progress.active .progress-bar` is deprecated as of v3.2.0 in favor of the\n// `.progress-bar.active` approach.\n.progress.active .progress-bar,\n.progress-bar.active {\n .animation(progress-bar-stripes 2s linear infinite);\n}\n\n\n// Variations\n// -------------------------\n\n.progress-bar-success {\n .progress-bar-variant(@progress-bar-success-bg);\n}\n\n.progress-bar-info {\n .progress-bar-variant(@progress-bar-info-bg);\n}\n\n.progress-bar-warning {\n .progress-bar-variant(@progress-bar-warning-bg);\n}\n\n.progress-bar-danger {\n .progress-bar-variant(@progress-bar-danger-bg);\n}\n","// Gradients\n\n#gradient {\n\n // Horizontal gradient, from left to right\n //\n // Creates two color stops, start and end, by specifying a color and position for each color stop.\n // Color stops are not available in IE9 and below.\n .horizontal(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) {\n background-image: -webkit-linear-gradient(left, @start-color @start-percent, @end-color @end-percent); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(left, @start-color @start-percent, @end-color @end-percent); // Opera 12\n background-image: linear-gradient(to right, @start-color @start-percent, @end-color @end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n background-repeat: repeat-x;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)\",argb(@start-color),argb(@end-color))); // IE9 and down\n }\n\n // Vertical gradient, from top to bottom\n //\n // Creates two color stops, start and end, by specifying a color and position for each color stop.\n // Color stops are not available in IE9 and below.\n .vertical(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) {\n background-image: -webkit-linear-gradient(top, @start-color @start-percent, @end-color @end-percent); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(top, @start-color @start-percent, @end-color @end-percent); // Opera 12\n background-image: linear-gradient(to bottom, @start-color @start-percent, @end-color @end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n background-repeat: repeat-x;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)\",argb(@start-color),argb(@end-color))); // IE9 and down\n }\n\n .directional(@start-color: #555; @end-color: #333; @deg: 45deg) {\n background-repeat: repeat-x;\n background-image: -webkit-linear-gradient(@deg, @start-color, @end-color); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(@deg, @start-color, @end-color); // Opera 12\n background-image: linear-gradient(@deg, @start-color, @end-color); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n }\n .horizontal-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) {\n background-image: -webkit-linear-gradient(left, @start-color, @mid-color @color-stop, @end-color);\n background-image: -o-linear-gradient(left, @start-color, @mid-color @color-stop, @end-color);\n background-image: linear-gradient(to right, @start-color, @mid-color @color-stop, @end-color);\n background-repeat: no-repeat;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)\",argb(@start-color),argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback\n }\n .vertical-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) {\n background-image: -webkit-linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-image: -o-linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-image: linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-repeat: no-repeat;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)\",argb(@start-color),argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback\n }\n .radial(@inner-color: #555; @outer-color: #333) {\n background-image: -webkit-radial-gradient(circle, @inner-color, @outer-color);\n background-image: radial-gradient(circle, @inner-color, @outer-color);\n background-repeat: no-repeat;\n }\n .striped(@color: rgba(255,255,255,.15); @angle: 45deg) {\n background-image: -webkit-linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n background-image: linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n }\n}\n","// Progress bars\n\n.progress-bar-variant(@color) {\n background-color: @color;\n\n // Deprecated parent class requirement as of v3.2.0\n .progress-striped & {\n #gradient > .striped();\n }\n}\n",".media {\n // Proper spacing between instances of .media\n margin-top: 15px;\n\n &:first-child {\n margin-top: 0;\n }\n}\n\n.media,\n.media-body {\n zoom: 1;\n overflow: hidden;\n}\n\n.media-body {\n width: 10000px;\n}\n\n.media-object {\n display: block;\n\n // Fix collapse in webkit from max-width: 100% and display: table-cell.\n &.img-thumbnail {\n max-width: none;\n }\n}\n\n.media-right,\n.media > .pull-right {\n padding-left: 10px;\n}\n\n.media-left,\n.media > .pull-left {\n padding-right: 10px;\n}\n\n.media-left,\n.media-right,\n.media-body {\n display: table-cell;\n vertical-align: top;\n}\n\n.media-middle {\n vertical-align: middle;\n}\n\n.media-bottom {\n vertical-align: bottom;\n}\n\n// Reset margins on headings for tighter default spacing\n.media-heading {\n margin-top: 0;\n margin-bottom: 5px;\n}\n\n// Media list variation\n//\n// Undo default ul/ol styles\n.media-list {\n padding-left: 0;\n list-style: none;\n}\n","//\n// List groups\n// --------------------------------------------------\n\n\n// Base class\n//\n// Easily usable on <ul>, <ol>, or <div>.\n\n.list-group {\n // No need to set list-style: none; since .list-group-item is block level\n margin-bottom: 20px;\n padding-left: 0; // reset padding because ul and ol\n}\n\n\n// Individual list items\n//\n// Use on `li`s or `div`s within the `.list-group` parent.\n\n.list-group-item {\n position: relative;\n display: block;\n padding: 10px 15px;\n // Place the border on the list items and negative margin up for better styling\n margin-bottom: -1px;\n background-color: @list-group-bg;\n border: 1px solid @list-group-border;\n\n // Round the first and last items\n &:first-child {\n .border-top-radius(@list-group-border-radius);\n }\n &:last-child {\n margin-bottom: 0;\n .border-bottom-radius(@list-group-border-radius);\n }\n}\n\n\n// Interactive list items\n//\n// Use anchor or button elements instead of `li`s or `div`s to create interactive items.\n// Includes an extra `.active` modifier class for showing selected items.\n\na.list-group-item,\nbutton.list-group-item {\n color: @list-group-link-color;\n\n .list-group-item-heading {\n color: @list-group-link-heading-color;\n }\n\n // Hover state\n &:hover,\n &:focus {\n text-decoration: none;\n color: @list-group-link-hover-color;\n background-color: @list-group-hover-bg;\n }\n}\n\nbutton.list-group-item {\n width: 100%;\n text-align: left;\n}\n\n.list-group-item {\n // Disabled state\n &.disabled,\n &.disabled:hover,\n &.disabled:focus {\n background-color: @list-group-disabled-bg;\n color: @list-group-disabled-color;\n cursor: @cursor-disabled;\n\n // Force color to inherit for custom content\n .list-group-item-heading {\n color: inherit;\n }\n .list-group-item-text {\n color: @list-group-disabled-text-color;\n }\n }\n\n // Active class on item itself, not parent\n &.active,\n &.active:hover,\n &.active:focus {\n z-index: 2; // Place active items above their siblings for proper border styling\n color: @list-group-active-color;\n background-color: @list-group-active-bg;\n border-color: @list-group-active-border;\n\n // Force color to inherit for custom content\n .list-group-item-heading,\n .list-group-item-heading > small,\n .list-group-item-heading > .small {\n color: inherit;\n }\n .list-group-item-text {\n color: @list-group-active-text-color;\n }\n }\n}\n\n\n// Contextual variants\n//\n// Add modifier classes to change text and background color on individual items.\n// Organizationally, this must come after the `:hover` states.\n\n.list-group-item-variant(success; @state-success-bg; @state-success-text);\n.list-group-item-variant(info; @state-info-bg; @state-info-text);\n.list-group-item-variant(warning; @state-warning-bg; @state-warning-text);\n.list-group-item-variant(danger; @state-danger-bg; @state-danger-text);\n\n\n// Custom content options\n//\n// Extra classes for creating well-formatted content within `.list-group-item`s.\n\n.list-group-item-heading {\n margin-top: 0;\n margin-bottom: 5px;\n}\n.list-group-item-text {\n margin-bottom: 0;\n line-height: 1.3;\n}\n","// List Groups\n\n.list-group-item-variant(@state; @background; @color) {\n .list-group-item-@{state} {\n color: @color;\n background-color: @background;\n\n a&,\n button& {\n color: @color;\n\n .list-group-item-heading {\n color: inherit;\n }\n\n &:hover,\n &:focus {\n color: @color;\n background-color: darken(@background, 5%);\n }\n &.active,\n &.active:hover,\n &.active:focus {\n color: #fff;\n background-color: @color;\n border-color: @color;\n }\n }\n }\n}\n","//\n// Panels\n// --------------------------------------------------\n\n\n// Base class\n.panel {\n margin-bottom: @line-height-computed;\n background-color: @panel-bg;\n border: 1px solid transparent;\n border-radius: @panel-border-radius;\n .box-shadow(0 1px 1px rgba(0,0,0,.05));\n}\n\n// Panel contents\n.panel-body {\n padding: @panel-body-padding;\n &:extend(.clearfix all);\n}\n\n// Optional heading\n.panel-heading {\n padding: @panel-heading-padding;\n border-bottom: 1px solid transparent;\n .border-top-radius((@panel-border-radius - 1));\n\n > .dropdown .dropdown-toggle {\n color: inherit;\n }\n}\n\n// Within heading, strip any `h*` tag of its default margins for spacing.\n.panel-title {\n margin-top: 0;\n margin-bottom: 0;\n font-size: ceil((@font-size-base * 1.125));\n color: inherit;\n\n > a,\n > small,\n > .small,\n > small > a,\n > .small > a {\n color: inherit;\n }\n}\n\n// Optional footer (stays gray in every modifier class)\n.panel-footer {\n padding: @panel-footer-padding;\n background-color: @panel-footer-bg;\n border-top: 1px solid @panel-inner-border;\n .border-bottom-radius((@panel-border-radius - 1));\n}\n\n\n// List groups in panels\n//\n// By default, space out list group content from panel headings to account for\n// any kind of custom content between the two.\n\n.panel {\n > .list-group,\n > .panel-collapse > .list-group {\n margin-bottom: 0;\n\n .list-group-item {\n border-width: 1px 0;\n border-radius: 0;\n }\n\n // Add border top radius for first one\n &:first-child {\n .list-group-item:first-child {\n border-top: 0;\n .border-top-radius((@panel-border-radius - 1));\n }\n }\n\n // Add border bottom radius for last one\n &:last-child {\n .list-group-item:last-child {\n border-bottom: 0;\n .border-bottom-radius((@panel-border-radius - 1));\n }\n }\n }\n > .panel-heading + .panel-collapse > .list-group {\n .list-group-item:first-child {\n .border-top-radius(0);\n }\n }\n}\n// Collapse space between when there's no additional content.\n.panel-heading + .list-group {\n .list-group-item:first-child {\n border-top-width: 0;\n }\n}\n.list-group + .panel-footer {\n border-top-width: 0;\n}\n\n// Tables in panels\n//\n// Place a non-bordered `.table` within a panel (not within a `.panel-body`) and\n// watch it go full width.\n\n.panel {\n > .table,\n > .table-responsive > .table,\n > .panel-collapse > .table {\n margin-bottom: 0;\n\n caption {\n padding-left: @panel-body-padding;\n padding-right: @panel-body-padding;\n }\n }\n // Add border top radius for first one\n > .table:first-child,\n > .table-responsive:first-child > .table:first-child {\n .border-top-radius((@panel-border-radius - 1));\n\n > thead:first-child,\n > tbody:first-child {\n > tr:first-child {\n border-top-left-radius: (@panel-border-radius - 1);\n border-top-right-radius: (@panel-border-radius - 1);\n\n td:first-child,\n th:first-child {\n border-top-left-radius: (@panel-border-radius - 1);\n }\n td:last-child,\n th:last-child {\n border-top-right-radius: (@panel-border-radius - 1);\n }\n }\n }\n }\n // Add border bottom radius for last one\n > .table:last-child,\n > .table-responsive:last-child > .table:last-child {\n .border-bottom-radius((@panel-border-radius - 1));\n\n > tbody:last-child,\n > tfoot:last-child {\n > tr:last-child {\n border-bottom-left-radius: (@panel-border-radius - 1);\n border-bottom-right-radius: (@panel-border-radius - 1);\n\n td:first-child,\n th:first-child {\n border-bottom-left-radius: (@panel-border-radius - 1);\n }\n td:last-child,\n th:last-child {\n border-bottom-right-radius: (@panel-border-radius - 1);\n }\n }\n }\n }\n > .panel-body + .table,\n > .panel-body + .table-responsive,\n > .table + .panel-body,\n > .table-responsive + .panel-body {\n border-top: 1px solid @table-border-color;\n }\n > .table > tbody:first-child > tr:first-child th,\n > .table > tbody:first-child > tr:first-child td {\n border-top: 0;\n }\n > .table-bordered,\n > .table-responsive > .table-bordered {\n border: 0;\n > thead,\n > tbody,\n > tfoot {\n > tr {\n > th:first-child,\n > td:first-child {\n border-left: 0;\n }\n > th:last-child,\n > td:last-child {\n border-right: 0;\n }\n }\n }\n > thead,\n > tbody {\n > tr:first-child {\n > td,\n > th {\n border-bottom: 0;\n }\n }\n }\n > tbody,\n > tfoot {\n > tr:last-child {\n > td,\n > th {\n border-bottom: 0;\n }\n }\n }\n }\n > .table-responsive {\n border: 0;\n margin-bottom: 0;\n }\n}\n\n\n// Collapsable panels (aka, accordion)\n//\n// Wrap a series of panels in `.panel-group` to turn them into an accordion with\n// the help of our collapse JavaScript plugin.\n\n.panel-group {\n margin-bottom: @line-height-computed;\n\n // Tighten up margin so it's only between panels\n .panel {\n margin-bottom: 0;\n border-radius: @panel-border-radius;\n\n + .panel {\n margin-top: 5px;\n }\n }\n\n .panel-heading {\n border-bottom: 0;\n\n + .panel-collapse > .panel-body,\n + .panel-collapse > .list-group {\n border-top: 1px solid @panel-inner-border;\n }\n }\n\n .panel-footer {\n border-top: 0;\n + .panel-collapse .panel-body {\n border-bottom: 1px solid @panel-inner-border;\n }\n }\n}\n\n\n// Contextual variations\n.panel-default {\n .panel-variant(@panel-default-border; @panel-default-text; @panel-default-heading-bg; @panel-default-border);\n}\n.panel-primary {\n .panel-variant(@panel-primary-border; @panel-primary-text; @panel-primary-heading-bg; @panel-primary-border);\n}\n.panel-success {\n .panel-variant(@panel-success-border; @panel-success-text; @panel-success-heading-bg; @panel-success-border);\n}\n.panel-info {\n .panel-variant(@panel-info-border; @panel-info-text; @panel-info-heading-bg; @panel-info-border);\n}\n.panel-warning {\n .panel-variant(@panel-warning-border; @panel-warning-text; @panel-warning-heading-bg; @panel-warning-border);\n}\n.panel-danger {\n .panel-variant(@panel-danger-border; @panel-danger-text; @panel-danger-heading-bg; @panel-danger-border);\n}\n","// Panels\n\n.panel-variant(@border; @heading-text-color; @heading-bg-color; @heading-border) {\n border-color: @border;\n\n & > .panel-heading {\n color: @heading-text-color;\n background-color: @heading-bg-color;\n border-color: @heading-border;\n\n + .panel-collapse > .panel-body {\n border-top-color: @border;\n }\n .badge {\n color: @heading-bg-color;\n background-color: @heading-text-color;\n }\n }\n & > .panel-footer {\n + .panel-collapse > .panel-body {\n border-bottom-color: @border;\n }\n }\n}\n","// Embeds responsive\n//\n// Credit: Nicolas Gallagher and SUIT CSS.\n\n.embed-responsive {\n position: relative;\n display: block;\n height: 0;\n padding: 0;\n overflow: hidden;\n\n .embed-responsive-item,\n iframe,\n embed,\n object,\n video {\n position: absolute;\n top: 0;\n left: 0;\n bottom: 0;\n height: 100%;\n width: 100%;\n border: 0;\n }\n}\n\n// Modifier class for 16:9 aspect ratio\n.embed-responsive-16by9 {\n padding-bottom: 56.25%;\n}\n\n// Modifier class for 4:3 aspect ratio\n.embed-responsive-4by3 {\n padding-bottom: 75%;\n}\n","//\n// Wells\n// --------------------------------------------------\n\n\n// Base class\n.well {\n min-height: 20px;\n padding: 19px;\n margin-bottom: 20px;\n background-color: @well-bg;\n border: 1px solid @well-border;\n border-radius: @border-radius-base;\n .box-shadow(inset 0 1px 1px rgba(0,0,0,.05));\n blockquote {\n border-color: #ddd;\n border-color: rgba(0,0,0,.15);\n }\n}\n\n// Sizes\n.well-lg {\n padding: 24px;\n border-radius: @border-radius-large;\n}\n.well-sm {\n padding: 9px;\n border-radius: @border-radius-small;\n}\n","//\n// Close icons\n// --------------------------------------------------\n\n\n.close {\n float: right;\n font-size: (@font-size-base * 1.5);\n font-weight: @close-font-weight;\n line-height: 1;\n color: @close-color;\n text-shadow: @close-text-shadow;\n .opacity(.2);\n\n &:hover,\n &:focus {\n color: @close-color;\n text-decoration: none;\n cursor: pointer;\n .opacity(.5);\n }\n\n // Additional properties for button version\n // iOS requires the button element instead of an anchor tag.\n // If you want the anchor version, it requires `href=\"#\"`.\n // See https://developer.mozilla.org/en-US/docs/Web/Events/click#Safari_Mobile\n button& {\n padding: 0;\n cursor: pointer;\n background: transparent;\n border: 0;\n -webkit-appearance: none;\n }\n}\n","//\n// Modals\n// --------------------------------------------------\n\n// .modal-open - body class for killing the scroll\n// .modal - container to scroll within\n// .modal-dialog - positioning shell for the actual modal\n// .modal-content - actual modal w/ bg and corners and shit\n\n// Kill the scroll on the body\n.modal-open {\n overflow: hidden;\n}\n\n// Container that the modal scrolls within\n.modal {\n display: none;\n overflow: hidden;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: @zindex-modal;\n -webkit-overflow-scrolling: touch;\n\n // Prevent Chrome on Windows from adding a focus outline. For details, see\n // https://github.com/twbs/bootstrap/pull/10951.\n outline: 0;\n\n // When fading in the modal, animate it to slide down\n &.fade .modal-dialog {\n .translate(0, -25%);\n .transition-transform(~\"0.3s ease-out\");\n }\n &.in .modal-dialog { .translate(0, 0) }\n}\n.modal-open .modal {\n overflow-x: hidden;\n overflow-y: auto;\n}\n\n// Shell div to position the modal with bottom padding\n.modal-dialog {\n position: relative;\n width: auto;\n margin: 10px;\n}\n\n// Actual modal\n.modal-content {\n position: relative;\n background-color: @modal-content-bg;\n border: 1px solid @modal-content-fallback-border-color; //old browsers fallback (ie8 etc)\n border: 1px solid @modal-content-border-color;\n border-radius: @border-radius-large;\n .box-shadow(0 3px 9px rgba(0,0,0,.5));\n background-clip: padding-box;\n // Remove focus outline from opened modal\n outline: 0;\n}\n\n// Modal background\n.modal-backdrop {\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: @zindex-modal-background;\n background-color: @modal-backdrop-bg;\n // Fade for backdrop\n &.fade { .opacity(0); }\n &.in { .opacity(@modal-backdrop-opacity); }\n}\n\n// Modal header\n// Top section of the modal w/ title and dismiss\n.modal-header {\n padding: @modal-title-padding;\n border-bottom: 1px solid @modal-header-border-color;\n &:extend(.clearfix all);\n}\n// Close icon\n.modal-header .close {\n margin-top: -2px;\n}\n\n// Title text within header\n.modal-title {\n margin: 0;\n line-height: @modal-title-line-height;\n}\n\n// Modal body\n// Where all modal content resides (sibling of .modal-header and .modal-footer)\n.modal-body {\n position: relative;\n padding: @modal-inner-padding;\n}\n\n// Footer (for actions)\n.modal-footer {\n padding: @modal-inner-padding;\n text-align: right; // right align buttons\n border-top: 1px solid @modal-footer-border-color;\n &:extend(.clearfix all); // clear it in case folks use .pull-* classes on buttons\n\n // Properly space out buttons\n .btn + .btn {\n margin-left: 5px;\n margin-bottom: 0; // account for input[type=\"submit\"] which gets the bottom margin like all other inputs\n }\n // but override that for button groups\n .btn-group .btn + .btn {\n margin-left: -1px;\n }\n // and override it for block buttons as well\n .btn-block + .btn-block {\n margin-left: 0;\n }\n}\n\n// Measure scrollbar width for padding body during modal show/hide\n.modal-scrollbar-measure {\n position: absolute;\n top: -9999px;\n width: 50px;\n height: 50px;\n overflow: scroll;\n}\n\n// Scale up the modal\n@media (min-width: @screen-sm-min) {\n // Automatically set modal's width for larger viewports\n .modal-dialog {\n width: @modal-md;\n margin: 30px auto;\n }\n .modal-content {\n .box-shadow(0 5px 15px rgba(0,0,0,.5));\n }\n\n // Modal sizes\n .modal-sm { width: @modal-sm; }\n}\n\n@media (min-width: @screen-md-min) {\n .modal-lg { width: @modal-lg; }\n}\n","//\n// Tooltips\n// --------------------------------------------------\n\n\n// Base class\n.tooltip {\n position: absolute;\n z-index: @zindex-tooltip;\n display: block;\n // Our parent element can be arbitrary since tooltips are by default inserted as a sibling of their target element.\n // So reset our font and text properties to avoid inheriting weird values.\n .reset-text();\n font-size: @font-size-small;\n\n .opacity(0);\n\n &.in { .opacity(@tooltip-opacity); }\n &.top { margin-top: -3px; padding: @tooltip-arrow-width 0; }\n &.right { margin-left: 3px; padding: 0 @tooltip-arrow-width; }\n &.bottom { margin-top: 3px; padding: @tooltip-arrow-width 0; }\n &.left { margin-left: -3px; padding: 0 @tooltip-arrow-width; }\n}\n\n// Wrapper for the tooltip content\n.tooltip-inner {\n max-width: @tooltip-max-width;\n padding: 3px 8px;\n color: @tooltip-color;\n text-align: center;\n background-color: @tooltip-bg;\n border-radius: @border-radius-base;\n}\n\n// Arrows\n.tooltip-arrow {\n position: absolute;\n width: 0;\n height: 0;\n border-color: transparent;\n border-style: solid;\n}\n// Note: Deprecated .top-left, .top-right, .bottom-left, and .bottom-right as of v3.3.1\n.tooltip {\n &.top .tooltip-arrow {\n bottom: 0;\n left: 50%;\n margin-left: -@tooltip-arrow-width;\n border-width: @tooltip-arrow-width @tooltip-arrow-width 0;\n border-top-color: @tooltip-arrow-color;\n }\n &.top-left .tooltip-arrow {\n bottom: 0;\n right: @tooltip-arrow-width;\n margin-bottom: -@tooltip-arrow-width;\n border-width: @tooltip-arrow-width @tooltip-arrow-width 0;\n border-top-color: @tooltip-arrow-color;\n }\n &.top-right .tooltip-arrow {\n bottom: 0;\n left: @tooltip-arrow-width;\n margin-bottom: -@tooltip-arrow-width;\n border-width: @tooltip-arrow-width @tooltip-arrow-width 0;\n border-top-color: @tooltip-arrow-color;\n }\n &.right .tooltip-arrow {\n top: 50%;\n left: 0;\n margin-top: -@tooltip-arrow-width;\n border-width: @tooltip-arrow-width @tooltip-arrow-width @tooltip-arrow-width 0;\n border-right-color: @tooltip-arrow-color;\n }\n &.left .tooltip-arrow {\n top: 50%;\n right: 0;\n margin-top: -@tooltip-arrow-width;\n border-width: @tooltip-arrow-width 0 @tooltip-arrow-width @tooltip-arrow-width;\n border-left-color: @tooltip-arrow-color;\n }\n &.bottom .tooltip-arrow {\n top: 0;\n left: 50%;\n margin-left: -@tooltip-arrow-width;\n border-width: 0 @tooltip-arrow-width @tooltip-arrow-width;\n border-bottom-color: @tooltip-arrow-color;\n }\n &.bottom-left .tooltip-arrow {\n top: 0;\n right: @tooltip-arrow-width;\n margin-top: -@tooltip-arrow-width;\n border-width: 0 @tooltip-arrow-width @tooltip-arrow-width;\n border-bottom-color: @tooltip-arrow-color;\n }\n &.bottom-right .tooltip-arrow {\n top: 0;\n left: @tooltip-arrow-width;\n margin-top: -@tooltip-arrow-width;\n border-width: 0 @tooltip-arrow-width @tooltip-arrow-width;\n border-bottom-color: @tooltip-arrow-color;\n }\n}\n",".reset-text() {\n font-family: @font-family-base;\n // We deliberately do NOT reset font-size.\n font-style: normal;\n font-weight: normal;\n letter-spacing: normal;\n line-break: auto;\n line-height: @line-height-base;\n text-align: left; // Fallback for where `start` is not supported\n text-align: start;\n text-decoration: none;\n text-shadow: none;\n text-transform: none;\n white-space: normal;\n word-break: normal;\n word-spacing: normal;\n word-wrap: normal;\n}\n","//\n// Popovers\n// --------------------------------------------------\n\n\n.popover {\n position: absolute;\n top: 0;\n left: 0;\n z-index: @zindex-popover;\n display: none;\n max-width: @popover-max-width;\n padding: 1px;\n // Our parent element can be arbitrary since popovers are by default inserted as a sibling of their target element.\n // So reset our font and text properties to avoid inheriting weird values.\n .reset-text();\n font-size: @font-size-base;\n\n background-color: @popover-bg;\n background-clip: padding-box;\n border: 1px solid @popover-fallback-border-color;\n border: 1px solid @popover-border-color;\n border-radius: @border-radius-large;\n .box-shadow(0 5px 10px rgba(0,0,0,.2));\n\n // Offset the popover to account for the popover arrow\n &.top { margin-top: -@popover-arrow-width; }\n &.right { margin-left: @popover-arrow-width; }\n &.bottom { margin-top: @popover-arrow-width; }\n &.left { margin-left: -@popover-arrow-width; }\n}\n\n.popover-title {\n margin: 0; // reset heading margin\n padding: 8px 14px;\n font-size: @font-size-base;\n background-color: @popover-title-bg;\n border-bottom: 1px solid darken(@popover-title-bg, 5%);\n border-radius: (@border-radius-large - 1) (@border-radius-large - 1) 0 0;\n}\n\n.popover-content {\n padding: 9px 14px;\n}\n\n// Arrows\n//\n// .arrow is outer, .arrow:after is inner\n\n.popover > .arrow {\n &,\n &:after {\n position: absolute;\n display: block;\n width: 0;\n height: 0;\n border-color: transparent;\n border-style: solid;\n }\n}\n.popover > .arrow {\n border-width: @popover-arrow-outer-width;\n}\n.popover > .arrow:after {\n border-width: @popover-arrow-width;\n content: \"\";\n}\n\n.popover {\n &.top > .arrow {\n left: 50%;\n margin-left: -@popover-arrow-outer-width;\n border-bottom-width: 0;\n border-top-color: @popover-arrow-outer-fallback-color; // IE8 fallback\n border-top-color: @popover-arrow-outer-color;\n bottom: -@popover-arrow-outer-width;\n &:after {\n content: \" \";\n bottom: 1px;\n margin-left: -@popover-arrow-width;\n border-bottom-width: 0;\n border-top-color: @popover-arrow-color;\n }\n }\n &.right > .arrow {\n top: 50%;\n left: -@popover-arrow-outer-width;\n margin-top: -@popover-arrow-outer-width;\n border-left-width: 0;\n border-right-color: @popover-arrow-outer-fallback-color; // IE8 fallback\n border-right-color: @popover-arrow-outer-color;\n &:after {\n content: \" \";\n left: 1px;\n bottom: -@popover-arrow-width;\n border-left-width: 0;\n border-right-color: @popover-arrow-color;\n }\n }\n &.bottom > .arrow {\n left: 50%;\n margin-left: -@popover-arrow-outer-width;\n border-top-width: 0;\n border-bottom-color: @popover-arrow-outer-fallback-color; // IE8 fallback\n border-bottom-color: @popover-arrow-outer-color;\n top: -@popover-arrow-outer-width;\n &:after {\n content: \" \";\n top: 1px;\n margin-left: -@popover-arrow-width;\n border-top-width: 0;\n border-bottom-color: @popover-arrow-color;\n }\n }\n\n &.left > .arrow {\n top: 50%;\n right: -@popover-arrow-outer-width;\n margin-top: -@popover-arrow-outer-width;\n border-right-width: 0;\n border-left-color: @popover-arrow-outer-fallback-color; // IE8 fallback\n border-left-color: @popover-arrow-outer-color;\n &:after {\n content: \" \";\n right: 1px;\n border-right-width: 0;\n border-left-color: @popover-arrow-color;\n bottom: -@popover-arrow-width;\n }\n }\n}\n","//\n// Carousel\n// --------------------------------------------------\n\n\n// Wrapper for the slide container and indicators\n.carousel {\n position: relative;\n}\n\n.carousel-inner {\n position: relative;\n overflow: hidden;\n width: 100%;\n\n > .item {\n display: none;\n position: relative;\n .transition(.6s ease-in-out left);\n\n // Account for jankitude on images\n > img,\n > a > img {\n &:extend(.img-responsive);\n line-height: 1;\n }\n\n // WebKit CSS3 transforms for supported devices\n @media all and (transform-3d), (-webkit-transform-3d) {\n .transition-transform(~'0.6s ease-in-out');\n .backface-visibility(~'hidden');\n .perspective(1000px);\n\n &.next,\n &.active.right {\n .translate3d(100%, 0, 0);\n left: 0;\n }\n &.prev,\n &.active.left {\n .translate3d(-100%, 0, 0);\n left: 0;\n }\n &.next.left,\n &.prev.right,\n &.active {\n .translate3d(0, 0, 0);\n left: 0;\n }\n }\n }\n\n > .active,\n > .next,\n > .prev {\n display: block;\n }\n\n > .active {\n left: 0;\n }\n\n > .next,\n > .prev {\n position: absolute;\n top: 0;\n width: 100%;\n }\n\n > .next {\n left: 100%;\n }\n > .prev {\n left: -100%;\n }\n > .next.left,\n > .prev.right {\n left: 0;\n }\n\n > .active.left {\n left: -100%;\n }\n > .active.right {\n left: 100%;\n }\n\n}\n\n// Left/right controls for nav\n// ---------------------------\n\n.carousel-control {\n position: absolute;\n top: 0;\n left: 0;\n bottom: 0;\n width: @carousel-control-width;\n .opacity(@carousel-control-opacity);\n font-size: @carousel-control-font-size;\n color: @carousel-control-color;\n text-align: center;\n text-shadow: @carousel-text-shadow;\n background-color: rgba(0, 0, 0, 0); // Fix IE9 click-thru bug\n // We can't have this transition here because WebKit cancels the carousel\n // animation if you trip this while in the middle of another animation.\n\n // Set gradients for backgrounds\n &.left {\n #gradient > .horizontal(@start-color: rgba(0,0,0,.5); @end-color: rgba(0,0,0,.0001));\n }\n &.right {\n left: auto;\n right: 0;\n #gradient > .horizontal(@start-color: rgba(0,0,0,.0001); @end-color: rgba(0,0,0,.5));\n }\n\n // Hover/focus state\n &:hover,\n &:focus {\n outline: 0;\n color: @carousel-control-color;\n text-decoration: none;\n .opacity(.9);\n }\n\n // Toggles\n .icon-prev,\n .icon-next,\n .glyphicon-chevron-left,\n .glyphicon-chevron-right {\n position: absolute;\n top: 50%;\n margin-top: -10px;\n z-index: 5;\n display: inline-block;\n }\n .icon-prev,\n .glyphicon-chevron-left {\n left: 50%;\n margin-left: -10px;\n }\n .icon-next,\n .glyphicon-chevron-right {\n right: 50%;\n margin-right: -10px;\n }\n .icon-prev,\n .icon-next {\n width: 20px;\n height: 20px;\n line-height: 1;\n font-family: serif;\n }\n\n\n .icon-prev {\n &:before {\n content: '\\2039';// SINGLE LEFT-POINTING ANGLE QUOTATION MARK (U+2039)\n }\n }\n .icon-next {\n &:before {\n content: '\\203a';// SINGLE RIGHT-POINTING ANGLE QUOTATION MARK (U+203A)\n }\n }\n}\n\n// Optional indicator pips\n//\n// Add an unordered list with the following class and add a list item for each\n// slide your carousel holds.\n\n.carousel-indicators {\n position: absolute;\n bottom: 10px;\n left: 50%;\n z-index: 15;\n width: 60%;\n margin-left: -30%;\n padding-left: 0;\n list-style: none;\n text-align: center;\n\n li {\n display: inline-block;\n width: 10px;\n height: 10px;\n margin: 1px;\n text-indent: -999px;\n border: 1px solid @carousel-indicator-border-color;\n border-radius: 10px;\n cursor: pointer;\n\n // IE8-9 hack for event handling\n //\n // Internet Explorer 8-9 does not support clicks on elements without a set\n // `background-color`. We cannot use `filter` since that's not viewed as a\n // background color by the browser. Thus, a hack is needed.\n // See https://developer.mozilla.org/en-US/docs/Web/Events/click#Internet_Explorer\n //\n // For IE8, we set solid black as it doesn't support `rgba()`. For IE9, we\n // set alpha transparency for the best results possible.\n background-color: #000 \\9; // IE8\n background-color: rgba(0,0,0,0); // IE9\n }\n .active {\n margin: 0;\n width: 12px;\n height: 12px;\n background-color: @carousel-indicator-active-bg;\n }\n}\n\n// Optional captions\n// -----------------------------\n// Hidden by default for smaller viewports\n.carousel-caption {\n position: absolute;\n left: 15%;\n right: 15%;\n bottom: 20px;\n z-index: 10;\n padding-top: 20px;\n padding-bottom: 20px;\n color: @carousel-caption-color;\n text-align: center;\n text-shadow: @carousel-text-shadow;\n & .btn {\n text-shadow: none; // No shadow for button elements in carousel-caption\n }\n}\n\n\n// Scale up controls for tablets and up\n@media screen and (min-width: @screen-sm-min) {\n\n // Scale up the controls a smidge\n .carousel-control {\n .glyphicon-chevron-left,\n .glyphicon-chevron-right,\n .icon-prev,\n .icon-next {\n width: (@carousel-control-font-size * 1.5);\n height: (@carousel-control-font-size * 1.5);\n margin-top: (@carousel-control-font-size / -2);\n font-size: (@carousel-control-font-size * 1.5);\n }\n .glyphicon-chevron-left,\n .icon-prev {\n margin-left: (@carousel-control-font-size / -2);\n }\n .glyphicon-chevron-right,\n .icon-next {\n margin-right: (@carousel-control-font-size / -2);\n }\n }\n\n // Show and left align the captions\n .carousel-caption {\n left: 20%;\n right: 20%;\n padding-bottom: 30px;\n }\n\n // Move up the indicators\n .carousel-indicators {\n bottom: 20px;\n }\n}\n","// Clearfix\n//\n// For modern browsers\n// 1. The space content is one way to avoid an Opera bug when the\n// contenteditable attribute is included anywhere else in the document.\n// Otherwise it causes space to appear at the top and bottom of elements\n// that are clearfixed.\n// 2. The use of `table` rather than `block` is only necessary if using\n// `:before` to contain the top-margins of child elements.\n//\n// Source: http://nicolasgallagher.com/micro-clearfix-hack/\n\n.clearfix() {\n &:before,\n &:after {\n content: \" \"; // 1\n display: table; // 2\n }\n &:after {\n clear: both;\n }\n}\n","// Center-align a block level element\n\n.center-block() {\n display: block;\n margin-left: auto;\n margin-right: auto;\n}\n","// CSS image replacement\n//\n// Heads up! v3 launched with only `.hide-text()`, but per our pattern for\n// mixins being reused as classes with the same name, this doesn't hold up. As\n// of v3.0.1 we have added `.text-hide()` and deprecated `.hide-text()`.\n//\n// Source: https://github.com/h5bp/html5-boilerplate/commit/aa0396eae757\n\n// Deprecated as of v3.0.1 (has been removed in v4)\n.hide-text() {\n font: ~\"0/0\" a;\n color: transparent;\n text-shadow: none;\n background-color: transparent;\n border: 0;\n}\n\n// New mixin to use as of v3.0.1\n.text-hide() {\n .hide-text();\n}\n","//\n// Responsive: Utility classes\n// --------------------------------------------------\n\n\n// IE10 in Windows (Phone) 8\n//\n// Support for responsive views via media queries is kind of borked in IE10, for\n// Surface/desktop in split view and for Windows Phone 8. This particular fix\n// must be accompanied by a snippet of JavaScript to sniff the user agent and\n// apply some conditional CSS to *only* the Surface/desktop Windows 8. Look at\n// our Getting Started page for more information on this bug.\n//\n// For more information, see the following:\n//\n// Issue: https://github.com/twbs/bootstrap/issues/10497\n// Docs: http://getbootstrap.com/getting-started/#support-ie10-width\n// Source: http://timkadlec.com/2013/01/windows-phone-8-and-device-width/\n// Source: http://timkadlec.com/2012/10/ie10-snap-mode-and-responsive-design/\n\n@-ms-viewport {\n width: device-width;\n}\n\n\n// Visibility utilities\n// Note: Deprecated .visible-xs, .visible-sm, .visible-md, and .visible-lg as of v3.2.0\n.visible-xs,\n.visible-sm,\n.visible-md,\n.visible-lg {\n .responsive-invisibility();\n}\n\n.visible-xs-block,\n.visible-xs-inline,\n.visible-xs-inline-block,\n.visible-sm-block,\n.visible-sm-inline,\n.visible-sm-inline-block,\n.visible-md-block,\n.visible-md-inline,\n.visible-md-inline-block,\n.visible-lg-block,\n.visible-lg-inline,\n.visible-lg-inline-block {\n display: none !important;\n}\n\n.visible-xs {\n @media (max-width: @screen-xs-max) {\n .responsive-visibility();\n }\n}\n.visible-xs-block {\n @media (max-width: @screen-xs-max) {\n display: block !important;\n }\n}\n.visible-xs-inline {\n @media (max-width: @screen-xs-max) {\n display: inline !important;\n }\n}\n.visible-xs-inline-block {\n @media (max-width: @screen-xs-max) {\n display: inline-block !important;\n }\n}\n\n.visible-sm {\n @media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) {\n .responsive-visibility();\n }\n}\n.visible-sm-block {\n @media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) {\n display: block !important;\n }\n}\n.visible-sm-inline {\n @media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) {\n display: inline !important;\n }\n}\n.visible-sm-inline-block {\n @media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) {\n display: inline-block !important;\n }\n}\n\n.visible-md {\n @media (min-width: @screen-md-min) and (max-width: @screen-md-max) {\n .responsive-visibility();\n }\n}\n.visible-md-block {\n @media (min-width: @screen-md-min) and (max-width: @screen-md-max) {\n display: block !important;\n }\n}\n.visible-md-inline {\n @media (min-width: @screen-md-min) and (max-width: @screen-md-max) {\n display: inline !important;\n }\n}\n.visible-md-inline-block {\n @media (min-width: @screen-md-min) and (max-width: @screen-md-max) {\n display: inline-block !important;\n }\n}\n\n.visible-lg {\n @media (min-width: @screen-lg-min) {\n .responsive-visibility();\n }\n}\n.visible-lg-block {\n @media (min-width: @screen-lg-min) {\n display: block !important;\n }\n}\n.visible-lg-inline {\n @media (min-width: @screen-lg-min) {\n display: inline !important;\n }\n}\n.visible-lg-inline-block {\n @media (min-width: @screen-lg-min) {\n display: inline-block !important;\n }\n}\n\n.hidden-xs {\n @media (max-width: @screen-xs-max) {\n .responsive-invisibility();\n }\n}\n.hidden-sm {\n @media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) {\n .responsive-invisibility();\n }\n}\n.hidden-md {\n @media (min-width: @screen-md-min) and (max-width: @screen-md-max) {\n .responsive-invisibility();\n }\n}\n.hidden-lg {\n @media (min-width: @screen-lg-min) {\n .responsive-invisibility();\n }\n}\n\n\n// Print utilities\n//\n// Media queries are placed on the inside to be mixin-friendly.\n\n// Note: Deprecated .visible-print as of v3.2.0\n.visible-print {\n .responsive-invisibility();\n\n @media print {\n .responsive-visibility();\n }\n}\n.visible-print-block {\n display: none !important;\n\n @media print {\n display: block !important;\n }\n}\n.visible-print-inline {\n display: none !important;\n\n @media print {\n display: inline !important;\n }\n}\n.visible-print-inline-block {\n display: none !important;\n\n @media print {\n display: inline-block !important;\n }\n}\n\n.hidden-print {\n @media print {\n .responsive-invisibility();\n }\n}\n","// Responsive utilities\n\n//\n// More easily include all the states for responsive-utilities.less.\n.responsive-visibility() {\n display: block !important;\n table& { display: table !important; }\n tr& { display: table-row !important; }\n th&,\n td& { display: table-cell !important; }\n}\n\n.responsive-invisibility() {\n display: none !important;\n}\n"]}
\ No newline at end of file diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/css/bootstrap.min.css b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/css/bootstrap.min.css index 2b927f84e..4cf729e43 100755 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/css/bootstrap.min.css +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/css/bootstrap.min.css @@ -1,9 +1,6 @@ /*! - * Bootstrap v2.3.0 - * - * Copyright 2012 Twitter, Inc - * Licensed under the Apache License v2.0 - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Designed and built with all the love in the world @twitter by @mdo and @fat. - */.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;line-height:0;content:""}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}audio:not([controls]){display:none}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}a:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}a:hover,a:active{outline:0}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{width:auto\9;height:auto;max-width:100%;vertical-align:middle;border:0;-ms-interpolation-mode:bicubic}#map_canvas img,.google-maps img{max-width:none}button,input,select,textarea{margin:0;font-size:100%;vertical-align:middle}button,input{*overflow:visible;line-height:normal}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}button,html input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button}label,select,button,input[type="button"],input[type="reset"],input[type="submit"],input[type="radio"],input[type="checkbox"]{cursor:pointer}input[type="search"]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type="search"]::-webkit-search-decoration,input[type="search"]::-webkit-search-cancel-button{-webkit-appearance:none}textarea{overflow:auto;vertical-align:top}@media print{*{color:#000!important;text-shadow:none!important;background:transparent!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100%!important}@page{margin:.5cm}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}}body{margin:0;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:20px;color:#333;background-color:#fff}a{color:#08c;text-decoration:none}a:hover,a:focus{color:#005580;text-decoration:underline}.img-rounded{-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.img-polaroid{padding:4px;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);-webkit-box-shadow:0 1px 3px rgba(0,0,0,0.1);-moz-box-shadow:0 1px 3px rgba(0,0,0,0.1);box-shadow:0 1px 3px rgba(0,0,0,0.1)}.img-circle{-webkit-border-radius:500px;-moz-border-radius:500px;border-radius:500px}.row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:20px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px}.span12{width:940px}.span11{width:860px}.span10{width:780px}.span9{width:700px}.span8{width:620px}.span7{width:540px}.span6{width:460px}.span5{width:380px}.span4{width:300px}.span3{width:220px}.span2{width:140px}.span1{width:60px}.offset12{margin-left:980px}.offset11{margin-left:900px}.offset10{margin-left:820px}.offset9{margin-left:740px}.offset8{margin-left:660px}.offset7{margin-left:580px}.offset6{margin-left:500px}.offset5{margin-left:420px}.offset4{margin-left:340px}.offset3{margin-left:260px}.offset2{margin-left:180px}.offset1{margin-left:100px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.127659574468085%;*margin-left:2.074468085106383%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.127659574468085%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.48936170212765%;*width:91.43617021276594%}.row-fluid .span10{width:82.97872340425532%;*width:82.92553191489361%}.row-fluid .span9{width:74.46808510638297%;*width:74.41489361702126%}.row-fluid .span8{width:65.95744680851064%;*width:65.90425531914893%}.row-fluid .span7{width:57.44680851063829%;*width:57.39361702127659%}.row-fluid .span6{width:48.93617021276595%;*width:48.88297872340425%}.row-fluid .span5{width:40.42553191489362%;*width:40.37234042553192%}.row-fluid .span4{width:31.914893617021278%;*width:31.861702127659576%}.row-fluid .span3{width:23.404255319148934%;*width:23.351063829787233%}.row-fluid .span2{width:14.893617021276595%;*width:14.840425531914894%}.row-fluid .span1{width:6.382978723404255%;*width:6.329787234042553%}.row-fluid .offset12{margin-left:104.25531914893617%;*margin-left:104.14893617021275%}.row-fluid .offset12:first-child{margin-left:102.12765957446808%;*margin-left:102.02127659574467%}.row-fluid .offset11{margin-left:95.74468085106382%;*margin-left:95.6382978723404%}.row-fluid .offset11:first-child{margin-left:93.61702127659574%;*margin-left:93.51063829787232%}.row-fluid .offset10{margin-left:87.23404255319149%;*margin-left:87.12765957446807%}.row-fluid .offset10:first-child{margin-left:85.1063829787234%;*margin-left:84.99999999999999%}.row-fluid .offset9{margin-left:78.72340425531914%;*margin-left:78.61702127659572%}.row-fluid .offset9:first-child{margin-left:76.59574468085106%;*margin-left:76.48936170212764%}.row-fluid .offset8{margin-left:70.2127659574468%;*margin-left:70.10638297872339%}.row-fluid .offset8:first-child{margin-left:68.08510638297872%;*margin-left:67.9787234042553%}.row-fluid .offset7{margin-left:61.70212765957446%;*margin-left:61.59574468085106%}.row-fluid .offset7:first-child{margin-left:59.574468085106375%;*margin-left:59.46808510638297%}.row-fluid .offset6{margin-left:53.191489361702125%;*margin-left:53.085106382978715%}.row-fluid .offset6:first-child{margin-left:51.063829787234035%;*margin-left:50.95744680851063%}.row-fluid .offset5{margin-left:44.68085106382979%;*margin-left:44.57446808510638%}.row-fluid .offset5:first-child{margin-left:42.5531914893617%;*margin-left:42.4468085106383%}.row-fluid .offset4{margin-left:36.170212765957444%;*margin-left:36.06382978723405%}.row-fluid .offset4:first-child{margin-left:34.04255319148936%;*margin-left:33.93617021276596%}.row-fluid .offset3{margin-left:27.659574468085104%;*margin-left:27.5531914893617%}.row-fluid .offset3:first-child{margin-left:25.53191489361702%;*margin-left:25.425531914893618%}.row-fluid .offset2{margin-left:19.148936170212764%;*margin-left:19.04255319148936%}.row-fluid .offset2:first-child{margin-left:17.02127659574468%;*margin-left:16.914893617021278%}.row-fluid .offset1{margin-left:10.638297872340425%;*margin-left:10.53191489361702%}.row-fluid .offset1:first-child{margin-left:8.51063829787234%;*margin-left:8.404255319148938%}[class*="span"].hide,.row-fluid [class*="span"].hide{display:none}[class*="span"].pull-right,.row-fluid [class*="span"].pull-right{float:right}.container{margin-right:auto;margin-left:auto;*zoom:1}.container:before,.container:after{display:table;line-height:0;content:""}.container:after{clear:both}.container-fluid{padding-right:20px;padding-left:20px;*zoom:1}.container-fluid:before,.container-fluid:after{display:table;line-height:0;content:""}.container-fluid:after{clear:both}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:21px;font-weight:200;line-height:30px}small{font-size:85%}strong{font-weight:bold}em{font-style:italic}cite{font-style:normal}.muted{color:#999}a.muted:hover,a.muted:focus{color:#808080}.text-warning{color:#c09853}a.text-warning:hover,a.text-warning:focus{color:#a47e3c}.text-error{color:#b94a48}a.text-error:hover,a.text-error:focus{color:#953b39}.text-info{color:#3a87ad}a.text-info:hover,a.text-info:focus{color:#2d6987}.text-success{color:#468847}a.text-success:hover,a.text-success:focus{color:#356635}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}h1,h2,h3,h4,h5,h6{margin:10px 0;font-family:inherit;font-weight:bold;line-height:20px;color:inherit;text-rendering:optimizelegibility}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small{font-weight:normal;line-height:1;color:#999}h1,h2,h3{line-height:40px}h1{font-size:38.5px}h2{font-size:31.5px}h3{font-size:24.5px}h4{font-size:17.5px}h5{font-size:14px}h6{font-size:11.9px}h1 small{font-size:24.5px}h2 small{font-size:17.5px}h3 small{font-size:14px}h4 small{font-size:14px}.page-header{padding-bottom:9px;margin:20px 0 30px;border-bottom:1px solid #eee}ul,ol{padding:0;margin:0 0 10px 25px}ul ul,ul ol,ol ol,ol ul{margin-bottom:0}li{line-height:20px}ul.unstyled,ol.unstyled{margin-left:0;list-style:none}ul.inline,ol.inline{margin-left:0;list-style:none}ul.inline>li,ol.inline>li{display:inline-block;*display:inline;padding-right:5px;padding-left:5px;*zoom:1}dl{margin-bottom:20px}dt,dd{line-height:20px}dt{font-weight:bold}dd{margin-left:10px}.dl-horizontal{*zoom:1}.dl-horizontal:before,.dl-horizontal:after{display:table;line-height:0;content:""}.dl-horizontal:after{clear:both}.dl-horizontal dt{float:left;width:200px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:220px}hr{margin:20px 0;border:0;border-top:1px solid #eee;border-bottom:1px solid #fff}abbr[title],abbr[data-original-title]{cursor:help;border-bottom:1px dotted #999}abbr.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:0 0 0 15px;margin:0 0 20px;border-left:5px solid #eee}blockquote p{margin-bottom:0;font-size:17.5px;font-weight:300;line-height:1.25}blockquote small{display:block;line-height:20px;color:#999}blockquote small:before{content:'\2014 \00A0'}blockquote.pull-right{float:right;padding-right:15px;padding-left:0;border-right:5px solid #eee;border-left:0}blockquote.pull-right p,blockquote.pull-right small{text-align:right}blockquote.pull-right small:before{content:''}blockquote.pull-right small:after{content:'\00A0 \2014'}q:before,q:after,blockquote:before,blockquote:after{content:""}address{display:block;margin-bottom:20px;font-style:normal;line-height:20px}code,pre{padding:0 3px 2px;font-family:Monaco,Menlo,Consolas,"Courier New",monospace;font-size:12px;color:#333;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}code{padding:2px 4px;color:#d14;white-space:nowrap;background-color:#f7f7f9;border:1px solid #e1e1e8}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:20px;word-break:break-all;word-wrap:break-word;white-space:pre;white-space:pre-wrap;background-color:#f5f5f5;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.15);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}pre.prettyprint{margin-bottom:20px}pre code{padding:0;color:inherit;white-space:pre;white-space:pre-wrap;background-color:transparent;border:0}.pre-scrollable{max-height:340px;overflow-y:scroll}form{margin:0 0 20px}fieldset{padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:40px;color:#333;border:0;border-bottom:1px solid #e5e5e5}legend small{font-size:15px;color:#999}label,input,button,select,textarea{font-size:14px;font-weight:normal;line-height:20px}input,button,select,textarea{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif}label{display:block;margin-bottom:5px}select,textarea,input[type="text"],input[type="password"],input[type="datetime"],input[type="datetime-local"],input[type="date"],input[type="month"],input[type="time"],input[type="week"],input[type="number"],input[type="email"],input[type="url"],input[type="search"],input[type="tel"],input[type="color"],.uneditable-input{display:inline-block;height:20px;padding:4px 6px;margin-bottom:10px;font-size:14px;line-height:20px;color:#555;vertical-align:middle;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}input,textarea,.uneditable-input{width:206px}textarea{height:auto}textarea,input[type="text"],input[type="password"],input[type="datetime"],input[type="datetime-local"],input[type="date"],input[type="month"],input[type="time"],input[type="week"],input[type="number"],input[type="email"],input[type="url"],input[type="search"],input[type="tel"],input[type="color"],.uneditable-input{background-color:#fff;border:1px solid #ccc;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-webkit-transition:border linear .2s,box-shadow linear .2s;-moz-transition:border linear .2s,box-shadow linear .2s;-o-transition:border linear .2s,box-shadow linear .2s;transition:border linear .2s,box-shadow linear .2s}textarea:focus,input[type="text"]:focus,input[type="password"]:focus,input[type="datetime"]:focus,input[type="datetime-local"]:focus,input[type="date"]:focus,input[type="month"]:focus,input[type="time"]:focus,input[type="week"]:focus,input[type="number"]:focus,input[type="email"]:focus,input[type="url"]:focus,input[type="search"]:focus,input[type="tel"]:focus,input[type="color"]:focus,.uneditable-input:focus{border-color:rgba(82,168,236,0.8);outline:0;outline:thin dotted \9;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6)}input[type="radio"],input[type="checkbox"]{margin:4px 0 0;margin-top:1px \9;*margin-top:0;line-height:normal}input[type="file"],input[type="image"],input[type="submit"],input[type="reset"],input[type="button"],input[type="radio"],input[type="checkbox"]{width:auto}select,input[type="file"]{height:30px;*margin-top:4px;line-height:30px}select{width:220px;background-color:#fff;border:1px solid #ccc}select[multiple],select[size]{height:auto}select:focus,input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.uneditable-input,.uneditable-textarea{color:#999;cursor:not-allowed;background-color:#fcfcfc;border-color:#ccc;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.025);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.025);box-shadow:inset 0 1px 2px rgba(0,0,0,0.025)}.uneditable-input{overflow:hidden;white-space:nowrap}.uneditable-textarea{width:auto;height:auto}input:-moz-placeholder,textarea:-moz-placeholder{color:#999}input:-ms-input-placeholder,textarea:-ms-input-placeholder{color:#999}input::-webkit-input-placeholder,textarea::-webkit-input-placeholder{color:#999}.radio,.checkbox{min-height:20px;padding-left:20px}.radio input[type="radio"],.checkbox input[type="checkbox"]{float:left;margin-left:-20px}.controls>.radio:first-child,.controls>.checkbox:first-child{padding-top:5px}.radio.inline,.checkbox.inline{display:inline-block;padding-top:5px;margin-bottom:0;vertical-align:middle}.radio.inline+.radio.inline,.checkbox.inline+.checkbox.inline{margin-left:10px}.input-mini{width:60px}.input-small{width:90px}.input-medium{width:150px}.input-large{width:210px}.input-xlarge{width:270px}.input-xxlarge{width:530px}input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input[class*="span"],.row-fluid input[class*="span"],.row-fluid select[class*="span"],.row-fluid textarea[class*="span"],.row-fluid .uneditable-input[class*="span"]{float:none;margin-left:0}.input-append input[class*="span"],.input-append .uneditable-input[class*="span"],.input-prepend input[class*="span"],.input-prepend .uneditable-input[class*="span"],.row-fluid input[class*="span"],.row-fluid select[class*="span"],.row-fluid textarea[class*="span"],.row-fluid .uneditable-input[class*="span"],.row-fluid .input-prepend [class*="span"],.row-fluid .input-append [class*="span"]{display:inline-block}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:20px}input.span12,textarea.span12,.uneditable-input.span12{width:926px}input.span11,textarea.span11,.uneditable-input.span11{width:846px}input.span10,textarea.span10,.uneditable-input.span10{width:766px}input.span9,textarea.span9,.uneditable-input.span9{width:686px}input.span8,textarea.span8,.uneditable-input.span8{width:606px}input.span7,textarea.span7,.uneditable-input.span7{width:526px}input.span6,textarea.span6,.uneditable-input.span6{width:446px}input.span5,textarea.span5,.uneditable-input.span5{width:366px}input.span4,textarea.span4,.uneditable-input.span4{width:286px}input.span3,textarea.span3,.uneditable-input.span3{width:206px}input.span2,textarea.span2,.uneditable-input.span2{width:126px}input.span1,textarea.span1,.uneditable-input.span1{width:46px}.controls-row{*zoom:1}.controls-row:before,.controls-row:after{display:table;line-height:0;content:""}.controls-row:after{clear:both}.controls-row [class*="span"],.row-fluid .controls-row [class*="span"]{float:left}.controls-row .checkbox[class*="span"],.controls-row .radio[class*="span"]{padding-top:5px}input[disabled],select[disabled],textarea[disabled],input[readonly],select[readonly],textarea[readonly]{cursor:not-allowed;background-color:#eee}input[type="radio"][disabled],input[type="checkbox"][disabled],input[type="radio"][readonly],input[type="checkbox"][readonly]{background-color:transparent}.control-group.warning .control-label,.control-group.warning .help-block,.control-group.warning .help-inline{color:#c09853}.control-group.warning .checkbox,.control-group.warning .radio,.control-group.warning input,.control-group.warning select,.control-group.warning textarea{color:#c09853}.control-group.warning input,.control-group.warning select,.control-group.warning textarea{border-color:#c09853;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.warning input:focus,.control-group.warning select:focus,.control-group.warning textarea:focus{border-color:#a47e3c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e}.control-group.warning .input-prepend .add-on,.control-group.warning .input-append .add-on{color:#c09853;background-color:#fcf8e3;border-color:#c09853}.control-group.error .control-label,.control-group.error .help-block,.control-group.error .help-inline{color:#b94a48}.control-group.error .checkbox,.control-group.error .radio,.control-group.error input,.control-group.error select,.control-group.error textarea{color:#b94a48}.control-group.error input,.control-group.error select,.control-group.error textarea{border-color:#b94a48;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.error input:focus,.control-group.error select:focus,.control-group.error textarea:focus{border-color:#953b39;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392}.control-group.error .input-prepend .add-on,.control-group.error .input-append .add-on{color:#b94a48;background-color:#f2dede;border-color:#b94a48}.control-group.success .control-label,.control-group.success .help-block,.control-group.success .help-inline{color:#468847}.control-group.success .checkbox,.control-group.success .radio,.control-group.success input,.control-group.success select,.control-group.success textarea{color:#468847}.control-group.success input,.control-group.success select,.control-group.success textarea{border-color:#468847;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.success input:focus,.control-group.success select:focus,.control-group.success textarea:focus{border-color:#356635;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b}.control-group.success .input-prepend .add-on,.control-group.success .input-append .add-on{color:#468847;background-color:#dff0d8;border-color:#468847}.control-group.info .control-label,.control-group.info .help-block,.control-group.info .help-inline{color:#3a87ad}.control-group.info .checkbox,.control-group.info .radio,.control-group.info input,.control-group.info select,.control-group.info textarea{color:#3a87ad}.control-group.info input,.control-group.info select,.control-group.info textarea{border-color:#3a87ad;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.info input:focus,.control-group.info select:focus,.control-group.info textarea:focus{border-color:#2d6987;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7ab5d3;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7ab5d3;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7ab5d3}.control-group.info .input-prepend .add-on,.control-group.info .input-append .add-on{color:#3a87ad;background-color:#d9edf7;border-color:#3a87ad}input:focus:invalid,textarea:focus:invalid,select:focus:invalid{color:#b94a48;border-color:#ee5f5b}input:focus:invalid:focus,textarea:focus:invalid:focus,select:focus:invalid:focus{border-color:#e9322d;-webkit-box-shadow:0 0 6px #f8b9b7;-moz-box-shadow:0 0 6px #f8b9b7;box-shadow:0 0 6px #f8b9b7}.form-actions{padding:19px 20px 20px;margin-top:20px;margin-bottom:20px;background-color:#f5f5f5;border-top:1px solid #e5e5e5;*zoom:1}.form-actions:before,.form-actions:after{display:table;line-height:0;content:""}.form-actions:after{clear:both}.help-block,.help-inline{color:#595959}.help-block{display:block;margin-bottom:10px}.help-inline{display:inline-block;*display:inline;padding-left:5px;vertical-align:middle;*zoom:1}.input-append,.input-prepend{display:inline-block;margin-bottom:10px;font-size:0;white-space:nowrap;vertical-align:middle}.input-append input,.input-prepend input,.input-append select,.input-prepend select,.input-append .uneditable-input,.input-prepend .uneditable-input,.input-append .dropdown-menu,.input-prepend .dropdown-menu,.input-append .popover,.input-prepend .popover{font-size:14px}.input-append input,.input-prepend input,.input-append select,.input-prepend select,.input-append .uneditable-input,.input-prepend .uneditable-input{position:relative;margin-bottom:0;*margin-left:0;vertical-align:top;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-append input:focus,.input-prepend input:focus,.input-append select:focus,.input-prepend select:focus,.input-append .uneditable-input:focus,.input-prepend .uneditable-input:focus{z-index:2}.input-append .add-on,.input-prepend .add-on{display:inline-block;width:auto;height:20px;min-width:16px;padding:4px 5px;font-size:14px;font-weight:normal;line-height:20px;text-align:center;text-shadow:0 1px 0 #fff;background-color:#eee;border:1px solid #ccc}.input-append .add-on,.input-prepend .add-on,.input-append .btn,.input-prepend .btn,.input-append .btn-group>.dropdown-toggle,.input-prepend .btn-group>.dropdown-toggle{vertical-align:top;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-append .active,.input-prepend .active{background-color:#a9dba9;border-color:#46a546}.input-prepend .add-on,.input-prepend .btn{margin-right:-1px}.input-prepend .add-on:first-child,.input-prepend .btn:first-child{-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.input-append input,.input-append select,.input-append .uneditable-input{-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.input-append input+.btn-group .btn:last-child,.input-append select+.btn-group .btn:last-child,.input-append .uneditable-input+.btn-group .btn:last-child{-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-append .add-on,.input-append .btn,.input-append .btn-group{margin-left:-1px}.input-append .add-on:last-child,.input-append .btn:last-child,.input-append .btn-group:last-child>.dropdown-toggle{-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-prepend.input-append input,.input-prepend.input-append select,.input-prepend.input-append .uneditable-input{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-prepend.input-append input+.btn-group .btn,.input-prepend.input-append select+.btn-group .btn,.input-prepend.input-append .uneditable-input+.btn-group .btn{-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-prepend.input-append .add-on:first-child,.input-prepend.input-append .btn:first-child{margin-right:-1px;-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.input-prepend.input-append .add-on:last-child,.input-prepend.input-append .btn:last-child{margin-left:-1px;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-prepend.input-append .btn-group:first-child{margin-left:0}input.search-query{padding-right:14px;padding-right:4px \9;padding-left:14px;padding-left:4px \9;margin-bottom:0;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px}.form-search .input-append .search-query,.form-search .input-prepend .search-query{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.form-search .input-append .search-query{-webkit-border-radius:14px 0 0 14px;-moz-border-radius:14px 0 0 14px;border-radius:14px 0 0 14px}.form-search .input-append .btn{-webkit-border-radius:0 14px 14px 0;-moz-border-radius:0 14px 14px 0;border-radius:0 14px 14px 0}.form-search .input-prepend .search-query{-webkit-border-radius:0 14px 14px 0;-moz-border-radius:0 14px 14px 0;border-radius:0 14px 14px 0}.form-search .input-prepend .btn{-webkit-border-radius:14px 0 0 14px;-moz-border-radius:14px 0 0 14px;border-radius:14px 0 0 14px}.form-search input,.form-inline input,.form-horizontal input,.form-search textarea,.form-inline textarea,.form-horizontal textarea,.form-search select,.form-inline select,.form-horizontal select,.form-search .help-inline,.form-inline .help-inline,.form-horizontal .help-inline,.form-search .uneditable-input,.form-inline .uneditable-input,.form-horizontal .uneditable-input,.form-search .input-prepend,.form-inline .input-prepend,.form-horizontal .input-prepend,.form-search .input-append,.form-inline .input-append,.form-horizontal .input-append{display:inline-block;*display:inline;margin-bottom:0;vertical-align:middle;*zoom:1}.form-search .hide,.form-inline .hide,.form-horizontal .hide{display:none}.form-search label,.form-inline label,.form-search .btn-group,.form-inline .btn-group{display:inline-block}.form-search .input-append,.form-inline .input-append,.form-search .input-prepend,.form-inline .input-prepend{margin-bottom:0}.form-search .radio,.form-search .checkbox,.form-inline .radio,.form-inline .checkbox{padding-left:0;margin-bottom:0;vertical-align:middle}.form-search .radio input[type="radio"],.form-search .checkbox input[type="checkbox"],.form-inline .radio input[type="radio"],.form-inline .checkbox input[type="checkbox"]{float:left;margin-right:3px;margin-left:0}.control-group{margin-bottom:10px}legend+.control-group{margin-top:20px;-webkit-margin-top-collapse:separate}.form-horizontal .control-group{margin-bottom:20px;*zoom:1}.form-horizontal .control-group:before,.form-horizontal .control-group:after{display:table;line-height:0;content:""}.form-horizontal .control-group:after{clear:both}.form-horizontal .control-label{float:left;width:160px;padding-top:5px;text-align:right}.form-horizontal .controls{*display:inline-block;*padding-left:20px;margin-left:180px;*margin-left:0}.form-horizontal .controls:first-child{*padding-left:180px}.form-horizontal .help-block{margin-bottom:0}.form-horizontal input+.help-block,.form-horizontal select+.help-block,.form-horizontal textarea+.help-block,.form-horizontal .uneditable-input+.help-block,.form-horizontal .input-prepend+.help-block,.form-horizontal .input-append+.help-block{margin-top:10px}.form-horizontal .form-actions{padding-left:180px}table{max-width:100%;background-color:transparent;border-collapse:collapse;border-spacing:0}.table{width:100%;margin-bottom:20px}.table th,.table td{padding:8px;line-height:20px;text-align:left;vertical-align:top;border-top:1px solid #ddd}.table th{font-weight:bold}.table thead th{vertical-align:bottom}.table caption+thead tr:first-child th,.table caption+thead tr:first-child td,.table colgroup+thead tr:first-child th,.table colgroup+thead tr:first-child td,.table thead:first-child tr:first-child th,.table thead:first-child tr:first-child td{border-top:0}.table tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed th,.table-condensed td{padding:4px 5px}.table-bordered{border:1px solid #ddd;border-collapse:separate;*border-collapse:collapse;border-left:0;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.table-bordered th,.table-bordered td{border-left:1px solid #ddd}.table-bordered caption+thead tr:first-child th,.table-bordered caption+tbody tr:first-child th,.table-bordered caption+tbody tr:first-child td,.table-bordered colgroup+thead tr:first-child th,.table-bordered colgroup+tbody tr:first-child th,.table-bordered colgroup+tbody tr:first-child td,.table-bordered thead:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child td{border-top:0}.table-bordered thead:first-child tr:first-child>th:first-child,.table-bordered tbody:first-child tr:first-child>td:first-child,.table-bordered tbody:first-child tr:first-child>th:first-child{-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topleft:4px}.table-bordered thead:first-child tr:first-child>th:last-child,.table-bordered tbody:first-child tr:first-child>td:last-child,.table-bordered tbody:first-child tr:first-child>th:last-child{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-moz-border-radius-topright:4px}.table-bordered thead:last-child tr:last-child>th:first-child,.table-bordered tbody:last-child tr:last-child>td:first-child,.table-bordered tbody:last-child tr:last-child>th:first-child,.table-bordered tfoot:last-child tr:last-child>td:first-child,.table-bordered tfoot:last-child tr:last-child>th:first-child{-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px}.table-bordered thead:last-child tr:last-child>th:last-child,.table-bordered tbody:last-child tr:last-child>td:last-child,.table-bordered tbody:last-child tr:last-child>th:last-child,.table-bordered tfoot:last-child tr:last-child>td:last-child,.table-bordered tfoot:last-child tr:last-child>th:last-child{-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px}.table-bordered tfoot+tbody:last-child tr:last-child td:first-child{-webkit-border-bottom-left-radius:0;border-bottom-left-radius:0;-moz-border-radius-bottomleft:0}.table-bordered tfoot+tbody:last-child tr:last-child td:last-child{-webkit-border-bottom-right-radius:0;border-bottom-right-radius:0;-moz-border-radius-bottomright:0}.table-bordered caption+thead tr:first-child th:first-child,.table-bordered caption+tbody tr:first-child td:first-child,.table-bordered colgroup+thead tr:first-child th:first-child,.table-bordered colgroup+tbody tr:first-child td:first-child{-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topleft:4px}.table-bordered caption+thead tr:first-child th:last-child,.table-bordered caption+tbody tr:first-child td:last-child,.table-bordered colgroup+thead tr:first-child th:last-child,.table-bordered colgroup+tbody tr:first-child td:last-child{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-moz-border-radius-topright:4px}.table-striped tbody>tr:nth-child(odd)>td,.table-striped tbody>tr:nth-child(odd)>th{background-color:#f9f9f9}.table-hover tbody tr:hover>td,.table-hover tbody tr:hover>th{background-color:#f5f5f5}table td[class*="span"],table th[class*="span"],.row-fluid table td[class*="span"],.row-fluid table th[class*="span"]{display:table-cell;float:none;margin-left:0}.table td.span1,.table th.span1{float:none;width:44px;margin-left:0}.table td.span2,.table th.span2{float:none;width:124px;margin-left:0}.table td.span3,.table th.span3{float:none;width:204px;margin-left:0}.table td.span4,.table th.span4{float:none;width:284px;margin-left:0}.table td.span5,.table th.span5{float:none;width:364px;margin-left:0}.table td.span6,.table th.span6{float:none;width:444px;margin-left:0}.table td.span7,.table th.span7{float:none;width:524px;margin-left:0}.table td.span8,.table th.span8{float:none;width:604px;margin-left:0}.table td.span9,.table th.span9{float:none;width:684px;margin-left:0}.table td.span10,.table th.span10{float:none;width:764px;margin-left:0}.table td.span11,.table th.span11{float:none;width:844px;margin-left:0}.table td.span12,.table th.span12{float:none;width:924px;margin-left:0}.table tbody tr.success>td{background-color:#dff0d8}.table tbody tr.error>td{background-color:#f2dede}.table tbody tr.warning>td{background-color:#fcf8e3}.table tbody tr.info>td{background-color:#d9edf7}.table-hover tbody tr.success:hover>td{background-color:#d0e9c6}.table-hover tbody tr.error:hover>td{background-color:#ebcccc}.table-hover tbody tr.warning:hover>td{background-color:#faf2cc}.table-hover tbody tr.info:hover>td{background-color:#c4e3f3}[class^="icon-"],[class*=" icon-"]{display:inline-block;width:14px;height:14px;margin-top:1px;*margin-right:.3em;line-height:14px;vertical-align:text-top;background-image:url("../img/glyphicons-halflings.png");background-position:14px 14px;background-repeat:no-repeat}.icon-white,.nav-pills>.active>a>[class^="icon-"],.nav-pills>.active>a>[class*=" icon-"],.nav-list>.active>a>[class^="icon-"],.nav-list>.active>a>[class*=" icon-"],.navbar-inverse .nav>.active>a>[class^="icon-"],.navbar-inverse .nav>.active>a>[class*=" icon-"],.dropdown-menu>li>a:hover>[class^="icon-"],.dropdown-menu>li>a:focus>[class^="icon-"],.dropdown-menu>li>a:hover>[class*=" icon-"],.dropdown-menu>li>a:focus>[class*=" icon-"],.dropdown-menu>.active>a>[class^="icon-"],.dropdown-menu>.active>a>[class*=" icon-"],.dropdown-submenu:hover>a>[class^="icon-"],.dropdown-submenu:focus>a>[class^="icon-"],.dropdown-submenu:hover>a>[class*=" icon-"],.dropdown-submenu:focus>a>[class*=" icon-"]{background-image:url("../img/glyphicons-halflings-white.png")}.icon-glass{background-position:0 0}.icon-music{background-position:-24px 0}.icon-search{background-position:-48px 0}.icon-envelope{background-position:-72px 0}.icon-heart{background-position:-96px 0}.icon-star{background-position:-120px 0}.icon-star-empty{background-position:-144px 0}.icon-user{background-position:-168px 0}.icon-film{background-position:-192px 0}.icon-th-large{background-position:-216px 0}.icon-th{background-position:-240px 0}.icon-th-list{background-position:-264px 0}.icon-ok{background-position:-288px 0}.icon-remove{background-position:-312px 0}.icon-zoom-in{background-position:-336px 0}.icon-zoom-out{background-position:-360px 0}.icon-off{background-position:-384px 0}.icon-signal{background-position:-408px 0}.icon-cog{background-position:-432px 0}.icon-trash{background-position:-456px 0}.icon-home{background-position:0 -24px}.icon-file{background-position:-24px -24px}.icon-time{background-position:-48px -24px}.icon-road{background-position:-72px -24px}.icon-download-alt{background-position:-96px -24px}.icon-download{background-position:-120px -24px}.icon-upload{background-position:-144px -24px}.icon-inbox{background-position:-168px -24px}.icon-play-circle{background-position:-192px -24px}.icon-repeat{background-position:-216px -24px}.icon-refresh{background-position:-240px -24px}.icon-list-alt{background-position:-264px -24px}.icon-lock{background-position:-287px -24px}.icon-flag{background-position:-312px -24px}.icon-headphones{background-position:-336px -24px}.icon-volume-off{background-position:-360px -24px}.icon-volume-down{background-position:-384px -24px}.icon-volume-up{background-position:-408px -24px}.icon-qrcode{background-position:-432px -24px}.icon-barcode{background-position:-456px -24px}.icon-tag{background-position:0 -48px}.icon-tags{background-position:-25px -48px}.icon-book{background-position:-48px -48px}.icon-bookmark{background-position:-72px -48px}.icon-print{background-position:-96px -48px}.icon-camera{background-position:-120px -48px}.icon-font{background-position:-144px -48px}.icon-bold{background-position:-167px -48px}.icon-italic{background-position:-192px -48px}.icon-text-height{background-position:-216px -48px}.icon-text-width{background-position:-240px -48px}.icon-align-left{background-position:-264px -48px}.icon-align-center{background-position:-288px -48px}.icon-align-right{background-position:-312px -48px}.icon-align-justify{background-position:-336px -48px}.icon-list{background-position:-360px -48px}.icon-indent-left{background-position:-384px -48px}.icon-indent-right{background-position:-408px -48px}.icon-facetime-video{background-position:-432px -48px}.icon-picture{background-position:-456px -48px}.icon-pencil{background-position:0 -72px}.icon-map-marker{background-position:-24px -72px}.icon-adjust{background-position:-48px -72px}.icon-tint{background-position:-72px -72px}.icon-edit{background-position:-96px -72px}.icon-share{background-position:-120px -72px}.icon-check{background-position:-144px -72px}.icon-move{background-position:-168px -72px}.icon-step-backward{background-position:-192px -72px}.icon-fast-backward{background-position:-216px -72px}.icon-backward{background-position:-240px -72px}.icon-play{background-position:-264px -72px}.icon-pause{background-position:-288px -72px}.icon-stop{background-position:-312px -72px}.icon-forward{background-position:-336px -72px}.icon-fast-forward{background-position:-360px -72px}.icon-step-forward{background-position:-384px -72px}.icon-eject{background-position:-408px -72px}.icon-chevron-left{background-position:-432px -72px}.icon-chevron-right{background-position:-456px -72px}.icon-plus-sign{background-position:0 -96px}.icon-minus-sign{background-position:-24px -96px}.icon-remove-sign{background-position:-48px -96px}.icon-ok-sign{background-position:-72px -96px}.icon-question-sign{background-position:-96px -96px}.icon-info-sign{background-position:-120px -96px}.icon-screenshot{background-position:-144px -96px}.icon-remove-circle{background-position:-168px -96px}.icon-ok-circle{background-position:-192px -96px}.icon-ban-circle{background-position:-216px -96px}.icon-arrow-left{background-position:-240px -96px}.icon-arrow-right{background-position:-264px -96px}.icon-arrow-up{background-position:-289px -96px}.icon-arrow-down{background-position:-312px -96px}.icon-share-alt{background-position:-336px -96px}.icon-resize-full{background-position:-360px -96px}.icon-resize-small{background-position:-384px -96px}.icon-plus{background-position:-408px -96px}.icon-minus{background-position:-433px -96px}.icon-asterisk{background-position:-456px -96px}.icon-exclamation-sign{background-position:0 -120px}.icon-gift{background-position:-24px -120px}.icon-leaf{background-position:-48px -120px}.icon-fire{background-position:-72px -120px}.icon-eye-open{background-position:-96px -120px}.icon-eye-close{background-position:-120px -120px}.icon-warning-sign{background-position:-144px -120px}.icon-plane{background-position:-168px -120px}.icon-calendar{background-position:-192px -120px}.icon-random{width:16px;background-position:-216px -120px}.icon-comment{background-position:-240px -120px}.icon-magnet{background-position:-264px -120px}.icon-chevron-up{background-position:-288px -120px}.icon-chevron-down{background-position:-313px -119px}.icon-retweet{background-position:-336px -120px}.icon-shopping-cart{background-position:-360px -120px}.icon-folder-close{width:16px;background-position:-384px -120px}.icon-folder-open{width:16px;background-position:-408px -120px}.icon-resize-vertical{background-position:-432px -119px}.icon-resize-horizontal{background-position:-456px -118px}.icon-hdd{background-position:0 -144px}.icon-bullhorn{background-position:-24px -144px}.icon-bell{background-position:-48px -144px}.icon-certificate{background-position:-72px -144px}.icon-thumbs-up{background-position:-96px -144px}.icon-thumbs-down{background-position:-120px -144px}.icon-hand-right{background-position:-144px -144px}.icon-hand-left{background-position:-168px -144px}.icon-hand-up{background-position:-192px -144px}.icon-hand-down{background-position:-216px -144px}.icon-circle-arrow-right{background-position:-240px -144px}.icon-circle-arrow-left{background-position:-264px -144px}.icon-circle-arrow-up{background-position:-288px -144px}.icon-circle-arrow-down{background-position:-312px -144px}.icon-globe{background-position:-336px -144px}.icon-wrench{background-position:-360px -144px}.icon-tasks{background-position:-384px -144px}.icon-filter{background-position:-408px -144px}.icon-briefcase{background-position:-432px -144px}.icon-fullscreen{background-position:-456px -144px}.dropup,.dropdown{position:relative}.dropdown-toggle{*margin-bottom:-3px}.dropdown-toggle:active,.open .dropdown-toggle{outline:0}.caret{display:inline-block;width:0;height:0;vertical-align:top;border-top:4px solid #000;border-right:4px solid transparent;border-left:4px solid transparent;content:""}.dropdown .caret{margin-top:8px;margin-left:2px}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;list-style:none;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);*border-right-width:2px;*border-bottom-width:2px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{*width:100%;height:1px;margin:9px 1px;*margin:-5px 0 5px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #fff}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:normal;line-height:20px;color:#333;white-space:nowrap}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus,.dropdown-submenu:hover>a,.dropdown-submenu:focus>a{color:#fff;text-decoration:none;background-color:#0081c2;background-image:-moz-linear-gradient(top,#08c,#0077b3);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#0077b3));background-image:-webkit-linear-gradient(top,#08c,#0077b3);background-image:-o-linear-gradient(top,#08c,#0077b3);background-image:linear-gradient(to bottom,#08c,#0077b3);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc',endColorstr='#ff0077b3',GradientType=0)}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{color:#fff;text-decoration:none;background-color:#0081c2;background-image:-moz-linear-gradient(top,#08c,#0077b3);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#0077b3));background-image:-webkit-linear-gradient(top,#08c,#0077b3);background-image:-o-linear-gradient(top,#08c,#0077b3);background-image:linear-gradient(to bottom,#08c,#0077b3);background-repeat:repeat-x;outline:0;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc',endColorstr='#ff0077b3',GradientType=0)}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{color:#999}.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{text-decoration:none;cursor:default;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open{*z-index:1000}.open>.dropdown-menu{display:block}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{border-top:0;border-bottom:4px solid #000;content:""}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:1px}.dropdown-submenu{position:relative}.dropdown-submenu>.dropdown-menu{top:0;left:100%;margin-top:-6px;margin-left:-1px;-webkit-border-radius:0 6px 6px 6px;-moz-border-radius:0 6px 6px 6px;border-radius:0 6px 6px 6px}.dropdown-submenu:hover>.dropdown-menu{display:block}.dropup .dropdown-submenu>.dropdown-menu{top:auto;bottom:0;margin-top:0;margin-bottom:-2px;-webkit-border-radius:5px 5px 5px 0;-moz-border-radius:5px 5px 5px 0;border-radius:5px 5px 5px 0}.dropdown-submenu>a:after{display:block;float:right;width:0;height:0;margin-top:5px;margin-right:-10px;border-color:transparent;border-left-color:#ccc;border-style:solid;border-width:5px 0 5px 5px;content:" "}.dropdown-submenu:hover>a:after{border-left-color:#fff}.dropdown-submenu.pull-left{float:none}.dropdown-submenu.pull-left>.dropdown-menu{left:-100%;margin-left:10px;-webkit-border-radius:6px 0 6px 6px;-moz-border-radius:6px 0 6px 6px;border-radius:6px 0 6px 6px}.dropdown .dropdown-menu .nav-header{padding-right:20px;padding-left:20px}.typeahead{z-index:1051;margin-top:2px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);box-shadow:inset 0 1px 1px rgba(0,0,0,0.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,0.15)}.well-large{padding:24px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.well-small{padding:9px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.fade{opacity:0;-webkit-transition:opacity .15s linear;-moz-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{position:relative;height:0;overflow:hidden;-webkit-transition:height .35s ease;-moz-transition:height .35s ease;-o-transition:height .35s ease;transition:height .35s ease}.collapse.in{height:auto}.close{float:right;font-size:20px;font-weight:bold;line-height:20px;color:#000;text-shadow:0 1px 0 #fff;opacity:.2;filter:alpha(opacity=20)}.close:hover,.close:focus{color:#000;text-decoration:none;cursor:pointer;opacity:.4;filter:alpha(opacity=40)}button.close{padding:0;cursor:pointer;background:transparent;border:0;-webkit-appearance:none}.btn{display:inline-block;*display:inline;padding:4px 12px;margin-bottom:0;*margin-left:.3em;font-size:14px;line-height:20px;color:#333;text-align:center;text-shadow:0 1px 1px rgba(255,255,255,0.75);vertical-align:middle;cursor:pointer;background-color:#f5f5f5;*background-color:#e6e6e6;background-image:-moz-linear-gradient(top,#fff,#e6e6e6);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#e6e6e6));background-image:-webkit-linear-gradient(top,#fff,#e6e6e6);background-image:-o-linear-gradient(top,#fff,#e6e6e6);background-image:linear-gradient(to bottom,#fff,#e6e6e6);background-repeat:repeat-x;border:1px solid #ccc;*border:0;border-color:#e6e6e6 #e6e6e6 #bfbfbf;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);border-bottom-color:#b3b3b3;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#ffe6e6e6',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);*zoom:1;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05)}.btn:hover,.btn:focus,.btn:active,.btn.active,.btn.disabled,.btn[disabled]{color:#333;background-color:#e6e6e6;*background-color:#d9d9d9}.btn:active,.btn.active{background-color:#ccc \9}.btn:first-child{*margin-left:0}.btn:hover,.btn:focus{color:#333;text-decoration:none;background-position:0 -15px;-webkit-transition:background-position .1s linear;-moz-transition:background-position .1s linear;-o-transition:background-position .1s linear;transition:background-position .1s linear}.btn:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn.active,.btn:active{background-image:none;outline:0;-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05)}.btn.disabled,.btn[disabled]{cursor:default;background-image:none;opacity:.65;filter:alpha(opacity=65);-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.btn-large{padding:11px 19px;font-size:17.5px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.btn-large [class^="icon-"],.btn-large [class*=" icon-"]{margin-top:4px}.btn-small{padding:2px 10px;font-size:11.9px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.btn-small [class^="icon-"],.btn-small [class*=" icon-"]{margin-top:0}.btn-mini [class^="icon-"],.btn-mini [class*=" icon-"]{margin-top:-1px}.btn-mini{padding:0 6px;font-size:10.5px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.btn-block{display:block;width:100%;padding-right:0;padding-left:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.btn-block+.btn-block{margin-top:5px}input[type="submit"].btn-block,input[type="reset"].btn-block,input[type="button"].btn-block{width:100%}.btn-primary.active,.btn-warning.active,.btn-danger.active,.btn-success.active,.btn-info.active,.btn-inverse.active{color:rgba(255,255,255,0.75)}.btn-primary{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#006dcc;*background-color:#04c;background-image:-moz-linear-gradient(top,#08c,#04c);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#04c));background-image:-webkit-linear-gradient(top,#08c,#04c);background-image:-o-linear-gradient(top,#08c,#04c);background-image:linear-gradient(to bottom,#08c,#04c);background-repeat:repeat-x;border-color:#04c #04c #002a80;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc',endColorstr='#ff0044cc',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-primary:hover,.btn-primary:focus,.btn-primary:active,.btn-primary.active,.btn-primary.disabled,.btn-primary[disabled]{color:#fff;background-color:#04c;*background-color:#003bb3}.btn-primary:active,.btn-primary.active{background-color:#039 \9}.btn-warning{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#faa732;*background-color:#f89406;background-image:-moz-linear-gradient(top,#fbb450,#f89406);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fbb450),to(#f89406));background-image:-webkit-linear-gradient(top,#fbb450,#f89406);background-image:-o-linear-gradient(top,#fbb450,#f89406);background-image:linear-gradient(to bottom,#fbb450,#f89406);background-repeat:repeat-x;border-color:#f89406 #f89406 #ad6704;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450',endColorstr='#fff89406',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-warning:hover,.btn-warning:focus,.btn-warning:active,.btn-warning.active,.btn-warning.disabled,.btn-warning[disabled]{color:#fff;background-color:#f89406;*background-color:#df8505}.btn-warning:active,.btn-warning.active{background-color:#c67605 \9}.btn-danger{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#da4f49;*background-color:#bd362f;background-image:-moz-linear-gradient(top,#ee5f5b,#bd362f);background-image:-webkit-gradient(linear,0 0,0 100%,from(#ee5f5b),to(#bd362f));background-image:-webkit-linear-gradient(top,#ee5f5b,#bd362f);background-image:-o-linear-gradient(top,#ee5f5b,#bd362f);background-image:linear-gradient(to bottom,#ee5f5b,#bd362f);background-repeat:repeat-x;border-color:#bd362f #bd362f #802420;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b',endColorstr='#ffbd362f',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-danger:hover,.btn-danger:focus,.btn-danger:active,.btn-danger.active,.btn-danger.disabled,.btn-danger[disabled]{color:#fff;background-color:#bd362f;*background-color:#a9302a}.btn-danger:active,.btn-danger.active{background-color:#942a25 \9}.btn-success{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#5bb75b;*background-color:#51a351;background-image:-moz-linear-gradient(top,#62c462,#51a351);background-image:-webkit-gradient(linear,0 0,0 100%,from(#62c462),to(#51a351));background-image:-webkit-linear-gradient(top,#62c462,#51a351);background-image:-o-linear-gradient(top,#62c462,#51a351);background-image:linear-gradient(to bottom,#62c462,#51a351);background-repeat:repeat-x;border-color:#51a351 #51a351 #387038;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462',endColorstr='#ff51a351',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-success:hover,.btn-success:focus,.btn-success:active,.btn-success.active,.btn-success.disabled,.btn-success[disabled]{color:#fff;background-color:#51a351;*background-color:#499249}.btn-success:active,.btn-success.active{background-color:#408140 \9}.btn-info{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#49afcd;*background-color:#2f96b4;background-image:-moz-linear-gradient(top,#5bc0de,#2f96b4);background-image:-webkit-gradient(linear,0 0,0 100%,from(#5bc0de),to(#2f96b4));background-image:-webkit-linear-gradient(top,#5bc0de,#2f96b4);background-image:-o-linear-gradient(top,#5bc0de,#2f96b4);background-image:linear-gradient(to bottom,#5bc0de,#2f96b4);background-repeat:repeat-x;border-color:#2f96b4 #2f96b4 #1f6377;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de',endColorstr='#ff2f96b4',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-info:hover,.btn-info:focus,.btn-info:active,.btn-info.active,.btn-info.disabled,.btn-info[disabled]{color:#fff;background-color:#2f96b4;*background-color:#2a85a0}.btn-info:active,.btn-info.active{background-color:#24748c \9}.btn-inverse{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#363636;*background-color:#222;background-image:-moz-linear-gradient(top,#444,#222);background-image:-webkit-gradient(linear,0 0,0 100%,from(#444),to(#222));background-image:-webkit-linear-gradient(top,#444,#222);background-image:-o-linear-gradient(top,#444,#222);background-image:linear-gradient(to bottom,#444,#222);background-repeat:repeat-x;border-color:#222 #222 #000;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff444444',endColorstr='#ff222222',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-inverse:hover,.btn-inverse:focus,.btn-inverse:active,.btn-inverse.active,.btn-inverse.disabled,.btn-inverse[disabled]{color:#fff;background-color:#222;*background-color:#151515}.btn-inverse:active,.btn-inverse.active{background-color:#080808 \9}button.btn,input[type="submit"].btn{*padding-top:3px;*padding-bottom:3px}button.btn::-moz-focus-inner,input[type="submit"].btn::-moz-focus-inner{padding:0;border:0}button.btn.btn-large,input[type="submit"].btn.btn-large{*padding-top:7px;*padding-bottom:7px}button.btn.btn-small,input[type="submit"].btn.btn-small{*padding-top:3px;*padding-bottom:3px}button.btn.btn-mini,input[type="submit"].btn.btn-mini{*padding-top:1px;*padding-bottom:1px}.btn-link,.btn-link:active,.btn-link[disabled]{background-color:transparent;background-image:none;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.btn-link{color:#08c;cursor:pointer;border-color:transparent;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-link:hover,.btn-link:focus{color:#005580;text-decoration:underline;background-color:transparent}.btn-link[disabled]:hover,.btn-link[disabled]:focus{color:#333;text-decoration:none}.btn-group{position:relative;display:inline-block;*display:inline;*margin-left:.3em;font-size:0;white-space:nowrap;vertical-align:middle;*zoom:1}.btn-group:first-child{*margin-left:0}.btn-group+.btn-group{margin-left:5px}.btn-toolbar{margin-top:10px;margin-bottom:10px;font-size:0}.btn-toolbar>.btn+.btn,.btn-toolbar>.btn-group+.btn,.btn-toolbar>.btn+.btn-group{margin-left:5px}.btn-group>.btn{position:relative;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-group>.btn+.btn{margin-left:-1px}.btn-group>.btn,.btn-group>.dropdown-menu,.btn-group>.popover{font-size:14px}.btn-group>.btn-mini{font-size:10.5px}.btn-group>.btn-small{font-size:11.9px}.btn-group>.btn-large{font-size:17.5px}.btn-group>.btn:first-child{margin-left:0;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-bottomleft:4px;-moz-border-radius-topleft:4px}.btn-group>.btn:last-child,.btn-group>.dropdown-toggle{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-moz-border-radius-topright:4px;-moz-border-radius-bottomright:4px}.btn-group>.btn.large:first-child{margin-left:0;-webkit-border-bottom-left-radius:6px;border-bottom-left-radius:6px;-webkit-border-top-left-radius:6px;border-top-left-radius:6px;-moz-border-radius-bottomleft:6px;-moz-border-radius-topleft:6px}.btn-group>.btn.large:last-child,.btn-group>.large.dropdown-toggle{-webkit-border-top-right-radius:6px;border-top-right-radius:6px;-webkit-border-bottom-right-radius:6px;border-bottom-right-radius:6px;-moz-border-radius-topright:6px;-moz-border-radius-bottomright:6px}.btn-group>.btn:hover,.btn-group>.btn:focus,.btn-group>.btn:active,.btn-group>.btn.active{z-index:2}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{*padding-top:5px;padding-right:8px;*padding-bottom:5px;padding-left:8px;-webkit-box-shadow:inset 1px 0 0 rgba(255,255,255,0.125),inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 1px 0 0 rgba(255,255,255,0.125),inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 1px 0 0 rgba(255,255,255,0.125),inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05)}.btn-group>.btn-mini+.dropdown-toggle{*padding-top:2px;padding-right:5px;*padding-bottom:2px;padding-left:5px}.btn-group>.btn-small+.dropdown-toggle{*padding-top:5px;*padding-bottom:4px}.btn-group>.btn-large+.dropdown-toggle{*padding-top:7px;padding-right:12px;*padding-bottom:7px;padding-left:12px}.btn-group.open .dropdown-toggle{background-image:none;-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05)}.btn-group.open .btn.dropdown-toggle{background-color:#e6e6e6}.btn-group.open .btn-primary.dropdown-toggle{background-color:#04c}.btn-group.open .btn-warning.dropdown-toggle{background-color:#f89406}.btn-group.open .btn-danger.dropdown-toggle{background-color:#bd362f}.btn-group.open .btn-success.dropdown-toggle{background-color:#51a351}.btn-group.open .btn-info.dropdown-toggle{background-color:#2f96b4}.btn-group.open .btn-inverse.dropdown-toggle{background-color:#222}.btn .caret{margin-top:8px;margin-left:0}.btn-large .caret{margin-top:6px}.btn-large .caret{border-top-width:5px;border-right-width:5px;border-left-width:5px}.btn-mini .caret,.btn-small .caret{margin-top:8px}.dropup .btn-large .caret{border-bottom-width:5px}.btn-primary .caret,.btn-warning .caret,.btn-danger .caret,.btn-info .caret,.btn-success .caret,.btn-inverse .caret{border-top-color:#fff;border-bottom-color:#fff}.btn-group-vertical{display:inline-block;*display:inline;*zoom:1}.btn-group-vertical>.btn{display:block;float:none;max-width:100%;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-group-vertical>.btn+.btn{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:first-child{-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0}.btn-group-vertical>.btn:last-child{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px}.btn-group-vertical>.btn-large:first-child{-webkit-border-radius:6px 6px 0 0;-moz-border-radius:6px 6px 0 0;border-radius:6px 6px 0 0}.btn-group-vertical>.btn-large:last-child{-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px}.alert{padding:8px 35px 8px 14px;margin-bottom:20px;text-shadow:0 1px 0 rgba(255,255,255,0.5);background-color:#fcf8e3;border:1px solid #fbeed5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.alert,.alert h4{color:#c09853}.alert h4{margin:0}.alert .close{position:relative;top:-2px;right:-21px;line-height:20px}.alert-success{color:#468847;background-color:#dff0d8;border-color:#d6e9c6}.alert-success h4{color:#468847}.alert-danger,.alert-error{color:#b94a48;background-color:#f2dede;border-color:#eed3d7}.alert-danger h4,.alert-error h4{color:#b94a48}.alert-info{color:#3a87ad;background-color:#d9edf7;border-color:#bce8f1}.alert-info h4{color:#3a87ad}.alert-block{padding-top:14px;padding-bottom:14px}.alert-block>p,.alert-block>ul{margin-bottom:0}.alert-block p+p{margin-top:5px}.nav{margin-bottom:20px;margin-left:0;list-style:none}.nav>li>a{display:block}.nav>li>a:hover,.nav>li>a:focus{text-decoration:none;background-color:#eee}.nav>li>a>img{max-width:none}.nav>.pull-right{float:right}.nav-header{display:block;padding:3px 15px;font-size:11px;font-weight:bold;line-height:20px;color:#999;text-shadow:0 1px 0 rgba(255,255,255,0.5);text-transform:uppercase}.nav li+.nav-header{margin-top:9px}.nav-list{padding-right:15px;padding-left:15px;margin-bottom:0}.nav-list>li>a,.nav-list .nav-header{margin-right:-15px;margin-left:-15px;text-shadow:0 1px 0 rgba(255,255,255,0.5)}.nav-list>li>a{padding:3px 15px}.nav-list>.active>a,.nav-list>.active>a:hover,.nav-list>.active>a:focus{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.2);background-color:#08c}.nav-list [class^="icon-"],.nav-list [class*=" icon-"]{margin-right:2px}.nav-list .divider{*width:100%;height:1px;margin:9px 1px;*margin:-5px 0 5px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #fff}.nav-tabs,.nav-pills{*zoom:1}.nav-tabs:before,.nav-pills:before,.nav-tabs:after,.nav-pills:after{display:table;line-height:0;content:""}.nav-tabs:after,.nav-pills:after{clear:both}.nav-tabs>li,.nav-pills>li{float:left}.nav-tabs>li>a,.nav-pills>li>a{padding-right:12px;padding-left:12px;margin-right:2px;line-height:14px}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{margin-bottom:-1px}.nav-tabs>li>a{padding-top:8px;padding-bottom:8px;line-height:20px;border:1px solid transparent;-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover,.nav-tabs>li>a:focus{border-color:#eee #eee #ddd}.nav-tabs>.active>a,.nav-tabs>.active>a:hover,.nav-tabs>.active>a:focus{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-pills>li>a{padding-top:8px;padding-bottom:8px;margin-top:2px;margin-bottom:2px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}.nav-pills>.active>a,.nav-pills>.active>a:hover,.nav-pills>.active>a:focus{color:#fff;background-color:#08c}.nav-stacked>li{float:none}.nav-stacked>li>a{margin-right:0}.nav-tabs.nav-stacked{border-bottom:0}.nav-tabs.nav-stacked>li>a{border:1px solid #ddd;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.nav-tabs.nav-stacked>li:first-child>a{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topright:4px;-moz-border-radius-topleft:4px}.nav-tabs.nav-stacked>li:last-child>a{-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-moz-border-radius-bottomright:4px;-moz-border-radius-bottomleft:4px}.nav-tabs.nav-stacked>li>a:hover,.nav-tabs.nav-stacked>li>a:focus{z-index:2;border-color:#ddd}.nav-pills.nav-stacked>li>a{margin-bottom:3px}.nav-pills.nav-stacked>li:last-child>a{margin-bottom:1px}.nav-tabs .dropdown-menu{-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px}.nav-pills .dropdown-menu{-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.nav .dropdown-toggle .caret{margin-top:6px;border-top-color:#08c;border-bottom-color:#08c}.nav .dropdown-toggle:hover .caret,.nav .dropdown-toggle:focus .caret{border-top-color:#005580;border-bottom-color:#005580}.nav-tabs .dropdown-toggle .caret{margin-top:8px}.nav .active .dropdown-toggle .caret{border-top-color:#fff;border-bottom-color:#fff}.nav-tabs .active .dropdown-toggle .caret{border-top-color:#555;border-bottom-color:#555}.nav>.dropdown.active>a:hover,.nav>.dropdown.active>a:focus{cursor:pointer}.nav-tabs .open .dropdown-toggle,.nav-pills .open .dropdown-toggle,.nav>li.dropdown.open.active>a:hover,.nav>li.dropdown.open.active>a:focus{color:#fff;background-color:#999;border-color:#999}.nav li.dropdown.open .caret,.nav li.dropdown.open.active .caret,.nav li.dropdown.open a:hover .caret,.nav li.dropdown.open a:focus .caret{border-top-color:#fff;border-bottom-color:#fff;opacity:1;filter:alpha(opacity=100)}.tabs-stacked .open>a:hover,.tabs-stacked .open>a:focus{border-color:#999}.tabbable{*zoom:1}.tabbable:before,.tabbable:after{display:table;line-height:0;content:""}.tabbable:after{clear:both}.tab-content{overflow:auto}.tabs-below>.nav-tabs,.tabs-right>.nav-tabs,.tabs-left>.nav-tabs{border-bottom:0}.tab-content>.tab-pane,.pill-content>.pill-pane{display:none}.tab-content>.active,.pill-content>.active{display:block}.tabs-below>.nav-tabs{border-top:1px solid #ddd}.tabs-below>.nav-tabs>li{margin-top:-1px;margin-bottom:0}.tabs-below>.nav-tabs>li>a{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px}.tabs-below>.nav-tabs>li>a:hover,.tabs-below>.nav-tabs>li>a:focus{border-top-color:#ddd;border-bottom-color:transparent}.tabs-below>.nav-tabs>.active>a,.tabs-below>.nav-tabs>.active>a:hover,.tabs-below>.nav-tabs>.active>a:focus{border-color:transparent #ddd #ddd #ddd}.tabs-left>.nav-tabs>li,.tabs-right>.nav-tabs>li{float:none}.tabs-left>.nav-tabs>li>a,.tabs-right>.nav-tabs>li>a{min-width:74px;margin-right:0;margin-bottom:3px}.tabs-left>.nav-tabs{float:left;margin-right:19px;border-right:1px solid #ddd}.tabs-left>.nav-tabs>li>a{margin-right:-1px;-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.tabs-left>.nav-tabs>li>a:hover,.tabs-left>.nav-tabs>li>a:focus{border-color:#eee #ddd #eee #eee}.tabs-left>.nav-tabs .active>a,.tabs-left>.nav-tabs .active>a:hover,.tabs-left>.nav-tabs .active>a:focus{border-color:#ddd transparent #ddd #ddd;*border-right-color:#fff}.tabs-right>.nav-tabs{float:right;margin-left:19px;border-left:1px solid #ddd}.tabs-right>.nav-tabs>li>a{margin-left:-1px;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.tabs-right>.nav-tabs>li>a:hover,.tabs-right>.nav-tabs>li>a:focus{border-color:#eee #eee #eee #ddd}.tabs-right>.nav-tabs .active>a,.tabs-right>.nav-tabs .active>a:hover,.tabs-right>.nav-tabs .active>a:focus{border-color:#ddd #ddd #ddd transparent;*border-left-color:#fff}.nav>.disabled>a{color:#999}.nav>.disabled>a:hover,.nav>.disabled>a:focus{text-decoration:none;cursor:default;background-color:transparent}.navbar{*position:relative;*z-index:2;margin-bottom:20px;overflow:visible}.navbar-inner{min-height:40px;padding-right:20px;padding-left:20px;background-color:#fafafa;background-image:-moz-linear-gradient(top,#fff,#f2f2f2);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#f2f2f2));background-image:-webkit-linear-gradient(top,#fff,#f2f2f2);background-image:-o-linear-gradient(top,#fff,#f2f2f2);background-image:linear-gradient(to bottom,#fff,#f2f2f2);background-repeat:repeat-x;border:1px solid #d4d4d4;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#fff2f2f2',GradientType=0);*zoom:1;-webkit-box-shadow:0 1px 4px rgba(0,0,0,0.065);-moz-box-shadow:0 1px 4px rgba(0,0,0,0.065);box-shadow:0 1px 4px rgba(0,0,0,0.065)}.navbar-inner:before,.navbar-inner:after{display:table;line-height:0;content:""}.navbar-inner:after{clear:both}.navbar .container{width:auto}.nav-collapse.collapse{height:auto;overflow:visible}.navbar .brand{display:block;float:left;padding:10px 20px 10px;margin-left:-20px;font-size:20px;font-weight:200;color:#777;text-shadow:0 1px 0 #fff}.navbar .brand:hover,.navbar .brand:focus{text-decoration:none}.navbar-text{margin-bottom:0;line-height:40px;color:#777}.navbar-link{color:#777}.navbar-link:hover,.navbar-link:focus{color:#333}.navbar .divider-vertical{height:40px;margin:0 9px;border-right:1px solid #fff;border-left:1px solid #f2f2f2}.navbar .btn,.navbar .btn-group{margin-top:5px}.navbar .btn-group .btn,.navbar .input-prepend .btn,.navbar .input-append .btn,.navbar .input-prepend .btn-group,.navbar .input-append .btn-group{margin-top:0}.navbar-form{margin-bottom:0;*zoom:1}.navbar-form:before,.navbar-form:after{display:table;line-height:0;content:""}.navbar-form:after{clear:both}.navbar-form input,.navbar-form select,.navbar-form .radio,.navbar-form .checkbox{margin-top:5px}.navbar-form input,.navbar-form select,.navbar-form .btn{display:inline-block;margin-bottom:0}.navbar-form input[type="image"],.navbar-form input[type="checkbox"],.navbar-form input[type="radio"]{margin-top:3px}.navbar-form .input-append,.navbar-form .input-prepend{margin-top:5px;white-space:nowrap}.navbar-form .input-append input,.navbar-form .input-prepend input{margin-top:0}.navbar-search{position:relative;float:left;margin-top:5px;margin-bottom:0}.navbar-search .search-query{padding:4px 14px;margin-bottom:0;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;font-weight:normal;line-height:1;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px}.navbar-static-top{position:static;margin-bottom:0}.navbar-static-top .navbar-inner{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030;margin-bottom:0}.navbar-fixed-top .navbar-inner,.navbar-static-top .navbar-inner{border-width:0 0 1px}.navbar-fixed-bottom .navbar-inner{border-width:1px 0 0}.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding-right:0;padding-left:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px}.navbar-fixed-top{top:0}.navbar-fixed-top .navbar-inner,.navbar-static-top .navbar-inner{-webkit-box-shadow:0 1px 10px rgba(0,0,0,0.1);-moz-box-shadow:0 1px 10px rgba(0,0,0,0.1);box-shadow:0 1px 10px rgba(0,0,0,0.1)}.navbar-fixed-bottom{bottom:0}.navbar-fixed-bottom .navbar-inner{-webkit-box-shadow:0 -1px 10px rgba(0,0,0,0.1);-moz-box-shadow:0 -1px 10px rgba(0,0,0,0.1);box-shadow:0 -1px 10px rgba(0,0,0,0.1)}.navbar .nav{position:relative;left:0;display:block;float:left;margin:0 10px 0 0}.navbar .nav.pull-right{float:right;margin-right:0}.navbar .nav>li{float:left}.navbar .nav>li>a{float:none;padding:10px 15px 10px;color:#777;text-decoration:none;text-shadow:0 1px 0 #fff}.navbar .nav .dropdown-toggle .caret{margin-top:8px}.navbar .nav>li>a:focus,.navbar .nav>li>a:hover{color:#333;text-decoration:none;background-color:transparent}.navbar .nav>.active>a,.navbar .nav>.active>a:hover,.navbar .nav>.active>a:focus{color:#555;text-decoration:none;background-color:#e5e5e5;-webkit-box-shadow:inset 0 3px 8px rgba(0,0,0,0.125);-moz-box-shadow:inset 0 3px 8px rgba(0,0,0,0.125);box-shadow:inset 0 3px 8px rgba(0,0,0,0.125)}.navbar .btn-navbar{display:none;float:right;padding:7px 10px;margin-right:5px;margin-left:5px;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#ededed;*background-color:#e5e5e5;background-image:-moz-linear-gradient(top,#f2f2f2,#e5e5e5);background-image:-webkit-gradient(linear,0 0,0 100%,from(#f2f2f2),to(#e5e5e5));background-image:-webkit-linear-gradient(top,#f2f2f2,#e5e5e5);background-image:-o-linear-gradient(top,#f2f2f2,#e5e5e5);background-image:linear-gradient(to bottom,#f2f2f2,#e5e5e5);background-repeat:repeat-x;border-color:#e5e5e5 #e5e5e5 #bfbfbf;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2f2f2',endColorstr='#ffe5e5e5',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.075);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.075);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.075)}.navbar .btn-navbar:hover,.navbar .btn-navbar:focus,.navbar .btn-navbar:active,.navbar .btn-navbar.active,.navbar .btn-navbar.disabled,.navbar .btn-navbar[disabled]{color:#fff;background-color:#e5e5e5;*background-color:#d9d9d9}.navbar .btn-navbar:active,.navbar .btn-navbar.active{background-color:#ccc \9}.navbar .btn-navbar .icon-bar{display:block;width:18px;height:2px;background-color:#f5f5f5;-webkit-border-radius:1px;-moz-border-radius:1px;border-radius:1px;-webkit-box-shadow:0 1px 0 rgba(0,0,0,0.25);-moz-box-shadow:0 1px 0 rgba(0,0,0,0.25);box-shadow:0 1px 0 rgba(0,0,0,0.25)}.btn-navbar .icon-bar+.icon-bar{margin-top:3px}.navbar .nav>li>.dropdown-menu:before{position:absolute;top:-7px;left:9px;display:inline-block;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-left:7px solid transparent;border-bottom-color:rgba(0,0,0,0.2);content:''}.navbar .nav>li>.dropdown-menu:after{position:absolute;top:-6px;left:10px;display:inline-block;border-right:6px solid transparent;border-bottom:6px solid #fff;border-left:6px solid transparent;content:''}.navbar-fixed-bottom .nav>li>.dropdown-menu:before{top:auto;bottom:-7px;border-top:7px solid #ccc;border-bottom:0;border-top-color:rgba(0,0,0,0.2)}.navbar-fixed-bottom .nav>li>.dropdown-menu:after{top:auto;bottom:-6px;border-top:6px solid #fff;border-bottom:0}.navbar .nav li.dropdown>a:hover .caret,.navbar .nav li.dropdown>a:focus .caret{border-top-color:#333;border-bottom-color:#333}.navbar .nav li.dropdown.open>.dropdown-toggle,.navbar .nav li.dropdown.active>.dropdown-toggle,.navbar .nav li.dropdown.open.active>.dropdown-toggle{color:#555;background-color:#e5e5e5}.navbar .nav li.dropdown>.dropdown-toggle .caret{border-top-color:#777;border-bottom-color:#777}.navbar .nav li.dropdown.open>.dropdown-toggle .caret,.navbar .nav li.dropdown.active>.dropdown-toggle .caret,.navbar .nav li.dropdown.open.active>.dropdown-toggle .caret{border-top-color:#555;border-bottom-color:#555}.navbar .pull-right>li>.dropdown-menu,.navbar .nav>li>.dropdown-menu.pull-right{right:0;left:auto}.navbar .pull-right>li>.dropdown-menu:before,.navbar .nav>li>.dropdown-menu.pull-right:before{right:12px;left:auto}.navbar .pull-right>li>.dropdown-menu:after,.navbar .nav>li>.dropdown-menu.pull-right:after{right:13px;left:auto}.navbar .pull-right>li>.dropdown-menu .dropdown-menu,.navbar .nav>li>.dropdown-menu.pull-right .dropdown-menu{right:100%;left:auto;margin-right:-1px;margin-left:0;-webkit-border-radius:6px 0 6px 6px;-moz-border-radius:6px 0 6px 6px;border-radius:6px 0 6px 6px}.navbar-inverse .navbar-inner{background-color:#1b1b1b;background-image:-moz-linear-gradient(top,#222,#111);background-image:-webkit-gradient(linear,0 0,0 100%,from(#222),to(#111));background-image:-webkit-linear-gradient(top,#222,#111);background-image:-o-linear-gradient(top,#222,#111);background-image:linear-gradient(to bottom,#222,#111);background-repeat:repeat-x;border-color:#252525;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222',endColorstr='#ff111111',GradientType=0)}.navbar-inverse .brand,.navbar-inverse .nav>li>a{color:#999;text-shadow:0 -1px 0 rgba(0,0,0,0.25)}.navbar-inverse .brand:hover,.navbar-inverse .nav>li>a:hover,.navbar-inverse .brand:focus,.navbar-inverse .nav>li>a:focus{color:#fff}.navbar-inverse .brand{color:#999}.navbar-inverse .navbar-text{color:#999}.navbar-inverse .nav>li>a:focus,.navbar-inverse .nav>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .nav .active>a,.navbar-inverse .nav .active>a:hover,.navbar-inverse .nav .active>a:focus{color:#fff;background-color:#111}.navbar-inverse .navbar-link{color:#999}.navbar-inverse .navbar-link:hover,.navbar-inverse .navbar-link:focus{color:#fff}.navbar-inverse .divider-vertical{border-right-color:#222;border-left-color:#111}.navbar-inverse .nav li.dropdown.open>.dropdown-toggle,.navbar-inverse .nav li.dropdown.active>.dropdown-toggle,.navbar-inverse .nav li.dropdown.open.active>.dropdown-toggle{color:#fff;background-color:#111}.navbar-inverse .nav li.dropdown>a:hover .caret,.navbar-inverse .nav li.dropdown>a:focus .caret{border-top-color:#fff;border-bottom-color:#fff}.navbar-inverse .nav li.dropdown>.dropdown-toggle .caret{border-top-color:#999;border-bottom-color:#999}.navbar-inverse .nav li.dropdown.open>.dropdown-toggle .caret,.navbar-inverse .nav li.dropdown.active>.dropdown-toggle .caret,.navbar-inverse .nav li.dropdown.open.active>.dropdown-toggle .caret{border-top-color:#fff;border-bottom-color:#fff}.navbar-inverse .navbar-search .search-query{color:#fff;background-color:#515151;border-color:#111;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1),0 1px 0 rgba(255,255,255,0.15);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1),0 1px 0 rgba(255,255,255,0.15);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1),0 1px 0 rgba(255,255,255,0.15);-webkit-transition:none;-moz-transition:none;-o-transition:none;transition:none}.navbar-inverse .navbar-search .search-query:-moz-placeholder{color:#ccc}.navbar-inverse .navbar-search .search-query:-ms-input-placeholder{color:#ccc}.navbar-inverse .navbar-search .search-query::-webkit-input-placeholder{color:#ccc}.navbar-inverse .navbar-search .search-query:focus,.navbar-inverse .navbar-search .search-query.focused{padding:5px 15px;color:#333;text-shadow:0 1px 0 #fff;background-color:#fff;border:0;outline:0;-webkit-box-shadow:0 0 3px rgba(0,0,0,0.15);-moz-box-shadow:0 0 3px rgba(0,0,0,0.15);box-shadow:0 0 3px rgba(0,0,0,0.15)}.navbar-inverse .btn-navbar{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#0e0e0e;*background-color:#040404;background-image:-moz-linear-gradient(top,#151515,#040404);background-image:-webkit-gradient(linear,0 0,0 100%,from(#151515),to(#040404));background-image:-webkit-linear-gradient(top,#151515,#040404);background-image:-o-linear-gradient(top,#151515,#040404);background-image:linear-gradient(to bottom,#151515,#040404);background-repeat:repeat-x;border-color:#040404 #040404 #000;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff151515',endColorstr='#ff040404',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.navbar-inverse .btn-navbar:hover,.navbar-inverse .btn-navbar:focus,.navbar-inverse .btn-navbar:active,.navbar-inverse .btn-navbar.active,.navbar-inverse .btn-navbar.disabled,.navbar-inverse .btn-navbar[disabled]{color:#fff;background-color:#040404;*background-color:#000}.navbar-inverse .btn-navbar:active,.navbar-inverse .btn-navbar.active{background-color:#000 \9}.breadcrumb{padding:8px 15px;margin:0 0 20px;list-style:none;background-color:#f5f5f5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.breadcrumb>li{display:inline-block;*display:inline;text-shadow:0 1px 0 #fff;*zoom:1}.breadcrumb>li>.divider{padding:0 5px;color:#ccc}.breadcrumb>.active{color:#999}.pagination{margin:20px 0}.pagination ul{display:inline-block;*display:inline;margin-bottom:0;margin-left:0;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;*zoom:1;-webkit-box-shadow:0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:0 1px 2px rgba(0,0,0,0.05);box-shadow:0 1px 2px rgba(0,0,0,0.05)}.pagination ul>li{display:inline}.pagination ul>li>a,.pagination ul>li>span{float:left;padding:4px 12px;line-height:20px;text-decoration:none;background-color:#fff;border:1px solid #ddd;border-left-width:0}.pagination ul>li>a:hover,.pagination ul>li>a:focus,.pagination ul>.active>a,.pagination ul>.active>span{background-color:#f5f5f5}.pagination ul>.active>a,.pagination ul>.active>span{color:#999;cursor:default}.pagination ul>.disabled>span,.pagination ul>.disabled>a,.pagination ul>.disabled>a:hover,.pagination ul>.disabled>a:focus{color:#999;cursor:default;background-color:transparent}.pagination ul>li:first-child>a,.pagination ul>li:first-child>span{border-left-width:1px;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-bottomleft:4px;-moz-border-radius-topleft:4px}.pagination ul>li:last-child>a,.pagination ul>li:last-child>span{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-moz-border-radius-topright:4px;-moz-border-radius-bottomright:4px}.pagination-centered{text-align:center}.pagination-right{text-align:right}.pagination-large ul>li>a,.pagination-large ul>li>span{padding:11px 19px;font-size:17.5px}.pagination-large ul>li:first-child>a,.pagination-large ul>li:first-child>span{-webkit-border-bottom-left-radius:6px;border-bottom-left-radius:6px;-webkit-border-top-left-radius:6px;border-top-left-radius:6px;-moz-border-radius-bottomleft:6px;-moz-border-radius-topleft:6px}.pagination-large ul>li:last-child>a,.pagination-large ul>li:last-child>span{-webkit-border-top-right-radius:6px;border-top-right-radius:6px;-webkit-border-bottom-right-radius:6px;border-bottom-right-radius:6px;-moz-border-radius-topright:6px;-moz-border-radius-bottomright:6px}.pagination-mini ul>li:first-child>a,.pagination-small ul>li:first-child>a,.pagination-mini ul>li:first-child>span,.pagination-small ul>li:first-child>span{-webkit-border-bottom-left-radius:3px;border-bottom-left-radius:3px;-webkit-border-top-left-radius:3px;border-top-left-radius:3px;-moz-border-radius-bottomleft:3px;-moz-border-radius-topleft:3px}.pagination-mini ul>li:last-child>a,.pagination-small ul>li:last-child>a,.pagination-mini ul>li:last-child>span,.pagination-small ul>li:last-child>span{-webkit-border-top-right-radius:3px;border-top-right-radius:3px;-webkit-border-bottom-right-radius:3px;border-bottom-right-radius:3px;-moz-border-radius-topright:3px;-moz-border-radius-bottomright:3px}.pagination-small ul>li>a,.pagination-small ul>li>span{padding:2px 10px;font-size:11.9px}.pagination-mini ul>li>a,.pagination-mini ul>li>span{padding:0 6px;font-size:10.5px}.pager{margin:20px 0;text-align:center;list-style:none;*zoom:1}.pager:before,.pager:after{display:table;line-height:0;content:""}.pager:after{clear:both}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px}.pager li>a:hover,.pager li>a:focus{text-decoration:none;background-color:#f5f5f5}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:hover,.pager .disabled>a:focus,.pager .disabled>span{color:#999;cursor:default;background-color:#fff}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop,.modal-backdrop.fade.in{opacity:.8;filter:alpha(opacity=80)}.modal{position:fixed;top:10%;left:50%;z-index:1050;width:560px;margin-left:-280px;background-color:#fff;border:1px solid #999;border:1px solid rgba(0,0,0,0.3);*border:1px solid #999;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;outline:0;-webkit-box-shadow:0 3px 7px rgba(0,0,0,0.3);-moz-box-shadow:0 3px 7px rgba(0,0,0,0.3);box-shadow:0 3px 7px rgba(0,0,0,0.3);-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box}.modal.fade{top:-25%;-webkit-transition:opacity .3s linear,top .3s ease-out;-moz-transition:opacity .3s linear,top .3s ease-out;-o-transition:opacity .3s linear,top .3s ease-out;transition:opacity .3s linear,top .3s ease-out}.modal.fade.in{top:10%}.modal-header{padding:9px 15px;border-bottom:1px solid #eee}.modal-header .close{margin-top:2px}.modal-header h3{margin:0;line-height:30px}.modal-body{position:relative;max-height:400px;padding:15px;overflow-y:auto}.modal-form{margin-bottom:0}.modal-footer{padding:14px 15px 15px;margin-bottom:0;text-align:right;background-color:#f5f5f5;border-top:1px solid #ddd;-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;*zoom:1;-webkit-box-shadow:inset 0 1px 0 #fff;-moz-box-shadow:inset 0 1px 0 #fff;box-shadow:inset 0 1px 0 #fff}.modal-footer:before,.modal-footer:after{display:table;line-height:0;content:""}.modal-footer:after{clear:both}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.tooltip{position:absolute;z-index:1030;display:block;font-size:11px;line-height:1.4;opacity:0;filter:alpha(opacity=0);visibility:visible}.tooltip.in{opacity:.8;filter:alpha(opacity=80)}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip-inner{max-width:200px;padding:8px;color:#fff;text-align:center;text-decoration:none;background-color:#000;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-top-color:#000;border-width:5px 5px 0}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-right-color:#000;border-width:5px 5px 5px 0}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-left-color:#000;border-width:5px 0 5px 5px}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-bottom-color:#000;border-width:0 5px 5px}.popover{position:absolute;top:0;left:0;z-index:1010;display:none;max-width:400px;padding:1px;text-align:left;white-space:normal;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{padding:8px 14px;margin:0;font-size:14px;font-weight:normal;line-height:18px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;-webkit-border-radius:5px 5px 0 0;-moz-border-radius:5px 5px 0 0;border-radius:5px 5px 0 0}.popover-title:empty{display:none}.popover-content{padding:9px 14px}.popover .arrow,.popover .arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover .arrow{border-width:11px}.popover .arrow:after{border-width:10px;content:""}.popover.top .arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,0.25);border-bottom-width:0}.popover.top .arrow:after{bottom:1px;margin-left:-10px;border-top-color:#fff;border-bottom-width:0}.popover.right .arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,0.25);border-left-width:0}.popover.right .arrow:after{bottom:-10px;left:1px;border-right-color:#fff;border-left-width:0}.popover.bottom .arrow{top:-11px;left:50%;margin-left:-11px;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,0.25);border-top-width:0}.popover.bottom .arrow:after{top:1px;margin-left:-10px;border-bottom-color:#fff;border-top-width:0}.popover.left .arrow{top:50%;right:-11px;margin-top:-11px;border-left-color:#999;border-left-color:rgba(0,0,0,0.25);border-right-width:0}.popover.left .arrow:after{right:1px;bottom:-10px;border-left-color:#fff;border-right-width:0}.thumbnails{margin-left:-20px;list-style:none;*zoom:1}.thumbnails:before,.thumbnails:after{display:table;line-height:0;content:""}.thumbnails:after{clear:both}.row-fluid .thumbnails{margin-left:0}.thumbnails>li{float:left;margin-bottom:20px;margin-left:20px}.thumbnail{display:block;padding:4px;line-height:20px;border:1px solid #ddd;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:0 1px 3px rgba(0,0,0,0.055);-moz-box-shadow:0 1px 3px rgba(0,0,0,0.055);box-shadow:0 1px 3px rgba(0,0,0,0.055);-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}a.thumbnail:hover,a.thumbnail:focus{border-color:#08c;-webkit-box-shadow:0 1px 4px rgba(0,105,214,0.25);-moz-box-shadow:0 1px 4px rgba(0,105,214,0.25);box-shadow:0 1px 4px rgba(0,105,214,0.25)}.thumbnail>img{display:block;max-width:100%;margin-right:auto;margin-left:auto}.thumbnail .caption{padding:9px;color:#555}.media,.media-body{overflow:hidden;*overflow:visible;zoom:1}.media,.media .media{margin-top:15px}.media:first-child{margin-top:0}.media-object{display:block}.media-heading{margin:0 0 5px}.media>.pull-left{margin-right:10px}.media>.pull-right{margin-left:10px}.media-list{margin-left:0;list-style:none}.label,.badge{display:inline-block;padding:2px 4px;font-size:11.844px;font-weight:bold;line-height:14px;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);white-space:nowrap;vertical-align:baseline;background-color:#999}.label{-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.badge{padding-right:9px;padding-left:9px;-webkit-border-radius:9px;-moz-border-radius:9px;border-radius:9px}.label:empty,.badge:empty{display:none}a.label:hover,a.label:focus,a.badge:hover,a.badge:focus{color:#fff;text-decoration:none;cursor:pointer}.label-important,.badge-important{background-color:#b94a48}.label-important[href],.badge-important[href]{background-color:#953b39}.label-warning,.badge-warning{background-color:#f89406}.label-warning[href],.badge-warning[href]{background-color:#c67605}.label-success,.badge-success{background-color:#468847}.label-success[href],.badge-success[href]{background-color:#356635}.label-info,.badge-info{background-color:#3a87ad}.label-info[href],.badge-info[href]{background-color:#2d6987}.label-inverse,.badge-inverse{background-color:#333}.label-inverse[href],.badge-inverse[href]{background-color:#1a1a1a}.btn .label,.btn .badge{position:relative;top:-1px}.btn-mini .label,.btn-mini .badge{top:0}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-moz-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-ms-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:0 0}to{background-position:40px 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f7f7f7;background-image:-moz-linear-gradient(top,#f5f5f5,#f9f9f9);background-image:-webkit-gradient(linear,0 0,0 100%,from(#f5f5f5),to(#f9f9f9));background-image:-webkit-linear-gradient(top,#f5f5f5,#f9f9f9);background-image:-o-linear-gradient(top,#f5f5f5,#f9f9f9);background-image:linear-gradient(to bottom,#f5f5f5,#f9f9f9);background-repeat:repeat-x;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5',endColorstr='#fff9f9f9',GradientType=0);-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1)}.progress .bar{float:left;width:0;height:100%;font-size:12px;color:#fff;text-align:center;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#0e90d2;background-image:-moz-linear-gradient(top,#149bdf,#0480be);background-image:-webkit-gradient(linear,0 0,0 100%,from(#149bdf),to(#0480be));background-image:-webkit-linear-gradient(top,#149bdf,#0480be);background-image:-o-linear-gradient(top,#149bdf,#0480be);background-image:linear-gradient(to bottom,#149bdf,#0480be);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff149bdf',endColorstr='#ff0480be',GradientType=0);-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-moz-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-transition:width .6s ease;-moz-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress .bar+.bar{-webkit-box-shadow:inset 1px 0 0 rgba(0,0,0,0.15),inset 0 -1px 0 rgba(0,0,0,0.15);-moz-box-shadow:inset 1px 0 0 rgba(0,0,0,0.15),inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 1px 0 0 rgba(0,0,0,0.15),inset 0 -1px 0 rgba(0,0,0,0.15)}.progress-striped .bar{background-color:#149bdf;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;-moz-background-size:40px 40px;-o-background-size:40px 40px;background-size:40px 40px}.progress.active .bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-moz-animation:progress-bar-stripes 2s linear infinite;-ms-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-danger .bar,.progress .bar-danger{background-color:#dd514c;background-image:-moz-linear-gradient(top,#ee5f5b,#c43c35);background-image:-webkit-gradient(linear,0 0,0 100%,from(#ee5f5b),to(#c43c35));background-image:-webkit-linear-gradient(top,#ee5f5b,#c43c35);background-image:-o-linear-gradient(top,#ee5f5b,#c43c35);background-image:linear-gradient(to bottom,#ee5f5b,#c43c35);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b',endColorstr='#ffc43c35',GradientType=0)}.progress-danger.progress-striped .bar,.progress-striped .bar-danger{background-color:#ee5f5b;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-success .bar,.progress .bar-success{background-color:#5eb95e;background-image:-moz-linear-gradient(top,#62c462,#57a957);background-image:-webkit-gradient(linear,0 0,0 100%,from(#62c462),to(#57a957));background-image:-webkit-linear-gradient(top,#62c462,#57a957);background-image:-o-linear-gradient(top,#62c462,#57a957);background-image:linear-gradient(to bottom,#62c462,#57a957);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462',endColorstr='#ff57a957',GradientType=0)}.progress-success.progress-striped .bar,.progress-striped .bar-success{background-color:#62c462;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-info .bar,.progress .bar-info{background-color:#4bb1cf;background-image:-moz-linear-gradient(top,#5bc0de,#339bb9);background-image:-webkit-gradient(linear,0 0,0 100%,from(#5bc0de),to(#339bb9));background-image:-webkit-linear-gradient(top,#5bc0de,#339bb9);background-image:-o-linear-gradient(top,#5bc0de,#339bb9);background-image:linear-gradient(to bottom,#5bc0de,#339bb9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de',endColorstr='#ff339bb9',GradientType=0)}.progress-info.progress-striped .bar,.progress-striped .bar-info{background-color:#5bc0de;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-warning .bar,.progress .bar-warning{background-color:#faa732;background-image:-moz-linear-gradient(top,#fbb450,#f89406);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fbb450),to(#f89406));background-image:-webkit-linear-gradient(top,#fbb450,#f89406);background-image:-o-linear-gradient(top,#fbb450,#f89406);background-image:linear-gradient(to bottom,#fbb450,#f89406);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450',endColorstr='#fff89406',GradientType=0)}.progress-warning.progress-striped .bar,.progress-striped .bar-warning{background-color:#fbb450;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.accordion{margin-bottom:20px}.accordion-group{margin-bottom:2px;border:1px solid #e5e5e5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.accordion-heading{border-bottom:0}.accordion-heading .accordion-toggle{display:block;padding:8px 15px}.accordion-toggle{cursor:pointer}.accordion-inner{padding:9px 15px;border-top:1px solid #e5e5e5}.carousel{position:relative;margin-bottom:20px;line-height:1}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-moz-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>img,.carousel-inner>.item>a>img{display:block;line-height:1}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:40%;left:15px;width:40px;height:40px;margin-top:-20px;font-size:60px;font-weight:100;line-height:30px;color:#fff;text-align:center;background:#222;border:3px solid #fff;-webkit-border-radius:23px;-moz-border-radius:23px;border-radius:23px;opacity:.5;filter:alpha(opacity=50)}.carousel-control.right{right:15px;left:auto}.carousel-control:hover,.carousel-control:focus{color:#fff;text-decoration:none;opacity:.9;filter:alpha(opacity=90)}.carousel-indicators{position:absolute;top:15px;right:15px;z-index:5;margin:0;list-style:none}.carousel-indicators li{display:block;float:left;width:10px;height:10px;margin-left:5px;text-indent:-999px;background-color:#ccc;background-color:rgba(255,255,255,0.25);border-radius:5px}.carousel-indicators .active{background-color:#fff}.carousel-caption{position:absolute;right:0;bottom:0;left:0;padding:15px;background:#333;background:rgba(0,0,0,0.75)}.carousel-caption h4,.carousel-caption p{line-height:20px;color:#fff}.carousel-caption h4{margin:0 0 5px}.carousel-caption p{margin-bottom:0}.hero-unit{padding:60px;margin-bottom:30px;font-size:18px;font-weight:200;line-height:30px;color:inherit;background-color:#eee;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.hero-unit h1{margin-bottom:0;font-size:60px;line-height:1;letter-spacing:-1px;color:inherit}.hero-unit li{line-height:30px}.pull-right{float:right}.pull-left{float:left}.hide{display:none}.show{display:block}.invisible{visibility:hidden}.affix{position:fixed} + * Bootstrap v3.3.6 (http://getbootstrap.com) + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + *//*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0;font-size:2em}mark{color:#000;background:#ff0}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{height:0;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{margin:0;font:inherit;color:inherit}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}input{line-height:normal}input[type=checkbox],input[type=radio]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{padding:.35em .625em .75em;margin:0 2px;border:1px solid silver}legend{padding:0;border:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-spacing:0;border-collapse:collapse}td,th{padding:0}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{*,:after,:before{color:#000!important;text-shadow:none!important;background:0 0!important;-webkit-box-shadow:none!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="javascript:"]:after,a[href^="#"]:after{content:""}blockquote,pre{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #ddd!important}}@font-face{font-family:'Glyphicons Halflings';src:url(../fonts/glyphicons-halflings-regular.eot);src:url(../fonts/glyphicons-halflings-regular.eot?#iefix) format('embedded-opentype'),url(../fonts/glyphicons-halflings-regular.woff2) format('woff2'),url(../fonts/glyphicons-halflings-regular.woff) format('woff'),url(../fonts/glyphicons-halflings-regular.ttf) format('truetype'),url(../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular) format('svg')}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\002a"}.glyphicon-plus:before{content:"\002b"}.glyphicon-eur:before,.glyphicon-euro:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-bookmark:before{content:"\e044"}.glyphicon-print:before{content:"\e045"}.glyphicon-camera:before{content:"\e046"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-fire:before{content:"\e104"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-calendar:before{content:"\e109"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-bell:before{content:"\e123"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-wrench:before{content:"\e136"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-briefcase:before{content:"\e139"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-paperclip:before{content:"\e142"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-pushpin:before{content:"\e146"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}.glyphicon-cd:before{content:"\e201"}.glyphicon-save-file:before{content:"\e202"}.glyphicon-open-file:before{content:"\e203"}.glyphicon-level-up:before{content:"\e204"}.glyphicon-copy:before{content:"\e205"}.glyphicon-paste:before{content:"\e206"}.glyphicon-alert:before{content:"\e209"}.glyphicon-equalizer:before{content:"\e210"}.glyphicon-king:before{content:"\e211"}.glyphicon-queen:before{content:"\e212"}.glyphicon-pawn:before{content:"\e213"}.glyphicon-bishop:before{content:"\e214"}.glyphicon-knight:before{content:"\e215"}.glyphicon-baby-formula:before{content:"\e216"}.glyphicon-tent:before{content:"\26fa"}.glyphicon-blackboard:before{content:"\e218"}.glyphicon-bed:before{content:"\e219"}.glyphicon-apple:before{content:"\f8ff"}.glyphicon-erase:before{content:"\e221"}.glyphicon-hourglass:before{content:"\231b"}.glyphicon-lamp:before{content:"\e223"}.glyphicon-duplicate:before{content:"\e224"}.glyphicon-piggy-bank:before{content:"\e225"}.glyphicon-scissors:before{content:"\e226"}.glyphicon-bitcoin:before{content:"\e227"}.glyphicon-btc:before{content:"\e227"}.glyphicon-xbt:before{content:"\e227"}.glyphicon-yen:before{content:"\00a5"}.glyphicon-jpy:before{content:"\00a5"}.glyphicon-ruble:before{content:"\20bd"}.glyphicon-rub:before{content:"\20bd"}.glyphicon-scale:before{content:"\e230"}.glyphicon-ice-lolly:before{content:"\e231"}.glyphicon-ice-lolly-tasted:before{content:"\e232"}.glyphicon-education:before{content:"\e233"}.glyphicon-option-horizontal:before{content:"\e234"}.glyphicon-option-vertical:before{content:"\e235"}.glyphicon-menu-hamburger:before{content:"\e236"}.glyphicon-modal-window:before{content:"\e237"}.glyphicon-oil:before{content:"\e238"}.glyphicon-grain:before{content:"\e239"}.glyphicon-sunglasses:before{content:"\e240"}.glyphicon-text-size:before{content:"\e241"}.glyphicon-text-color:before{content:"\e242"}.glyphicon-text-background:before{content:"\e243"}.glyphicon-object-align-top:before{content:"\e244"}.glyphicon-object-align-bottom:before{content:"\e245"}.glyphicon-object-align-horizontal:before{content:"\e246"}.glyphicon-object-align-left:before{content:"\e247"}.glyphicon-object-align-vertical:before{content:"\e248"}.glyphicon-object-align-right:before{content:"\e249"}.glyphicon-triangle-right:before{content:"\e250"}.glyphicon-triangle-left:before{content:"\e251"}.glyphicon-triangle-bottom:before{content:"\e252"}.glyphicon-triangle-top:before{content:"\e253"}.glyphicon-console:before{content:"\e254"}.glyphicon-superscript:before{content:"\e255"}.glyphicon-subscript:before{content:"\e256"}.glyphicon-menu-left:before{content:"\e257"}.glyphicon-menu-right:before{content:"\e258"}.glyphicon-menu-down:before{content:"\e259"}.glyphicon-menu-up:before{content:"\e260"}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}:after,:before{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333;background-color:#fff}button,input,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#337ab7;text-decoration:none}a:focus,a:hover{color:#23527c;text-decoration:underline}a:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.carousel-inner>.item>a>img,.carousel-inner>.item>img,.img-responsive,.thumbnail a>img,.thumbnail>img{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{display:inline-block;max-width:100%;height:auto;padding:4px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}[role=button]{cursor:pointer}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{font-family:inherit;font-weight:500;line-height:1.1;color:inherit}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-weight:400;line-height:1;color:#777}.h1,.h2,.h3,h1,h2,h3{margin-top:20px;margin-bottom:10px}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small{font-size:65%}.h4,.h5,.h6,h4,h5,h6{margin-top:10px;margin-bottom:10px}.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-size:75%}.h1,h1{font-size:36px}.h2,h2{font-size:30px}.h3,h3{font-size:24px}.h4,h4{font-size:18px}.h5,h5{font-size:14px}.h6,h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:300;line-height:1.4}@media (min-width:768px){.lead{font-size:21px}}.small,small{font-size:85%}.mark,mark{padding:.2em;background-color:#fcf8e3}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#777}.text-primary{color:#337ab7}a.text-primary:focus,a.text-primary:hover{color:#286090}.text-success{color:#3c763d}a.text-success:focus,a.text-success:hover{color:#2b542c}.text-info{color:#31708f}a.text-info:focus,a.text-info:hover{color:#245269}.text-warning{color:#8a6d3b}a.text-warning:focus,a.text-warning:hover{color:#66512c}.text-danger{color:#a94442}a.text-danger:focus,a.text-danger:hover{color:#843534}.bg-primary{color:#fff;background-color:#337ab7}a.bg-primary:focus,a.bg-primary:hover{background-color:#286090}.bg-success{background-color:#dff0d8}a.bg-success:focus,a.bg-success:hover{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:focus,a.bg-info:hover{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:focus,a.bg-warning:hover{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:focus,a.bg-danger:hover{background-color:#e4b9b9}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}ol,ul{margin-top:0;margin-bottom:10px}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;margin-left:-5px;list-style:none}.list-inline>li{display:inline-block;padding-right:5px;padding-left:5px}dl{margin-top:0;margin-bottom:20px}dd,dt{line-height:1.42857143}dt{font-weight:700}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[data-original-title],abbr[title]{cursor:help;border-bottom:1px dotted #777}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eee}blockquote ol:last-child,blockquote p:last-child,blockquote ul:last-child{margin-bottom:0}blockquote .small,blockquote footer,blockquote small{display:block;font-size:80%;line-height:1.42857143;color:#777}blockquote .small:before,blockquote footer:before,blockquote small:before{content:'\2014 \00A0'}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;text-align:right;border-right:5px solid #eee;border-left:0}.blockquote-reverse .small:before,.blockquote-reverse footer:before,.blockquote-reverse small:before,blockquote.pull-right .small:before,blockquote.pull-right footer:before,blockquote.pull-right small:before{content:''}.blockquote-reverse .small:after,.blockquote-reverse footer:after,.blockquote-reverse small:after,blockquote.pull-right .small:after,blockquote.pull-right footer:after,blockquote.pull-right small:after{content:'\00A0 \2014'}address{margin-bottom:20px;font-style:normal;line-height:1.42857143}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:4px}kbd{padding:2px 4px;font-size:90%;color:#fff;background-color:#333;border-radius:3px;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.25);box-shadow:inset 0 -1px 0 rgba(0,0,0,.25)}kbd kbd{padding:0;font-size:100%;font-weight:700;-webkit-box-shadow:none;box-shadow:none}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;color:#333;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.container-fluid{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{margin-right:-15px;margin-left:-15px}.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{position:relative;min-height:1px;padding-right:15px;padding-left:15px}.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0}@media (min-width:768px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0}}@media (min-width:992px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0}}@media (min-width:1200px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0}}table{background-color:transparent}caption{padding-top:8px;padding-bottom:8px;color:#777;text-align:left}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>tbody>tr>td,.table>tbody>tr>th,.table>tfoot>tr>td,.table>tfoot>tr>th,.table>thead>tr>td,.table>thead>tr>th{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #ddd}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.table>caption+thead>tr:first-child>td,.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>td,.table>thead:first-child>tr:first-child>th{border-top:0}.table>tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed>tbody>tr>td,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>td,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>thead>tr>th{padding:5px}.table-bordered{border:1px solid #ddd}.table-bordered>tbody>tr>td,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>td,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border:1px solid #ddd}.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border-bottom-width:2px}.table-striped>tbody>tr:nth-of-type(odd){background-color:#f9f9f9}.table-hover>tbody>tr:hover{background-color:#f5f5f5}table col[class*=col-]{position:static;display:table-column;float:none}table td[class*=col-],table th[class*=col-]{position:static;display:table-cell;float:none}.table>tbody>tr.active>td,.table>tbody>tr.active>th,.table>tbody>tr>td.active,.table>tbody>tr>th.active,.table>tfoot>tr.active>td,.table>tfoot>tr.active>th,.table>tfoot>tr>td.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>thead>tr.active>th,.table>thead>tr>td.active,.table>thead>tr>th.active{background-color:#f5f5f5}.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr.active:hover>th,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover{background-color:#e8e8e8}.table>tbody>tr.success>td,.table>tbody>tr.success>th,.table>tbody>tr>td.success,.table>tbody>tr>th.success,.table>tfoot>tr.success>td,.table>tfoot>tr.success>th,.table>tfoot>tr>td.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>thead>tr.success>th,.table>thead>tr>td.success,.table>thead>tr>th.success{background-color:#dff0d8}.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr.success:hover>th,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover{background-color:#d0e9c6}.table>tbody>tr.info>td,.table>tbody>tr.info>th,.table>tbody>tr>td.info,.table>tbody>tr>th.info,.table>tfoot>tr.info>td,.table>tfoot>tr.info>th,.table>tfoot>tr>td.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>thead>tr.info>th,.table>thead>tr>td.info,.table>thead>tr>th.info{background-color:#d9edf7}.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr.info:hover>th,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover{background-color:#c4e3f3}.table>tbody>tr.warning>td,.table>tbody>tr.warning>th,.table>tbody>tr>td.warning,.table>tbody>tr>th.warning,.table>tfoot>tr.warning>td,.table>tfoot>tr.warning>th,.table>tfoot>tr>td.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>thead>tr.warning>th,.table>thead>tr>td.warning,.table>thead>tr>th.warning{background-color:#fcf8e3}.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr.warning:hover>th,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover{background-color:#faf2cc}.table>tbody>tr.danger>td,.table>tbody>tr.danger>th,.table>tbody>tr>td.danger,.table>tbody>tr>th.danger,.table>tfoot>tr.danger>td,.table>tfoot>tr.danger>th,.table>tfoot>tr>td.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>thead>tr.danger>th,.table>thead>tr>td.danger,.table>thead>tr>th.danger{background-color:#f2dede}.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr.danger:hover>th,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover{background-color:#ebcccc}.table-responsive{min-height:.01%;overflow-x:auto}@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>td,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>thead>tr>th{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:700}input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=checkbox],input[type=radio]{margin:4px 0 0;margin-top:1px\9;line-height:normal}input[type=file]{display:block}input[type=range]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type=file]:focus,input[type=checkbox]:focus,input[type=radio]:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:7px;font-size:14px;line-height:1.42857143;color:#555}.form-control{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.42857143;color:#555;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075);-webkit-transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.form-control::-moz-placeholder{color:#999;opacity:1}.form-control:-ms-input-placeholder{color:#999}.form-control::-webkit-input-placeholder{color:#999}.form-control::-ms-expand{background-color:transparent;border:0}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{background-color:#eee;opacity:1}.form-control[disabled],fieldset[disabled] .form-control{cursor:not-allowed}textarea.form-control{height:auto}input[type=search]{-webkit-appearance:none}@media screen and (-webkit-min-device-pixel-ratio:0){input[type=date].form-control,input[type=time].form-control,input[type=datetime-local].form-control,input[type=month].form-control{line-height:34px}.input-group-sm input[type=date],.input-group-sm input[type=time],.input-group-sm input[type=datetime-local],.input-group-sm input[type=month],input[type=date].input-sm,input[type=time].input-sm,input[type=datetime-local].input-sm,input[type=month].input-sm{line-height:30px}.input-group-lg input[type=date],.input-group-lg input[type=time],.input-group-lg input[type=datetime-local],.input-group-lg input[type=month],input[type=date].input-lg,input[type=time].input-lg,input[type=datetime-local].input-lg,input[type=month].input-lg{line-height:46px}}.form-group{margin-bottom:15px}.checkbox,.radio{position:relative;display:block;margin-top:10px;margin-bottom:10px}.checkbox label,.radio label{min-height:20px;padding-left:20px;margin-bottom:0;font-weight:400;cursor:pointer}.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox],.radio input[type=radio],.radio-inline input[type=radio]{position:absolute;margin-top:4px\9;margin-left:-20px}.checkbox+.checkbox,.radio+.radio{margin-top:-5px}.checkbox-inline,.radio-inline{position:relative;display:inline-block;padding-left:20px;margin-bottom:0;font-weight:400;vertical-align:middle;cursor:pointer}.checkbox-inline+.checkbox-inline,.radio-inline+.radio-inline{margin-top:0;margin-left:10px}fieldset[disabled] input[type=checkbox],fieldset[disabled] input[type=radio],input[type=checkbox].disabled,input[type=checkbox][disabled],input[type=radio].disabled,input[type=radio][disabled]{cursor:not-allowed}.checkbox-inline.disabled,.radio-inline.disabled,fieldset[disabled] .checkbox-inline,fieldset[disabled] .radio-inline{cursor:not-allowed}.checkbox.disabled label,.radio.disabled label,fieldset[disabled] .checkbox label,fieldset[disabled] .radio label{cursor:not-allowed}.form-control-static{min-height:34px;padding-top:7px;padding-bottom:7px;margin-bottom:0}.form-control-static.input-lg,.form-control-static.input-sm{padding-right:0;padding-left:0}.input-sm{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm{height:30px;line-height:30px}select[multiple].input-sm,textarea.input-sm{height:auto}.form-group-sm .form-control{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.form-group-sm select.form-control{height:30px;line-height:30px}.form-group-sm select[multiple].form-control,.form-group-sm textarea.form-control{height:auto}.form-group-sm .form-control-static{height:30px;min-height:32px;padding:6px 10px;font-size:12px;line-height:1.5}.input-lg{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-lg{height:46px;line-height:46px}select[multiple].input-lg,textarea.input-lg{height:auto}.form-group-lg .form-control{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.form-group-lg select.form-control{height:46px;line-height:46px}.form-group-lg select[multiple].form-control,.form-group-lg textarea.form-control{height:auto}.form-group-lg .form-control-static{height:46px;min-height:38px;padding:11px 16px;font-size:18px;line-height:1.3333333}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:34px;height:34px;line-height:34px;text-align:center;pointer-events:none}.form-group-lg .form-control+.form-control-feedback,.input-group-lg+.form-control-feedback,.input-lg+.form-control-feedback{width:46px;height:46px;line-height:46px}.form-group-sm .form-control+.form-control-feedback,.input-group-sm+.form-control-feedback,.input-sm+.form-control-feedback{width:30px;height:30px;line-height:30px}.has-success .checkbox,.has-success .checkbox-inline,.has-success .control-label,.has-success .help-block,.has-success .radio,.has-success .radio-inline,.has-success.checkbox label,.has-success.checkbox-inline label,.has-success.radio label,.has-success.radio-inline label{color:#3c763d}.has-success .form-control{border-color:#3c763d;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168}.has-success .input-group-addon{color:#3c763d;background-color:#dff0d8;border-color:#3c763d}.has-success .form-control-feedback{color:#3c763d}.has-warning .checkbox,.has-warning .checkbox-inline,.has-warning .control-label,.has-warning .help-block,.has-warning .radio,.has-warning .radio-inline,.has-warning.checkbox label,.has-warning.checkbox-inline label,.has-warning.radio label,.has-warning.radio-inline label{color:#8a6d3b}.has-warning .form-control{border-color:#8a6d3b;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-warning .form-control:focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b}.has-warning .input-group-addon{color:#8a6d3b;background-color:#fcf8e3;border-color:#8a6d3b}.has-warning .form-control-feedback{color:#8a6d3b}.has-error .checkbox,.has-error .checkbox-inline,.has-error .control-label,.has-error .help-block,.has-error .radio,.has-error .radio-inline,.has-error.checkbox label,.has-error.checkbox-inline label,.has-error.radio label,.has-error.radio-inline label{color:#a94442}.has-error .form-control{border-color:#a94442;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-error .form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483}.has-error .input-group-addon{color:#a94442;background-color:#f2dede;border-color:#a94442}.has-error .form-control-feedback{color:#a94442}.has-feedback label~.form-control-feedback{top:25px}.has-feedback label.sr-only~.form-control-feedback{top:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media (min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-static{display:inline-block}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .form-control,.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .checkbox,.form-inline .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .checkbox label,.form-inline .radio label{padding-left:0}.form-inline .checkbox input[type=checkbox],.form-inline .radio input[type=radio]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .checkbox,.form-horizontal .checkbox-inline,.form-horizontal .radio,.form-horizontal .radio-inline{padding-top:7px;margin-top:0;margin-bottom:0}.form-horizontal .checkbox,.form-horizontal .radio{min-height:27px}.form-horizontal .form-group{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.form-horizontal .control-label{padding-top:7px;margin-bottom:0;text-align:right}}.form-horizontal .has-feedback .form-control-feedback{right:15px}@media (min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:11px;font-size:18px}}@media (min-width:768px){.form-horizontal .form-group-sm .control-label{padding-top:6px;font-size:12px}}.btn{display:inline-block;padding:6px 12px;margin-bottom:0;font-size:14px;font-weight:400;line-height:1.42857143;text-align:center;white-space:nowrap;vertical-align:middle;-ms-touch-action:manipulation;touch-action:manipulation;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-image:none;border:1px solid transparent;border-radius:4px}.btn.active.focus,.btn.active:focus,.btn.focus,.btn:active.focus,.btn:active:focus,.btn:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn.focus,.btn:focus,.btn:hover{color:#333;text-decoration:none}.btn.active,.btn:active{background-image:none;outline:0;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{cursor:not-allowed;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none;opacity:.65}a.btn.disabled,fieldset[disabled] a.btn{pointer-events:none}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default.focus,.btn-default:focus{color:#333;background-color:#e6e6e6;border-color:#8c8c8c}.btn-default:hover{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active.focus,.btn-default.active:focus,.btn-default.active:hover,.btn-default:active.focus,.btn-default:active:focus,.btn-default:active:hover,.open>.dropdown-toggle.btn-default.focus,.open>.dropdown-toggle.btn-default:focus,.open>.dropdown-toggle.btn-default:hover{color:#333;background-color:#d4d4d4;border-color:#8c8c8c}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{background-image:none}.btn-default.disabled.focus,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled].focus,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#fff;border-color:#ccc}.btn-default .badge{color:#fff;background-color:#333}.btn-primary{color:#fff;background-color:#337ab7;border-color:#2e6da4}.btn-primary.focus,.btn-primary:focus{color:#fff;background-color:#286090;border-color:#122b40}.btn-primary:hover{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active.focus,.btn-primary.active:focus,.btn-primary.active:hover,.btn-primary:active.focus,.btn-primary:active:focus,.btn-primary:active:hover,.open>.dropdown-toggle.btn-primary.focus,.open>.dropdown-toggle.btn-primary:focus,.open>.dropdown-toggle.btn-primary:hover{color:#fff;background-color:#204d74;border-color:#122b40}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled.focus,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled].focus,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#337ab7;border-color:#2e6da4}.btn-primary .badge{color:#337ab7;background-color:#fff}.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.btn-success.focus,.btn-success:focus{color:#fff;background-color:#449d44;border-color:#255625}.btn-success:hover{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active.focus,.btn-success.active:focus,.btn-success.active:hover,.btn-success:active.focus,.btn-success:active:focus,.btn-success:active:hover,.open>.dropdown-toggle.btn-success.focus,.open>.dropdown-toggle.btn-success:focus,.open>.dropdown-toggle.btn-success:hover{color:#fff;background-color:#398439;border-color:#255625}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{background-image:none}.btn-success.disabled.focus,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled].focus,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{background-color:#5cb85c;border-color:#4cae4c}.btn-success .badge{color:#5cb85c;background-color:#fff}.btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da}.btn-info.focus,.btn-info:focus{color:#fff;background-color:#31b0d5;border-color:#1b6d85}.btn-info:hover{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active.focus,.btn-info.active:focus,.btn-info.active:hover,.btn-info:active.focus,.btn-info:active:focus,.btn-info:active:hover,.open>.dropdown-toggle.btn-info.focus,.open>.dropdown-toggle.btn-info:focus,.open>.dropdown-toggle.btn-info:hover{color:#fff;background-color:#269abc;border-color:#1b6d85}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{background-image:none}.btn-info.disabled.focus,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled].focus,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{background-color:#5bc0de;border-color:#46b8da}.btn-info .badge{color:#5bc0de;background-color:#fff}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236}.btn-warning.focus,.btn-warning:focus{color:#fff;background-color:#ec971f;border-color:#985f0d}.btn-warning:hover{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active.focus,.btn-warning.active:focus,.btn-warning.active:hover,.btn-warning:active.focus,.btn-warning:active:focus,.btn-warning:active:hover,.open>.dropdown-toggle.btn-warning.focus,.open>.dropdown-toggle.btn-warning:focus,.open>.dropdown-toggle.btn-warning:hover{color:#fff;background-color:#d58512;border-color:#985f0d}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled.focus,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled].focus,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{background-color:#f0ad4e;border-color:#eea236}.btn-warning .badge{color:#f0ad4e;background-color:#fff}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a}.btn-danger.focus,.btn-danger:focus{color:#fff;background-color:#c9302c;border-color:#761c19}.btn-danger:hover{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active.focus,.btn-danger.active:focus,.btn-danger.active:hover,.btn-danger:active.focus,.btn-danger:active:focus,.btn-danger:active:hover,.open>.dropdown-toggle.btn-danger.focus,.open>.dropdown-toggle.btn-danger:focus,.open>.dropdown-toggle.btn-danger:hover{color:#fff;background-color:#ac2925;border-color:#761c19}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled.focus,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled].focus,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-link{font-weight:400;color:#337ab7;border-radius:0}.btn-link,.btn-link.active,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:active,.btn-link:focus,.btn-link:hover{border-color:transparent}.btn-link:focus,.btn-link:hover{color:#23527c;text-decoration:underline;background-color:transparent}.btn-link[disabled]:focus,.btn-link[disabled]:hover,fieldset[disabled] .btn-link:focus,fieldset[disabled] .btn-link:hover{color:#777;text-decoration:none}.btn-group-lg>.btn,.btn-lg{padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.btn-group-sm>.btn,.btn-sm{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-group-xs>.btn,.btn-xs{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}tr.collapse.in{display:table-row}tbody.collapse.in{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition-timing-function:ease;-o-transition-timing-function:ease;transition-timing-function:ease;-webkit-transition-duration:.35s;-o-transition-duration:.35s;transition-duration:.35s;-webkit-transition-property:height,visibility;-o-transition-property:height,visibility;transition-property:height,visibility}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px dashed;border-top:4px solid\9;border-right:4px solid transparent;border-left:4px solid transparent}.dropdown,.dropup{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;font-size:14px;text-align:left;list-style:none;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px rgba(0,0,0,.175)}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.42857143;color:#333;white-space:nowrap}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{color:#262626;text-decoration:none;background-color:#f5f5f5}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{color:#fff;text-decoration:none;background-color:#337ab7;outline:0}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{color:#777}.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{text-decoration:none;cursor:not-allowed;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{right:0;left:auto}.dropdown-menu-left{right:auto;left:0}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.42857143;color:#777;white-space:nowrap}.dropdown-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{content:"";border-top:0;border-bottom:4px dashed;border-bottom:4px solid\9}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px}@media (min-width:768px){.navbar-right .dropdown-menu{right:0;left:auto}.navbar-right .dropdown-menu-left{right:auto;left:0}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;float:left}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:2}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar .btn,.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-bottom-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-right:8px;padding-left:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-right:12px;padding-left:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-top-left-radius:0;border-top-right-radius:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-top-right-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{display:table-cell;float:none;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle=buttons]>.btn input[type=checkbox],[data-toggle=buttons]>.btn input[type=radio],[data-toggle=buttons]>.btn-group>.btn input[type=checkbox],[data-toggle=buttons]>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*=col-]{float:none;padding-right:0;padding-left:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group .form-control:focus{z-index:3}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:46px;line-height:46px}select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn,textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px}select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn,textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn{height:auto}.input-group .form-control,.input-group-addon,.input-group-btn{display:table-cell}.input-group .form-control:not(:first-child):not(:last-child),.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:6px 12px;font-size:14px;font-weight:400;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px}.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg{padding:10px 16px;font-size:18px;border-radius:6px}.input-group-addon input[type=checkbox],.input-group-addon input[type=radio]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn-group:not(:last-child)>.btn,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:first-child>.btn-group:not(:first-child)>.btn,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:active,.input-group-btn>.btn:focus,.input-group-btn>.btn:hover{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{z-index:2;margin-left:-1px}.nav{padding-left:0;margin-bottom:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:focus,.nav>li>a:hover{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#777}.nav>li.disabled>a:focus,.nav>li.disabled>a:hover{color:#777;text-decoration:none;cursor:not-allowed;background-color:transparent}.nav .open>a,.nav .open>a:focus,.nav .open>a:hover{background-color:#eee;border-color:#337ab7}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:focus,.nav-tabs>li.active>a:hover{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border-bottom-color:#fff}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:focus,.nav-pills>li.active>a:hover{color:#fff;background-color:#337ab7}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border-bottom-color:#fff}}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}@media (min-width:768px){.navbar{border-radius:4px}}@media (min-width:768px){.navbar-header{float:left}}.navbar-collapse{padding-right:15px;padding-left:15px;overflow-x:visible;-webkit-overflow-scrolling:touch;border-top:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1)}.navbar-collapse.in{overflow-y:auto}@media (min-width:768px){.navbar-collapse{width:auto;border-top:0;-webkit-box-shadow:none;box-shadow:none}.navbar-collapse.collapse{display:block!important;height:auto!important;padding-bottom:0;overflow:visible!important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse{padding-right:0;padding-left:0}}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:340px}@media (max-device-width:480px) and (orientation:landscape){.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:200px}}.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width:768px){.navbar-static-top{border-radius:0}}.navbar-fixed-bottom,.navbar-fixed-top{position:fixed;right:0;left:0;z-index:1030}@media (min-width:768px){.navbar-fixed-bottom,.navbar-fixed-top{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.navbar-brand{float:left;height:50px;padding:15px 15px;font-size:18px;line-height:20px}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-brand>img{display:block}@media (min-width:768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;padding:9px 10px;margin-top:8px;margin-right:15px;margin-bottom:8px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;-webkit-box-shadow:none;box-shadow:none}.navbar-nav .open .dropdown-menu .dropdown-header,.navbar-nav .open .dropdown-menu>li>a{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:focus,.navbar-nav .open .dropdown-menu>li>a:hover{background-image:none}}@media (min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}}.navbar-form{padding:10px 15px;margin-top:8px;margin-right:-15px;margin-bottom:8px;margin-left:-15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1)}@media (min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .form-control-static{display:inline-block}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .form-control,.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .checkbox,.navbar-form .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .checkbox label,.navbar-form .radio label{padding-left:0}.navbar-form .checkbox input[type=checkbox],.navbar-form .radio input[type=radio]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}.navbar-form .form-group:last-child{margin-bottom:0}}@media (min-width:768px){.navbar-form{width:auto;padding-top:0;padding-bottom:0;margin-right:0;margin-left:0;border:0;-webkit-box-shadow:none;box-shadow:none}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-left-radius:0;border-top-right-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{margin-bottom:0;border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-btn.btn-sm{margin-top:10px;margin-bottom:10px}.navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width:768px){.navbar-text{float:left;margin-right:15px;margin-left:15px}}@media (min-width:768px){.navbar-left{float:left!important}.navbar-right{float:right!important;margin-right:-15px}.navbar-right~.navbar-right{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#777}.navbar-default .navbar-brand:focus,.navbar-default .navbar-brand:hover{color:#5e5e5e;background-color:transparent}.navbar-default .navbar-text{color:#777}.navbar-default .navbar-nav>li>a{color:#777}.navbar-default .navbar-nav>li>a:focus,.navbar-default .navbar-nav>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:focus,.navbar-default .navbar-nav>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:focus,.navbar-default .navbar-nav>.disabled>a:hover{color:#ccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:focus,.navbar-default .navbar-toggle:hover{background-color:#ddd}.navbar-default .navbar-toggle .icon-bar{background-color:#888}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e7e7e7}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:focus,.navbar-default .navbar-nav>.open>a:hover{color:#555;background-color:#e7e7e7}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#ccc;background-color:transparent}}.navbar-default .navbar-link{color:#777}.navbar-default .navbar-link:hover{color:#333}.navbar-default .btn-link{color:#777}.navbar-default .btn-link:focus,.navbar-default .btn-link:hover{color:#333}.navbar-default .btn-link[disabled]:focus,.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:focus,fieldset[disabled] .navbar-default .btn-link:hover{color:#ccc}.navbar-inverse{background-color:#222;border-color:#080808}.navbar-inverse .navbar-brand{color:#9d9d9d}.navbar-inverse .navbar-brand:focus,.navbar-inverse .navbar-brand:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-text{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a:focus,.navbar-inverse .navbar-nav>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:focus,.navbar-inverse .navbar-nav>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:focus,.navbar-inverse .navbar-nav>.disabled>a:hover{color:#444;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:focus,.navbar-inverse .navbar-toggle:hover{background-color:#333}.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#101010}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:focus,.navbar-inverse .navbar-nav>.open>a:hover{color:#fff;background-color:#080808}@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#444;background-color:transparent}}.navbar-inverse .navbar-link{color:#9d9d9d}.navbar-inverse .navbar-link:hover{color:#fff}.navbar-inverse .btn-link{color:#9d9d9d}.navbar-inverse .btn-link:focus,.navbar-inverse .btn-link:hover{color:#fff}.navbar-inverse .btn-link[disabled]:focus,.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:focus,fieldset[disabled] .navbar-inverse .btn-link:hover{color:#444}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#f5f5f5;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{padding:0 5px;color:#ccc;content:"/\00a0"}.breadcrumb>.active{color:#777}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;margin-left:-1px;line-height:1.42857143;color:#337ab7;text-decoration:none;background-color:#fff;border:1px solid #ddd}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-top-left-radius:4px;border-bottom-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-top-right-radius:4px;border-bottom-right-radius:4px}.pagination>li>a:focus,.pagination>li>a:hover,.pagination>li>span:focus,.pagination>li>span:hover{z-index:2;color:#23527c;background-color:#eee;border-color:#ddd}.pagination>.active>a,.pagination>.active>a:focus,.pagination>.active>a:hover,.pagination>.active>span,.pagination>.active>span:focus,.pagination>.active>span:hover{z-index:3;color:#fff;cursor:default;background-color:#337ab7;border-color:#337ab7}.pagination>.disabled>a,.pagination>.disabled>a:focus,.pagination>.disabled>a:hover,.pagination>.disabled>span,.pagination>.disabled>span:focus,.pagination>.disabled>span:hover{color:#777;cursor:not-allowed;background-color:#fff;border-color:#ddd}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px;line-height:1.3333333}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-top-left-radius:6px;border-bottom-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-top-right-radius:6px;border-bottom-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px;line-height:1.5}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-top-left-radius:3px;border-bottom-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-top-right-radius:3px;border-bottom-right-radius:3px}.pager{padding-left:0;margin:20px 0;text-align:center;list-style:none}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px}.pager li>a:focus,.pager li>a:hover{text-decoration:none;background-color:#eee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:focus,.pager .disabled>a:hover,.pager .disabled>span{color:#777;cursor:not-allowed;background-color:#fff}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}a.label:focus,a.label:hover{color:#fff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#777}.label-default[href]:focus,.label-default[href]:hover{background-color:#5e5e5e}.label-primary{background-color:#337ab7}.label-primary[href]:focus,.label-primary[href]:hover{background-color:#286090}.label-success{background-color:#5cb85c}.label-success[href]:focus,.label-success[href]:hover{background-color:#449d44}.label-info{background-color:#5bc0de}.label-info[href]:focus,.label-info[href]:hover{background-color:#31b0d5}.label-warning{background-color:#f0ad4e}.label-warning[href]:focus,.label-warning[href]:hover{background-color:#ec971f}.label-danger{background-color:#d9534f}.label-danger[href]:focus,.label-danger[href]:hover{background-color:#c9302c}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:middle;background-color:#777;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-group-xs>.btn .badge,.btn-xs .badge{top:0;padding:1px 5px}a.badge:focus,a.badge:hover{color:#fff;text-decoration:none;cursor:pointer}.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#337ab7;background-color:#fff}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding-top:30px;padding-bottom:30px;margin-bottom:30px;color:inherit;background-color:#eee}.jumbotron .h1,.jumbotron h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.jumbotron>hr{border-top-color:#d5d5d5}.container .jumbotron,.container-fluid .jumbotron{padding-right:15px;padding-left:15px;border-radius:6px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron,.container-fluid .jumbotron{padding-right:60px;padding-left:60px}.jumbotron .h1,.jumbotron h1{font-size:63px}}.thumbnail{display:block;padding:4px;margin-bottom:20px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:border .2s ease-in-out;-o-transition:border .2s ease-in-out;transition:border .2s ease-in-out}.thumbnail a>img,.thumbnail>img{margin-right:auto;margin-left:auto}a.thumbnail.active,a.thumbnail:focus,a.thumbnail:hover{border-color:#337ab7}.thumbnail .caption{padding:9px;color:#333}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:700}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable,.alert-dismissible{padding-right:35px}.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#2b542c}.alert-info{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#245269}.alert-warning{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.alert-warning hr{border-top-color:#f7e1b5}.alert-warning .alert-link{color:#66512c}.alert-danger{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.alert-danger hr{border-top-color:#e4b9c0}.alert-danger .alert-link{color:#843534}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.progress-bar{float:left;width:0;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#337ab7;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);-webkit-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress-bar-striped,.progress-striped .progress-bar{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;background-size:40px 40px}.progress-bar.active,.progress.active .progress-bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#5cb85c}.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-info{background-color:#5bc0de}.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-warning{background-color:#f0ad4e}.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-danger{background-color:#d9534f}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.media{margin-top:15px}.media:first-child{margin-top:0}.media,.media-body{overflow:hidden;zoom:1}.media-body{width:10000px}.media-object{display:block}.media-object.img-thumbnail{max-width:none}.media-right,.media>.pull-right{padding-left:10px}.media-left,.media>.pull-left{padding-right:10px}.media-body,.media-left,.media-right{display:table-cell;vertical-align:top}.media-middle{vertical-align:middle}.media-bottom{vertical-align:bottom}.media-heading{margin-top:0;margin-bottom:5px}.media-list{padding-left:0;list-style:none}.list-group{padding-left:0;margin-bottom:20px}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd}.list-group-item:first-child{border-top-left-radius:4px;border-top-right-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}a.list-group-item,button.list-group-item{color:#555}a.list-group-item .list-group-item-heading,button.list-group-item .list-group-item-heading{color:#333}a.list-group-item:focus,a.list-group-item:hover,button.list-group-item:focus,button.list-group-item:hover{color:#555;text-decoration:none;background-color:#f5f5f5}button.list-group-item{width:100%;text-align:left}.list-group-item.disabled,.list-group-item.disabled:focus,.list-group-item.disabled:hover{color:#777;cursor:not-allowed;background-color:#eee}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text{color:#777}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{z-index:2;color:#fff;background-color:#337ab7;border-color:#337ab7}.list-group-item.active .list-group-item-heading,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:focus .list-group-item-text,.list-group-item.active:hover .list-group-item-text{color:#c7ddef}.list-group-item-success{color:#3c763d;background-color:#dff0d8}a.list-group-item-success,button.list-group-item-success{color:#3c763d}a.list-group-item-success .list-group-item-heading,button.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:focus,a.list-group-item-success:hover,button.list-group-item-success:focus,button.list-group-item-success:hover{color:#3c763d;background-color:#d0e9c6}a.list-group-item-success.active,a.list-group-item-success.active:focus,a.list-group-item-success.active:hover,button.list-group-item-success.active,button.list-group-item-success.active:focus,button.list-group-item-success.active:hover{color:#fff;background-color:#3c763d;border-color:#3c763d}.list-group-item-info{color:#31708f;background-color:#d9edf7}a.list-group-item-info,button.list-group-item-info{color:#31708f}a.list-group-item-info .list-group-item-heading,button.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:focus,a.list-group-item-info:hover,button.list-group-item-info:focus,button.list-group-item-info:hover{color:#31708f;background-color:#c4e3f3}a.list-group-item-info.active,a.list-group-item-info.active:focus,a.list-group-item-info.active:hover,button.list-group-item-info.active,button.list-group-item-info.active:focus,button.list-group-item-info.active:hover{color:#fff;background-color:#31708f;border-color:#31708f}.list-group-item-warning{color:#8a6d3b;background-color:#fcf8e3}a.list-group-item-warning,button.list-group-item-warning{color:#8a6d3b}a.list-group-item-warning .list-group-item-heading,button.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:focus,a.list-group-item-warning:hover,button.list-group-item-warning:focus,button.list-group-item-warning:hover{color:#8a6d3b;background-color:#faf2cc}a.list-group-item-warning.active,a.list-group-item-warning.active:focus,a.list-group-item-warning.active:hover,button.list-group-item-warning.active,button.list-group-item-warning.active:focus,button.list-group-item-warning.active:hover{color:#fff;background-color:#8a6d3b;border-color:#8a6d3b}.list-group-item-danger{color:#a94442;background-color:#f2dede}a.list-group-item-danger,button.list-group-item-danger{color:#a94442}a.list-group-item-danger .list-group-item-heading,button.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:focus,a.list-group-item-danger:hover,button.list-group-item-danger:focus,button.list-group-item-danger:hover{color:#a94442;background-color:#ebcccc}a.list-group-item-danger.active,a.list-group-item-danger.active:focus,a.list-group-item-danger.active:hover,button.list-group-item-danger.active,button.list-group-item-danger.active:focus,button.list-group-item-danger.active:hover{color:#fff;background-color:#a94442;border-color:#a94442}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.05);box-shadow:0 1px 1px rgba(0,0,0,.05)}.panel-body{padding:15px}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-left-radius:3px;border-top-right-radius:3px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:16px;color:inherit}.panel-title>.small,.panel-title>.small>a,.panel-title>a,.panel-title>small,.panel-title>small>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.list-group,.panel>.panel-collapse>.list-group{margin-bottom:0}.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-left-radius:3px;border-top-right-radius:3px}.panel>.list-group:last-child .list-group-item:last-child,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.panel-heading+.panel-collapse>.list-group .list-group-item:first-child{border-top-left-radius:0;border-top-right-radius:0}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.list-group+.panel-footer{border-top-width:0}.panel>.panel-collapse>.table,.panel>.table,.panel>.table-responsive>.table{margin-bottom:0}.panel>.panel-collapse>.table caption,.panel>.table caption,.panel>.table-responsive>.table caption{padding-right:15px;padding-left:15px}.panel>.table-responsive:first-child>.table:first-child,.panel>.table:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table:first-child>thead:first-child>tr:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table-responsive:last-child>.table:last-child,.panel>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{border-top:1px solid #ddd}.panel>.table>tbody:first-child>tr:first-child td,.panel>.table>tbody:first-child>tr:first-child th{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive{margin-bottom:0;border:0}.panel-group{margin-bottom:20px}.panel-group .panel{margin-bottom:0;border-radius:4px}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse>.list-group,.panel-group .panel-heading+.panel-collapse>.panel-body{border-top:1px solid #ddd}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd}.panel-default{border-color:#ddd}.panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd}.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ddd}.panel-default>.panel-heading .badge{color:#f5f5f5;background-color:#333}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ddd}.panel-primary{border-color:#337ab7}.panel-primary>.panel-heading{color:#fff;background-color:#337ab7;border-color:#337ab7}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#337ab7}.panel-primary>.panel-heading .badge{color:#337ab7;background-color:#fff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#337ab7}.panel-success{border-color:#d6e9c6}.panel-success>.panel-heading{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:#d6e9c6}.panel-success>.panel-heading .badge{color:#dff0d8;background-color:#3c763d}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#d6e9c6}.panel-info{border-color:#bce8f1}.panel-info>.panel-heading{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:#bce8f1}.panel-info>.panel-heading .badge{color:#d9edf7;background-color:#31708f}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#bce8f1}.panel-warning{border-color:#faebcc}.panel-warning>.panel-heading{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:#faebcc}.panel-warning>.panel-heading .badge{color:#fcf8e3;background-color:#8a6d3b}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#faebcc}.panel-danger{border-color:#ebccd1}.panel-danger>.panel-heading{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ebccd1}.panel-danger>.panel-heading .badge{color:#f2dede;background-color:#a94442}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ebccd1}.embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive-4by3{padding-bottom:75%}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,.15)}.well-lg{padding:24px;border-radius:6px}.well-sm{padding:9px;border-radius:3px}.close{float:right;font-size:21px;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;filter:alpha(opacity=20);opacity:.2}.close:focus,.close:hover{color:#000;text-decoration:none;cursor:pointer;filter:alpha(opacity=50);opacity:.5}button.close{-webkit-appearance:none;padding:0;cursor:pointer;background:0 0;border:0}.modal-open{overflow:hidden}.modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;display:none;overflow:hidden;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transition:-webkit-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out;-webkit-transform:translate(0,-25%);-ms-transform:translate(0,-25%);-o-transform:translate(0,-25%);transform:translate(0,-25%)}.modal.in .modal-dialog{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);-o-transform:translate(0,0);transform:translate(0,0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #999;border:1px solid rgba(0,0,0,.2);border-radius:6px;outline:0;-webkit-box-shadow:0 3px 9px rgba(0,0,0,.5);box-shadow:0 3px 9px rgba(0,0,0,.5)}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{filter:alpha(opacity=0);opacity:0}.modal-backdrop.in{filter:alpha(opacity=50);opacity:.5}.modal-header{padding:15px;border-bottom:1px solid #e5e5e5}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.42857143}.modal-body{position:relative;padding:15px}.modal-footer{padding:15px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,.5);box-shadow:0 5px 15px rgba(0,0,0,.5)}.modal-sm{width:300px}}@media (min-width:992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1070;display:block;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:12px;font-style:normal;font-weight:400;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;filter:alpha(opacity=0);opacity:0;line-break:auto}.tooltip.in{filter:alpha(opacity=90);opacity:.9}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;background-color:#000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-left .tooltip-arrow{right:5px;bottom:0;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-right .tooltip-arrow{bottom:0;left:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-left .tooltip-arrow{top:0;right:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-right .tooltip-arrow{top:0;left:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;padding:1px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;font-style:normal;font-weight:400;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2);line-break:auto}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{padding:8px 14px;margin:0;font-size:14px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover>.arrow{border-width:11px}.popover>.arrow:after{content:"";border-width:10px}.popover.top>.arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,.25);border-bottom-width:0}.popover.top>.arrow:after{bottom:1px;margin-left:-10px;content:" ";border-top-color:#fff;border-bottom-width:0}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,.25);border-left-width:0}.popover.right>.arrow:after{bottom:-10px;left:1px;content:" ";border-right-color:#fff;border-left-width:0}.popover.bottom>.arrow{top:-11px;left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,.25)}.popover.bottom>.arrow:after{top:1px;margin-left:-10px;content:" ";border-top-width:0;border-bottom-color:#fff}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0,0,0,.25)}.popover.left>.arrow:after{right:1px;bottom:-10px;content:" ";border-right-width:0;border-left-color:#fff}.carousel{position:relative}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>a>img,.carousel-inner>.item>img{line-height:1}@media all and (transform-3d),(-webkit-transform-3d){.carousel-inner>.item{-webkit-transition:-webkit-transform .6s ease-in-out;-o-transition:-o-transform .6s ease-in-out;transition:transform .6s ease-in-out;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000px;perspective:1000px}.carousel-inner>.item.active.right,.carousel-inner>.item.next{left:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}.carousel-inner>.item.active.left,.carousel-inner>.item.prev{left:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}.carousel-inner>.item.active,.carousel-inner>.item.next.left,.carousel-inner>.item.prev.right{left:0;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;bottom:0;left:0;width:15%;font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6);background-color:rgba(0,0,0,0);filter:alpha(opacity=50);opacity:.5}.carousel-control.left{background-image:-webkit-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.5)),to(rgba(0,0,0,.0001)));background-image:linear-gradient(to right,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);background-repeat:repeat-x}.carousel-control.right{right:0;left:auto;background-image:-webkit-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.0001)),to(rgba(0,0,0,.5)));background-image:linear-gradient(to right,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);background-repeat:repeat-x}.carousel-control:focus,.carousel-control:hover{color:#fff;text-decoration:none;filter:alpha(opacity=90);outline:0;opacity:.9}.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{position:absolute;top:50%;z-index:5;display:inline-block;margin-top:-10px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{left:50%;margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{right:50%;margin-right:-10px}.carousel-control .icon-next,.carousel-control .icon-prev{width:20px;height:20px;font-family:serif;line-height:1}.carousel-control .icon-prev:before{content:'\2039'}.carousel-control .icon-next:before{content:'\203a'}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;padding-left:0;margin-left:-30%;text-align:center;list-style:none}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;cursor:pointer;background-color:#000\9;background-color:rgba(0,0,0,0);border:1px solid #fff;border-radius:10px}.carousel-indicators .active{width:12px;height:12px;margin:0;background-color:#fff}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{width:30px;height:30px;margin-top:-10px;font-size:30px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-10px}.carousel-caption{right:20%;left:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.btn-group-vertical>.btn-group:after,.btn-group-vertical>.btn-group:before,.btn-toolbar:after,.btn-toolbar:before,.clearfix:after,.clearfix:before,.container-fluid:after,.container-fluid:before,.container:after,.container:before,.dl-horizontal dd:after,.dl-horizontal dd:before,.form-horizontal .form-group:after,.form-horizontal .form-group:before,.modal-footer:after,.modal-footer:before,.modal-header:after,.modal-header:before,.nav:after,.nav:before,.navbar-collapse:after,.navbar-collapse:before,.navbar-header:after,.navbar-header:before,.navbar:after,.navbar:before,.pager:after,.pager:before,.panel-body:after,.panel-body:before,.row:after,.row:before{display:table;content:" "}.btn-group-vertical>.btn-group:after,.btn-toolbar:after,.clearfix:after,.container-fluid:after,.container:after,.dl-horizontal dd:after,.form-horizontal .form-group:after,.modal-footer:after,.modal-header:after,.nav:after,.navbar-collapse:after,.navbar-header:after,.navbar:after,.pager:after,.panel-body:after,.row:after{clear:both}.center-block{display:block;margin-right:auto;margin-left:auto}.pull-right{float:right!important}.pull-left{float:left!important}.hide{display:none!important}.show{display:block!important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none!important}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-lg,.visible-md,.visible-sm,.visible-xs{display:none!important}.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block{display:none!important}@media (max-width:767px){.visible-xs{display:block!important}table.visible-xs{display:table!important}tr.visible-xs{display:table-row!important}td.visible-xs,th.visible-xs{display:table-cell!important}}@media (max-width:767px){.visible-xs-block{display:block!important}}@media (max-width:767px){.visible-xs-inline{display:inline!important}}@media (max-width:767px){.visible-xs-inline-block{display:inline-block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm{display:block!important}table.visible-sm{display:table!important}tr.visible-sm{display:table-row!important}td.visible-sm,th.visible-sm{display:table-cell!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-block{display:block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline{display:inline!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline-block{display:inline-block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md{display:block!important}table.visible-md{display:table!important}tr.visible-md{display:table-row!important}td.visible-md,th.visible-md{display:table-cell!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-block{display:block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline{display:inline!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline-block{display:inline-block!important}}@media (min-width:1200px){.visible-lg{display:block!important}table.visible-lg{display:table!important}tr.visible-lg{display:table-row!important}td.visible-lg,th.visible-lg{display:table-cell!important}}@media (min-width:1200px){.visible-lg-block{display:block!important}}@media (min-width:1200px){.visible-lg-inline{display:inline!important}}@media (min-width:1200px){.visible-lg-inline-block{display:inline-block!important}}@media (max-width:767px){.hidden-xs{display:none!important}}@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none!important}}@media (min-width:992px) and (max-width:1199px){.hidden-md{display:none!important}}@media (min-width:1200px){.hidden-lg{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:block!important}table.visible-print{display:table!important}tr.visible-print{display:table-row!important}td.visible-print,th.visible-print{display:table-cell!important}}.visible-print-block{display:none!important}@media print{.visible-print-block{display:block!important}}.visible-print-inline{display:none!important}@media print{.visible-print-inline{display:inline!important}}.visible-print-inline-block{display:none!important}@media print{.visible-print-inline-block{display:inline-block!important}}@media print{.hidden-print{display:none!important}} +/*# sourceMappingURL=bootstrap.min.css.map */
\ No newline at end of file diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/css/bootstrap.min.css.map b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/css/bootstrap.min.css.map new file mode 100644 index 000000000..5f49bb374 --- /dev/null +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/css/bootstrap.min.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["less/normalize.less","less/print.less","bootstrap.css","dist/css/bootstrap.css","less/glyphicons.less","less/scaffolding.less","less/mixins/vendor-prefixes.less","less/mixins/tab-focus.less","less/mixins/image.less","less/type.less","less/mixins/text-emphasis.less","less/mixins/background-variant.less","less/mixins/text-overflow.less","less/code.less","less/grid.less","less/mixins/grid.less","less/mixins/grid-framework.less","less/tables.less","less/mixins/table-row.less","less/forms.less","less/mixins/forms.less","less/buttons.less","less/mixins/buttons.less","less/mixins/opacity.less","less/component-animations.less","less/dropdowns.less","less/mixins/nav-divider.less","less/mixins/reset-filter.less","less/button-groups.less","less/mixins/border-radius.less","less/input-groups.less","less/navs.less","less/navbar.less","less/mixins/nav-vertical-align.less","less/utilities.less","less/breadcrumbs.less","less/pagination.less","less/mixins/pagination.less","less/pager.less","less/labels.less","less/mixins/labels.less","less/badges.less","less/jumbotron.less","less/thumbnails.less","less/alerts.less","less/mixins/alerts.less","less/progress-bars.less","less/mixins/gradients.less","less/mixins/progress-bar.less","less/media.less","less/list-group.less","less/mixins/list-group.less","less/panels.less","less/mixins/panels.less","less/responsive-embed.less","less/wells.less","less/close.less","less/modals.less","less/tooltip.less","less/mixins/reset-text.less","less/popovers.less","less/carousel.less","less/mixins/clearfix.less","less/mixins/center-block.less","less/mixins/hide-text.less","less/responsive-utilities.less","less/mixins/responsive-visibility.less"],"names":[],"mappings":";;;;4EAQA,KACE,YAAA,WACA,yBAAA,KACA,qBAAA,KAOF,KACE,OAAA,EAaF,QAAA,MAAA,QAAA,WAAA,OAAA,OAAA,OAAA,OAAA,KAAA,KAAA,IAAA,QAAA,QAaE,QAAA,MAQF,MAAA,OAAA,SAAA,MAIE,QAAA,aACA,eAAA,SAQF,sBACE,QAAA,KACA,OAAA,EAQF,SAAA,SAEE,QAAA,KAUF,EACE,iBAAA,YAQF,SAAA,QAEE,QAAA,EAUF,YACE,cAAA,IAAA,OAOF,EAAA,OAEE,YAAA,IAOF,IACE,WAAA,OAQF,GACE,OAAA,MAAA,EACA,UAAA,IAOF,KACE,MAAA,KACA,WAAA,KAOF,MACE,UAAA,IAOF,IAAA,IAEE,SAAA,SACA,UAAA,IACA,YAAA,EACA,eAAA,SAGF,IACE,IAAA,MAGF,IACE,OAAA,OAUF,IACE,OAAA,EAOF,eACE,SAAA,OAUF,OACE,OAAA,IAAA,KAOF,GACE,OAAA,EAAA,mBAAA,YAAA,gBAAA,YACA,WAAA,YAOF,IACE,SAAA,KAOF,KAAA,IAAA,IAAA,KAIE,YAAA,UAAA,UACA,UAAA,IAkBF,OAAA,MAAA,SAAA,OAAA,SAKE,OAAA,EACA,KAAA,QACA,MAAA,QAOF,OACE,SAAA,QAUF,OAAA,OAEE,eAAA,KAWF,OAAA,wBAAA,kBAAA,mBAIE,mBAAA,OACA,OAAA,QAOF,iBAAA,qBAEE,OAAA,QAOF,yBAAA,wBAEE,QAAA,EACA,OAAA,EAQF,MACE,YAAA,OAWF,qBAAA,kBAEE,mBAAA,WAAA,gBAAA,WAAA,WAAA,WACA,QAAA,EASF,8CAAA,8CAEE,OAAA,KAQF,mBACE,mBAAA,YACA,gBAAA,YAAA,WAAA,YAAA,mBAAA,UASF,iDAAA,8CAEE,mBAAA,KAOF,SACE,QAAA,MAAA,OAAA,MACA,OAAA,EAAA,IACA,OAAA,IAAA,MAAA,OAQF,OACE,QAAA,EACA,OAAA,EAOF,SACE,SAAA,KAQF,SACE,YAAA,IAUF,MACE,eAAA,EACA,gBAAA,SAGF,GAAA,GAEE,QAAA,uFCjUF,aA7FI,EAAA,OAAA,QAGI,MAAA,eACA,YAAA,eACA,WAAA,cAAA,mBAAA,eACA,WAAA,eAGJ,EAAA,UAEI,gBAAA,UAGJ,cACI,QAAA,KAAA,WAAA,IAGJ,kBACI,QAAA,KAAA,YAAA,IAKJ,6BAAA,mBAEI,QAAA,GAGJ,WAAA,IAEI,OAAA,IAAA,MAAA,KC4KL,kBAAA,MDvKK,MC0KL,QAAA,mBDrKK,IE8KN,GDLC,kBAAA,MDrKK,ICwKL,UAAA,eCUD,GF5KM,GE2KN,EF1KM,QAAA,ECuKL,OAAA,ECSD,GF3KM,GCsKL,iBAAA,MD/JK,QCkKL,QAAA,KCSD,YFtKU,oBCiKT,iBAAA,eD7JK,OCgKL,OAAA,IAAA,MAAA,KD5JK,OC+JL,gBAAA,mBCSD,UFpKU,UC+JT,iBAAA,eDzJS,mBEkKV,mBDLC,OAAA,IAAA,MAAA,gBEjPD,WACA,YAAA,uBFsPD,IAAA,+CE7OC,IAAK,sDAAuD,4BAA6B,iDAAkD,gBAAiB,gDAAiD,eAAgB,+CAAgD,mBAAoB,2EAA4E,cAE7W,WACA,SAAA,SACA,IAAA,IACA,QAAA,aACA,YAAA,uBACA,WAAA,OACA,YAAA,IACA,YAAA,EAIkC,uBAAA,YAAW,wBAAA,UACX,2BAAW,QAAA,QAEX,uBDuPlC,QAAS,QCtPyB,sBFiPnC,uBEjP8C,QAAA,QACX,wBAAW,QAAA,QACX,wBAAW,QAAA,QACX,2BAAW,QAAA,QACX,yBAAW,QAAA,QACX,wBAAW,QAAA,QACX,wBAAW,QAAA,QACX,yBAAW,QAAA,QACX,wBAAW,QAAA,QACX,uBAAW,QAAA,QACX,6BAAW,QAAA,QACX,uBAAW,QAAA,QACX,uBAAW,QAAA,QACX,2BAAW,QAAA,QACX,qBAAW,QAAA,QACX,0BAAW,QAAA,QACX,qBAAW,QAAA,QACX,yBAAW,QAAA,QACX,0BAAW,QAAA,QACX,2BAAW,QAAA,QACX,sBAAW,QAAA,QACX,yBAAW,QAAA,QACX,sBAAW,QAAA,QACX,wBAAW,QAAA,QACX,uBAAW,QAAA,QACX,uBAAW,QAAA,QACX,uBAAW,QAAA,QACX,uBAAW,QAAA,QACX,+BAAW,QAAA,QACX,2BAAW,QAAA,QACX,yBAAW,QAAA,QACX,wBAAW,QAAA,QACX,8BAAW,QAAA,QACX,yBAAW,QAAA,QACX,0BAAW,QAAA,QACX,2BAAW,QAAA,QACX,uBAAW,QAAA,QACX,uBAAW,QAAA,QACX,6BAAW,QAAA,QACX,6BAAW,QAAA,QACX,8BAAW,QAAA,QACX,4BAAW,QAAA,QACX,yBAAW,QAAA,QACX,0BAAW,QAAA,QACX,sBAAW,QAAA,QACX,uBAAW,QAAA,QACX,uBAAW,QAAA,QACX,2BAAW,QAAA,QACX,wBAAW,QAAA,QACX,yBAAW,QAAA,QACX,uBAAW,QAAA,QACX,uBAAW,QAAA,QACX,yBAAW,QAAA,QACX,8BAAW,QAAA,QACX,6BAAW,QAAA,QACX,6BAAW,QAAA,QACX,+BAAW,QAAA,QACX,8BAAW,QAAA,QACX,gCAAW,QAAA,QACX,uBAAW,QAAA,QACX,8BAAW,QAAA,QACX,+BAAW,QAAA,QACX,iCAAW,QAAA,QACX,0BAAW,QAAA,QACX,6BAAW,QAAA,QACX,yBAAW,QAAA,QACX,uBAAW,QAAA,QACX,uBAAW,QAAA,QACX,wBAAW,QAAA,QACX,wBAAW,QAAA,QACX,uBAAW,QAAA,QACX,gCAAW,QAAA,QACX,gCAAW,QAAA,QACX,2BAAW,QAAA,QACX,uBAAW,QAAA,QACX,wBAAW,QAAA,QACX,uBAAW,QAAA,QACX,0BAAW,QAAA,QACX,+BAAW,QAAA,QACX,+BAAW,QAAA,QACX,wBAAW,QAAA,QACX,+BAAW,QAAA,QACX,gCAAW,QAAA,QACX,4BAAW,QAAA,QACX,6BAAW,QAAA,QACX,8BAAW,QAAA,QACX,0BAAW,QAAA,QACX,gCAAW,QAAA,QACX,4BAAW,QAAA,QACX,6BAAW,QAAA,QACX,gCAAW,QAAA,QACX,4BAAW,QAAA,QACX,6BAAW,QAAA,QACX,6BAAW,QAAA,QACX,8BAAW,QAAA,QACX,2BAAW,QAAA,QACX,6BAAW,QAAA,QACX,4BAAW,QAAA,QACX,8BAAW,QAAA,QACX,+BAAW,QAAA,QACX,mCAAW,QAAA,QACX,uBAAW,QAAA,QACX,uBAAW,QAAA,QACX,uBAAW,QAAA,QACX,2BAAW,QAAA,QACX,4BAAW,QAAA,QACX,+BAAW,QAAA,QACX,wBAAW,QAAA,QACX,2BAAW,QAAA,QACX,yBAAW,QAAA,QACX,0BAAW,QAAA,QACX,yBAAW,QAAA,QACX,6BAAW,QAAA,QACX,+BAAW,QAAA,QACX,0BAAW,QAAA,QACX,gCAAW,QAAA,QACX,+BAAW,QAAA,QACX,8BAAW,QAAA,QACX,kCAAW,QAAA,QACX,oCAAW,QAAA,QACX,sBAAW,QAAA,QACX,2BAAW,QAAA,QACX,uBAAW,QAAA,QACX,8BAAW,QAAA,QACX,4BAAW,QAAA,QACX,8BAAW,QAAA,QACX,6BAAW,QAAA,QACX,4BAAW,QAAA,QACX,0BAAW,QAAA,QACX,4BAAW,QAAA,QACX,qCAAW,QAAA,QACX,oCAAW,QAAA,QACX,kCAAW,QAAA,QACX,oCAAW,QAAA,QACX,wBAAW,QAAA,QACX,yBAAW,QAAA,QACX,wBAAW,QAAA,QACX,yBAAW,QAAA,QACX,4BAAW,QAAA,QACX,6BAAW,QAAA,QACX,4BAAW,QAAA,QACX,4BAAW,QAAA,QACX,8BAAW,QAAA,QACX,uBAAW,QAAA,QACX,wBAAW,QAAA,QACX,0BAAW,QAAA,QACX,sBAAW,QAAA,QACX,sBAAW,QAAA,QACX,uBAAW,QAAA,QACX,mCAAW,QAAA,QACX,uCAAW,QAAA,QACX,gCAAW,QAAA,QACX,oCAAW,QAAA,QACX,qCAAW,QAAA,QACX,yCAAW,QAAA,QACX,4BAAW,QAAA,QACX,yBAAW,QAAA,QACX,gCAAW,QAAA,QACX,8BAAW,QAAA,QACX,yBAAW,QAAA,QACX,wBAAW,QAAA,QACX,0BAAW,QAAA,QACX,6BAAW,QAAA,QACX,yBAAW,QAAA,QACX,uBAAW,QAAA,QACX,uBAAW,QAAA,QACX,wBAAW,QAAA,QACX,yBAAW,QAAA,QACX,yBAAW,QAAA,QACX,uBAAW,QAAA,QACX,8BAAW,QAAA,QACX,+BAAW,QAAA,QACX,gCAAW,QAAA,QACX,8BAAW,QAAA,QACX,8BAAW,QAAA,QACX,8BAAW,QAAA,QACX,2BAAW,QAAA,QACX,0BAAW,QAAA,QACX,yBAAW,QAAA,QACX,6BAAW,QAAA,QACX,2BAAW,QAAA,QACX,4BAAW,QAAA,QACX,wBAAW,QAAA,QACX,wBAAW,QAAA,QACX,2BAAW,QAAA,QACX,2BAAW,QAAA,QACX,4BAAW,QAAA,QACX,+BAAW,QAAA,QACX,8BAAW,QAAA,QACX,4BAAW,QAAA,QACX,4BAAW,QAAA,QACX,4BAAW,QAAA,QACX,iCAAW,QAAA,QACX,oCAAW,QAAA,QACX,iCAAW,QAAA,QACX,+BAAW,QAAA,QACX,+BAAW,QAAA,QACX,iCAAW,QAAA,QACX,qBAAW,QAAA,QACX,4BAAW,QAAA,QACX,4BAAW,QAAA,QACX,2BAAW,QAAA,QACX,uBAAW,QAAA,QASX,wBAAW,QAAA,QACX,wBAAW,QAAA,QACX,4BAAW,QAAA,QACX,uBAAW,QAAA,QACX,wBAAW,QAAA,QACX,uBAAW,QAAA,QACX,yBAAW,QAAA,QACX,yBAAW,QAAA,QACX,+BAAW,QAAA,QACX,uBAAW,QAAA,QACX,6BAAW,QAAA,QACX,sBAAW,QAAA,QACX,wBAAW,QAAA,QACX,wBAAW,QAAA,QACX,4BAAW,QAAA,QACX,uBAAW,QAAA,QACX,4BAAW,QAAA,QACX,6BAAW,QAAA,QACX,2BAAW,QAAA,QACX,0BAAW,QAAA,QACX,sBAAW,QAAA,QACX,sBAAW,QAAA,QACX,sBAAW,QAAA,QACX,sBAAW,QAAA,QACX,wBAAW,QAAA,QACX,sBAAW,QAAA,QACX,wBAAW,QAAA,QACX,4BAAW,QAAA,QACX,mCAAW,QAAA,QACX,4BAAW,QAAA,QACX,oCAAW,QAAA,QACX,kCAAW,QAAA,QACX,iCAAW,QAAA,QACX,+BAAW,QAAA,QACX,sBAAW,QAAA,QACX,wBAAW,QAAA,QACX,6BAAW,QAAA,QACX,4BAAW,QAAA,QACX,6BAAW,QAAA,QACX,kCAAW,QAAA,QACX,mCAAW,QAAA,QACX,sCAAW,QAAA,QACX,0CAAW,QAAA,QACX,oCAAW,QAAA,QACX,wCAAW,QAAA,QACX,qCAAW,QAAA,QACX,iCAAW,QAAA,QACX,gCAAW,QAAA,QACX,kCAAW,QAAA,QACX,+BAAW,QAAA,QACX,0BAAW,QAAA,QACX,8BAAW,QAAA,QACX,4BAAW,QAAA,QACX,4BAAW,QAAA,QACX,6BAAW,QAAA,QACX,4BAAW,QAAA,QCtS/C,0BCgEE,QAAA,QHi+BF,EDNC,mBAAA,WGxhCI,gBAAiB,WFiiCZ,WAAY,WGl+BZ,OADL,QJg+BJ,mBAAA,WGthCI,gBAAiB,WACpB,WAAA,WHyhCD,KGrhCC,UAAW,KAEX,4BAAA,cAEA,KACA,YAAA,iBAAA,UAAA,MAAA,WHuhCD,UAAA,KGnhCC,YAAa,WF4hCb,MAAO,KACP,iBAAkB,KExhClB,OADA,MAEA,OHqhCD,SG/gCC,YAAa,QACb,UAAA,QACA,YAAA,QAEA,EFwhCA,MAAO,QEthCL,gBAAA,KAIF,QH8gCD,QKnkCC,MAAA,QAEA,gBAAA,ULskCD,QGxgCC,QAAS,KAAK,OACd,QAAA,IAAA,KAAA,yBH0gCD,eAAA,KGngCC,OHsgCD,OAAA,ECSD,IACE,eAAgB,ODDjB,4BMhlCC,0BLmlCF,gBKplCE,iBADA,eH4EA,QAAS,MACT,UAAA,KHwgCD,OAAA,KGjgCC,aACA,cAAA,IAEA,eACA,QAAA,aC6FA,UAAA,KACK,OAAA,KACG,QAAA,IEvLR,YAAA,WACA,iBAAA,KACA,OAAA,IAAA,MAAA,KNgmCD,cAAA,IGlgCC,mBAAoB,IAAI,IAAI,YAC5B,cAAA,IAAA,IAAA,YHogCD,WAAA,IAAA,IAAA,YG7/BC,YACA,cAAA,IAEA,GHggCD,WAAA,KGx/BC,cAAe,KACf,OAAA,EACA,WAAA,IAAA,MAAA,KAEA,SACA,SAAA,SACA,MAAA,IACA,OAAA,IACA,QAAA,EH0/BD,OAAA,KGl/BC,SAAA,OF2/BA,KAAM,cEz/BJ,OAAA,EAEA,0BACA,yBACA,SAAA,OACA,MAAA,KHo/BH,OAAA,KGz+BC,OAAQ,EACR,SAAA,QH2+BD,KAAA,KCSD,cACE,OAAQ,QAQV,IACA,IMnpCE,IACA,IACA,IACA,INyoCF,GACA,GACA,GACA,GACA,GACA,GDAC,YAAA,QOnpCC,YAAa,IN4pCb,YAAa,IACb,MAAO,QAoBT,WAZA,UAaA,WAZA,UM7pCI,WN8pCJ,UM7pCI,WN8pCJ,UM7pCI,WN8pCJ,UDMC,WCLD,UACA,UAZA,SAaA,UAZA,SAaA,UAZA,SAaA,UAZA,SAaA,UAZA,SAaA,UAZA,SMrpCE,YAAa,INyqCb,YAAa,EACb,MAAO,KAGT,IMzqCE,IAJF,IN4qCA,GAEA,GDLC,GCSC,WAAY,KACZ,cAAe,KASjB,WANA,UDCC,WCCD,UM7qCA,WN+qCA,UACA,UANA,SM7qCI,UN+qCJ,SM5qCA,UN8qCA,SAQE,UAAW,IAGb,IMrrCE,IAJF,INwrCA,GAEA,GDLC,GCSC,WAAY,KACZ,cAAe,KASjB,WANA,UDCC,WCCD,UMxrCA,WN0rCA,UACA,UANA,SMzrCI,UN2rCJ,SMvrCA,UNyrCA,SMzrCU,UAAA,IACV,IAAA,GAAU,UAAA,KACV,IAAA,GAAU,UAAA,KACV,IAAA,GAAU,UAAA,KACV,IAAA,GAAU,UAAA,KACV,IAAA,GAAU,UAAA,KAOR,IADF,GPusCC,UAAA,KCSD,EM1sCE,OAAA,EAAA,EAAA,KAEA,MPqsCD,cAAA,KOhsCC,UAAW,KAwOX,YAAa,IA1OX,YAAA,IPusCH,yBO9rCC,MNusCE,UAAW,MMlsCf,OAAA,MAEE,UAAA,IAKF,MP2rCC,KO3rCsB,QAAA,KP8rCtB,iBAAA,QO7rCsB,WPgsCtB,WAAA,KO/rCsB,YPksCtB,WAAA,MOjsCsB,aPosCtB,WAAA,OOnsCsB,cPssCtB,WAAA,QOnsCsB,aPssCtB,YAAA,OOrsCsB,gBPwsCtB,eAAA,UOvsCsB,gBP0sCtB,eAAA,UOtsCC,iBPysCD,eAAA,WQ5yCC,YR+yCD,MAAA,KCSD,cOrzCI,MAAA,QAHF,qBDwGF,qBP8sCC,MAAA,QCSD,cO5zCI,MAAA,QAHF,qBD2GF,qBPktCC,MAAA,QCSD,WOn0CI,MAAA,QAHF,kBD8GF,kBPstCC,MAAA,QCSD,cO10CI,MAAA,QAHF,qBDiHF,qBP0tCC,MAAA,QCSD,aOj1CI,MAAA,QDwHF,oBAHF,oBExHE,MAAA,QACA,YR21CA,MAAO,KQz1CL,iBAAA,QAHF,mBF8HF,mBP4tCC,iBAAA,QCSD,YQh2CI,iBAAA,QAHF,mBFiIF,mBPguCC,iBAAA,QCSD,SQv2CI,iBAAA,QAHF,gBFoIF,gBPouCC,iBAAA,QCSD,YQ92CI,iBAAA,QAHF,mBFuIF,mBPwuCC,iBAAA,QCSD,WQr3CI,iBAAA,QF6IF,kBADF,kBAEE,iBAAA,QPuuCD,aO9tCC,eAAgB,INuuChB,OAAQ,KAAK,EAAE,KMruCf,cAAA,IAAA,MAAA,KAFF,GPmuCC,GCSC,WAAY,EACZ,cAAe,KM/tCf,MP2tCD,MO5tCD,MAPI,MASF,cAAA,EAIF,eALE,aAAA,EACA,WAAA,KPmuCD,aO/tCC,aAAc,EAKZ,YAAA,KACA,WAAA,KP8tCH,gBOxtCC,QAAS,aACT,cAAA,IACA,aAAA,IAEF,GNiuCE,WAAY,EM/tCZ,cAAA,KAGA,GADF,GP2tCC,YAAA,WOvtCC,GP0tCD,YAAA,IOpnCD,GAvFM,YAAA,EAEA,yBACA,kBGtNJ,MAAA,KACA,MAAA,MACA,SAAA,OVs6CC,MAAA,KO9nCC,WAAY,MAhFV,cAAA,SPitCH,YAAA,OOvsCD,kBNitCE,YAAa,OM3sCjB,0BPusCC,YOtsCC,OAAA,KA9IqB,cAAA,IAAA,OAAA,KAmJvB,YACE,UAAA,IACA,eAAA,UAEA,WPusCD,QAAA,KAAA,KOlsCG,OAAA,EAAA,EAAA,KN2sCF,UAAW,OACX,YAAa,IAAI,MAAM,KMrtCzB,yBPgtCC,wBOhtCD,yBN0tCE,cAAe,EMpsCb,kBAFA,kBACA,iBPmsCH,QAAA,MOhsCG,UAAA,INysCF,YAAa,WACb,MAAO,KMjsCT,yBP4rCC,yBO5rCD,wBAEE,QAAA,cAEA,oBACA,sBACA,cAAA,KP8rCD,aAAA,EOxrCG,WAAA,MNisCF,aAAc,IAAI,MAAM,KACxB,YAAa,EMjsCX,kCNmsCJ,kCMpsCe,iCACX,oCNosCJ,oCDLC,mCCUC,QAAS,GMlsCX,iCNosCA,iCM1sCM,gCAOJ,mCNosCF,mCDLC,kCO9rCC,QAAA,cPmsCD,QWx+CC,cAAe,KVi/Cf,WAAY,OACZ,YAAa,WU9+Cb,KX0+CD,IWt+CD,IACE,KACA,YAAA,MAAA,OAAA,SAAA,cAAA,UAEA,KACA,QAAA,IAAA,IXw+CD,UAAA,IWp+CC,MAAO,QACP,iBAAA,QACA,cAAA,IAEA,IACA,QAAA,IAAA,IACA,UAAA,IV6+CA,MU7+CA,KXs+CD,iBAAA,KW5+CC,cAAe,IASb,mBAAA,MAAA,EAAA,KAAA,EAAA,gBACA,WAAA,MAAA,EAAA,KAAA,EAAA,gBAEA,QV8+CF,QU9+CE,EXs+CH,UAAA,KWj+CC,YAAa,IACb,mBAAA,KACA,WAAA,KAEA,IACA,QAAA,MACA,QAAA,MACA,OAAA,EAAA,EAAA,KACA,UAAA,KACA,YAAA,WACA,MAAA,KACA,WAAA,UXm+CD,UAAA,WW9+CC,iBAAkB,QAehB,OAAA,IAAA,MAAA,KACA,cAAA,IAEA,SACA,QAAA,EACA,UAAA,QXk+CH,MAAA,QW79CC,YAAa,SACb,iBAAA,YACA,cAAA,EC1DF,gBCHE,WAAA,MACA,WAAA,OAEA,Wb+hDD,cAAA,KYzhDC,aAAA,KAqEA,aAAc,KAvEZ,YAAA,KZgiDH,yBY3hDC,WAkEE,MAAO,OZ89CV,yBY7hDC,WA+DE,MAAO,OZm+CV,0BY1hDC,WCvBA,MAAA,QAGA,iBbojDD,cAAA,KYvhDC,aAAc,KCvBd,aAAA,KACA,YAAA,KCAE,KACE,aAAA,MAEA,YAAA,MAGA,UAAA,WAAA,WAAA,WAAA,UAAA,UAAA,UAAA,UAAA,UAAA,UAAA,UAAA,UAAA,UAAA,WAAA,WAAA,WAAA,UAAA,UAAA,UAAA,UAAA,UAAA,UAAA,UAAA,UAAA,UAAA,WAAA,WAAA,WAAA,UAAA,UAAA,UAAA,UAAA,UAAA,UAAA,UAAA,UAAA,UAAA,WAAA,WAAA,WAAA,UAAA,UAAA,UAAA,UAAA,UAAA,UAAA,UAAA,UdijDL,SAAA,ScjiDG,WAAA,IACE,cAAA,KdmiDL,aAAA,Kc3hDG,UAAA,WAAA,WAAA,WAAA,UAAA,UAAA,UAAA,UAAA,UAAA,UAAA,UAAA,Ud8hDH,MAAA,Kc9hDG,WdiiDH,MAAA,KcjiDG,WdoiDH,MAAA,acpiDG,WduiDH,MAAA,acviDG,Ud0iDH,MAAA,Ic1iDG,Ud6iDH,MAAA,ac7iDG,UdgjDH,MAAA,achjDG,UdmjDH,MAAA,IcnjDG,UdsjDH,MAAA,actjDG,UdyjDH,MAAA,aczjDG,Ud4jDH,MAAA,Ic5jDG,Ud+jDH,MAAA,achjDG,UdmjDH,MAAA,YcnjDG,gBdsjDH,MAAA,KctjDG,gBdyjDH,MAAA,aczjDG,gBd4jDH,MAAA,ac5jDG,ed+jDH,MAAA,Ic/jDG,edkkDH,MAAA,aclkDG,edqkDH,MAAA,acrkDG,edwkDH,MAAA,IcxkDG,ed2kDH,MAAA,ac3kDG,ed8kDH,MAAA,ac9kDG,edilDH,MAAA,IcjlDG,edolDH,MAAA,ac/kDG,edklDH,MAAA,YcjmDG,edomDH,MAAA,KcpmDG,gBdumDH,KAAA,KcvmDG,gBd0mDH,KAAA,ac1mDG,gBd6mDH,KAAA,ac7mDG,edgnDH,KAAA,IchnDG,edmnDH,KAAA,acnnDG,edsnDH,KAAA,actnDG,edynDH,KAAA,IcznDG,ed4nDH,KAAA,ac5nDG,ed+nDH,KAAA,ac/nDG,edkoDH,KAAA,IcloDG,edqoDH,KAAA,achoDG,edmoDH,KAAA,YcpnDG,edunDH,KAAA,KcvnDG,kBd0nDH,YAAA,Kc1nDG,kBd6nDH,YAAA,ac7nDG,kBdgoDH,YAAA,achoDG,iBdmoDH,YAAA,IcnoDG,iBdsoDH,YAAA,actoDG,iBdyoDH,YAAA,aczoDG,iBd4oDH,YAAA,Ic5oDG,iBd+oDH,YAAA,ac/oDG,iBdkpDH,YAAA,aclpDG,iBdqpDH,YAAA,IcrpDG,iBdwpDH,YAAA,acxpDG,iBd2pDH,YAAA,Yc7rDG,iBACE,YAAA,EAOJ,yBACE,UAAA,WAAA,WAAA,WAAA,UAAA,UAAA,UAAA,UAAA,UAAA,UAAA,UAAA,Ud2rDD,MAAA,Kc3rDC,Wd8rDD,MAAA,Kc9rDC,WdisDD,MAAA,acjsDC,WdosDD,MAAA,acpsDC,UdusDD,MAAA,IcvsDC,Ud0sDD,MAAA,ac1sDC,Ud6sDD,MAAA,ac7sDC,UdgtDD,MAAA,IchtDC,UdmtDD,MAAA,acntDC,UdstDD,MAAA,acttDC,UdytDD,MAAA,IcztDC,Ud4tDD,MAAA,ac7sDC,UdgtDD,MAAA,YchtDC,gBdmtDD,MAAA,KcntDC,gBdstDD,MAAA,acttDC,gBdytDD,MAAA,acztDC,ed4tDD,MAAA,Ic5tDC,ed+tDD,MAAA,ac/tDC,edkuDD,MAAA,acluDC,edquDD,MAAA,IcruDC,edwuDD,MAAA,acxuDC,ed2uDD,MAAA,ac3uDC,ed8uDD,MAAA,Ic9uDC,edivDD,MAAA,ac5uDC,ed+uDD,MAAA,Yc9vDC,ediwDD,MAAA,KcjwDC,gBdowDD,KAAA,KcpwDC,gBduwDD,KAAA,acvwDC,gBd0wDD,KAAA,ac1wDC,ed6wDD,KAAA,Ic7wDC,edgxDD,KAAA,achxDC,edmxDD,KAAA,acnxDC,edsxDD,KAAA,IctxDC,edyxDD,KAAA,aczxDC,ed4xDD,KAAA,ac5xDC,ed+xDD,KAAA,Ic/xDC,edkyDD,KAAA,ac7xDC,edgyDD,KAAA,YcjxDC,edoxDD,KAAA,KcpxDC,kBduxDD,YAAA,KcvxDC,kBd0xDD,YAAA,ac1xDC,kBd6xDD,YAAA,ac7xDC,iBdgyDD,YAAA,IchyDC,iBdmyDD,YAAA,acnyDC,iBdsyDD,YAAA,actyDC,iBdyyDD,YAAA,IczyDC,iBd4yDD,YAAA,ac5yDC,iBd+yDD,YAAA,ac/yDC,iBdkzDD,YAAA,IclzDC,iBdqzDD,YAAA,acrzDC,iBdwzDD,YAAA,YY/yDD,iBE3CE,YAAA,GAQF,yBACE,UAAA,WAAA,WAAA,WAAA,UAAA,UAAA,UAAA,UAAA,UAAA,UAAA,UAAA,Udy1DD,MAAA,Kcz1DC,Wd41DD,MAAA,Kc51DC,Wd+1DD,MAAA,ac/1DC,Wdk2DD,MAAA,acl2DC,Udq2DD,MAAA,Icr2DC,Udw2DD,MAAA,acx2DC,Ud22DD,MAAA,ac32DC,Ud82DD,MAAA,Ic92DC,Udi3DD,MAAA,acj3DC,Udo3DD,MAAA,acp3DC,Udu3DD,MAAA,Icv3DC,Ud03DD,MAAA,ac32DC,Ud82DD,MAAA,Yc92DC,gBdi3DD,MAAA,Kcj3DC,gBdo3DD,MAAA,acp3DC,gBdu3DD,MAAA,acv3DC,ed03DD,MAAA,Ic13DC,ed63DD,MAAA,ac73DC,edg4DD,MAAA,ach4DC,edm4DD,MAAA,Icn4DC,eds4DD,MAAA,act4DC,edy4DD,MAAA,acz4DC,ed44DD,MAAA,Ic54DC,ed+4DD,MAAA,ac14DC,ed64DD,MAAA,Yc55DC,ed+5DD,MAAA,Kc/5DC,gBdk6DD,KAAA,Kcl6DC,gBdq6DD,KAAA,acr6DC,gBdw6DD,KAAA,acx6DC,ed26DD,KAAA,Ic36DC,ed86DD,KAAA,ac96DC,edi7DD,KAAA,acj7DC,edo7DD,KAAA,Icp7DC,edu7DD,KAAA,acv7DC,ed07DD,KAAA,ac17DC,ed67DD,KAAA,Ic77DC,edg8DD,KAAA,ac37DC,ed87DD,KAAA,Yc/6DC,edk7DD,KAAA,Kcl7DC,kBdq7DD,YAAA,Kcr7DC,kBdw7DD,YAAA,acx7DC,kBd27DD,YAAA,ac37DC,iBd87DD,YAAA,Ic97DC,iBdi8DD,YAAA,acj8DC,iBdo8DD,YAAA,acp8DC,iBdu8DD,YAAA,Icv8DC,iBd08DD,YAAA,ac18DC,iBd68DD,YAAA,ac78DC,iBdg9DD,YAAA,Ich9DC,iBdm9DD,YAAA,acn9DC,iBds9DD,YAAA,YY18DD,iBE9CE,YAAA,GAQF,0BACE,UAAA,WAAA,WAAA,WAAA,UAAA,UAAA,UAAA,UAAA,UAAA,UAAA,UAAA,Udu/DD,MAAA,Kcv/DC,Wd0/DD,MAAA,Kc1/DC,Wd6/DD,MAAA,ac7/DC,WdggED,MAAA,achgEC,UdmgED,MAAA,IcngEC,UdsgED,MAAA,actgEC,UdygED,MAAA,aczgEC,Ud4gED,MAAA,Ic5gEC,Ud+gED,MAAA,ac/gEC,UdkhED,MAAA,aclhEC,UdqhED,MAAA,IcrhEC,UdwhED,MAAA,aczgEC,Ud4gED,MAAA,Yc5gEC,gBd+gED,MAAA,Kc/gEC,gBdkhED,MAAA,aclhEC,gBdqhED,MAAA,acrhEC,edwhED,MAAA,IcxhEC,ed2hED,MAAA,ac3hEC,ed8hED,MAAA,ac9hEC,ediiED,MAAA,IcjiEC,edoiED,MAAA,acpiEC,eduiED,MAAA,acviEC,ed0iED,MAAA,Ic1iEC,ed6iED,MAAA,acxiEC,ed2iED,MAAA,Yc1jEC,ed6jED,MAAA,Kc7jEC,gBdgkED,KAAA,KchkEC,gBdmkED,KAAA,acnkEC,gBdskED,KAAA,actkEC,edykED,KAAA,IczkEC,ed4kED,KAAA,ac5kEC,ed+kED,KAAA,ac/kEC,edklED,KAAA,IcllEC,edqlED,KAAA,acrlEC,edwlED,KAAA,acxlEC,ed2lED,KAAA,Ic3lEC,ed8lED,KAAA,aczlEC,ed4lED,KAAA,Yc7kEC,edglED,KAAA,KchlEC,kBdmlED,YAAA,KcnlEC,kBdslED,YAAA,actlEC,kBdylED,YAAA,aczlEC,iBd4lED,YAAA,Ic5lEC,iBd+lED,YAAA,ac/lEC,iBdkmED,YAAA,aclmEC,iBdqmED,YAAA,IcrmEC,iBdwmED,YAAA,acxmEC,iBd2mED,YAAA,ac3mEC,iBd8mED,YAAA,Ic9mEC,iBdinED,YAAA,acjnEC,iBdonED,YAAA,YevrED,iBACA,YAAA,GAGA,MACA,iBAAA,YAEA,Qf0rED,YAAA,IexrEC,eAAgB,IAChB,MAAA,Kf0rED,WAAA,KenrEC,GACA,WAAA,KfurED,OezrEC,MAAO,KdosEP,UAAW,KACX,cAAe,KcxrET,mBd2rER,mBc1rEQ,mBAHA,mBACA,mBd2rER,mBDHC,QAAA,IepsEC,YAAa,WAoBX,eAAA,IACA,WAAA,IAAA,MAAA,KArBJ,mBdmtEE,eAAgB,OAChB,cAAe,IAAI,MAAM,KDJ1B,uCCMD,uCcttEA,wCdutEA,wCcnrEI,2CANI,2CfqrEP,WAAA,Ee1qEG,mBf6qEH,WAAA,IAAA,MAAA,KCWD,cACE,iBAAkB,KchqEpB,6BdmqEA,6BclqEE,6BAZM,6BfuqEP,6BCMD,6BDHC,QAAA,ICWD,gBACE,OAAQ,IAAI,MAAM,Kc3qEpB,4Bd8qEA,4Bc9qEA,4BAQQ,4Bf+pEP,4BCMD,4Bc9pEM,OAAA,IAAA,MAAA,KAYF,4BAFJ,4BfqpEC,oBAAA,IexoEG,yCf2oEH,iBAAA,QejoEC,4BACA,iBAAA,QfqoED,uBe/nEG,SAAA,Od0oEF,QAAS,aczoEL,MAAA,KAEA,sBfkoEL,sBgB9wEC,SAAA,OfyxEA,QAAS,WACT,MAAO,KAST,0BetxEE,0BfgxEF,0BAGA,0BezxEM,0BAMJ,0BfixEF,0BAGA,0BACA,0BDNC,0BCAD,0BAGA,0BASE,iBAAkB,QDLnB,sCgBnyEC,sCAAA,oCf0yEF,sCevxEM,sCf4xEJ,iBAAkB,QASpB,2Be3yEE,2BfqyEF,2BAGA,2Be9yEM,2BAMJ,2BfsyEF,2BAGA,2BACA,2BDNC,2BCAD,2BAGA,2BASE,iBAAkB,QDLnB,uCgBxzEC,uCAAA,qCf+zEF,uCe5yEM,uCfizEJ,iBAAkB,QASpB,wBeh0EE,wBf0zEF,wBAGA,wBen0EM,wBAMJ,wBf2zEF,wBAGA,wBACA,wBDNC,wBCAD,wBAGA,wBASE,iBAAkB,QDLnB,oCgB70EC,oCAAA,kCfo1EF,oCej0EM,oCfs0EJ,iBAAkB,QASpB,2Ber1EE,2Bf+0EF,2BAGA,2Bex1EM,2BAMJ,2Bfg1EF,2BAGA,2BACA,2BDNC,2BCAD,2BAGA,2BASE,iBAAkB,QDLnB,uCgBl2EC,uCAAA,qCfy2EF,uCet1EM,uCf21EJ,iBAAkB,QASpB,0Be12EE,0Bfo2EF,0BAGA,0Be72EM,0BAMJ,0Bfq2EF,0BAGA,0BACA,0BDNC,0BCAD,0BAGA,0BASE,iBAAkB,QDLnB,sCejtEC,sCADF,oCdytEA,sCe32EM,sCDoJJ,iBAAA,QA6DF,kBACE,WAAY,KA3DV,WAAA,KAEA,oCACA,kBACA,MAAA,KfqtED,cAAA,Ke9pEC,WAAY,OAnDV,mBAAA,yBfotEH,OAAA,IAAA,MAAA,KCWD,yBACE,cAAe,Ec7qEjB,qCdgrEA,qCcltEI,qCARM,qCfmtET,qCCMD,qCDHC,YAAA,OCWD,kCACE,OAAQ,EcxrEV,0Dd2rEA,0Dc3rEA,0DAzBU,0Df6sET,0DCMD,0DAME,YAAa,EchsEf,yDdmsEA,yDcnsEA,yDArBU,yDfitET,yDCMD,yDAME,aAAc,EDLjB,yDe3sEW,yDEzNV,yDjBm6EC,yDiBl6ED,cAAA,GAMA,SjBm6ED,UAAA,EiBh6EC,QAAS,EACT,OAAA,EACA,OAAA,EAEA,OACA,QAAA,MACA,MAAA,KACA,QAAA,EACA,cAAA,KACA,UAAA,KjBk6ED,YAAA,QiB/5EC,MAAO,KACP,OAAA,EACA,cAAA,IAAA,MAAA,QAEA,MjBi6ED,QAAA,aiBt5EC,UAAW,Kb4BX,cAAA,IACG,YAAA,IJ83EJ,mBiBt5EC,mBAAoB,WhBi6EjB,gBAAiB,WgB/5EpB,WAAA,WjB05ED,qBiBx5EC,kBAGA,OAAQ,IAAI,EAAE,EACd,WAAA,MjBu5ED,YAAA,OiBl5EC,iBACA,QAAA,MAIF,kBhB45EE,QAAS,MgB15ET,MAAA,KAIF,iBAAA,ahB25EE,OAAQ,KIh+ER,uBL29ED,2BK19EC,wBY2EA,QAAS,KAAK,OACd,QAAA,IAAA,KAAA,yBACA,eAAA,KAEA,OACA,QAAA,MjBi5ED,YAAA,IiBv3EC,UAAW,KACX,YAAA,WACA,MAAA,KAEA,cACA,QAAA,MACA,MAAA,KACA,OAAA,KACA,QAAA,IAAA,KACA,UAAA,KACA,YAAA,WACA,MAAA,KbxDA,iBAAA,KACQ,iBAAA,KAyHR,OAAA,IAAA,MAAA,KACK,cAAA,IACG,mBAAA,MAAA,EAAA,IAAA,IAAA,iBJ0zET,WAAA,MAAA,EAAA,IAAA,IAAA,iBkBl8EC,mBAAA,aAAA,YAAA,KAAA,mBAAA,YAAA,KACE,cAAA,aAAA,YAAA,KAAA,WAAA,YAAA,KACA,WAAA,aAAA,YAAA,KAAA,WAAA,YAAA,KdWM,oBJ27ET,aAAA,QI15EC,QAAA,EACE,mBAAA,MAAA,EAAA,IAAA,IAAA,iBAAA,EAAA,EAAA,IAAA,qBACA,WAAA,MAAA,EAAA,IAAA,IAAA,iBAAA,EAAA,EAAA,IAAA,qBAEF,gCAA0B,MAAA,KJ65E3B,QAAA,EI55EiC,oCJ+5EjC,MAAA,KiBl4EG,yCACA,MAAA,KAQF,0BhBw4EA,iBAAkB,YAClB,OAAQ,EgBr4EN,wBjB+3EH,wBiB53EC,iChBu4EA,iBAAkB,KgBr4EhB,QAAA,EAIF,wBACE,iCjB43EH,OAAA,YiB/2EC,sBjBk3ED,OAAA,KiBh2EG,mBhB42EF,mBAAoB,KAEtB,qDgB72EM,8BjBs2EH,8BiBn2EC,wCAAA,+BhB+2EA,YAAa,KgB72EX,iCjB22EH,iCiBx2EC,2CAAA,kChB42EF,0BACA,0BACA,oCACA,2BAKE,YAAa,KgBl3EX,iCjBg3EH,iCACF,2CiBt2EC,kChBy2EA,0BACA,0BACA,oCACA,2BgB32EA,YAAA,MhBm3EF,YgBz2EE,cAAA,KAGA,UADA,OjBm2ED,SAAA,SiBv2EC,QAAS,MhBk3ET,WAAY,KgB12EV,cAAA,KAGA,gBADA,aAEA,WAAA,KjBm2EH,aAAA,KiBh2EC,cAAe,EhB22Ef,YAAa,IACb,OAAQ,QgBt2ER,+BjBk2ED,sCiBp2EC,yBACA,gCAIA,SAAU,ShB02EV,WAAY,MgBx2EZ,YAAA,MAIF,oBAAA,cAEE,WAAA,KAGA,iBADA,cAEA,SAAA,SACA,QAAA,aACA,aAAA,KjB+1ED,cAAA,EiB71EC,YAAa,IhBw2Eb,eAAgB,OgBt2EhB,OAAA,QAUA,kCjBs1ED,4BCWC,WAAY,EACZ,YAAa,KgBz1Eb,wCAAA,qCjBq1ED,8BCOD,+BgBl2EI,2BhBi2EJ,4BAME,OAAQ,YDNT,0BiBz1EG,uBAMF,oCAAA,iChB+1EA,OAAQ,YDNT,yBiBt1EK,sBAaJ,mCAFF,gCAGE,OAAA,YAGA,qBjB20ED,WAAA,KiBz0EC,YAAA,IhBo1EA,eAAgB,IgBl1Ed,cAAA,EjB40EH,8BiB9zED,8BCnQE,cAAA,EACA,aAAA,EAEA,UACA,OAAA,KlBokFD,QAAA,IAAA,KkBlkFC,UAAA,KACE,YAAA,IACA,cAAA,IAGF,gBjB4kFA,OAAQ,KiB1kFN,YAAA,KD2PA,0BAFJ,kBAGI,OAAA,KAEA,6BACA,OAAA,KjB20EH,QAAA,IAAA,KiBj1EC,UAAW,KAST,YAAA,IACA,cAAA,IAVJ,mChBg2EE,OAAQ,KgBl1EN,YAAA,KAGA,6CAjBJ,qCAkBI,OAAA,KAEA,oCACA,OAAA,KjB20EH,WAAA,KiBv0EC,QAAS,IAAI,KC/Rb,UAAA,KACA,YAAA,IAEA,UACA,OAAA,KlBymFD,QAAA,KAAA,KkBvmFC,UAAA,KACE,YAAA,UACA,cAAA,IAGF,gBjBinFA,OAAQ,KiB/mFN,YAAA,KDuRA,0BAFJ,kBAGI,OAAA,KAEA,6BACA,OAAA,KjBo1EH,QAAA,KAAA,KiB11EC,UAAW,KAST,YAAA,UACA,cAAA,IAVJ,mChBy2EE,OAAQ,KgB31EN,YAAA,KAGA,6CAjBJ,qCAkBI,OAAA,KAEA,oCACA,OAAA,KjBo1EH,WAAA,KiB30EC,QAAS,KAAK,KAEd,UAAA,KjB40ED,YAAA,UiBx0EG,cjB20EH,SAAA,SiBt0EC,4BACA,cAAA,OAEA,uBACA,SAAA,SACA,IAAA,EACA,MAAA,EACA,QAAA,EACA,QAAA,MACA,MAAA,KjBy0ED,OAAA,KiBv0EC,YAAa,KhBk1Eb,WAAY,OACZ,eAAgB,KDLjB,oDiBz0EC,uCADA,iCAGA,MAAO,KhBk1EP,OAAQ,KACR,YAAa,KDLd,oDiBz0EC,uCADA,iCAKA,MAAO,KhBg1EP,OAAQ,KACR,YAAa,KAKf,uBAEA,8BAJA,4BADA,yBAEA,oBAEA,2BDNC,4BkBvuFG,mCAJA,yBD0ZJ,gCbvWE,MAAA,QJ6rFD,2BkB1uFG,aAAA,QACE,mBAAA,MAAA,EAAA,IAAA,IAAA,iBd4CJ,WAAA,MAAA,EAAA,IAAA,IAAA,iBJksFD,iCiB31EC,aAAc,QC5YZ,mBAAA,MAAA,EAAA,IAAA,IAAA,iBAAA,EAAA,EAAA,IAAA,QACA,WAAA,MAAA,EAAA,IAAA,IAAA,iBAAA,EAAA,EAAA,IAAA,QlB2uFH,gCiBh2EC,MAAO,QCtYL,iBAAA,QlByuFH,aAAA,QCWD,oCACE,MAAO,QAKT,uBAEA,8BAJA,4BADA,yBAEA,oBAEA,2BDNC,4BkBrwFG,mCAJA,yBD6ZJ,gCb1WE,MAAA,QJ2tFD,2BkBxwFG,aAAA,QACE,mBAAA,MAAA,EAAA,IAAA,IAAA,iBd4CJ,WAAA,MAAA,EAAA,IAAA,IAAA,iBJguFD,iCiBt3EC,aAAc,QC/YZ,mBAAA,MAAA,EAAA,IAAA,IAAA,iBAAA,EAAA,EAAA,IAAA,QACA,WAAA,MAAA,EAAA,IAAA,IAAA,iBAAA,EAAA,EAAA,IAAA,QlBywFH,gCiB33EC,MAAO,QCzYL,iBAAA,QlBuwFH,aAAA,QCWD,oCACE,MAAO,QAKT,qBAEA,4BAJA,0BADA,uBAEA,kBAEA,yBDNC,0BkBnyFG,iCAJA,uBDgaJ,8Bb7WE,MAAA,QJyvFD,yBkBtyFG,aAAA,QACE,mBAAA,MAAA,EAAA,IAAA,IAAA,iBd4CJ,WAAA,MAAA,EAAA,IAAA,IAAA,iBJ8vFD,+BiBj5EC,aAAc,QClZZ,mBAAA,MAAA,EAAA,IAAA,IAAA,iBAAA,EAAA,EAAA,IAAA,QACA,WAAA,MAAA,EAAA,IAAA,IAAA,iBAAA,EAAA,EAAA,IAAA,QlBuyFH,8BiBt5EC,MAAO,QC5YL,iBAAA,QlBqyFH,aAAA,QiBj5EG,kCjBo5EH,MAAA,QiBj5EG,2CjBo5EH,IAAA,KiBz4EC,mDACA,IAAA,EAEA,YjB44ED,QAAA,MiBzzEC,WAAY,IAwEZ,cAAe,KAtIX,MAAA,QAEA,yBjB23EH,yBiBvvEC,QAAS,aA/HP,cAAA,EACA,eAAA,OjB03EH,2BiB5vEC,QAAS,aAxHP,MAAA,KjBu3EH,eAAA,OiBn3EG,kCACA,QAAA,aAmHJ,0BhB8wEE,QAAS,aACT,eAAgB,OgBv3Ed,wCjBg3EH,6CiBxwED,2CjB2wEC,MAAA,KiB/2EG,wCACA,MAAA,KAmGJ,4BhB0xEE,cAAe,EgBt3Eb,eAAA,OAGA,uBADA,oBjBg3EH,QAAA,aiBtxEC,WAAY,EhBiyEZ,cAAe,EgBv3EX,eAAA,OAsFN,6BAAA,0BAjFI,aAAA,EAiFJ,4CjB+xEC,sCiB12EG,SAAA,SjB62EH,YAAA,EiBl2ED,kDhB82EE,IAAK,GgBp2EL,2BjBi2EH,kCiBl2EG,wBAEA,+BAXF,YAAa,IhBs3Eb,WAAY,EgBr2EV,cAAA,EJviBF,2BIshBF,wBJrhBE,WAAA,KI4jBA,6BAyBA,aAAc,MAnCV,YAAA,MAEA,yBjB01EH,gCACF,YAAA,IiB13EG,cAAe,EAwCf,WAAA,OAwBJ,sDAdQ,MAAA,KjBg1EL,yBACF,+CiBr0EC,YAAA,KAEE,UAAW,MjBw0EZ,yBACF,+CmBt6FG,YAAa,IACf,UAAA,MAGA,KACA,QAAA,aACA,QAAA,IAAA,KAAA,cAAA,EACA,UAAA,KACA,YAAA,IACA,YAAA,WACA,WAAA,OC0CA,YAAA,OACA,eAAA,OACA,iBAAA,aACA,aAAA,ahB+JA,OAAA,QACG,oBAAA,KACC,iBAAA,KACI,gBAAA,KJiuFT,YAAA,KmBz6FG,iBAAA,KlBq7FF,OAAQ,IAAI,MAAM,YAClB,cAAe,IDHhB,kBKx8FC,kBAEA,WACA,kBJ28FF,kBADA,WkBl7FE,QAAA,KAAA,OlBy7FA,QAAS,IAAI,KAAK,yBAClB,eAAgB,KkBn7FhB,WnB46FD,WmB/6FG,WlB27FF,MAAO,KkBt7FL,gBAAA,Kf6BM,YADR,YJq5FD,iBAAA,KmB56FC,QAAA,ElBw7FA,mBAAoB,MAAM,EAAE,IAAI,IAAI,iBAC5B,WAAY,MAAM,EAAE,IAAI,IAAI,iBoBn+FpC,cAGA,ejB8DA,wBACQ,OAAA,YJ65FT,OAAA,kBmB56FG,mBAAA,KlBw7FM,WAAY,KkBt7FhB,QAAA,IASN,eC3DE,yBACA,eAAA,KpBo+FD,aoBj+FC,MAAA,KnB6+FA,iBAAkB,KmB3+FhB,aAAA,KpBq+FH,mBoBn+FO,mBAEN,MAAA,KACE,iBAAA,QACA,aAAA,QpBo+FH,mBoBj+FC,MAAA,KnB6+FA,iBAAkB,QAClB,aAAc,QmBz+FR,oBADJ,oBpBo+FH,mCoBj+FG,MAAA,KnB6+FF,iBAAkB,QAClB,aAAc,QmBz+FN,0BnB++FV,0BAHA,0BmB7+FM,0BnB++FN,0BAHA,0BDFC,yCoB3+FK,yCnB++FN,yCmB1+FE,MAAA,KnBk/FA,iBAAkB,QAClB,aAAc,QmB3+FZ,oBpBm+FH,oBoBn+FG,mCnBg/FF,iBAAkB,KmB5+FV,4BnBi/FV,4BAHA,4BDHC,6BCOD,6BAHA,6BkB99FA,sCClBM,sCnBi/FN,sCmB3+FI,iBAAA,KACA,aAAA,KDcJ,oBC9DE,MAAA,KACA,iBAAA,KpB6hGD,aoB1hGC,MAAA,KnBsiGA,iBAAkB,QmBpiGhB,aAAA,QpB8hGH,mBoB5hGO,mBAEN,MAAA,KACE,iBAAA,QACA,aAAA,QpB6hGH,mBoB1hGC,MAAA,KnBsiGA,iBAAkB,QAClB,aAAc,QmBliGR,oBADJ,oBpB6hGH,mCoB1hGG,MAAA,KnBsiGF,iBAAkB,QAClB,aAAc,QmBliGN,0BnBwiGV,0BAHA,0BmBtiGM,0BnBwiGN,0BAHA,0BDFC,yCoBpiGK,yCnBwiGN,yCmBniGE,MAAA,KnB2iGA,iBAAkB,QAClB,aAAc,QmBpiGZ,oBpB4hGH,oBoB5hGG,mCnByiGF,iBAAkB,KmBriGV,4BnB0iGV,4BAHA,4BDHC,6BCOD,6BAHA,6BkBphGA,sCCrBM,sCnB0iGN,sCmBpiGI,iBAAA,QACA,aAAA,QDkBJ,oBClEE,MAAA,QACA,iBAAA,KpBslGD,aoBnlGC,MAAA,KnB+lGA,iBAAkB,QmB7lGhB,aAAA,QpBulGH,mBoBrlGO,mBAEN,MAAA,KACE,iBAAA,QACA,aAAA,QpBslGH,mBoBnlGC,MAAA,KnB+lGA,iBAAkB,QAClB,aAAc,QmB3lGR,oBADJ,oBpBslGH,mCoBnlGG,MAAA,KnB+lGF,iBAAkB,QAClB,aAAc,QmB3lGN,0BnBimGV,0BAHA,0BmB/lGM,0BnBimGN,0BAHA,0BDFC,yCoB7lGK,yCnBimGN,yCmB5lGE,MAAA,KnBomGA,iBAAkB,QAClB,aAAc,QmB7lGZ,oBpBqlGH,oBoBrlGG,mCnBkmGF,iBAAkB,KmB9lGV,4BnBmmGV,4BAHA,4BDHC,6BCOD,6BAHA,6BkBzkGA,sCCzBM,sCnBmmGN,sCmB7lGI,iBAAA,QACA,aAAA,QDsBJ,oBCtEE,MAAA,QACA,iBAAA,KpB+oGD,UoB5oGC,MAAA,KnBwpGA,iBAAkB,QmBtpGhB,aAAA,QpBgpGH,gBoB9oGO,gBAEN,MAAA,KACE,iBAAA,QACA,aAAA,QpB+oGH,gBoB5oGC,MAAA,KnBwpGA,iBAAkB,QAClB,aAAc,QmBppGR,iBADJ,iBpB+oGH,gCoB5oGG,MAAA,KnBwpGF,iBAAkB,QAClB,aAAc,QmBppGN,uBnB0pGV,uBAHA,uBmBxpGM,uBnB0pGN,uBAHA,uBDFC,sCoBtpGK,sCnB0pGN,sCmBrpGE,MAAA,KnB6pGA,iBAAkB,QAClB,aAAc,QmBtpGZ,iBpB8oGH,iBoB9oGG,gCnB2pGF,iBAAkB,KmBvpGV,yBnB4pGV,yBAHA,yBDHC,0BCOD,0BAHA,0BkB9nGA,mCC7BM,mCnB4pGN,mCmBtpGI,iBAAA,QACA,aAAA,QD0BJ,iBC1EE,MAAA,QACA,iBAAA,KpBwsGD,aoBrsGC,MAAA,KnBitGA,iBAAkB,QmB/sGhB,aAAA,QpBysGH,mBoBvsGO,mBAEN,MAAA,KACE,iBAAA,QACA,aAAA,QpBwsGH,mBoBrsGC,MAAA,KnBitGA,iBAAkB,QAClB,aAAc,QmB7sGR,oBADJ,oBpBwsGH,mCoBrsGG,MAAA,KnBitGF,iBAAkB,QAClB,aAAc,QmB7sGN,0BnBmtGV,0BAHA,0BmBjtGM,0BnBmtGN,0BAHA,0BDFC,yCoB/sGK,yCnBmtGN,yCmB9sGE,MAAA,KnBstGA,iBAAkB,QAClB,aAAc,QmB/sGZ,oBpBusGH,oBoBvsGG,mCnBotGF,iBAAkB,KmBhtGV,4BnBqtGV,4BAHA,4BDHC,6BCOD,6BAHA,6BkBnrGA,sCCjCM,sCnBqtGN,sCmB/sGI,iBAAA,QACA,aAAA,QD8BJ,oBC9EE,MAAA,QACA,iBAAA,KpBiwGD,YoB9vGC,MAAA,KnB0wGA,iBAAkB,QmBxwGhB,aAAA,QpBkwGH,kBoBhwGO,kBAEN,MAAA,KACE,iBAAA,QACA,aAAA,QpBiwGH,kBoB9vGC,MAAA,KnB0wGA,iBAAkB,QAClB,aAAc,QmBtwGR,mBADJ,mBpBiwGH,kCoB9vGG,MAAA,KnB0wGF,iBAAkB,QAClB,aAAc,QmBtwGN,yBnB4wGV,yBAHA,yBmB1wGM,yBnB4wGN,yBAHA,yBDFC,wCoBxwGK,wCnB4wGN,wCmBvwGE,MAAA,KnB+wGA,iBAAkB,QAClB,aAAc,QmBxwGZ,mBpBgwGH,mBoBhwGG,kCnB6wGF,iBAAkB,KmBzwGV,2BnB8wGV,2BAHA,2BDHC,4BCOD,4BAHA,4BkBxuGA,qCCrCM,qCnB8wGN,qCmBxwGI,iBAAA,QACA,aAAA,QDuCJ,mBACE,MAAA,QACA,iBAAA,KnBkuGD,UmB/tGC,YAAA,IlB2uGA,MAAO,QACP,cAAe,EAEjB,UG5wGE,iBemCE,iBflCM,oBJqwGT,6BmBhuGC,iBAAA,YlB4uGA,mBAAoB,KACZ,WAAY,KkBzuGlB,UAEF,iBAAA,gBnBguGD,gBmB9tGG,aAAA,YnBouGH,gBmBluGG,gBAIA,MAAA,QlB0uGF,gBAAiB,UACjB,iBAAkB,YDNnB,0BmBnuGK,0BAUN,mCATM,mClB8uGJ,MAAO,KmB7yGP,gBAAA,KAGA,mBADA,QpBsyGD,QAAA,KAAA,KmB5tGC,UAAW,KlBwuGX,YAAa,UmBpzGb,cAAA,IAGA,mBADA,QpB6yGD,QAAA,IAAA,KmB/tGC,UAAW,KlB2uGX,YAAa,ImB3zGb,cAAA,IAGA,mBADA,QpBozGD,QAAA,IAAA,ImB9tGC,UAAW,KACX,YAAA,IACA,cAAA,IAIF,WACE,QAAA,MnB8tGD,MAAA,KCYD,sBACE,WAAY,IqB53GZ,6BADF,4BtBq3GC,6BIhsGC,MAAA,KAEQ,MJosGT,QAAA,EsBx3GC,mBAAA,QAAA,KAAA,OACE,cAAA,QAAA,KAAA,OtB03GH,WAAA,QAAA,KAAA,OsBr3GC,StBw3GD,QAAA,EsBt3Ga,UtBy3Gb,QAAA,KsBx3Ga,atB23Gb,QAAA,MsB13Ga,etB63Gb,QAAA,UsBz3GC,kBACA,QAAA,gBlBwKA,YACQ,SAAA,SAAA,OAAA,EAOR,SAAA,OACQ,mCAAA,KAAA,8BAAA,KAGR,2BAAA,KACQ,4BAAA,KAAA,uBAAA,KJ8sGT,oBAAA,KuBx5GC,4BAA6B,OAAQ,WACrC,uBAAA,OAAA,WACA,oBAAA,OAAA,WAEA,OACA,QAAA,aACA,MAAA,EACA,OAAA,EACA,YAAA,IACA,eAAA,OvB05GD,WAAA,IAAA,OuBt5GC,WAAY,IAAI,QtBq6GhB,aAAc,IAAI,MAAM,YsBn6GxB,YAAA,IAAA,MAAA,YAKA,UADF,QvBu5GC,SAAA,SuBj5GC,uBACA,QAAA,EAEA,eACA,SAAA,SACA,IAAA,KACA,KAAA,EACA,QAAA,KACA,QAAA,KACA,MAAA,KACA,UAAA,MACA,QAAA,IAAA,EACA,OAAA,IAAA,EAAA,EACA,UAAA,KACA,WAAA,KACA,WAAA,KnBsBA,iBAAA,KACQ,wBAAA,YmBrBR,gBAAA,YtBk6GA,OsBl6GA,IAAA,MAAA,KvBq5GD,OAAA,IAAA,MAAA,gBuBh5GC,cAAA,IACE,mBAAA,EAAA,IAAA,KAAA,iBACA,WAAA,EAAA,IAAA,KAAA,iBAzBJ,0BCzBE,MAAA,EACA,KAAA,KAEA,wBxBu8GD,OAAA,IuBj7GC,OAAQ,IAAI,EAmCV,SAAA,OACA,iBAAA,QAEA,oBACA,QAAA,MACA,QAAA,IAAA,KACA,MAAA,KvBi5GH,YAAA,IuB34GC,YAAA,WtB25GA,MAAO,KsBz5GL,YAAA,OvB+4GH,0BuB74GG,0BAMF,MAAA,QtBu5GA,gBAAiB,KACjB,iBAAkB,QsBp5GhB,yBAEA,+BADA,+BvB04GH,MAAA,KuBh4GC,gBAAA,KtBg5GA,iBAAkB,QAClB,QAAS,EDZV,2BuB93GC,iCAAA,iCAEE,MAAA,KEzGF,iCF2GE,iCAEA,gBAAA,KvBg4GH,OAAA,YuB33GC,iBAAkB,YAGhB,iBAAA,KvB23GH,OAAA,0DuBt3GG,qBvBy3GH,QAAA,MuBh3GC,QACA,QAAA,EAQF,qBACE,MAAA,EACA,KAAA,KAIF,oBACE,MAAA,KACA,KAAA,EAEA,iBACA,QAAA,MACA,QAAA,IAAA,KvB22GD,UAAA,KuBv2GC,YAAa,WACb,MAAA,KACA,YAAA,OAEA,mBACA,SAAA,MACA,IAAA,EvBy2GD,MAAA,EuBr2GC,OAAQ,EACR,KAAA,EACA,QAAA,IAQF,2BtB+2GE,MAAO,EsB32GL,KAAA,KAEA,eACA,sCvB+1GH,QAAA,GuBt2GC,WAAY,EtBs3GZ,cAAe,IAAI,OsB32GjB,cAAA,IAAA,QAEA,uBvB+1GH,8CuB10GC,IAAK,KAXL,OAAA,KApEA,cAAA,IvB85GC,yBuB11GD,6BA1DA,MAAA,EACA,KAAA,KvBw5GD,kC0BviHG,MAAO,KzBujHP,KAAM,GyBnjHR,W1ByiHD,oB0B7iHC,SAAU,SzB6jHV,QAAS,ayBvjHP,eAAA,OAGA,yB1ByiHH,gBCgBC,SAAU,SACV,MAAO,KyBhjHT,gC1ByiHC,gCCYD,+BAFA,+ByBnjHA,uBANM,uBzB0jHN,sBAFA,sBAQE,QAAS,EyBrjHP,qB1B0iHH,2B0BriHD,2BACE,iC1BuiHD,YAAA,KCgBD,aACE,YAAa,KDZd,kB0B7iHD,wBAAA,0BzB8jHE,MAAO,KDZR,kB0BliHD,wBACE,0B1BoiHD,YAAA,I0B/hHC,yE1BkiHD,cAAA,E2BnlHC,4BACG,YAAA,EDsDL,mEzBgjHE,wBAAyB,E0B/lHzB,2BAAA,E3BolHD,6C0B/hHD,8CACE,uBAAA,E1BiiHD,0BAAA,E0B9hHC,sB1BiiHD,MAAA,KCgBD,8D0BlnHE,cAAA,E3BumHD,mE0B9hHD,oECjEE,wBAAA,EACG,2BAAA,EDqEL,oEzB6iHE,uBAAwB,EyB3iHxB,0BAAA,EAiBF,mCACE,iCACA,QAAA,EAEF,iCACE,cAAA,IACA,aAAA,IAKF,oCtB/CE,cAAA,KACQ,aAAA,KsBkDR,iCtBnDA,mBAAA,MAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,MAAA,EAAA,IAAA,IAAA,iBsByDV,0CACE,mBAAA,K1B0gHD,WAAA,K0BtgHC,YACA,YAAA,EAGF,eACE,aAAA,IAAA,IAAA,E1BwgHD,oBAAA,ECgBD,uBACE,aAAc,EAAE,IAAI,IyB7gHlB,yBACA,+BACA,oC1BkgHH,QAAA,M0BzgHC,MAAO,KAcH,MAAA,K1B8/GL,UAAA,KCgBD,oCACE,MAAO,KyBvgHL,8BACA,oC1B4/GH,oC0Bv/GC,0CACE,WAAA,K1By/GH,YAAA,E2BlqHC,4DACC,cAAA,EAQA,sD3B+pHF,uBAAA,I0Bz/GC,wBAAA,IC/KA,2BAAA,EACC,0BAAA,EAQA,sD3BqqHF,uBAAA,E0B1/GC,wBAAyB,EACzB,2BAAA,I1B4/GD,0BAAA,ICgBD,uE0BzrHE,cAAA,E3B8qHD,4E0Bz/GD,6EC7LE,2BAAA,EACC,0BAAA,EDoMH,6EACE,uBAAA,EACA,wBAAA,EAEA,qB1Bu/GD,QAAA,M0B3/GC,MAAO,KzB2gHP,aAAc,MyBpgHZ,gBAAA,SAEA,0B1Bw/GH,gC0BjgHC,QAAS,WAYP,MAAA,K1Bw/GH,MAAA,G0Bp/GG,qC1Bu/GH,MAAA,KCgBD,+CACE,KAAM,KyBh/GF,gDAFA,6C1By+GL,2D0Bx+GK,wDEzOJ,SAAU,SACV,KAAA,cACA,eAAA,K5BotHD,a4BhtHC,SAAA,SACE,QAAA,MACA,gBAAA,S5BmtHH,0B4B3tHC,MAAO,KAeL,cAAA,EACA,aAAA,EAOA,2BACA,SAAA,S5B0sHH,QAAA,E4BxsHG,MAAA,KACE,MAAA,K5B0sHL,cAAA,ECgBD,iCACE,QAAS,EiBtrHT,8BACA,mCACA,sCACA,OAAA,KlB2qHD,QAAA,KAAA,KkBzqHC,UAAA,KjByrHA,YAAa,UACb,cAAe,IiBxrHb,oClB6qHH,yCkB1qHC,4CjB0rHA,OAAQ,KACR,YAAa,KDTd,8C4BltHD,mDAAA,sD3B6tHA,sCACA,2CiB5rHI,8CjBisHF,OAAQ,KiB7sHR,8BACA,mCACA,sCACA,OAAA,KlBksHD,QAAA,IAAA,KkBhsHC,UAAA,KjBgtHA,YAAa,IACb,cAAe,IiB/sHb,oClBosHH,yCkBjsHC,4CjBitHA,OAAQ,KACR,YAAa,KDTd,8C4BhuHD,mDAAA,sD3B2uHA,sCACA,2CiBntHI,8CjBwtHF,OAAQ,K2B5uHR,2B5BguHD,mB4BhuHC,iB3BivHA,QAAS,W2B5uHX,8D5BguHC,sD4BhuHD,oDAEE,cAAA,EAEA,mB5BkuHD,iB4B7tHC,MAAO,GACP,YAAA,OACA,eAAA,OAEA,mBACA,QAAA,IAAA,KACA,UAAA,KACA,YAAA,IACA,YAAA,EACA,MAAA,K5B+tHD,WAAA,O4B5tHC,iBAAA,KACE,OAAA,IAAA,MAAA,KACA,cAAA,I5B+tHH,4B4B5tHC,QAAA,IAAA,KACE,UAAA,KACA,cAAA,I5B+tHH,4B4BlvHC,QAAS,KAAK,K3BkwHd,UAAW,K2BxuHT,cAAA,IAKJ,wCAAA,qC3BwuHE,WAAY,EAEd,uCACA,+BACA,kC0Bh1HE,6CACG,8CC4GL,6D5BwtHC,wE4BvtHC,wBAAA,E5B0tHD,2BAAA,ECgBD,+BACE,aAAc,EAEhB,sCACA,8B2BnuHA,+D5BytHC,oDCWD,iC0Br1HE,4CACG,6CCiHH,uBAAA,E5B2tHD,0BAAA,E4BrtHC,8BAGA,YAAA,E5ButHD,iB4B3tHC,SAAU,SAUR,UAAA,E5BotHH,YAAA,O4BltHK,sB5BqtHL,SAAA,SCgBD,2BACE,YAAa,K2B3tHb,6BAAA,4B5B+sHD,4B4B5sHK,QAAA,EAGJ,kCAAA,wCAGI,aAAA,K5B+sHL,iC6B72HD,uCACE,QAAA,EACA,YAAA,K7Bg3HD,K6Bl3HC,aAAc,EAOZ,cAAA,EACA,WAAA,KARJ,QAWM,SAAA,SACA,QAAA,M7B+2HL,U6B72HK,SAAA,S5B63HJ,QAAS,M4B33HH,QAAA,KAAA,KAMJ,gB7B02HH,gB6Bz2HK,gBAAA,K7B42HL,iBAAA,KCgBD,mB4Bx3HQ,MAAA,KAGA,yBADA,yB7B62HP,MAAA,K6Br2HG,gBAAA,K5Bq3HF,OAAQ,YACR,iBAAkB,Y4Bl3Hd,aAzCN,mB7Bg5HC,mBwBn5HC,iBAAA,KACA,aAAA,QAEA,kBxBs5HD,OAAA,I6Bt5HC,OAAQ,IAAI,EA0DV,SAAA,O7B+1HH,iBAAA,Q6Br1HC,c7Bw1HD,UAAA,K6Bt1HG,UAEA,cAAA,IAAA,MAAA,KALJ,aASM,MAAA,KACA,cAAA,KAEA,e7Bu1HL,aAAA,I6Bt1HK,YAAA,WACE,OAAA,IAAA,MAAA,Y7Bw1HP,cAAA,IAAA,IAAA,EAAA,ECgBD,qBACE,aAAc,KAAK,KAAK,K4B/1HlB,sBAEA,4BADA,4BAEA,MAAA,K7Bo1HP,OAAA,Q6B/0HC,iBAAA,KAqDA,OAAA,IAAA,MAAA,KA8BA,oBAAA,YAnFA,wBAwDE,MAAA,K7B8xHH,cAAA,E6B5xHK,2BACA,MAAA,KA3DJ,6BAgEE,cAAA,IACA,WAAA,OAYJ,iDA0DE,IAAK,KAjED,KAAA,K7B6xHH,yB6B5tHD,2BA9DM,QAAA,W7B6xHL,MAAA,G6Bt2HD,6BAuFE,cAAA,GAvFF,6B5B23HA,aAAc,EACd,cAAe,IDZhB,kC6BzuHD,wCA3BA,wCATM,OAAA,IAAA,MAAA,K7BkxHH,yB6B9uHD,6B5B8vHE,cAAe,IAAI,MAAM,KACzB,cAAe,IAAI,IAAI,EAAE,EDZ1B,kC6Bj3HD,wC7Bk3HD,wC6Bh3HG,oBAAA,MAIE,c7Bk3HL,MAAA,K6B/2HK,gB7Bk3HL,cAAA,ICgBD,iBACE,YAAa,I4B13HP,uBAQR,6B7Bu2HC,6B6Br2HG,MAAA,K7Bw2HH,iBAAA,Q6Bt2HK,gBACA,MAAA,KAYN,mBACE,WAAA,I7B+1HD,YAAA,E6B51HG,e7B+1HH,MAAA,K6B71HK,kBACA,MAAA,KAPN,oBAYI,cAAA,IACA,WAAA,OAYJ,wCA0DE,IAAK,KAjED,KAAA,K7B81HH,yB6B7xHD,kBA9DM,QAAA,W7B81HL,MAAA,G6Br1HD,oBACA,cAAA,GAIE,oBACA,cAAA,EANJ,yB5B62HE,aAAc,EACd,cAAe,IDZhB,8B6B7yHD,oCA3BA,oCATM,OAAA,IAAA,MAAA,K7Bs1HH,yB6BlzHD,yB5Bk0HE,cAAe,IAAI,MAAM,KACzB,cAAe,IAAI,IAAI,EAAE,EDZ1B,8B6B30HD,oC7B40HD,oC6B10HG,oBAAA,MAGA,uB7B60HH,QAAA,K6Bl0HC,qBF3OA,QAAA,M3BkjID,yB8B3iIC,WAAY,KACZ,uBAAA,EACA,wBAAA,EAEA,Q9B6iID,SAAA,S8BriIC,WAAY,KA8nBZ,cAAe,KAhoBb,OAAA,IAAA,MAAA,Y9B4iIH,yB8B5hIC,QAgnBE,cAAe,K9Bi7GlB,yB8BphIC,eACA,MAAA,MAGA,iBACA,cAAA,KAAA,aAAA,KAEA,WAAA,Q9BqhID,2BAAA,M8BnhIC,WAAA,IAAA,MAAA,YACE,mBAAA,MAAA,EAAA,IAAA,EAAA,qB9BqhIH,WAAA,MAAA,EAAA,IAAA,EAAA,qB8B57GD,oBArlBI,WAAA,KAEA,yBAAA,iB9BqhID,MAAA,K8BnhIC,WAAA,EACE,mBAAA,KACA,WAAA,KAEA,0B9BqhIH,QAAA,gB8BlhIC,OAAA,eACE,eAAA,E9BohIH,SAAA,kBCkBD,oBACE,WAAY,QDZf,sC8BlhIK,mC9BihIH,oC8B5gIC,cAAe,E7B+hIf,aAAc,G6Bp+GlB,sCAnjBE,mC7B4hIA,WAAY,MDdX,4D8BtgID,sC9BugID,mCCkBG,WAAY,O6B9gId,kCANE,gC9BygIH,4B8B1gIG,0BAuiBF,aAAc,M7Bs/Gd,YAAa,MAEf,yBDZC,kC8B9gIK,gC9B6gIH,4B8B9gIG,0BAcF,aAAc,EAChB,YAAA,GAMF,mBA8gBE,QAAS,KAhhBP,aAAA,EAAA,EAAA,I9BqgIH,yB8BhgIC,mB7BkhIE,cAAe,G6B7gIjB,qBADA,kB9BmgID,SAAA,M8B5/HC,MAAO,EAggBP,KAAM,E7B+gHN,QAAS,KDdR,yB8BhgID,qB9BigID,kB8BhgIC,cAAA,GAGF,kBACE,IAAA,EACA,aAAA,EAAA,EAAA,I9BogID,qB8B7/HC,OAAQ,EACR,cAAA,EACA,aAAA,IAAA,EAAA,EAEA,cACA,MAAA,K9B+/HD,OAAA,K8B7/HC,QAAA,KAAA,K7B+gIA,UAAW,K6B7gIT,YAAA,KAIA,oBAbJ,oB9B2gIC,gBAAA,K8B1/HG,kB7B6gIF,QAAS,MDdR,yBACF,iC8Bn/HC,uCACA,YAAA,OAGA,eC9LA,SAAA,SACA,MAAA,MD+LA,QAAA,IAAA,KACA,WAAA,IACA,aAAA,KACA,cAAA,I9Bs/HD,iBAAA,Y8Bl/HC,iBAAA,KACE,OAAA,IAAA,MAAA,Y9Bo/HH,cAAA,I8B/+HG,qBACA,QAAA,EAEA,yB9Bk/HH,QAAA,M8BxgIC,MAAO,KAyBL,OAAA,I9Bk/HH,cAAA,I8BvjHD,mCAvbI,WAAA,I9Bm/HH,yB8Bz+HC,eACA,QAAA,MAGE,YACA,OAAA,MAAA,M9B4+HH,iB8B/8HC,YAAA,KA2YA,eAAgB,KAjaZ,YAAA,KAEA,yBACA,iCACA,SAAA,OACA,MAAA,KACA,MAAA,KAAA,WAAA,E9By+HH,iBAAA,Y8B9kHC,OAAQ,E7BimHR,mBAAoB,K6Bz/HhB,WAAA,KAGA,kDAqZN,sC9BqlHC,QAAA,IAAA,KAAA,IAAA,KCmBD,sC6B1/HQ,YAAA,KAmBR,4C9By9HD,4C8B1lHG,iBAAkB,M9B+lHnB,yB8B/lHD,YAtYI,MAAA,K9Bw+HH,OAAA,E8Bt+HK,eACA,MAAA,K9B0+HP,iB8B99HG,YAAa,KACf,eAAA,MAGA,aACA,QAAA,KAAA,K1B9NA,WAAA,IACQ,aAAA,M2B/DR,cAAA,IACA,YAAA,M/B+vID,WAAA,IAAA,MAAA,YiBzuHC,cAAe,IAAI,MAAM,YAwEzB,mBAAoB,MAAM,EAAE,IAAI,EAAE,qBAAyB,EAAE,IAAI,EAAE,qBAtI/D,WAAA,MAAA,EAAA,IAAA,EAAA,qBAAA,EAAA,IAAA,EAAA,qBAEA,yBjB2yHH,yBiBvqHC,QAAS,aA/HP,cAAA,EACA,eAAA,OjB0yHH,2BiB5qHC,QAAS,aAxHP,MAAA,KjBuyHH,eAAA,OiBnyHG,kCACA,QAAA,aAmHJ,0BhBssHE,QAAS,aACT,eAAgB,OgB/yHd,wCjBgyHH,6CiBxrHD,2CjB2rHC,MAAA,KiB/xHG,wCACA,MAAA,KAmGJ,4BhBktHE,cAAe,EgB9yHb,eAAA,OAGA,uBADA,oBjBgyHH,QAAA,aiBtsHC,WAAY,EhBytHZ,cAAe,EgB/yHX,eAAA,OAsFN,6BAAA,0BAjFI,aAAA,EAiFJ,4CjB+sHC,sCiB1xHG,SAAA,SjB6xHH,YAAA,E8BtgID,kDAmWE,IAAK,GAvWH,yBACE,yB9BihIL,cAAA,I8B//HD,oCAoVE,cAAe,GA1Vf,yBACA,aACA,MAAA,KACA,YAAA,E1BzPF,eAAA,EACQ,aAAA,EJswIP,YAAA,EACF,OAAA,E8BtgIG,mBAAoB,KACtB,WAAA,M9B0gID,8B8BtgIC,WAAY,EACZ,uBAAA,EHzUA,wBAAA,EAQA,mDACC,cAAA,E3B40IF,uBAAA,I8BlgIC,wBAAyB,IChVzB,2BAAA,EACA,0BAAA,EDkVA,YCnVA,WAAA,IACA,cAAA,IDqVA,mBCtVA,WAAA,KACA,cAAA,KD+VF,mBChWE,WAAA,KACA,cAAA,KDuWF,aAsSE,WAAY,KA1SV,cAAA,KAEA,yB9BkgID,aACF,MAAA,K8Br+HG,aAAc,KAhBhB,YAAA,MACA,yBE5WA,aF8WE,MAAA,eAFF,cAKI,MAAA,gB9B0/HH,aAAA,M8Bh/HD,4BACA,aAAA,GADF,gBAKI,iBAAA,Q9Bm/HH,aAAA,QCmBD,8B6BngIM,MAAA,KARN,oC9B6/HC,oC8B/+HG,MAAA,Q9Bk/HH,iBAAA,Y8B7+HK,6B9Bg/HL,MAAA,KCmBD,iC6B//HQ,MAAA,KAKF,uC9B4+HL,uCCmBC,MAAO,KACP,iBAAkB,Y6B5/HZ,sCAIF,4C9B0+HL,4CCmBC,MAAO,KACP,iBAAkB,Q6B1/HZ,wCAxCR,8C9BohIC,8C8Bt+HG,MAAA,K9By+HH,iBAAA,YCmBD,+B6Bz/HM,aAAA,KAGA,qCApDN,qC9B8hIC,iBAAA,KCmBD,yC6Bv/HI,iBAAA,KAOE,iCAAA,6B7Bq/HJ,aAAc,Q6Bj/HR,oCAiCN,0C9Bk8HD,0C8B9xHC,MAAO,KA7LC,iBAAA,QACA,yB7Bi/HR,sD6B/+HU,MAAA,KAKF,4D9B49HP,4DCmBC,MAAO,KACP,iBAAkB,Y6B5+HV,2DAIF,iE9B09HP,iECmBC,MAAO,KACP,iBAAkB,Q6B1+HV,6D9B69HX,mEADE,mE8B7jIC,MAAO,KA8GP,iBAAA,aAEE,6B9Bo9HL,MAAA,K8B/8HG,mC9Bk9HH,MAAA,KCmBD,0B6Bl+HM,MAAA,KAIA,gCAAA,gC7Bm+HJ,MAAO,K6Bz9HT,0CARQ,0CASN,mD9B08HD,mD8Bz8HC,MAAA,KAFF,gBAKI,iBAAA,K9B68HH,aAAA,QCmBD,8B6B79HM,MAAA,QARN,oC9Bu9HC,oC8Bz8HG,MAAA,K9B48HH,iBAAA,Y8Bv8HK,6B9B08HL,MAAA,QCmBD,iC6Bz9HQ,MAAA,QAKF,uC9Bs8HL,uCCmBC,MAAO,KACP,iBAAkB,Y6Bt9HZ,sCAIF,4C9Bo8HL,4CCmBC,MAAO,KACP,iBAAkB,Q6Bp9HZ,wCAxCR,8C9B8+HC,8C8B/7HG,MAAA,K9Bk8HH,iBAAA,YCmBD,+B6Bl9HM,aAAA,KAGA,qCArDN,qC9Bw/HC,iBAAA,KCmBD,yC6Bh9HI,iBAAA,KAME,iCAAA,6B7B+8HJ,aAAc,Q6B38HR,oCAuCN,0C9Bs5HD,0C8B93HC,MAAO,KAvDC,iBAAA,QAuDV,yBApDU,kE9By7HP,aAAA,Q8Bt7HO,0D9By7HP,iBAAA,QCmBD,sD6Bz8HU,MAAA,QAKF,4D9Bs7HP,4DCmBC,MAAO,KACP,iBAAkB,Y6Bt8HV,2DAIF,iE9Bo7HP,iECmBC,MAAO,KACP,iBAAkB,Q6Bp8HV,6D9Bu7HX,mEADE,mE8B7hIC,MAAO,KA+GP,iBAAA,aAEE,6B9Bm7HL,MAAA,Q8B96HG,mC9Bi7HH,MAAA,KCmBD,0B6Bj8HM,MAAA,QAIA,gCAAA,gC7Bk8HJ,MAAO,KgC1kJT,0CH0oBQ,0CGzoBN,mDjC2jJD,mDiC1jJC,MAAA,KAEA,YACA,QAAA,IAAA,KjC8jJD,cAAA,KiCnkJC,WAAY,KAQV,iBAAA,QjC8jJH,cAAA,IiC3jJK,eACA,QAAA,ajC+jJL,yBiC3kJC,QAAS,EAAE,IAkBT,MAAA,KjC4jJH,QAAA,SkC/kJC,oBACA,MAAA,KAEA,YlCklJD,QAAA,akCtlJC,aAAc,EAOZ,OAAA,KAAA,ElCklJH,cAAA,ICmBD,eiClmJM,QAAA,OAEA,iBACA,oBACA,SAAA,SACA,MAAA,KACA,QAAA,IAAA,KACA,YAAA,KACA,YAAA,WlCmlJL,MAAA,QkCjlJG,gBAAA,KjComJF,iBAAkB,KiCjmJZ,OAAA,IAAA,MAAA,KPVH,6B3B8lJJ,gCkChlJG,YAAA,EjCmmJF,uBAAwB,I0B1nJxB,0BAAA,I3B4mJD,4BkC3kJG,+BjC8lJF,wBAAyB,IACzB,2BAA4B,IiC3lJxB,uBAFA,uBAGA,0BAFA,0BlCilJL,QAAA,EkCzkJG,MAAA,QjC4lJF,iBAAkB,KAClB,aAAc,KAEhB,sBiC1lJM,4BAFA,4BjC6lJN,yBiC1lJM,+BAFA,+BAGA,QAAA,ElC8kJL,MAAA,KkCroJC,OAAQ,QjCwpJR,iBAAkB,QAClB,aAAc,QiCtlJV,wBAEA,8BADA,8BjCulJN,2BiCzlJM,iCjC0lJN,iCDZC,MAAA,KkClkJC,OAAQ,YjCqlJR,iBAAkB,KkChqJd,aAAA,KAEA,oBnCipJL,uBmC/oJG,QAAA,KAAA,KlCkqJF,UAAW,K0B7pJX,YAAA,U3B+oJD,gCmC9oJG,mClCiqJF,uBAAwB,I0B1qJxB,0BAAA,I3B4pJD,+BkC7kJD,kCjCgmJE,wBAAyB,IkChrJrB,2BAAA,IAEA,oBnCiqJL,uBmC/pJG,QAAA,IAAA,KlCkrJF,UAAW,K0B7qJX,YAAA,I3B+pJD,gCmC9pJG,mClCirJF,uBAAwB,I0B1rJxB,0BAAA,I3B4qJD,+BoC9qJD,kCACE,wBAAA,IACA,2BAAA,IAEA,OpCgrJD,aAAA,EoCprJC,OAAQ,KAAK,EAOX,WAAA,OpCgrJH,WAAA,KCmBD,UmChsJM,QAAA,OAEA,YACA,eACA,QAAA,apCirJL,QAAA,IAAA,KoC/rJC,iBAAkB,KnCktJlB,OAAQ,IAAI,MAAM,KmC/rJd,cAAA,KAnBN,kBpCosJC,kBCmBC,gBAAiB,KmC5rJb,iBAAA,KA3BN,eAAA,kBAkCM,MAAA,MAlCN,mBAAA,sBnCguJE,MAAO,KmCrrJH,mBAEA,yBADA,yBpCwqJL,sBqCrtJC,MAAO,KACP,OAAA,YACA,iBAAA,KAEA,OACA,QAAA,OACA,QAAA,KAAA,KAAA,KACA,UAAA,IACA,YAAA,IACA,YAAA,EACA,MAAA,KrCutJD,WAAA,OqCntJG,YAAA,OpCsuJF,eAAgB,SoCpuJZ,cAAA,MrCutJL,cqCrtJK,cAKJ,MAAA,KACE,gBAAA,KrCktJH,OAAA,QqC7sJG,aACA,QAAA,KAOJ,YCtCE,SAAA,StCkvJD,IAAA,KCmBD,eqChwJM,iBAAA,KALJ,2BD0CF,2BrC+sJC,iBAAA,QCmBD,eqCvwJM,iBAAA,QALJ,2BD8CF,2BrCktJC,iBAAA,QCmBD,eqC9wJM,iBAAA,QALJ,2BDkDF,2BrCqtJC,iBAAA,QCmBD,YqCrxJM,iBAAA,QALJ,wBDsDF,wBrCwtJC,iBAAA,QCmBD,eqC5xJM,iBAAA,QALJ,2BD0DF,2BrC2tJC,iBAAA,QCmBD,cqCnyJM,iBAAA,QCDJ,0BADF,0BAEE,iBAAA,QAEA,OACA,QAAA,aACA,UAAA,KACA,QAAA,IAAA,IACA,UAAA,KACA,YAAA,IACA,YAAA,EACA,MAAA,KACA,WAAA,OvCwxJD,YAAA,OuCrxJC,eAAA,OACE,iBAAA,KvCuxJH,cAAA,KuClxJG,aACA,QAAA,KAGF,YtCqyJA,SAAU,SsCnyJR,IAAA,KAMA,0BvC+wJH,eCmBC,IAAK,EsChyJD,QAAA,IAAA,IvCmxJL,cuCjxJK,cAKJ,MAAA,KtC+xJA,gBAAiB,KsC7xJf,OAAA,QvC+wJH,+BuC3wJC,4BACE,MAAA,QvC6wJH,iBAAA,KuCzwJG,wBvC4wJH,MAAA,MuCxwJG,+BvC2wJH,aAAA,IwCp0JC,uBACA,YAAA,IAEA,WACA,YAAA,KxCu0JD,eAAA,KwC50JC,cAAe,KvC+1Jf,MAAO,QuCt1JL,iBAAA,KAIA,eAbJ,cAcI,MAAA,QxCu0JH,awCr1JC,cAAe,KAmBb,UAAA,KxCq0JH,YAAA,ICmBD,cuCn1JI,iBAAA,QAEA,sBxCo0JH,4BwC91JC,cAAe,KA8Bb,aAAA,KxCm0JH,cAAA,IwChzJD,sBAfI,UAAA,KxCo0JD,oCwCj0JC,WvCo1JA,YAAa,KuCl1JX,eAAA,KxCo0JH,sBwC1zJD,4BvC60JE,cAAe,KuCj1Jb,aAAA,KC5CJ,ezC+2JD,cyC92JC,UAAA,MAGA,WACA,QAAA,MACA,QAAA,IACA,cAAA,KrCiLA,YAAA,WACK,iBAAA,KACG,OAAA,IAAA,MAAA,KJisJT,cAAA,IyC33JC,mBAAoB,OAAO,IAAI,YxC84J1B,cAAe,OAAO,IAAI,YwCj4J7B,WAAA,OAAA,IAAA,YAKF,iBzC82JD,eCmBC,aAAc,KACd,YAAa,KwC13JX,mBA1BJ,kBzCq4JC,kByC12JG,aAAA,QCzBJ,oBACE,QAAA,IACA,MAAA,KAEA,O1Cy4JD,QAAA,K0C74JC,cAAe,KAQb,OAAA,IAAA,MAAA,YAEA,cAAA,IAVJ,UAeI,WAAA,E1Cq4JH,MAAA,QCmBD,mByCl5JI,YAAA,IArBJ,SAyBI,U1Ck4JH,cAAA,ECmBD,WyC34JE,WAAA,IAFF,mBAAA,mBAMI,cAAA,KAEA,0BACA,0B1C43JH,SAAA,S0Cp3JC,IAAK,KCvDL,MAAA,MACA,MAAA,Q3C+6JD,e0Cz3JC,MAAO,QClDL,iBAAA,Q3C86JH,aAAA,Q2C36JG,kB3C86JH,iBAAA,Q2Ct7JC,2BACA,MAAA,Q3C07JD,Y0Ch4JC,MAAO,QCtDL,iBAAA,Q3Cy7JH,aAAA,Q2Ct7JG,e3Cy7JH,iBAAA,Q2Cj8JC,wBACA,MAAA,Q3Cq8JD,e0Cv4JC,MAAO,QC1DL,iBAAA,Q3Co8JH,aAAA,Q2Cj8JG,kB3Co8JH,iBAAA,Q2C58JC,2BACA,MAAA,Q3Cg9JD,c0C94JC,MAAO,QC9DL,iBAAA,Q3C+8JH,aAAA,Q2C58JG,iB3C+8JH,iBAAA,Q4Ch9JC,0BAAQ,MAAA,QACR,wCAAQ,K5Cs9JP,oBAAA,KAAA,E4Cl9JD,GACA,oBAAA,EAAA,GACA,mCAAQ,K5Cw9JP,oBAAA,KAAA,E4C19JD,GACA,oBAAA,EAAA,GACA,gCAAQ,K5Cw9JP,oBAAA,KAAA,E4Ch9JD,GACA,oBAAA,EAAA,GAGA,UACA,OAAA,KxCsCA,cAAA,KACQ,SAAA,OJ86JT,iBAAA,Q4Ch9JC,cAAe,IACf,mBAAA,MAAA,EAAA,IAAA,IAAA,eACA,WAAA,MAAA,EAAA,IAAA,IAAA,eAEA,cACA,MAAA,KACA,MAAA,EACA,OAAA,KACA,UAAA,KxCyBA,YAAA,KACQ,MAAA,KAyHR,WAAA,OACK,iBAAA,QACG,mBAAA,MAAA,EAAA,KAAA,EAAA,gBJk0JT,WAAA,MAAA,EAAA,KAAA,EAAA,gB4C78JC,mBAAoB,MAAM,IAAI,K3Cw+JzB,cAAe,MAAM,IAAI,K4Cv+J5B,WAAA,MAAA,IAAA,KDEF,sBCAE,gCDAF,iBAAA,yK5Ci9JD,iBAAA,oK4C18JC,iBAAiB,iK3Cs+JjB,wBAAyB,KAAK,KGlhK9B,gBAAA,KAAA,KJ4/JD,qBI1/JS,+BwCmDR,kBAAmB,qBAAqB,GAAG,OAAO,SErElD,aAAA,qBAAA,GAAA,OAAA,S9C+gKD,UAAA,qBAAA,GAAA,OAAA,S6C59JG,sBACA,iBAAA,Q7Cg+JH,wC4C38JC,iBAAkB,yKEzElB,iBAAA,oK9CuhKD,iBAAA,iK6Cp+JG,mBACA,iBAAA,Q7Cw+JH,qC4C/8JC,iBAAkB,yKE7ElB,iBAAA,oK9C+hKD,iBAAA,iK6C5+JG,sBACA,iBAAA,Q7Cg/JH,wC4Cn9JC,iBAAkB,yKEjFlB,iBAAA,oK9CuiKD,iBAAA,iK6Cp/JG,qBACA,iBAAA,Q7Cw/JH,uC+C/iKC,iBAAkB,yKAElB,iBAAA,oK/CgjKD,iBAAA,iK+C7iKG,O/CgjKH,WAAA,KC4BD,mB8CtkKE,WAAA,E/C+iKD,O+C3iKD,YACE,SAAA,O/C6iKD,KAAA,E+CziKC,Y/C4iKD,MAAA,Q+CxiKG,c/C2iKH,QAAA,MC4BD,4B8CjkKE,UAAA,KAGF,aAAA,mBAEE,aAAA,KAGF,YAAA,kB9CkkKE,cAAe,K8C3jKjB,YAHE,Y/CuiKD,a+CniKC,QAAA,W/CsiKD,eAAA,I+CliKC,c/CqiKD,eAAA,O+ChiKC,cACA,eAAA,OAMF,eACE,WAAA,EACA,cAAA,ICvDF,YAEE,aAAA,EACA,WAAA,KAQF,YACE,aAAA,EACA,cAAA,KAGA,iBACA,SAAA,SACA,QAAA,MhDglKD,QAAA,KAAA,KgD7kKC,cAAA,KrB3BA,iBAAA,KACC,OAAA,IAAA,MAAA,KqB6BD,6BACE,uBAAA,IrBvBF,wBAAA,I3BymKD,4BgDvkKC,cAAe,E/CmmKf,2BAA4B,I+CjmK5B,0BAAA,IAFF,kBAAA,uBAKI,MAAA,KAIF,2CAAA,gD/CmmKA,MAAO,K+C/lKL,wBAFA,wBhD4kKH,6BgD3kKG,6BAKF,MAAO,KACP,gBAAA,KACA,iBAAA,QAKA,uB/C+lKA,MAAO,KACP,WAAY,K+C5lKV,0BhDskKH,gCgDrkKG,gCALF,MAAA,K/CsmKA,OAAQ,YACR,iBAAkB,KDxBnB,mDgD/kKC,yDAAA,yD/C4mKA,MAAO,QDxBR,gDgDnkKC,sDAAA,sD/CgmKA,MAAO,K+C5lKL,wBAEA,8BADA,8BhDskKH,QAAA,EgD3kKC,MAAA,K/CumKA,iBAAkB,QAClB,aAAc,QAEhB,iDDpBC,wDCuBD,uDADA,uD+C5mKE,8DAYI,6D/C+lKN,uD+C3mKE,8D/C8mKF,6DAKE,MAAO,QDxBR,8CiD7qKG,oDADF,oDAEE,MAAA,QAEA,yBhD0sKF,MAAO,QgDxsKH,iBAAA,QAFF,0BAAA,+BAKI,MAAA,QAGF,mDAAA,wDhD2sKJ,MAAO,QDtBR,gCiDnrKO,gCAGF,qCAFE,qChD8sKN,MAAO,QACP,iBAAkB,QAEpB,iCgD1sKQ,uCAFA,uChD6sKR,sCDtBC,4CiDtrKO,4CArBN,MAAA,KACE,iBAAA,QACA,aAAA,QAEA,sBhDuuKF,MAAO,QgDruKH,iBAAA,QAFF,uBAAA,4BAKI,MAAA,QAGF,gDAAA,qDhDwuKJ,MAAO,QDtBR,6BiDhtKO,6BAGF,kCAFE,kChD2uKN,MAAO,QACP,iBAAkB,QAEpB,8BgDvuKQ,oCAFA,oChD0uKR,mCDtBC,yCiDntKO,yCArBN,MAAA,KACE,iBAAA,QACA,aAAA,QAEA,yBhDowKF,MAAO,QgDlwKH,iBAAA,QAFF,0BAAA,+BAKI,MAAA,QAGF,mDAAA,wDhDqwKJ,MAAO,QDtBR,gCiD7uKO,gCAGF,qCAFE,qChDwwKN,MAAO,QACP,iBAAkB,QAEpB,iCgDpwKQ,uCAFA,uChDuwKR,sCDtBC,4CiDhvKO,4CArBN,MAAA,KACE,iBAAA,QACA,aAAA,QAEA,wBhDiyKF,MAAO,QgD/xKH,iBAAA,QAFF,yBAAA,8BAKI,MAAA,QAGF,kDAAA,uDhDkyKJ,MAAO,QDtBR,+BiD1wKO,+BAGF,oCAFE,oChDqyKN,MAAO,QACP,iBAAkB,QAEpB,gCgDjyKQ,sCAFA,sChDoyKR,qCDtBC,2CiD7wKO,2CDkGN,MAAO,KACP,iBAAA,QACA,aAAA,QAEF,yBACE,WAAA,EACA,cAAA,IE1HF,sBACE,cAAA,EACA,YAAA,IAEA,O9C0DA,cAAA,KACQ,iBAAA,KJgvKT,OAAA,IAAA,MAAA,YkDtyKC,cAAe,IACf,mBAAA,EAAA,IAAA,IAAA,gBlDwyKD,WAAA,EAAA,IAAA,IAAA,gBkDlyKC,YACA,QAAA,KvBnBC,e3B0zKF,QAAA,KAAA,KkDzyKC,cAAe,IAAI,MAAM,YAMvB,uBAAA,IlDsyKH,wBAAA,IkDhyKC,0CACA,MAAA,QAEA,alDmyKD,WAAA,EkDvyKC,cAAe,EjDm0Kf,UAAW,KACX,MAAO,QDtBR,oBkD7xKC,sBjDqzKF,eiD3zKI,mBAKJ,qBAEE,MAAA,QvBvCA,cACC,QAAA,KAAA,K3By0KF,iBAAA,QkDxxKC,WAAY,IAAI,MAAM,KjDozKtB,2BAA4B,IiDjzK1B,0BAAA,IAHJ,mBAAA,mCAMM,cAAA,ElD2xKL,oCkDtxKG,oDjDkzKF,aAAc,IAAI,EiDhzKZ,cAAA,EvBtEL,4D3Bg2KF,4EkDpxKG,WAAA,EjDgzKF,uBAAwB,IiD9yKlB,wBAAA,IvBtEL,0D3B81KF,0EkD7yKC,cAAe,EvB1Df,2BAAA,IACC,0BAAA,IuB0FH,+EAEI,uBAAA,ElDixKH,wBAAA,EkD7wKC,wDlDgxKD,iBAAA,EC4BD,0BACE,iBAAkB,EiDryKpB,8BlD6wKC,ckD7wKD,gCjD0yKE,cAAe,EiD1yKjB,sCAQM,sBlD2wKL,wCC4BC,cAAe,K0Bx5Kf,aAAA,KuByGF,wDlDwxKC,0BC4BC,uBAAwB,IACxB,wBAAyB,IiDrzK3B,yFAoBQ,yFlD2wKP,2DkD5wKO,2DjDwyKN,uBAAwB,IACxB,wBAAyB,IAK3B,wGiDj0KA,wGjD+zKA,wGDtBC,wGCuBD,0EiDh0KA,0EjD8zKA,0EiDtyKU,0EjD8yKR,uBAAwB,IAK1B,uGiD30KA,uGjDy0KA,uGDtBC,uGCuBD,yEiD10KA,yEjDw0KA,yEiD5yKU,yEvB7HR,wBAAA,IuBiGF,sDlDwzKC,yBC4BC,2BAA4B,IAC5B,0BAA2B,IiD3yKrB,qFA1CR,qFAyCQ,wDlDsxKP,wDC4BC,2BAA4B,IAC5B,0BAA2B,IAG7B,oGDtBC,oGCwBD,oGiDj2KA,oGjD81KA,uEiDhzKU,uEjDkzKV,uEiDh2KA,uEjDs2KE,0BAA2B,IAG7B,mGDtBC,mGCwBD,mGiD32KA,mGjDw2KA,sEiDtzKU,sEjDwzKV,sEiD12KA,sEjDg3KE,2BAA4B,IiDrzK1B,0BlD8xKH,qCkDz1KD,0BAAA,qCA+DI,WAAA,IAAA,MAAA,KA/DJ,kDAAA,kDAmEI,WAAA,EAnEJ,uBAAA,yCjD83KE,OAAQ,EiDpzKA,+CjDwzKV,+CiDl4KA,+CjDo4KA,+CAEA,+CANA,+CDjBC,iECoBD,iEiDn4KA,iEjDq4KA,iEAEA,iEANA,iEAWE,YAAa,EiD9zKL,8CjDk0KV,8CiDh5KA,8CjDk5KA,8CAEA,8CANA,8CDjBC,gECoBD,gEiDj5KA,gEjDm5KA,gEAEA,gEANA,gEAWE,aAAc,EAIhB,+CiD95KA,+CjD45KA,+CiDr0KU,+CjDw0KV,iEiD/5KA,iEjD65KA,iEDtBC,iEC6BC,cAAe,EAEjB,8CiDt0KU,8CjDw0KV,8CiDx6KA,8CjDu6KA,gEDtBC,gECwBD,gEiDn0KI,gEACA,cAAA,EAUJ,yBACE,cAAA,ElDsyKD,OAAA,EkDlyKG,aACA,cAAA,KANJ,oBASM,cAAA,ElDqyKL,cAAA,IkDhyKG,2BlDmyKH,WAAA,IC4BD,4BiD3zKM,cAAA,EAKF,wDAvBJ,wDlDwzKC,WAAA,IAAA,MAAA,KkD/xKK,2BlDkyKL,WAAA,EmDrhLC,uDnDwhLD,cAAA,IAAA,MAAA,KmDrhLG,eACA,aAAA,KnDyhLH,8BmD3hLC,MAAA,KAMI,iBAAA,QnDwhLL,aAAA,KmDrhLK,0DACA,iBAAA,KAGJ,qCAEI,MAAA,QnDshLL,iBAAA,KmDviLC,yDnD0iLD,oBAAA,KmDviLG,eACA,aAAA,QnD2iLH,8BmD7iLC,MAAA,KAMI,iBAAA,QnD0iLL,aAAA,QmDviLK,0DACA,iBAAA,QAGJ,qCAEI,MAAA,QnDwiLL,iBAAA,KmDzjLC,yDnD4jLD,oBAAA,QmDzjLG,eACA,aAAA,QnD6jLH,8BmD/jLC,MAAA,QAMI,iBAAA,QnD4jLL,aAAA,QmDzjLK,0DACA,iBAAA,QAGJ,qCAEI,MAAA,QnD0jLL,iBAAA,QmD3kLC,yDnD8kLD,oBAAA,QmD3kLG,YACA,aAAA,QnD+kLH,2BmDjlLC,MAAA,QAMI,iBAAA,QnD8kLL,aAAA,QmD3kLK,uDACA,iBAAA,QAGJ,kCAEI,MAAA,QnD4kLL,iBAAA,QmD7lLC,sDnDgmLD,oBAAA,QmD7lLG,eACA,aAAA,QnDimLH,8BmDnmLC,MAAA,QAMI,iBAAA,QnDgmLL,aAAA,QmD7lLK,0DACA,iBAAA,QAGJ,qCAEI,MAAA,QnD8lLL,iBAAA,QmD/mLC,yDnDknLD,oBAAA,QmD/mLG,cACA,aAAA,QnDmnLH,6BmDrnLC,MAAA,QAMI,iBAAA,QnDknLL,aAAA,QmD/mLK,yDACA,iBAAA,QAGJ,oCAEI,MAAA,QnDgnLL,iBAAA,QoD/nLC,wDACA,oBAAA,QAEA,kBACA,SAAA,SpDkoLD,QAAA,MoDvoLC,OAAQ,EnDmqLR,QAAS,EACT,SAAU,OAEZ,yCmDzpLI,wBADA,yBAEA,yBACA,wBACA,SAAA,SACA,IAAA,EACA,OAAA,EpDkoLH,KAAA,EoD7nLC,MAAO,KACP,OAAA,KpD+nLD,OAAA,EoD1nLC,wBpD6nLD,eAAA,OqDvpLC,uBACA,eAAA,IAEA,MACA,WAAA,KACA,QAAA,KjDwDA,cAAA,KACQ,iBAAA,QJmmLT,OAAA,IAAA,MAAA,QqDlqLC,cAAe,IASb,mBAAA,MAAA,EAAA,IAAA,IAAA,gBACA,WAAA,MAAA,EAAA,IAAA,IAAA,gBAKJ,iBACE,aAAA,KACA,aAAA,gBAEF,SACE,QAAA,KACA,cAAA,ICtBF,SACE,QAAA,IACA,cAAA,IAEA,OACA,MAAA,MACA,UAAA,KjCRA,YAAA,IAGA,YAAA,ErBwrLD,MAAA,KsDhrLC,YAAA,EAAA,IAAA,EAAA,KrD4sLA,OAAQ,kBqD1sLN,QAAA,GjCbF,aiCeE,ajCZF,MAAA,KrBgsLD,gBAAA,KsD5qLC,OAAA,QACE,OAAA,kBACA,QAAA,GAEA,aACA,mBAAA,KtD8qLH,QAAA,EuDnsLC,OAAQ,QACR,WAAA,IvDqsLD,OAAA,EuDhsLC,YACA,SAAA,OAEA,OACA,SAAA,MACA,IAAA,EACA,MAAA,EACA,OAAA,EACA,KAAA,EAIA,QAAA,KvDgsLD,QAAA,KuD7rLC,SAAA,OnD+GA,2BAAA,MACI,QAAA,EAEI,0BAkER,mBAAA,kBAAA,IAAA,SAEK,cAAA,aAAA,IAAA,SACG,WAAA,UAAA,IAAA,SJghLT,kBAAA,kBuDnsLC,cAAA,kBnD2GA,aAAA,kBACI,UAAA,kBAEI,wBJ2lLT,kBAAA,euDvsLK,cAAe,eACnB,aAAA,eACA,UAAA,eAIF,mBACE,WAAA,OACA,WAAA,KvDwsLD,cuDnsLC,SAAU,SACV,MAAA,KACA,OAAA,KAEA,eACA,SAAA,SnDaA,iBAAA,KACQ,wBAAA,YmDZR,gBAAA,YtD+tLA,OsD/tLA,IAAA,MAAA,KAEA,OAAA,IAAA,MAAA,evDqsLD,cAAA,IuDjsLC,QAAS,EACT,mBAAA,EAAA,IAAA,IAAA,eACA,WAAA,EAAA,IAAA,IAAA,eAEA,gBACA,SAAA,MACA,IAAA,EACA,MAAA,EvDmsLD,OAAA,EuDjsLC,KAAA,ElCrEA,QAAA,KAGA,iBAAA,KkCmEA,qBlCtEA,OAAA,iBAGA,QAAA,EkCwEF,mBACE,OAAA,kBACA,QAAA,GAIF,cACE,QAAA,KvDmsLD,cAAA,IAAA,MAAA,QuD9rLC,qBACA,WAAA,KAKF,aACE,OAAA,EACA,YAAA,WAIF,YACE,SAAA,SACA,QAAA,KvD6rLD,cuD/rLC,QAAS,KAQP,WAAA,MACA,WAAA,IAAA,MAAA,QATJ,wBAaI,cAAA,EvDyrLH,YAAA,IuDrrLG,mCvDwrLH,YAAA,KuDlrLC,oCACA,YAAA,EAEA,yBACA,SAAA,SvDqrLD,IAAA,QuDnqLC,MAAO,KAZP,OAAA,KACE,SAAA,OvDmrLD,yBuDhrLD,cnDvEA,MAAA,MACQ,OAAA,KAAA,KmD2ER,eAAY,mBAAA,EAAA,IAAA,KAAA,evDkrLX,WAAA,EAAA,IAAA,KAAA,euD5qLD,UAFA,MAAA,OvDorLD,yBwDl0LC,UACA,MAAA,OCNA,SAEA,SAAA,SACA,QAAA,KACA,QAAA,MACA,YAAA,iBAAA,UAAA,MAAA,WACA,UAAA,KACA,WAAA,OACA,YAAA,IACA,YAAA,WACA,WAAA,KACA,WAAA,MACA,gBAAA,KACA,YAAA,KACA,eAAA,KACA,eAAA,ODHA,WAAA,OnCVA,aAAA,OAGA,UAAA,OrBy1LD,YAAA,OwD90LC,OAAA,iBnCdA,QAAA,ErBg2LD,WAAA,KwDj1LY,YAAmB,OAAA,kBxDq1L/B,QAAA,GwDp1LY,aAAmB,QAAA,IAAA,ExDw1L/B,WAAA,KwDv1LY,eAAmB,QAAA,EAAA,IxD21L/B,YAAA,IwD11LY,gBAAmB,QAAA,IAAA,ExD81L/B,WAAA,IwDz1LC,cACA,QAAA,EAAA,IACA,YAAA,KAEA,eACA,UAAA,MxD41LD,QAAA,IAAA,IwDx1LC,MAAO,KACP,WAAA,OACA,iBAAA,KACA,cAAA,IAEA,exD01LD,SAAA,SwDt1LC,MAAA,EACE,OAAA,EACA,aAAA,YACA,aAAA,MAEA,4BxDw1LH,OAAA,EwDt1LC,KAAA,IACE,YAAA,KACA,aAAA,IAAA,IAAA,EACA,iBAAA,KAEA,iCxDw1LH,MAAA,IwDt1LC,OAAA,EACE,cAAA,KACA,aAAA,IAAA,IAAA,EACA,iBAAA,KAEA,kCxDw1LH,OAAA,EwDt1LC,KAAA,IACE,cAAA,KACA,aAAA,IAAA,IAAA,EACA,iBAAA,KAEA,8BxDw1LH,IAAA,IwDt1LC,KAAA,EACE,WAAA,KACA,aAAA,IAAA,IAAA,IAAA,EACA,mBAAA,KAEA,6BxDw1LH,IAAA,IwDt1LC,MAAA,EACE,WAAA,KACA,aAAA,IAAA,EAAA,IAAA,IACA,kBAAA,KAEA,+BxDw1LH,IAAA,EwDt1LC,KAAA,IACE,YAAA,KACA,aAAA,EAAA,IAAA,IACA,oBAAA,KAEA,oCxDw1LH,IAAA,EwDt1LC,MAAA,IACE,WAAA,KACA,aAAA,EAAA,IAAA,IACA,oBAAA,KAEA,qCxDw1LH,IAAA,E0Dr7LC,KAAM,IACN,WAAA,KACA,aAAA,EAAA,IAAA,IACA,oBAAA,KAEA,SACA,SAAA,SACA,IAAA,EDXA,KAAA,EAEA,QAAA,KACA,QAAA,KACA,UAAA,MACA,QAAA,IACA,YAAA,iBAAA,UAAA,MAAA,WACA,UAAA,KACA,WAAA,OACA,YAAA,IACA,YAAA,WACA,WAAA,KACA,WAAA,MACA,gBAAA,KACA,YAAA,KACA,eAAA,KCAA,eAAA,OAEA,WAAA,OACA,aAAA,OAAA,UAAA,OACA,YAAA,OACA,iBAAA,KACA,wBAAA,YtD8CA,gBAAA,YACQ,OAAA,IAAA,MAAA,KJq5LT,OAAA,IAAA,MAAA,e0Dh8LC,cAAA,IAAY,mBAAA,EAAA,IAAA,KAAA,e1Dm8Lb,WAAA,EAAA,IAAA,KAAA,e0Dl8La,WAAA,KACZ,aAAY,WAAA,MACZ,eAAY,YAAA,KAGd,gBACE,WAAA,KAEA,cACA,YAAA,MAEA,e1Dw8LD,QAAA,IAAA,K0Dr8LC,OAAQ,EACR,UAAA,K1Du8LD,iBAAA,Q0D/7LC,cAAA,IAAA,MAAA,QzD49LA,cAAe,IAAI,IAAI,EAAE,EyDz9LvB,iBACA,QAAA,IAAA,KAEA,gBACA,sB1Di8LH,SAAA,S0D97LC,QAAS,MACT,MAAA,E1Dg8LD,OAAA,E0D97LC,aAAc,YACd,aAAA,M1Di8LD,gB0D57LC,aAAA,KAEE,sBACA,QAAA,GACA,aAAA,KAEA,oB1D87LH,OAAA,M0D77LG,KAAA,IACE,YAAA,MACA,iBAAA,KACA,iBAAA,gBACA,oBAAA,E1Dg8LL,0B0D57LC,OAAA,IACE,YAAA,MACA,QAAA,IACA,iBAAA,KACA,oBAAA,EAEA,sB1D87LH,IAAA,I0D77LG,KAAA,MACE,WAAA,MACA,mBAAA,KACA,mBAAA,gBACA,kBAAA,E1Dg8LL,4B0D57LC,OAAA,MACE,KAAA,IACA,QAAA,IACA,mBAAA,KACA,kBAAA,EAEA,uB1D87LH,IAAA,M0D77LG,KAAA,IACE,YAAA,MACA,iBAAA,EACA,oBAAA,KACA,oBAAA,gB1Dg8LL,6B0D37LC,IAAA,IACE,YAAA,MACA,QAAA,IACA,iBAAA,EACA,oBAAA,KAEA,qB1D67LH,IAAA,I0D57LG,MAAA,MACE,WAAA,MACA,mBAAA,EACA,kBAAA,KACA,kBAAA,gB1D+7LL,2B2DvjMC,MAAO,IACP,OAAA,M3DyjMD,QAAA,I2DtjMC,mBAAoB,EACpB,kBAAA,KAEA,U3DwjMD,SAAA,S2DrjMG,gBACA,SAAA,SvD6KF,MAAA,KACK,SAAA,OJ64LN,sB2DlkMC,SAAU,S1D+lMV,QAAS,K0DjlML,mBAAA,IAAA,YAAA,K3DwjML,cAAA,IAAA,YAAA,K2D9hMC,WAAA,IAAA,YAAA,KvDmKK,4BAFL,0BAGQ,YAAA,EA3JA,qDA+GR,sBAEQ,mBAAA,kBAAA,IAAA,YJi7LP,cAAA,aAAA,IAAA,Y2D5jMG,WAAA,UAAA,IAAA,YvDmHJ,4BAAA,OACQ,oBAAA,OuDjHF,oBAAA,O3D+jML,YAAA,OI/8LD,mCHy+LA,2BGx+LQ,KAAA,EuD5GF,kBAAA,sB3DgkML,UAAA,sBC2BD,kCADA,2BG/+LA,KAAA,EACQ,kBAAA,uBuDtGF,UAAA,uBArCN,6B3DumMD,gC2DvmMC,iC1DkoME,KAAM,E0DrlMN,kBAAA,mB3D+jMH,UAAA,oBAGA,wB2D/mMD,sBAAA,sBAsDI,QAAA,MAEA,wB3D6jMH,KAAA,E2DzjMG,sB3D4jMH,sB2DxnMC,SAAU,SA+DR,IAAA,E3D4jMH,MAAA,KC0BD,sB0DllMI,KAAA,KAnEJ,sBAuEI,KAAA,MAvEJ,2BA0EI,4B3D2jMH,KAAA,E2DljMC,6BACA,KAAA,MAEA,8BACA,KAAA,KtC3FA,kBsC6FA,SAAA,SACA,IAAA,EACA,OAAA,EACA,KAAA,EACA,MAAA,I3DsjMD,UAAA,K2DjjMC,MAAA,KdnGE,WAAA,OACA,YAAA,EAAA,IAAA,IAAA,eACA,iBAAA,cAAA,OAAA,kBACA,QAAA,G7CwpMH,uB2DrjMC,iBAAA,sEACE,iBAAA,iEACA,iBAAA,uFdxGA,iBAAA,kEACA,OAAA,+GACA,kBAAA,SACA,wBACA,MAAA,E7CgqMH,KAAA,K2DvjMC,iBAAA,sE1DmlMA,iBAAiB,iE0DjlMf,iBAAA,uFACA,iBAAA,kEACA,OAAA,+GtCvHF,kBAAA,SsCyFF,wB3DylMC,wBC4BC,MAAO,KACP,gBAAiB,KACjB,OAAQ,kB0DhlMN,QAAA,EACA,QAAA,G3D2jMH,0C2DnmMD,2CA2CI,6BADA,6B1DqlMF,SAAU,S0DhlMR,IAAA,IACA,QAAA,E3DwjMH,QAAA,a2DxmMC,WAAY,MAqDV,0CADA,6B3DyjMH,KAAA,I2D7mMC,YAAa,MA0DX,2CADA,6BAEA,MAAA,IACA,aAAA,MAME,6BADF,6B3DsjMH,MAAA,K2DjjMG,OAAA,KACE,YAAA,M3DmjML,YAAA,E2DxiMC,oCACA,QAAA,QAEA,oCACA,QAAA,QAEA,qBACA,SAAA,SACA,OAAA,K3D2iMD,KAAA,I2DpjMC,QAAS,GAYP,MAAA,IACA,aAAA,EACA,YAAA,KACA,WAAA,OACA,WAAA,KAEA,wBACA,QAAA,aAWA,MAAA,KACA,OAAA,K3DiiMH,OAAA,I2DhkMC,YAAa,OAkCX,OAAA,QACA,iBAAA,OACA,iBAAA,cACA,OAAA,IAAA,MAAA,K3DiiMH,cAAA,K2DzhMC,6BACA,MAAA,KACA,OAAA,KACA,OAAA,EACA,iBAAA,KAEA,kBACA,SAAA,SACA,MAAA,IACA,OAAA,K3D4hMD,KAAA,I2D3hMC,QAAA,GACE,YAAA,K3D6hMH,eAAA,K2Dp/LC,MAAO,KAhCP,WAAA,O1DijMA,YAAa,EAAE,IAAI,IAAI,eAEzB,uB0D9iMM,YAAA,KAEA,oCACA,0C3DshMH,2C2D9hMD,6BAAA,6BAYI,MAAA,K3DshMH,OAAA,K2DliMD,WAAA,M1D8jME,UAAW,KDxBZ,0C2DjhMD,6BACE,YAAA,MAEA,2C3DmhMD,6B2D/gMD,aAAA,M3DkhMC,kBACF,MAAA,I4DhxMC,KAAA,I3D4yME,eAAgB,KAElB,qBACE,OAAQ,MAkBZ,qCADA,sCADA,mBADA,oBAXA,gBADA,iBAOA,uBADA,wBADA,iBADA,kBADA,wBADA,yBASA,mCADA,oC2DvzME,oBAAA,qBAAA,oBAAA,qB3D8zMF,WADA,YAOA,uBADA,wBADA,qBADA,sBADA,cADA,e2Dl0MI,a3Dw0MJ,cDvBC,kB4DhzMG,mB3DwzMJ,WADA,YAwBE,QAAS,MACT,QAAS,IASX,qCADA,mBANA,gBAGA,uBADA,iBADA,wBAIA,mCDhBC,oB6Dl1MC,oB5Dq2MF,W+B/1MA,uBhCu0MC,qB4D/zMG,cChBF,aACA,kB5Dk2MF,W+Bx1ME,MAAO,KhC40MR,cgCz0MC,QAAS,MACT,aAAA,KhC20MD,YAAA,KgCl0MC,YhCq0MD,MAAA,gBgCl0MC,WhCq0MD,MAAA,egCl0MC,MhCq0MD,QAAA,e8D51MC,MACA,QAAA,gBAEA,WACA,WAAA,O9B8BF,WACE,KAAA,EAAA,EAAA,EhCm0MD,MAAA,YgC5zMC,YAAa,KACb,iBAAA,YhC8zMD,OAAA,E+D91MC,Q/Di2MD,QAAA,eC4BD,OACE,SAAU,M+Dt4MV,chE+2MD,MAAA,aC+BD,YADA,YADA,YADA,YAIE,QAAS,e+Dv5MT,kBhEy4MC,mBgEx4MD,yBhEo4MD,kB+Dr1MD,mBA6IA,yB9D+tMA,kBACA,mB8Dp3ME,yB9Dg3MF,kBACA,mBACA,yB+D15MY,QAAA,eACV,yBAAU,YhE64MT,QAAA,gBC4BD,iB+Dv6MU,QAAA,gBhEg5MX,c+D/1MG,QAAS,oB/Dm2MV,c+Dr2MC,c/Ds2MH,QAAA,sB+Dj2MG,yB/Dq2MD,kBACF,QAAA,iB+Dj2MG,yB/Dq2MD,mBACF,QAAA,kBgEn6MC,yBhEu6MC,yBgEt6MD,QAAA,wBACA,+CAAU,YhE26MT,QAAA,gBC4BD,iB+Dr8MU,QAAA,gBhE86MX,c+Dx2MG,QAAS,oB/D42MV,c+D92MC,c/D+2MH,QAAA,sB+D12MG,+C/D82MD,kBACF,QAAA,iB+D12MG,+C/D82MD,mBACF,QAAA,kBgEj8MC,+ChEq8MC,yBgEp8MD,QAAA,wBACA,gDAAU,YhEy8MT,QAAA,gBC4BD,iB+Dn+MU,QAAA,gBhE48MX,c+Dj3MG,QAAS,oB/Dq3MV,c+Dv3MC,c/Dw3MH,QAAA,sB+Dn3MG,gD/Du3MD,kBACF,QAAA,iB+Dn3MG,gD/Du3MD,mBACF,QAAA,kBgE/9MC,gDhEm+MC,yBgEl+MD,QAAA,wBACA,0BAAU,YhEu+MT,QAAA,gBC4BD,iB+DjgNU,QAAA,gBhE0+MX,c+D13MG,QAAS,oB/D83MV,c+Dh4MC,c/Di4MH,QAAA,sB+D53MG,0B/Dg4MD,kBACF,QAAA,iB+D53MG,0B/Dg4MD,mBACF,QAAA,kBgEr/MC,0BhEy/MC,yBACF,QAAA,wBgE1/MC,yBhE8/MC,WACF,QAAA,gBgE//MC,+ChEmgNC,WACF,QAAA,gBgEpgNC,gDhEwgNC,WACF,QAAA,gBAGA,0B+Dn3MC,WA4BE,QAAS,gBC5LX,eAAU,QAAA,eACV,aAAU,ehE4hNT,QAAA,gBC4BD,oB+DtjNU,QAAA,gBhE+hNX,iB+Dj4MG,QAAS,oBAMX,iB/D83MD,iB+Dz2MG,QAAS,sB/D82MZ,qB+Dl4MC,QAAS,e/Dq4MV,a+D/3MC,qBAcE,QAAS,iB/Ds3MZ,sB+Dn4MC,QAAS,e/Ds4MV,a+Dh4MC,sBAOE,QAAS,kB/D83MZ,4B+D/3MC,QAAS,eCpLT,ahEujNC,4BACF,QAAA,wBC6BD,aACE,cACE,QAAS"}
\ No newline at end of file diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/css/bootstrap3-transition.css b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/css/bootstrap3-transition.css new file mode 100644 index 000000000..9644485ca --- /dev/null +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/css/bootstrap3-transition.css @@ -0,0 +1,238 @@ +/* Changes to the top bar. Template: base.html */ + +.navbar .brand { + padding: 5px 20px; +} + +.logo { + padding-top: 2px !important; +} + +/* Changes to the tables pagination */ + +.pagination { + margin: 0 0 40px 0; +} + +select[class^="pagesize"] { + margin-bottom: 0; +} + +/* Project configuration page */ + +ul.configuration-list { + margin-left: 0px; +} + +.configuration-list .checkbox { + margin-top: 0; +} + +/* Breadcrumbs */ + +.breadcrumb { + padding: 8px 0; +} + +.breadcrumb > li + li::before { + content: none; +} + +.breadcrumb .divider { + color: #999; + padding: 0 5px; +} + +/* Remove the hovering from the .btn-primary buttons when they are disabled */ + +.btn-primary.disabled.focus, +.btn-primary.disabled:focus, +.btn-primary.disabled:hover, +.btn-primary.focus[disabled], +.btn-primary[disabled]:focus, +.btn-primary[disabled]:hover, +fieldset[disabled] .btn-primary.focus, +fieldset[disabled] .btn-primary:focus, +fieldset[disabled] .btn-primary:hover { + background-color: #04c; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); +} + +/* Table search input field */ + +.navbar-search { + width: 60%; +} + +.navbar-search .input-append { + width: 90%; +} + +.navbar-search .input-append input[type="text"] { + width: 80%; +} + +#search { + width: 60%; +} + +/* Definition lists */ + +.dl-horizontal dd { + margin-left: 220px; +} + +.dl-horizontal dt { + width: 200px; +} + +/* Table controls */ + +.navbar-inner > .navbar-search .input-append { + margin-bottom: 5px; +} + +.navbar-search.input-append { + margin-bottom: 20px; +} + +/* Modal dialogs */ + +.modal-dialog { + width: 700px; +} + +.modal-body { + overflow-y: scroll; + max-height: 350px; +} + +.modal-body ul.list-unstyled { + margin-left: 0; +} + +.modal-footer { + background-color: #f5f5f5; +} + +.modal-content form { + margin-bottom: 0; +} + +.modal-dialog .checkbox label, +.modal-dialog .radio label { + padding-left: 0; +} + +/* Typeahead */ + +.tt-menu { + width: 120%; + padding: 10px 8px; +} + +.tt-suggestion { + padding: 3px 8px; + cursor: pointer; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} + +.tt-suggestion.active, +.tt-suggestion:hover { + background-color: #0081c2; + color: white; +} + +/* Build form */ + +#build-input { + width: 20em; +} + +/* Clear filter tooltips in toastertables */ + +.tooltip .btn-small { + margin: 10px; +} + +/* Table cell notifications */ + +.inline-notification, +#temp-inline-notify { + padding: 10px; +} + +/* Table buttons */ + +td .btn { + white-space: normal; +} + +th.add-del-layers { + width: 18%; +} + +th.add_rm_pkg_btn { + width: 20%; +} + +/* Edit columsn menu */ + +.dropdown-menu { + min-width: 200px; +} + +/* Popover content */ + +.popover-content > ul { + margin-left: 0; +} + +h3.popover-title { + line-height: 20px; +} + +.popover { + max-width: 400px; +} + +/* Errors and warnings accordions */ + +#errors .panel-heading, +#warnings .panel-heading { + background-color: transparent; +} + +a.toggle-errors:hover, +a.toggle-warnings:hover, +a.warning:focus { + text-decoration: none; +} + +a.toggle-errors:focus, +a.toggle-warnings:focus { + outline: none; +} + +/* Landing page */ + +.jumbotron p { + margin-top: 20px; + margin-bottom: 30px; +} + +.jumbotron ul { + margin-left: 10px; + font-size: 21px; + font-weight: 200; +} + +.jumbotron ul > li { + line-height: 30px; +} + +.jumbotron .img-thumbnail { + padding: 0; +} diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/css/default.css b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/css/default.css index b024d962a..ff24e8c1a 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/css/default.css +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/css/default.css @@ -1,298 +1,336 @@ +/* New Toaster custom css file for Bootstrap 3 */ + +/* Set required top body padding for the fixed top navbard */ +body { padding-top: 50px; } + /* Style the Yocto Project logo */ -.logo img { height: 30px; width: auto !important; } -.logo { padding-top: 4px !important; padding-bottom:0px !important; } +img.logo { height: 30px; vertical-align: bottom; } + +/* Style the Yocto Project logo and the Toaster name in the top navbar */ +.toaster-navbar-brand { float: left; margin: 7px 25px 0 0; } +.toaster-navbar-brand a.brand { color: #777; height: 50px; padding: 15px 5px 15px 15px; font-size: 20px; line-height: 25px; display: inline; } +.toaster-navbar-brand > a { text-decoration: none; } +.toaster-navbar-brand > a.brand:hover { color: #5e5e5e; } + +/* Style the debugging information in the top navbar */ +.glyphicon-info-sign { color: #777; font-size: 16px; } +.glyphicon-info-sign:hover { color: #999; cursor: pointer; } + +/* Override the negative right margin for the navbar-right class */ +#new-project-button { margin-right: 0; } + +/* Increase popovers width to fit commit SHAs */ +.popover { max-width: 350px; } + +/* Set a limit to popover height to handle long dependency lists */ +.popover-content { max-height: 350px; overflow: scroll; } + +/* Set a limit to modal dialogs height to handle long variable history */ +[id^="variable-"] .modal-content { max-height: 550px; overflow-y: scroll; } + +/* Make sure long values in variable history do not make the modal dialogs + * scroll horizontally */ +[id^="variable-"] .modal-content p { word-break: break-all; } -/* style the version information */ -.brand > a { color: #777; } -.brand > a:hover { color: #999; text-decoration: none; } -.icon-info-sign { color: #777; font-size: 16px; margin-left: 5px;} -.icon-info-sign:hover { color: #999; cursor: pointer; } +/* Increase bottom margin of definition lists inside popovers for the Toaster version information in the top navbar, and also inside the right hand columns of our details pages */ +.popover-content dd, +.item-info dd { margin-bottom: 15px; } -/* Style the breadcrumb */ -.breadcrumb { display: inline-block; background-color: transparent; } +/* Style the horizontal definition lists */ +.dl-horizontal dt { width: 200px; line-height: 25px; } +.dl-horizontal dd { margin-left: 220px; line-height: 25px; } + +/* Style our build results */ +.build-result .progress { margin-bottom: 0; } +.alert-link.build-warnings, +.glyphicon-warning-sign.build-warnings { color: #8a6d3b; } +.build-result .project-name { margin-top: -10px; margin-bottom: 5px; } +.rebuild-btn, .cancel-build-btn { cursor: pointer; } /* Styles for the help information */ .get-help { color: #CCCCCC; } -.get-help:hover, .icon-plus-sign:hover { color: #999999; cursor: pointer; } -.get-help-green { color: #468847; } -.get-help-green:hover { color: #347132; cursor: pointer; } +.get-help:hover { color: #999999; cursor: pointer; } +.get-help-green { color: #3c763d; } +.get-help-green:hover { color: #2b542c; cursor: pointer; } .get-help-blue { color: #3A87AD; } .get-help-blue:hover { color: #005580; cursor: pointer; } -.get-help-yellow { color: #C09853; } -.get-help-yellow:hover { color: #B38942; cursor: pointer; } -.get-help-red { color: #B94A48; font-size: 16px; padding-left: 2px; } -.get-help-red:hover { color: #943A38; cursor: pointer; } -.build-form>i:first-of-type { margin-left: 5px; } -.manual { margin: 11px 15px 0 11px;} +.get-help-red { color: #a94442; } +.get-help-red:hover { color: #843534; cursor: pointer; } + +/* Styles for our table controls */ +.form-control[id^="search-input-"], +.form-control[id^="new-search-input-"], +#search{ width: 30em; } +#search-input-selectpackagestable, +#search-input-packagestable, +.form-control[id^="no-results-search-input-"] { width: 20em; } +#edit-columns-button { margin-right: 30px; } +.navbar-default[id^="table-chrome-"], +#variables .navbar-default { background-color: transparent; } +[id^="table-chrome-collapse-"] .navbar-form { margin-left: -15px; } +.dropdown-menu.editcol { padding-left: 10px; min-width: 200px; } +span[class^="remove-search-btn-"] { position: absolute; right: 5px; top: 0; bottom: 0; height: 14px; margin: auto; font-size: 14px; cursor: pointer; color: #777;} +span[class^="remove-search-btn-"]:hover { color: #333; } +#no-results-special-selectpackagestable .form-inline { margin-top: 20px; } +[id^="pagination-"] .pagination, +[id^="pagination-"] .navbar-form { margin-top: 0; } +[id^="table-chrome-"] .navbar-form { margin-left: -15px; margin-right: -15px; } +[id^="table-chrome-"] .detail-page-contols, +#packages-built .detail-page-controls { padding-left: 0; padding-right: 0; } + +/* Override the default font-weight for labels: it's a bit too much */ +label { font-weight: normal; } + +/* Firefox workaround for awkward fieldset styling. See http://getbootstrap.com/css/#tables-responsive */ +@-moz-document url-prefix() { fieldset { display: table-cell; } } + +/* Table heading sortable / not sortable states */ +thead > tr > th > a { font-weight: normal; } +thead > tr > th > a.sorted { font-weight: bold; color: #333; } + +/* Give some extra space to the 'clear filter' buttons */ +.tooltip .btn { margin: 5px; } + +/* In table headings, separate the help bubble from the column heading */ +thead > tr > th > .glyphicon-question-sign { margin-right: 5px; } + +/* Style build outcome in tables, download, remove and change icons */ +tbody > tr > td > .glyphicon-ok-circle, +dd > .glyphicon-ok-circle { color: #3c763d; } +tbody > tr > td > .glyphicon-minus-sign { color: #a94442; } +.glyphicon-download-alt, +.glyphicon-edit { color: #337ab7; } +.failed_tasks .glyphicon-download-alt { margin-left: 5px; } +.glyphicon-download-alt:hover, +.glyphicon-edit:hover { color: #23527c; cursor: pointer; text-decoration: none; } +.glyphicon-trash { color: #a94442; } +.btn-danger > .glyphicon-trash, +.btn-danger > .glyphicon-trash:hover { color: #fff; } +.glyphicon-trash:hover { color: #843534; cursor: pointer; } + +/* Set the font size for icons inside headings, lead paragraphs and definition lists */ +h1 > .glyphicon-edit, +p.lead .glyphicon { font-size: 16px; } +h2 > .glyphicon-question-sign, +h3 > .glyphicon-question-sign, .heading-help { font-size: 14px; } -/* Styles for the external link */ -.get-info { color: #0088CC; } -.get-info:hover { color: #005580; cursor: pointer; text-decoration: none; } - -/* Styles for code and pre tags */ -code { background-color: transparent; border: none; color: #333333; } -dd code, .alert code { white-space: pre-wrap; word-break: break-all; word-wrap: break-word; } -.alert-warning code, .alert-warning pre { background-color: transparent; border: none; color: #C09853; margin-bottom: 0px; } -.alert-error code { background-color: transparent; border: none; color: #B94A48; margin-bottom:0px; } -.alert-error pre { background-color: transparent; border: none; color: #B94A48; word-break: normal; margin-bottom: 0px; } -.alert-warning pre { word-break: normal; } -.alert-info a { font-weight: 300; } -.alert-info code { color: #3A87AD; } -.tooltip code { background-color: transparent; color: #FFFFFF; font-weight: normal; border: none; font-size: 1em; } - -/* Style for definition lists */ -dd ul { list-style-type: none; margin: 0px; } -dt, dd {line-height: 25px; } -dd li { line-height: 25px; } -.item-info dd { line-height: 20px; margin-bottom: 10px; } - -/* Style the filter modal dialogs */ -.modal { width: 800px; margin-left: -400px; } -.modal-footer .btn { float: left; } -.modal-body { max-height: 300px; } - -/* Hover style for the clear search icon */ -.icon-remove-sign:hover { color: #999999; cursor: pointer; } - -/* Some extra space before headings when needed */ -.details { margin-top: 30px; } -.air { margin-top: 30px; } - -/* Required classes for the highlight behaviour in tables */ -.highlight { -webkit-animation: target-fade 10s 1; -moz-animation: target-fade 10s 1; animation: target-fade 10s 1; } -@-webkit-keyframes target-fade { 0% { background-color: #D9EDF7; } 25% { background-color: #D9EDF7; } 100% { background-color: white; } } -@-moz-keyframes target-fade { 0% { background-color: #D9EDF7; } 25% { background-color: #D9EDF7; } 100% { background-color: white; } } -@keyframes target-fade { 0% { background-color: #D9EDF7; } 25% { background-color: #D9EDF7; } 100% { background-color: white; } } - -/* This makes tooltips work inside modal dialogs */ -.tooltip { z-index: 2000 !important; } - -/* Override default Twitter Boostrap styles for anchor tags inside tables */ -td a, td a > code { color: #333333; } -td code { white-space: normal; } -td a:hover, td a > code:hover { color: #000000; text-decoration: underline; } - -/* Override default Twitter Bootstrap styles for tr.error */ -.table tbody tr.error > td { background-color: transparent; } /* override default Bootstrap behaviour */ -.table-hover tbody tr.error:hover > td { background-color: #F5F5F5;} /* override default Bootstrap behaviour */ - -/* Right justify Bootstrap table columns for size fields */ -.table .sizecol { text-align: right; } - -/* Set error, warning, success and muted styles */ -.error, .red, td.error a, tr.error a { color: #b94a48; } -a.error:hover, a.error:focus, tr.error a:hover { color: #943A38; text-decoration: underline; } -.warning, .yellow { color: #c09853;} -a.warning { background-color: transparent; } -a.warning:hover, a.warning:focus { color: #B38942; text-decoration: underline; } -.success, .green { color: #468847;} -.success:hover { color: #347132; text-decoration: underline; } -td > .success:hover { text-decoration: underline; } -.muted a { color:#999999; } -.muted a:hover { color:#999999; } - -/* Sorting functionality styles for table headings */ -.sorted { color: #333333; font-weight: bold; } -.sorted:hover { color: #000000; text-decoration: underline; } -th > a, th > span { font-weight: normal; } - -/* Force long strings like commit hashes to wrap */ -.iscommit { white-space: pre-wrap; word-break: break-all; word-wrap: break-word;} - -/* Make the popovers scrollable if they are too long */ -.popover-content { max-height: 30em; overflow-y: scroll; } - -/* Styles for the directory structure table. We'll probably won't use those in production */ -.one { padding-left: 18px !important; } -.two { padding-left: 36px !important; } -.three { padding-left: 54px !important; } -.content-directory a { color: #0088CC; } -.content-directory a:hover { color: #005580; text-decoration: underline; } -.symlink { color: #CCCCCC; } - -/* Styles for the navbar actions */ -.btn-group + .btn-group { margin-right: 10px; } -.navbar-inner > .btn-group { margin-top: 6px; } -[id^="search-input-"], #search { width: 80%; } - -/* Styles for the parent item in the left navigation */ - -.nav > li > a.nav-parent { font-size: 18px; line-height: 25px; } - -/* Other styles */ -.dropdown-menu { padding: 10px; } -select { width: auto; } -.page-header { color: #5A5A5A; } -.top-air { margin-top: 40px;} -.progress { margin-bottom: 0px; } -.lead .badge { font-size: 18px; font-weight: normal; border-radius: 15px; padding: 9px; } -.lead ol, .lead ul { padding: 10px 0 0 20px; } -.lead ol > li, .lead ul > li { - line-height: 35px; -} -.well > .lead, .alert .lead { margin-bottom: 0px; } +/* Create a class for wells without background colour */ .well-transparent { background-color: transparent; } -.no-results { margin: 10px 0; } -.task-name { margin-left: 7px; } -.icon-hand-right {color: #CCCCCC; } -.help-inline { margin: 5px; } -.dashboard-section { background-color: transparent; } - -/* styles for landing page - analysis mode */ -.hero-unit { margin: 20px 0 30px; } -.hero-unit > .close { font-size:40px; } -.hero-actions { margin-top: 30px; } - -/* styles for landing page - build mode */ -.hero-unit p { line-height: 25px; } -.hero-unit p, .hero-unit .btn-large { margin-top: 15px; } -.hero-unit ul { margin-top: 20px; } -.hero-unit li { line-height: 30px; } -.hero-unit img { background-color: #eee; margin-top: 15px; } - -/* make tables Chrome-happy (me, not so much) */ -table { table-layout: fixed; word-wrap: break-word; } - -table p { margin-bottom: 0 } -.table td { vertical-align: middle; } - -/* styles for the new build button */ -.new-build .btn-primary { padding: 4px 30px; } -.new-build .alert { margin-top: 10px; } -.new-build .alert p { margin-top: 10px; } - -/* styles for showing the project name in build mode */ -.project-name { padding-top: 0; } -.project-name .label { font-weight: normal; margin-bottom: 5px; margin-left: -15px; padding: 5px; } -.project-name .label > a { color: #fff; font-weight: normal; } - -/* styles for showing help icons next to command-line builds */ -.build-result .get-help-green, .build-result .get-help-red, .build-result .get-help-blue { margin-right: 35px; margin-top: 8px; font-size: 16px; } - -/* Remove bottom margin for forms inside modal dialogs */ -#dependencies-modal-form { margin-bottom: 0px; } - -/* Custom column widths */ -.narrow-col { width: 8%; } -.medium-col { width: 12%; } - -/* Configuration styles */ -.icon-trash { color: #B94A48; font-size: 16px; padding-left: 5px; } -.icon-trash:hover { color: #943A38; text-decoration: none; cursor: pointer; } -.icon-pencil, .icon-download-alt, .icon-refresh, .icon-star-empty, .icon-star { font-size: 16px; color: #0088CC; padding-left: 2px; } -.icon-pencil:hover, .icon-download-alt:hover, .icon-refresh:hover, .icon-star-empty:hover, .icon-star:hover, .icon-tasks:hover { color: #005580; text-decoration: none; cursor: pointer; } -.icon-share { padding-left: 2px; } -.alert-success .icon-refresh, .alert-success .icon-tasks { color: #468847; } -.alert-success .icon-refresh:hover, .alert-success .icon-tasks:hover { color: #347132; } -.alert-error .icon-refresh, .alert-error .icon-tasks { color: #b94a48; } -.alert-error .icon-refresh:hover, .alert-error .icon-tasks:hover { color: #943A38; } -.configuration-list li, .configuration-list label { line-height: 35px; font-size: 21px; font-weight: 200; margin-bottom: 0px;} -.configuration-list { font-size: 16px; margin-bottom: 1.5em; } -.configuration-list i { font-size: 16px; } -/*.configuration-layers { height: 135px; overflow: scroll; }*/ -.counter { font-weight: normal; } -.well-alert { background-color: #FCF8E3; border: 1px solid #FBEED5; border-radius: 4px; } -.well-alert > .lead { color: #C09853; padding-bottom: .75em; } -.configuration-alert { margin-bottom: 0px; padding: 8px 14px; } -.configuration-alert p { margin-bottom: 0px; } -.project-form { margin-top: 10px; } -.add-layers .btn-block + .btn-block, .build .btn-block + .btn-block { margin-top: 0px; } -input.huge { font-size: 17.5px; padding: 11px; min-width: 60%; } -.build-form { margin-bottom: 0px; } -.build-form .input-append { margin-bottom: 0px; } -.build-form .btn-large { padding: 11px 35px; } -.build-form p { font-size:17.5px ;margin:12px 0 0 10px;} -#layer-container form, #target-container form { margin-bottom: 0px; } -.btn-primary .icon-question-sign, .btn-danger .icon-question-sign { color: #fff; } -.btn-primary .icon-question-sign:hover, .btn-danger .icon-question-sign:hover { color: #999; } -a code { color: #0088CC; } -a code:hover { color: #005580; } -.localconf { font-size: 17.5px; margin-top: 40px; } -.localconf code { font-size: 17.5px; } -#add-layer-dependencies { margin-top: 5px; } -.link-action { font-size: 17.5px; margin-top: 40px; } -.link-action code { font-size: 17.5px; } -.artifact { width: 9em; } -.control-group { margin-bottom: 0px; } -#project-details form { margin: 0px; } -dd form { margin: 10px 0 0 0; } -dl textarea { resize: vertical; } -.navbar-fixed-top { z-index: 1; } -.popover { z-index: 2; } -.btn-danger .icon-trash { color: #fff; } -.bbappends { list-style-type: none; margin-left: 0; } -.bbappends li { line-height: 25px; } -.configuration-list input[type="checkbox"] { margin-top:13px;margin-right:10px; } -.alert input[type="checkbox"] { margin-top: 0px; margin-right: 3px; } -.alert ol { padding: 10px 0px 0px 20px; } -.alert ol > li { line-height: 35px; } -.dl-vertical form { margin-top: 10px; } -.scrolling { border: 1px solid #dddddd; height: 154px; overflow: auto; padding: 8px; width: 27.5%; margin-bottom: 10px; } -.lead .help-block { font-size: 14px; line-height: 20px; font-weight: normal; } -.button-place .btn { margin: 0 0 20px 0; } -.tooltip-inner { max-width: 250px; } -.new-build { padding: 20px; } -.new-build li { line-height: 30px; } -.new-build li .alert { line-height: 20px; width: 200px; white-space: normal; } -.new-build h6 { margin: 10px 0 0 0; color: #5a5a5a; } -.new-build h3 { margin: 0; color: #5a5a5a; } -.new-build form { margin: 5px 0 0; } -.new-build .input-append { margin-bottom: 0; } -#build-selected { margin-top: 15px; } -div.add-deps { margin-top: 15px; } -.btn.log { margin-left: 20px; } - - -.animate-repeat { - list-style:none; - box-sizing:border-box; -} -.animate-repeat.ng-move, -.animate-repeat.ng-enter, -.animate-repeat.ng-leave { - -webkit-transition:all linear 0.5s; - transition:all linear 0.5s; +/* Create a class for the left navigation headers */ +.nav-header { display: block; font-size: 12px; font-weight: bold; line-height: 20px; color: #999; text-transform: uppercase; margin-top: 20px !important; margin-bottom: 15px; padding-left: 15px; } + +/* Increase the tabs padding and margin in the project pages to fit the build form and the main content */ +#project-topbar .nav-tabs > li > a { padding: 15px; } +#project-topbar { margin-bottom: 20px; } + +/* Style the project name change form in the project pages */ +#project-name-change-input { width: 20em; } + +/* Style the build form in the project pages */ +#project-topbar .input-lg { width: 18em; } +#project-topbar form .glyphicon { top: 15px; right: 4px; } +#build-button { padding: 10px 30px; } + +/* Style the form links in the project page (all machines, all layers, etc) */ +.form-link { margin-top: 10px; } + +/* Style the most built recipes list in the project page */ +#freq-build-list .checkbox input[type="checkbox"] { position: relative; margin: 0 10px 0 0; vertical-align: middle; } +#freq-build-list.lead > li { line-height: 25px; } +#freq-build-list { margin-top: 20px; } +#freq-build-list label { padding-left: 0; } +#freq-build-btn { margin-top: 10px; } +#no-most-built { margin-top: 20px; } + +/* Style the layers section in the project page and the layer dependencies in the import layer form */ +#layer-container .form-inline { margin-top: 20px; } +#layer-add-input { width: 17em; } +ul.lead { margin-top: 20px; } +ul.lead > li { line-height: 38px; } +ul.lead .glyphicon-trash, +ul.lead .glyphicon-trash { font-size: 16px; margin-left: 7px; } +#layers-in-project-list .tooltip-inner { max-width: 600px; } +#no-layers-in-project { margin-top: 20px; } +#no-layers-in-project ul { margin-top: 10px; } + +/* Style the layer information icons in the layer details pages */ +dd .glyphicon-trash, +dd .glyphicon-edit { margin-left: 5px; } + +/* Style the forms and definition lists in the layer details pages */ +#change-repo-form .form-control { width: 17em; } +#information { margin-bottom: 5em; } +#information dd > form { margin-bottom: 5px; margin-top: 5px; } +#edit-layer-source-form fieldset { margin-top: 20px; } +#directory-info, +#git-repo-info { margin-top: 20px; } +#layer-dir-path-in-details { width: 55%; } +.add-deps .form-control { width: 15em; } + +/* Style the forms and definition lists in the BitBake variables page */ +.variable-list { margin-bottom: 20px; } +dd.variable-list form { margin-top: 10px; } +#new-dl_dir, +#filter-image_fstypes, +#new-image_install, +#new-sstate_dir, +#new-imagefs_types { width: 20em; } +#package_classes-select { width: 10em; } +.scrolling { border: 1px solid #dddddd; height: 154px; overflow: auto; padding: 0 10px; width: 27.5%; margin-bottom: 10px; margin-top: 10px; } +.scrolling.has-error { border-color: #a94442; } +.help-block.text-danger { color: #a94442; } +.tooltip-inner code { color: #fff; } +.text-danger > code { color: #a94442; } +dd.variable-list .glyphicon-question-sign { font-size: 14px; } +dd.variable-list .glyphicon-edit { font-size: 16px; } +dt .glyphicon-trash { margin-left: 5px; font-size: 16px; } +#change-package_classes-form .checkbox { margin-top: 5px; } +#variable-form h5 { margin-top: 0; } +#variable-form .col-md-5 { padding-left: 45px; } + +/* Create a class for additional top margin that we can use in headings */ +.top-air { margin-top: 40px; } + +/* Add some bottom margin to our h2's */ +h2 { margin-bottom: 25px; } + +/* Style the typeahead */ +.tt-menu { min-width: 400px; padding-bottom: 10px; } +.tt-suggestion { padding: 5px 10px; } +.tt-suggestion:hover, +.tt-suggestion:active { background-color: #f5f5f5; cursor: pointer; } + +/* Style the import layer form controls*/ +legend { border: none; } +fieldset.fields-apart-from-layer-name { margin-top: 20px; } +.radioLegend { margin-bottom: 0; } +#layer-name-ctrl { margin-top: 20px; } +#import-layer-name, +#layer-subdir { width: 20%; } +#layer-git-repo-url { width: 40%; } +#layer-git-ref { width: 32%; } +#local-dir-path { width: 45%; } +#layer-dependency { width: 16em; } +#layer-deps-list { margin-top: 0; } +#form-actions { margin-bottom: 30px; } +#duplicate-layer-info dl { margin-top: 10px; } +#duplicate-layer-info dd { margin-bottom: 10px; } +.help-inline { color: #737373; margin-left: 10px; } +.radio-help { width: 50%; margin-left: 20px; } +#repo-select div:nth-of-type(2) { margin-top: 15px; } + +/* Give some padding to the in-cell tooltips we use for notifications in tables */ +td > .tooltip-inner, +.inline-notification { padding: 10px; } + +/* Set sane widths for table columns */ +#newcustomimagestable .get_description_or_summary, +#imagerecipestable .get_description_or_summary, +#softwarerecipestable .get_description_or_summary, +#layerstable .layer__summary { width: 30%; } +#recipestable .get_description_or_summary { width: 40%; } +#machinestable .name { white-space: nowrap; } +#machinestable .description { width: 45%; } +#otable .variable_value, +#otable .file { word-break: break-all; width: 25%; } +[id^="variable-"] .file { word-break: break-all; } + +/* For the tables still not ported to ToasterTables, style the table headings + * that are not sortable */ +th > span.text-muted { font-weight: normal; } + +/* Override the rather ugly default code styles */ +code { color: #333; background-color: transparent; } + +/* Style our breadcrumbs */ +.breadcrumb > li + li::before { content: none; } +.breadcrumb { background-color: transparent; padding-left: 0; padding-top: 15px; } +.breadcrumb .divider { color: #777; margin: 0 5px; } + +/* Reduce top margin for the page-header class */ +.page-header { margin-top: 30px; } + +/* Set some space around the layer button in the layer details pages */ +.tab-content { margin-top: 20px; } +.tab-pane { margin-top: 20px; } + +/* Style the new window icons */ +.glyphicon-new-window:hover { text-decoration: none; } +.dl-horizontal > dd > .glyphicon-new-window { margin-left: 5px; } + +/* Style the special no results message in the custom image details page */ +[id^="no-results-special-"] > .alert-warning > ol { margin-top: 10px; } + +/* style the loading spinner in the new custom image dialog */ +#create-new-custom-image-btn [data-role="loading-state"] { + padding-left: 16px; } -.animate-repeat.ng-leave.ng-leave-active, -.animate-repeat.ng-move, -.animate-repeat.ng-enter { - opacity:0; +/* icon has to be absolutely positioned, otherwise the spin animation doesn't work */ +#create-new-custom-image-btn [data-role="loading-state"] .icon-spinner { + position: absolute; + left: 26px; + bottom: 26px; } -.animate-repeat.ng-leave, -.animate-repeat.ng-enter.ng-enter-active { - opacity:1; -} +/* Style the content of modal dialogs */ +.modal-footer { text-align: left; } +.date-filter-controls { margin-top: 10px; } +.date-filter-controls span { margin: 0 10px; } -.tab-pane table { margin-top: 10px; } +/* Style the fixed positioned notifications */ +#loading-notification { position: fixed; z-index: 1101; top: 3%; left: 40%; right: 40%; -webkit-box-shadow: 0 0 10px #c09853; -moz-box-shadow: 0 0 10px #c09853; box-shadow: 0 0 10px #c09853; } -thead .description, .get_description_or_summary { width: 364px; } -thead .add-del-layers { width: 124px; } +.change-notification { position: fixed; z-index: 1101; top: 4%; left: 30%; right: 30%; -webkit-box-shadow: 0 0 10px #3a87ad; -moz-box-shadow: 0 0 10px #3a87ad; box-shadow: 0 0 10px #3a87ad; } -#loading-notification { - position: fixed; - z-index: 101; - top: 3%; - left: 40%; - right: 40%; - -webkit-box-shadow: 0 0 10px #c09853; - -moz-box-shadow: 0 0 10px #c09853; - box-shadow: 0 0 10px #c09853; -} +.alert-success.change-notification { -webkit-box-shadow: 0 0 10px #3c763d; -moz-box-shadow: 0 0 10px #3c763d; box-shadow: 0 0 10px #3c763d; } -#change-notification { - position: fixed; - z-index: 101; - top: 3%; - left: 20%; - right: 20%; - -webkit-box-shadow: 0 0 10px #3a87ad; - -moz-box-shadow: 0 0 10px #3a87ad; - box-shadow: 0 0 10px #3a87ad; -} +/* Style the new project form */ +#new-project-name { width: 33%; } +#projectversion { width: 20%; margin-bottom: 10px; } + +/* Style the Toaster screenshot in the landing page */ +.img-thumbnail { padding: 0; } + +/* Set the layout for the build information pages */ + +#nav { margin-top: 10px; } +.page-header.build-data { margin-top: 0px; } +.build-data > h1 { margin-top: 8px; } + +/* Style the build outcome information in the build dashboard */ +.log { margin-left: 30px; } +.show-warnings { font-weight: 700; color: #8a6d3b; } +.show-warnings:hover { color: #66512c; } + +/* Style the errors and warnings information in the build dashboard */ +#errors .panel-heading { background-color: transparent; color: #843534; } +#warnings .panel-heading { background-color: transparent; color: #8a6d3b; } +#warnings .panel-heading a:hover { color: #66512c; } +h2.panel-title { font-size: 30px; } +.alert-danger pre, +.alert-warning pre { background-color: transparent; border: none; } +.alert-danger pre { color: #a94442; } +#error-info pre, +#warning-info pre { white-space: pre-wrap; } +.alert-warning pre { color: #8a6d3b; } + +/* Style the wells in the build dashboard */ +.dashboard-section h3 { margin-top: 10px; margin-bottom: 20px; } +.col-md-4.dashboard-section dd { margin-bottom: 10px; } + +/* Make the help in tables insivisble until you hover over the right cell */ +.hover-help { visibility: hidden; } + +#add-remove-layer-btn { margin-bottom: 20px; } + +/* Blue hightlight animation for tasks and directory structure tables */ +.highlight { -webkit-animation: target-fade 15s 1; -moz-animation: target-fade 15s 1; animation: target-fade 15s 1; } +@-webkit-keyframes target-fade { 0% { background-color: #D9EDF7; } 25% { background-color: #D9EDF7; } 100% { background-color: white; } } +@-moz-keyframes target-fade { 0% { background-color: #D9EDF7; } 25% { background-color: #D9EDF7; } 100% { background-color: white; } } +@keyframes target-fade { 0% { background-color: #D9EDF7; } 25% { background-color: #D9EDF7; } 100% { background-color: white; } } /* Copied in from newer version of Font-Awesome 4.3.0 */ .fa-spin { @@ -330,14 +368,3 @@ thead .add-del-layers { width: 124px; } } } /* End copied in from newer version of Font-Awesome 4.3.0 */ - -.top-padded { - padding-top: 60px; -} - -input.input-lg { - font-size: 18px; - height: 22px; - line-height: 1.33333; - padding: 10px 16px; -} diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/fonts/glyphicons-halflings-regular.eot b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/fonts/glyphicons-halflings-regular.eot Binary files differindex 423bd5d3a..b93a4953f 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/fonts/glyphicons-halflings-regular.eot +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/fonts/glyphicons-halflings-regular.eot diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/fonts/glyphicons-halflings-regular.svg b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/fonts/glyphicons-halflings-regular.svg index 446948874..94fb5490a 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/fonts/glyphicons-halflings-regular.svg +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/fonts/glyphicons-halflings-regular.svg @@ -6,224 +6,283 @@ <font id="glyphicons_halflingsregular" horiz-adv-x="1200" > <font-face units-per-em="1200" ascent="960" descent="-240" /> <missing-glyph horiz-adv-x="500" /> -<glyph /> -<glyph /> -<glyph unicode="
" /> +<glyph horiz-adv-x="0" /> +<glyph horiz-adv-x="400" /> <glyph unicode=" " /> -<glyph unicode="*" d="M100 500v200h259l-183 183l141 141l183 -183v259h200v-259l183 183l141 -141l-183 -183h259v-200h-259l183 -183l-141 -141l-183 183v-259h-200v259l-183 -183l-141 141l183 183h-259z" /> -<glyph unicode="+" d="M0 400v300h400v400h300v-400h400v-300h-400v-400h-300v400h-400z" /> +<glyph unicode="*" d="M600 1100q15 0 34 -1.5t30 -3.5l11 -1q10 -2 17.5 -10.5t7.5 -18.5v-224l158 158q7 7 18 8t19 -6l106 -106q7 -8 6 -19t-8 -18l-158 -158h224q10 0 18.5 -7.5t10.5 -17.5q6 -41 6 -75q0 -15 -1.5 -34t-3.5 -30l-1 -11q-2 -10 -10.5 -17.5t-18.5 -7.5h-224l158 -158 q7 -7 8 -18t-6 -19l-106 -106q-8 -7 -19 -6t-18 8l-158 158v-224q0 -10 -7.5 -18.5t-17.5 -10.5q-41 -6 -75 -6q-15 0 -34 1.5t-30 3.5l-11 1q-10 2 -17.5 10.5t-7.5 18.5v224l-158 -158q-7 -7 -18 -8t-19 6l-106 106q-7 8 -6 19t8 18l158 158h-224q-10 0 -18.5 7.5 t-10.5 17.5q-6 41 -6 75q0 15 1.5 34t3.5 30l1 11q2 10 10.5 17.5t18.5 7.5h224l-158 158q-7 7 -8 18t6 19l106 106q8 7 19 6t18 -8l158 -158v224q0 10 7.5 18.5t17.5 10.5q41 6 75 6z" /> +<glyph unicode="+" d="M450 1100h200q21 0 35.5 -14.5t14.5 -35.5v-350h350q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-350v-350q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v350h-350q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5 h350v350q0 21 14.5 35.5t35.5 14.5z" /> <glyph unicode=" " /> -<glyph unicode=" " horiz-adv-x="652" /> -<glyph unicode=" " horiz-adv-x="1304" /> -<glyph unicode=" " horiz-adv-x="652" /> -<glyph unicode=" " horiz-adv-x="1304" /> -<glyph unicode=" " horiz-adv-x="434" /> -<glyph unicode=" " horiz-adv-x="326" /> -<glyph unicode=" " horiz-adv-x="217" /> -<glyph unicode=" " horiz-adv-x="217" /> -<glyph unicode=" " horiz-adv-x="163" /> +<glyph unicode="¥" d="M825 1100h250q10 0 12.5 -5t-5.5 -13l-364 -364q-6 -6 -11 -18h268q10 0 13 -6t-3 -14l-120 -160q-6 -8 -18 -14t-22 -6h-125v-100h275q10 0 13 -6t-3 -14l-120 -160q-6 -8 -18 -14t-22 -6h-125v-174q0 -11 -7.5 -18.5t-18.5 -7.5h-148q-11 0 -18.5 7.5t-7.5 18.5v174 h-275q-10 0 -13 6t3 14l120 160q6 8 18 14t22 6h125v100h-275q-10 0 -13 6t3 14l120 160q6 8 18 14t22 6h118q-5 12 -11 18l-364 364q-8 8 -5.5 13t12.5 5h250q25 0 43 -18l164 -164q8 -8 18 -8t18 8l164 164q18 18 43 18z" /> +<glyph unicode=" " horiz-adv-x="650" /> +<glyph unicode=" " horiz-adv-x="1300" /> +<glyph unicode=" " horiz-adv-x="650" /> +<glyph unicode=" " horiz-adv-x="1300" /> +<glyph unicode=" " horiz-adv-x="433" /> +<glyph unicode=" " horiz-adv-x="325" /> +<glyph unicode=" " horiz-adv-x="216" /> +<glyph unicode=" " horiz-adv-x="216" /> +<glyph unicode=" " horiz-adv-x="162" /> <glyph unicode=" " horiz-adv-x="260" /> <glyph unicode=" " horiz-adv-x="72" /> <glyph unicode=" " horiz-adv-x="260" /> -<glyph unicode=" " horiz-adv-x="326" /> -<glyph unicode="€" d="M100 500l100 100h113q0 47 5 100h-218l100 100h135q37 167 112 257q117 141 297 141q242 0 354 -189q60 -103 66 -209h-181q0 55 -25.5 99t-63.5 68t-75 36.5t-67 12.5q-24 0 -52.5 -10t-62.5 -32t-65.5 -67t-50.5 -107h379l-100 -100h-300q-6 -46 -6 -100h406l-100 -100 h-300q9 -74 33 -132t52.5 -91t62 -54.5t59 -29t46.5 -7.5q29 0 66 13t75 37t63.5 67.5t25.5 96.5h174q-31 -172 -128 -278q-107 -117 -274 -117q-205 0 -324 158q-36 46 -69 131.5t-45 205.5h-217z" /> -<glyph unicode="−" d="M200 400h900v300h-900v-300z" /> -<glyph unicode="☁" d="M-14 494q0 -80 56.5 -137t135.5 -57h750q120 0 205 86t85 208q0 120 -85 206.5t-205 86.5q-46 0 -90 -14q-44 97 -134.5 156.5t-200.5 59.5q-152 0 -260 -107.5t-108 -260.5q0 -25 2 -37q-66 -14 -108.5 -67.5t-42.5 -122.5z" /> -<glyph unicode="✉" d="M0 100l400 400l200 -200l200 200l400 -400h-1200zM0 300v600l300 -300zM0 1100l600 -603l600 603h-1200zM900 600l300 300v-600z" /> -<glyph unicode="✏" d="M-13 -13l333 112l-223 223zM187 403l214 -214l614 614l-214 214zM887 1103l214 -214l99 92q13 13 13 32.5t-13 33.5l-153 153q-15 13 -33 13t-33 -13z" /> -<glyph unicode="" horiz-adv-x="500" d="M0 0z" /> -<glyph unicode="" d="M0 1200h1200l-500 -550v-550h300v-100h-800v100h300v550z" /> -<glyph unicode="" d="M14 84q18 -55 86 -75.5t147 5.5q65 21 109 69t44 90v606l600 155v-521q-64 16 -138 -7q-79 -26 -122.5 -83t-25.5 -111q17 -55 85.5 -75.5t147.5 4.5q70 23 111.5 63.5t41.5 95.5v881q0 10 -7 15.5t-17 2.5l-752 -193q-10 -3 -17 -12.5t-7 -19.5v-689q-64 17 -138 -7 q-79 -25 -122.5 -82t-25.5 -112z" /> -<glyph unicode="" d="M23 693q0 200 142 342t342 142t342 -142t142 -342q0 -142 -78 -261l300 -300q7 -8 7 -18t-7 -18l-109 -109q-8 -7 -18 -7t-18 7l-300 300q-119 -78 -261 -78q-200 0 -342 142t-142 342zM176 693q0 -136 97 -233t234 -97t233.5 96.5t96.5 233.5t-96.5 233.5t-233.5 96.5 t-234 -97t-97 -233z" /> -<glyph unicode="" d="M100 784q0 64 28 123t73 100.5t104.5 64t119 20.5t120 -38.5t104.5 -104.5q48 69 109.5 105t121.5 38t118.5 -20.5t102.5 -64t71 -100.5t27 -123q0 -57 -33.5 -117.5t-94 -124.5t-126.5 -127.5t-150 -152.5t-146 -174q-62 85 -145.5 174t-149.5 152.5t-126.5 127.5 t-94 124.5t-33.5 117.5z" /> -<glyph unicode="" d="M-72 800h479l146 400h2l146 -400h472l-382 -278l145 -449l-384 275l-382 -275l146 447zM168 71l2 1z" /> -<glyph unicode="" d="M-72 800h479l146 400h2l146 -400h472l-382 -278l145 -449l-384 275l-382 -275l146 447zM168 71l2 1zM237 700l196 -142l-73 -226l192 140l195 -141l-74 229l193 140h-235l-77 211l-78 -211h-239z" /> -<glyph unicode="" d="M0 0v143l400 257v100q-37 0 -68.5 74.5t-31.5 125.5v200q0 124 88 212t212 88t212 -88t88 -212v-200q0 -51 -31.5 -125.5t-68.5 -74.5v-100l400 -257v-143h-1200z" /> -<glyph unicode="" d="M0 0v1100h1200v-1100h-1200zM100 100h100v100h-100v-100zM100 300h100v100h-100v-100zM100 500h100v100h-100v-100zM100 700h100v100h-100v-100zM100 900h100v100h-100v-100zM300 100h600v400h-600v-400zM300 600h600v400h-600v-400zM1000 100h100v100h-100v-100z M1000 300h100v100h-100v-100zM1000 500h100v100h-100v-100zM1000 700h100v100h-100v-100zM1000 900h100v100h-100v-100z" /> -<glyph unicode="" d="M0 50v400q0 21 14.5 35.5t35.5 14.5h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5zM0 650v400q0 21 14.5 35.5t35.5 14.5h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400 q-21 0 -35.5 14.5t-14.5 35.5zM600 50v400q0 21 14.5 35.5t35.5 14.5h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5zM600 650v400q0 21 14.5 35.5t35.5 14.5h400q21 0 35.5 -14.5t14.5 -35.5v-400 q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5z" /> -<glyph unicode="" d="M0 50v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM0 450v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200 q-21 0 -35.5 14.5t-14.5 35.5zM0 850v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM400 50v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5 t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM400 450v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM400 850v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5 v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM800 50v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM800 450v200q0 21 14.5 35.5t35.5 14.5h200 q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM800 850v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5z" /> -<glyph unicode="" d="M0 50v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM0 450q0 -21 14.5 -35.5t35.5 -14.5h200q21 0 35.5 14.5t14.5 35.5v200q0 21 -14.5 35.5t-35.5 14.5h-200q-21 0 -35.5 -14.5 t-14.5 -35.5v-200zM0 850v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM400 50v200q0 21 14.5 35.5t35.5 14.5h700q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5 t-35.5 -14.5h-700q-21 0 -35.5 14.5t-14.5 35.5zM400 450v200q0 21 14.5 35.5t35.5 14.5h700q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-700q-21 0 -35.5 14.5t-14.5 35.5zM400 850v200q0 21 14.5 35.5t35.5 14.5h700q21 0 35.5 -14.5t14.5 -35.5 v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-700q-21 0 -35.5 14.5t-14.5 35.5z" /> -<glyph unicode="" d="M29 454l419 -420l818 820l-212 212l-607 -607l-206 207z" /> -<glyph unicode="" d="M106 318l282 282l-282 282l212 212l282 -282l282 282l212 -212l-282 -282l282 -282l-212 -212l-282 282l-282 -282z" /> -<glyph unicode="" d="M23 693q0 200 142 342t342 142t342 -142t142 -342q0 -142 -78 -261l300 -300q7 -8 7 -18t-7 -18l-109 -109q-8 -7 -18 -7t-18 7l-300 300q-119 -78 -261 -78q-200 0 -342 142t-142 342zM176 693q0 -136 97 -233t234 -97t233.5 96.5t96.5 233.5t-96.5 233.5t-233.5 96.5 t-234 -97t-97 -233zM300 600v200h100v100h200v-100h100v-200h-100v-100h-200v100h-100z" /> -<glyph unicode="" d="M23 694q0 200 142 342t342 142t342 -142t142 -342q0 -141 -78 -262l300 -299q7 -7 7 -18t-7 -18l-109 -109q-8 -8 -18 -8t-18 8l-300 299q-120 -77 -261 -77q-200 0 -342 142t-142 342zM176 694q0 -136 97 -233t234 -97t233.5 97t96.5 233t-96.5 233t-233.5 97t-234 -97 t-97 -233zM300 601h400v200h-400v-200z" /> -<glyph unicode="" d="M23 600q0 183 105 331t272 210v-166q-103 -55 -165 -155t-62 -220q0 -177 125 -302t302 -125t302 125t125 302q0 120 -62 220t-165 155v166q167 -62 272 -210t105 -331q0 -118 -45.5 -224.5t-123 -184t-184 -123t-224.5 -45.5t-224.5 45.5t-184 123t-123 184t-45.5 224.5 zM500 750q0 -21 14.5 -35.5t35.5 -14.5h100q21 0 35.5 14.5t14.5 35.5v400q0 21 -14.5 35.5t-35.5 14.5h-100q-21 0 -35.5 -14.5t-14.5 -35.5v-400z" /> -<glyph unicode="" d="M100 1h200v300h-200v-300zM400 1v500h200v-500h-200zM700 1v800h200v-800h-200zM1000 1v1200h200v-1200h-200z" /> -<glyph unicode="" d="M26 601q0 -33 6 -74l151 -38l2 -6q14 -49 38 -93l3 -5l-80 -134q45 -59 105 -105l133 81l5 -3q45 -26 94 -39l5 -2l38 -151q40 -5 74 -5q27 0 74 5l38 151l6 2q46 13 93 39l5 3l134 -81q56 44 104 105l-80 134l3 5q24 44 39 93l1 6l152 38q5 40 5 74q0 28 -5 73l-152 38 l-1 6q-16 51 -39 93l-3 5l80 134q-44 58 -104 105l-134 -81l-5 3q-45 25 -93 39l-6 1l-38 152q-40 5 -74 5q-27 0 -74 -5l-38 -152l-5 -1q-50 -14 -94 -39l-5 -3l-133 81q-59 -47 -105 -105l80 -134l-3 -5q-25 -47 -38 -93l-2 -6l-151 -38q-6 -48 -6 -73zM385 601 q0 88 63 151t152 63t152 -63t63 -151q0 -89 -63 -152t-152 -63t-152 63t-63 152z" /> -<glyph unicode="" d="M100 1025v50q0 10 7.5 17.5t17.5 7.5h275v100q0 41 29.5 70.5t70.5 29.5h300q41 0 70.5 -29.5t29.5 -70.5v-100h275q10 0 17.5 -7.5t7.5 -17.5v-50q0 -11 -7 -18t-18 -7h-1050q-11 0 -18 7t-7 18zM200 100v800h900v-800q0 -41 -29.5 -71t-70.5 -30h-700q-41 0 -70.5 30 t-29.5 71zM300 100h100v700h-100v-700zM500 100h100v700h-100v-700zM500 1100h300v100h-300v-100zM700 100h100v700h-100v-700zM900 100h100v700h-100v-700z" /> -<glyph unicode="" d="M1 601l656 644l644 -644h-200v-600h-300v400h-300v-400h-300v600h-200z" /> -<glyph unicode="" d="M100 25v1150q0 11 7 18t18 7h475v-500h400v-675q0 -11 -7 -18t-18 -7h-850q-11 0 -18 7t-7 18zM700 800v300l300 -300h-300z" /> -<glyph unicode="" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM500 500v400h100 v-300h200v-100h-300z" /> -<glyph unicode="" d="M-100 0l431 1200h209l-21 -300h162l-20 300h208l431 -1200h-538l-41 400h-242l-40 -400h-539zM488 500h224l-27 300h-170z" /> -<glyph unicode="" d="M0 0v400h490l-290 300h200v500h300v-500h200l-290 -300h490v-400h-1100zM813 200h175v100h-175v-100z" /> -<glyph unicode="" d="M1 600q0 122 47.5 233t127.5 191t191 127.5t233 47.5t233 -47.5t191 -127.5t127.5 -191t47.5 -233t-47.5 -233t-127.5 -191t-191 -127.5t-233 -47.5t-233 47.5t-191 127.5t-127.5 191t-47.5 233zM188 600q0 -170 121 -291t291 -121t291 121t121 291t-121 291t-291 121 t-291 -121t-121 -291zM350 600h150v300h200v-300h150l-250 -300z" /> -<glyph unicode="" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM350 600l250 300 l250 -300h-150v-300h-200v300h-150z" /> -<glyph unicode="" d="M0 25v475l200 700h800q199 -700 200 -700v-475q0 -11 -7 -18t-18 -7h-1150q-11 0 -18 7t-7 18zM200 500h200l50 -200h300l50 200h200l-97 500h-606z" /> -<glyph unicode="" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -172 121.5 -293t292.5 -121t292.5 121t121.5 293q0 171 -121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM500 397v401 l297 -200z" /> -<glyph unicode="" d="M23 600q0 -118 45.5 -224.5t123 -184t184 -123t224.5 -45.5t224.5 45.5t184 123t123 184t45.5 224.5h-150q0 -177 -125 -302t-302 -125t-302 125t-125 302t125 302t302 125q136 0 246 -81l-146 -146h400v400l-145 -145q-157 122 -355 122q-118 0 -224.5 -45.5t-184 -123 t-123 -184t-45.5 -224.5z" /> -<glyph unicode="" d="M23 600q0 118 45.5 224.5t123 184t184 123t224.5 45.5q198 0 355 -122l145 145v-400h-400l147 147q-112 80 -247 80q-177 0 -302 -125t-125 -302h-150zM100 0v400h400l-147 -147q112 -80 247 -80q177 0 302 125t125 302h150q0 -118 -45.5 -224.5t-123 -184t-184 -123 t-224.5 -45.5q-198 0 -355 122z" /> -<glyph unicode="" d="M100 0h1100v1200h-1100v-1200zM200 100v900h900v-900h-900zM300 200v100h100v-100h-100zM300 400v100h100v-100h-100zM300 600v100h100v-100h-100zM300 800v100h100v-100h-100zM500 200h500v100h-500v-100zM500 400v100h500v-100h-500zM500 600v100h500v-100h-500z M500 800v100h500v-100h-500z" /> -<glyph unicode="" d="M0 100v600q0 41 29.5 70.5t70.5 29.5h100v200q0 82 59 141t141 59h300q82 0 141 -59t59 -141v-200h100q41 0 70.5 -29.5t29.5 -70.5v-600q0 -41 -29.5 -70.5t-70.5 -29.5h-900q-41 0 -70.5 29.5t-29.5 70.5zM400 800h300v150q0 21 -14.5 35.5t-35.5 14.5h-200 q-21 0 -35.5 -14.5t-14.5 -35.5v-150z" /> -<glyph unicode="" d="M100 0v1100h100v-1100h-100zM300 400q60 60 127.5 84t127.5 17.5t122 -23t119 -30t110 -11t103 42t91 120.5v500q-40 -81 -101.5 -115.5t-127.5 -29.5t-138 25t-139.5 40t-125.5 25t-103 -29.5t-65 -115.5v-500z" /> -<glyph unicode="" d="M0 275q0 -11 7 -18t18 -7h50q11 0 18 7t7 18v300q0 127 70.5 231.5t184.5 161.5t245 57t245 -57t184.5 -161.5t70.5 -231.5v-300q0 -11 7 -18t18 -7h50q11 0 18 7t7 18v300q0 116 -49.5 227t-131 192.5t-192.5 131t-227 49.5t-227 -49.5t-192.5 -131t-131 -192.5 t-49.5 -227v-300zM200 20v460q0 8 6 14t14 6h160q8 0 14 -6t6 -14v-460q0 -8 -6 -14t-14 -6h-160q-8 0 -14 6t-6 14zM800 20v460q0 8 6 14t14 6h160q8 0 14 -6t6 -14v-460q0 -8 -6 -14t-14 -6h-160q-8 0 -14 6t-6 14z" /> -<glyph unicode="" d="M0 400h300l300 -200v800l-300 -200h-300v-400zM688 459l141 141l-141 141l71 71l141 -141l141 141l71 -71l-141 -141l141 -141l-71 -71l-141 141l-141 -141z" /> -<glyph unicode="" d="M0 400h300l300 -200v800l-300 -200h-300v-400zM700 857l69 53q111 -135 111 -310q0 -169 -106 -302l-67 54q86 110 86 248q0 146 -93 257z" /> -<glyph unicode="" d="M0 401v400h300l300 200v-800l-300 200h-300zM702 858l69 53q111 -135 111 -310q0 -170 -106 -303l-67 55q86 110 86 248q0 145 -93 257zM889 951l7 -8q123 -151 123 -344q0 -189 -119 -339l-7 -8l81 -66l6 8q142 178 142 405q0 230 -144 408l-6 8z" /> -<glyph unicode="" d="M0 0h500v500h-200v100h-100v-100h-200v-500zM0 600h100v100h400v100h100v100h-100v300h-500v-600zM100 100v300h300v-300h-300zM100 800v300h300v-300h-300zM200 200v100h100v-100h-100zM200 900h100v100h-100v-100zM500 500v100h300v-300h200v-100h-100v-100h-200v100 h-100v100h100v200h-200zM600 0v100h100v-100h-100zM600 1000h100v-300h200v-300h300v200h-200v100h200v500h-600v-200zM800 800v300h300v-300h-300zM900 0v100h300v-100h-300zM900 900v100h100v-100h-100zM1100 200v100h100v-100h-100z" /> -<glyph unicode="" d="M0 200h100v1000h-100v-1000zM100 0v100h300v-100h-300zM200 200v1000h100v-1000h-100zM500 0v91h100v-91h-100zM500 200v1000h200v-1000h-200zM700 0v91h100v-91h-100zM800 200v1000h100v-1000h-100zM900 0v91h200v-91h-200zM1000 200v1000h200v-1000h-200z" /> -<glyph unicode="" d="M1 700v475q0 10 7.5 17.5t17.5 7.5h474l700 -700l-500 -500zM148 953q0 -42 29 -71q30 -30 71.5 -30t71.5 30q29 29 29 71t-29 71q-30 30 -71.5 30t-71.5 -30q-29 -29 -29 -71z" /> -<glyph unicode="" d="M2 700v475q0 11 7 18t18 7h474l700 -700l-500 -500zM148 953q0 -42 30 -71q29 -30 71 -30t71 30q30 29 30 71t-30 71q-29 30 -71 30t-71 -30q-30 -29 -30 -71zM701 1200h100l700 -700l-500 -500l-50 50l450 450z" /> -<glyph unicode="" d="M100 0v1025l175 175h925v-1000l-100 -100v1000h-750l-100 -100h750v-1000h-900z" /> -<glyph unicode="" d="M200 0l450 444l450 -443v1150q0 20 -14.5 35t-35.5 15h-800q-21 0 -35.5 -15t-14.5 -35v-1151z" /> -<glyph unicode="" d="M0 100v700h200l100 -200h600l100 200h200v-700h-200v200h-800v-200h-200zM253 829l40 -124h592l62 124l-94 346q-2 11 -10 18t-18 7h-450q-10 0 -18 -7t-10 -18zM281 24l38 152q2 10 11.5 17t19.5 7h500q10 0 19.5 -7t11.5 -17l38 -152q2 -10 -3.5 -17t-15.5 -7h-600 q-10 0 -15.5 7t-3.5 17z" /> -<glyph unicode="" d="M0 200q0 -41 29.5 -70.5t70.5 -29.5h1000q41 0 70.5 29.5t29.5 70.5v600q0 41 -29.5 70.5t-70.5 29.5h-150q-4 8 -11.5 21.5t-33 48t-53 61t-69 48t-83.5 21.5h-200q-41 0 -82 -20.5t-70 -50t-52 -59t-34 -50.5l-12 -20h-150q-41 0 -70.5 -29.5t-29.5 -70.5v-600z M356 500q0 100 72 172t172 72t172 -72t72 -172t-72 -172t-172 -72t-172 72t-72 172zM494 500q0 -44 31 -75t75 -31t75 31t31 75t-31 75t-75 31t-75 -31t-31 -75zM900 700v100h100v-100h-100z" /> -<glyph unicode="" d="M53 0h365v66q-41 0 -72 11t-49 38t1 71l92 234h391l82 -222q16 -45 -5.5 -88.5t-74.5 -43.5v-66h417v66q-34 1 -74 43q-18 19 -33 42t-21 37l-6 13l-385 998h-93l-399 -1006q-24 -48 -52 -75q-12 -12 -33 -25t-36 -20l-15 -7v-66zM416 521l178 457l46 -140l116 -317h-340 z" /> -<glyph unicode="" d="M100 0v89q41 7 70.5 32.5t29.5 65.5v827q0 28 -1 39.5t-5.5 26t-15.5 21t-29 14t-49 14.5v70h471q120 0 213 -88t93 -228q0 -55 -11.5 -101.5t-28 -74t-33.5 -47.5t-28 -28l-12 -7q8 -3 21.5 -9t48 -31.5t60.5 -58t47.5 -91.5t21.5 -129q0 -84 -59 -156.5t-142 -111 t-162 -38.5h-500zM400 200h161q89 0 153 48.5t64 132.5q0 90 -62.5 154.5t-156.5 64.5h-159v-400zM400 700h139q76 0 130 61.5t54 138.5q0 82 -84 130.5t-239 48.5v-379z" /> -<glyph unicode="" d="M200 0v57q77 7 134.5 40.5t65.5 80.5l173 849q10 56 -10 74t-91 37q-6 1 -10.5 2.5t-9.5 2.5v57h425l2 -57q-33 -8 -62 -25.5t-46 -37t-29.5 -38t-17.5 -30.5l-5 -12l-128 -825q-10 -52 14 -82t95 -36v-57h-500z" /> -<glyph unicode="" d="M-75 200h75v800h-75l125 167l125 -167h-75v-800h75l-125 -167zM300 900v300h150h700h150v-300h-50q0 29 -8 48.5t-18.5 30t-33.5 15t-39.5 5.5t-50.5 1h-200v-850l100 -50v-100h-400v100l100 50v850h-200q-34 0 -50.5 -1t-40 -5.5t-33.5 -15t-18.5 -30t-8.5 -48.5h-49z " /> -<glyph unicode="" d="M33 51l167 125v-75h800v75l167 -125l-167 -125v75h-800v-75zM100 901v300h150h700h150v-300h-50q0 29 -8 48.5t-18 30t-33.5 15t-40 5.5t-50.5 1h-200v-650l100 -50v-100h-400v100l100 50v650h-200q-34 0 -50.5 -1t-39.5 -5.5t-33.5 -15t-18.5 -30t-8 -48.5h-50z" /> -<glyph unicode="" d="M0 50q0 -20 14.5 -35t35.5 -15h1100q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-1100q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM0 350q0 -20 14.5 -35t35.5 -15h800q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-800q-21 0 -35.5 -14.5t-14.5 -35.5 v-100zM0 650q0 -20 14.5 -35t35.5 -15h1000q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-1000q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM0 950q0 -20 14.5 -35t35.5 -15h600q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-600q-21 0 -35.5 -14.5 t-14.5 -35.5v-100z" /> -<glyph unicode="" d="M0 50q0 -20 14.5 -35t35.5 -15h1100q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-1100q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM0 650q0 -20 14.5 -35t35.5 -15h1100q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-1100q-21 0 -35.5 -14.5t-14.5 -35.5 v-100zM200 350q0 -20 14.5 -35t35.5 -15h700q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-700q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM200 950q0 -20 14.5 -35t35.5 -15h700q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-700q-21 0 -35.5 -14.5 t-14.5 -35.5v-100z" /> -<glyph unicode="" d="M0 50v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100q-21 0 -35.5 15t-14.5 35zM100 650v100q0 21 14.5 35.5t35.5 14.5h1000q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1000q-21 0 -35.5 15 t-14.5 35zM300 350v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-800q-21 0 -35.5 15t-14.5 35zM500 950v100q0 21 14.5 35.5t35.5 14.5h600q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-600 q-21 0 -35.5 15t-14.5 35z" /> -<glyph unicode="" d="M0 50v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100q-21 0 -35.5 15t-14.5 35zM0 350v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100q-21 0 -35.5 15 t-14.5 35zM0 650v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100q-21 0 -35.5 15t-14.5 35zM0 950v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100 q-21 0 -35.5 15t-14.5 35z" /> -<glyph unicode="" d="M0 50v100q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-100q-21 0 -35.5 15t-14.5 35zM0 350v100q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-100q-21 0 -35.5 15 t-14.5 35zM0 650v100q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-100q-21 0 -35.5 15t-14.5 35zM0 950v100q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-100q-21 0 -35.5 15 t-14.5 35zM300 50v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-800q-21 0 -35.5 15t-14.5 35zM300 350v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-800 q-21 0 -35.5 15t-14.5 35zM300 650v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-800q-21 0 -35.5 15t-14.5 35zM300 950v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15 h-800q-21 0 -35.5 15t-14.5 35z" /> -<glyph unicode="" d="M-101 500v100h201v75l166 -125l-166 -125v75h-201zM300 0h100v1100h-100v-1100zM500 50q0 -20 14.5 -35t35.5 -15h600q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-600q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM500 350q0 -20 14.5 -35t35.5 -15h300q20 0 35 15t15 35 v100q0 21 -15 35.5t-35 14.5h-300q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM500 650q0 -20 14.5 -35t35.5 -15h500q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-500q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM500 950q0 -20 14.5 -35t35.5 -15h100q20 0 35 15t15 35v100 q0 21 -15 35.5t-35 14.5h-100q-21 0 -35.5 -14.5t-14.5 -35.5v-100z" /> -<glyph unicode="" d="M1 50q0 -20 14.5 -35t35.5 -15h600q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-600q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM1 350q0 -20 14.5 -35t35.5 -15h300q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-300q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM1 650 q0 -20 14.5 -35t35.5 -15h500q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-500q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM1 950q0 -20 14.5 -35t35.5 -15h100q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-100q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM801 0v1100h100v-1100 h-100zM934 550l167 -125v75h200v100h-200v75z" /> -<glyph unicode="" d="M0 275v650q0 31 22 53t53 22h750q31 0 53 -22t22 -53v-650q0 -31 -22 -53t-53 -22h-750q-31 0 -53 22t-22 53zM900 600l300 300v-600z" /> -<glyph unicode="" d="M0 44v1012q0 18 13 31t31 13h1112q19 0 31.5 -13t12.5 -31v-1012q0 -18 -12.5 -31t-31.5 -13h-1112q-18 0 -31 13t-13 31zM100 263l247 182l298 -131l-74 156l293 318l236 -288v500h-1000v-737zM208 750q0 56 39 95t95 39t95 -39t39 -95t-39 -95t-95 -39t-95 39t-39 95z " /> -<glyph unicode="" d="M148 745q0 124 60.5 231.5t165 172t226.5 64.5q123 0 227 -63t164.5 -169.5t60.5 -229.5t-73 -272q-73 -114 -166.5 -237t-150.5 -189l-57 -66q-10 9 -27 26t-66.5 70.5t-96 109t-104 135.5t-100.5 155q-63 139 -63 262zM342 772q0 -107 75.5 -182.5t181.5 -75.5 q107 0 182.5 75.5t75.5 182.5t-75.5 182t-182.5 75t-182 -75.5t-75 -181.5z" /> -<glyph unicode="" d="M1 600q0 122 47.5 233t127.5 191t191 127.5t233 47.5t233 -47.5t191 -127.5t127.5 -191t47.5 -233t-47.5 -233t-127.5 -191t-191 -127.5t-233 -47.5t-233 47.5t-191 127.5t-127.5 191t-47.5 233zM173 600q0 -177 125.5 -302t301.5 -125v854q-176 0 -301.5 -125 t-125.5 -302z" /> -<glyph unicode="" d="M117 406q0 94 34 186t88.5 172.5t112 159t115 177t87.5 194.5q21 -71 57.5 -142.5t76 -130.5t83 -118.5t82 -117t70 -116t50 -125.5t18.5 -136q0 -89 -39 -165.5t-102 -126.5t-140 -79.5t-156 -33.5q-114 6 -211.5 53t-161.5 138.5t-64 210.5zM243 414q14 -82 59.5 -136 t136.5 -80l16 98q-7 6 -18 17t-34 48t-33 77q-15 73 -14 143.5t10 122.5l9 51q-92 -110 -119.5 -185t-12.5 -156z" /> -<glyph unicode="" d="M0 400v300q0 165 117.5 282.5t282.5 117.5q366 -6 397 -14l-186 -186h-311q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v125l200 200v-225q0 -165 -117.5 -282.5t-282.5 -117.5h-300q-165 0 -282.5 117.5 t-117.5 282.5zM436 341l161 50l412 412l-114 113l-405 -405zM995 1015l113 -113l113 113l-21 85l-92 28z" /> -<glyph unicode="" d="M0 400v300q0 165 117.5 282.5t282.5 117.5h261l2 -80q-133 -32 -218 -120h-145q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5l200 153v-53q0 -165 -117.5 -282.5t-282.5 -117.5h-300q-165 0 -282.5 117.5t-117.5 282.5 zM423 524q30 38 81.5 64t103 35.5t99 14t77.5 3.5l29 -1v-209l360 324l-359 318v-216q-7 0 -19 -1t-48 -8t-69.5 -18.5t-76.5 -37t-76.5 -59t-62 -88t-39.5 -121.5z" /> -<glyph unicode="" d="M0 400v300q0 165 117.5 282.5t282.5 117.5h300q60 0 127 -23l-178 -177h-349q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v69l200 200v-169q0 -165 -117.5 -282.5t-282.5 -117.5h-300q-165 0 -282.5 117.5 t-117.5 282.5zM342 632l283 -284l566 567l-136 137l-430 -431l-147 147z" /> -<glyph unicode="" d="M0 603l300 296v-198h200v200h-200l300 300l295 -300h-195v-200h200v198l300 -296l-300 -300v198h-200v-200h195l-295 -300l-300 300h200v200h-200v-198z" /> -<glyph unicode="" d="M200 50v1000q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-437l500 487v-1100l-500 488v-438q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5z" /> -<glyph unicode="" d="M0 50v1000q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-437l500 487v-487l500 487v-1100l-500 488v-488l-500 488v-438q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5z" /> -<glyph unicode="" d="M136 550l564 550v-487l500 487v-1100l-500 488v-488z" /> -<glyph unicode="" d="M200 0l900 550l-900 550v-1100z" /> -<glyph unicode="" d="M200 150q0 -21 14.5 -35.5t35.5 -14.5h200q21 0 35.5 14.5t14.5 35.5v800q0 21 -14.5 35.5t-35.5 14.5h-200q-21 0 -35.5 -14.5t-14.5 -35.5v-800zM600 150q0 -21 14.5 -35.5t35.5 -14.5h200q21 0 35.5 14.5t14.5 35.5v800q0 21 -14.5 35.5t-35.5 14.5h-200 q-21 0 -35.5 -14.5t-14.5 -35.5v-800z" /> -<glyph unicode="" d="M200 150q0 -20 14.5 -35t35.5 -15h800q21 0 35.5 15t14.5 35v800q0 21 -14.5 35.5t-35.5 14.5h-800q-21 0 -35.5 -14.5t-14.5 -35.5v-800z" /> -<glyph unicode="" d="M0 0v1100l500 -487v487l564 -550l-564 -550v488z" /> -<glyph unicode="" d="M0 0v1100l500 -487v487l500 -487v437q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-1000q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v438l-500 -488v488z" /> -<glyph unicode="" d="M300 0v1100l500 -487v437q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-1000q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v438z" /> -<glyph unicode="" d="M100 250v100q0 21 14.5 35.5t35.5 14.5h1000q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1000q-21 0 -35.5 14.5t-14.5 35.5zM100 500h1100l-550 564z" /> -<glyph unicode="" d="M185 599l592 -592l240 240l-353 353l353 353l-240 240z" /> -<glyph unicode="" d="M272 194l353 353l-353 353l241 240l572 -571l21 -22l-1 -1v-1l-592 -591z" /> -<glyph unicode="" d="M3 600q0 162 80 299.5t217.5 217.5t299.5 80t299.5 -80t217.5 -217.5t80 -299.5t-80 -300t-217.5 -218t-299.5 -80t-299.5 80t-217.5 218t-80 300zM300 500h200v-200h200v200h200v200h-200v200h-200v-200h-200v-200z" /> -<glyph unicode="" d="M3 600q0 162 80 299.5t217.5 217.5t299.5 80t299.5 -80t217.5 -217.5t80 -299.5t-80 -300t-217.5 -218t-299.5 -80t-299.5 80t-217.5 218t-80 300zM300 500h600v200h-600v-200z" /> -<glyph unicode="" d="M3 600q0 162 80 299.5t217.5 217.5t299.5 80t299.5 -80t217.5 -217.5t80 -299.5t-80 -300t-217.5 -218t-299.5 -80t-299.5 80t-217.5 218t-80 300zM246 459l213 -213l141 142l141 -142l213 213l-142 141l142 141l-213 212l-141 -141l-141 142l-212 -213l141 -141z" /> -<glyph unicode="" d="M3 600q0 162 80 299.5t217.5 217.5t299.5 80t299.5 -80t217.5 -217.5t80 -299.5t-80 -299.5t-217.5 -217.5t-299.5 -80t-299.5 80t-217.5 217.5t-80 299.5zM270 551l276 -277l411 411l-175 174l-236 -236l-102 102z" /> -<glyph unicode="" d="M3 600q0 162 80 299.5t217.5 217.5t299.5 80t299.5 -80t217.5 -217.5t80 -299.5t-80 -300t-217.5 -218t-299.5 -80t-299.5 80t-217.5 218t-80 300zM363 700h144q4 0 11.5 -1t11 -1t6.5 3t3 9t1 11t3.5 8.5t3.5 6t5.5 4t6.5 2.5t9 1.5t9 0.5h11.5h12.5q19 0 30 -10t11 -26 q0 -22 -4 -28t-27 -22q-5 -1 -12.5 -3t-27 -13.5t-34 -27t-26.5 -46t-11 -68.5h200q5 3 14 8t31.5 25.5t39.5 45.5t31 69t14 94q0 51 -17.5 89t-42 58t-58.5 32t-58.5 15t-51.5 3q-105 0 -172 -56t-67 -183zM500 300h200v100h-200v-100z" /> -<glyph unicode="" d="M3 600q0 162 80 299.5t217.5 217.5t299.5 80t299.5 -80t217.5 -217.5t80 -299.5t-80 -300t-217.5 -218t-299.5 -80t-299.5 80t-217.5 218t-80 300zM400 300h400v100h-100v300h-300v-100h100v-200h-100v-100zM500 800h200v100h-200v-100z" /> -<glyph unicode="" d="M0 500v200h194q15 60 36 104.5t55.5 86t88 69t126.5 40.5v200h200v-200q54 -20 113 -60t112.5 -105.5t71.5 -134.5h203v-200h-203q-25 -102 -116.5 -186t-180.5 -117v-197h-200v197q-140 27 -208 102.5t-98 200.5h-194zM290 500q24 -73 79.5 -127.5t130.5 -78.5v206h200 v-206q149 48 201 206h-201v200h200q-25 74 -76 127.5t-124 76.5v-204h-200v203q-75 -24 -130 -77.5t-79 -125.5h209v-200h-210z" /> -<glyph unicode="" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM356 465l135 135 l-135 135l109 109l135 -135l135 135l109 -109l-135 -135l135 -135l-109 -109l-135 135l-135 -135z" /> -<glyph unicode="" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM322 537l141 141 l87 -87l204 205l142 -142l-346 -345z" /> -<glyph unicode="" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -115 62 -215l568 567q-100 62 -216 62q-171 0 -292.5 -121.5t-121.5 -292.5zM391 245q97 -59 209 -59q171 0 292.5 121.5t121.5 292.5 q0 112 -59 209z" /> -<glyph unicode="" d="M0 547l600 453v-300h600v-300h-600v-301z" /> -<glyph unicode="" d="M0 400v300h600v300l600 -453l-600 -448v301h-600z" /> -<glyph unicode="" d="M204 600l450 600l444 -600h-298v-600h-300v600h-296z" /> -<glyph unicode="" d="M104 600h296v600h300v-600h298l-449 -600z" /> -<glyph unicode="" d="M0 200q6 132 41 238.5t103.5 193t184 138t271.5 59.5v271l600 -453l-600 -448v301q-95 -2 -183 -20t-170 -52t-147 -92.5t-100 -135.5z" /> -<glyph unicode="" d="M0 0v400l129 -129l294 294l142 -142l-294 -294l129 -129h-400zM635 777l142 -142l294 294l129 -129v400h-400l129 -129z" /> -<glyph unicode="" d="M34 176l295 295l-129 129h400v-400l-129 130l-295 -295zM600 600v400l129 -129l295 295l142 -141l-295 -295l129 -130h-400z" /> -<glyph unicode="" d="M23 600q0 118 45.5 224.5t123 184t184 123t224.5 45.5t224.5 -45.5t184 -123t123 -184t45.5 -224.5t-45.5 -224.5t-123 -184t-184 -123t-224.5 -45.5t-224.5 45.5t-184 123t-123 184t-45.5 224.5zM456 851l58 -302q4 -20 21.5 -34.5t37.5 -14.5h54q20 0 37.5 14.5 t21.5 34.5l58 302q4 20 -8 34.5t-33 14.5h-207q-20 0 -32 -14.5t-8 -34.5zM500 300h200v100h-200v-100z" /> -<glyph unicode="" d="M0 800h100v-200h400v300h200v-300h400v200h100v100h-111v6t-1 15t-3 18l-34 172q-11 39 -41.5 63t-69.5 24q-32 0 -61 -17l-239 -144q-22 -13 -40 -35q-19 24 -40 36l-238 144q-33 18 -62 18q-39 0 -69.5 -23t-40.5 -61l-35 -177q-2 -8 -3 -18t-1 -15v-6h-111v-100z M100 0h400v400h-400v-400zM200 900q-3 0 14 48t35 96l18 47l214 -191h-281zM700 0v400h400v-400h-400zM731 900l202 197q5 -12 12 -32.5t23 -64t25 -72t7 -28.5h-269z" /> -<glyph unicode="" d="M0 -22v143l216 193q-9 53 -13 83t-5.5 94t9 113t38.5 114t74 124q47 60 99.5 102.5t103 68t127.5 48t145.5 37.5t184.5 43.5t220 58.5q0 -189 -22 -343t-59 -258t-89 -181.5t-108.5 -120t-122 -68t-125.5 -30t-121.5 -1.5t-107.5 12.5t-87.5 17t-56.5 7.5l-99 -55z M238.5 300.5q19.5 -6.5 86.5 76.5q55 66 367 234q70 38 118.5 69.5t102 79t99 111.5t86.5 148q22 50 24 60t-6 19q-7 5 -17 5t-26.5 -14.5t-33.5 -39.5q-35 -51 -113.5 -108.5t-139.5 -89.5l-61 -32q-369 -197 -458 -401q-48 -111 -28.5 -117.5z" /> -<glyph unicode="" d="M111 408q0 -33 5 -63q9 -56 44 -119.5t105 -108.5q31 -21 64 -16t62 23.5t57 49.5t48 61.5t35 60.5q32 66 39 184.5t-13 157.5q79 -80 122 -164t26 -184q-5 -33 -20.5 -69.5t-37.5 -80.5q-10 -19 -14.5 -29t-12 -26t-9 -23.5t-3 -19t2.5 -15.5t11 -9.5t19.5 -5t30.5 2.5 t42 8q57 20 91 34t87.5 44.5t87 64t65.5 88.5t47 122q38 172 -44.5 341.5t-246.5 278.5q22 -44 43 -129q39 -159 -32 -154q-15 2 -33 9q-79 33 -120.5 100t-44 175.5t48.5 257.5q-13 -8 -34 -23.5t-72.5 -66.5t-88.5 -105.5t-60 -138t-8 -166.5q2 -12 8 -41.5t8 -43t6 -39.5 t3.5 -39.5t-1 -33.5t-6 -31.5t-13.5 -24t-21 -20.5t-31 -12q-38 -10 -67 13t-40.5 61.5t-15 81.5t10.5 75q-52 -46 -83.5 -101t-39 -107t-7.5 -85z" /> -<glyph unicode="" d="M-61 600l26 40q6 10 20 30t49 63.5t74.5 85.5t97 90t116.5 83.5t132.5 59t145.5 23.5t145.5 -23.5t132.5 -59t116.5 -83.5t97 -90t74.5 -85.5t49 -63.5t20 -30l26 -40l-26 -40q-6 -10 -20 -30t-49 -63.5t-74.5 -85.5t-97 -90t-116.5 -83.5t-132.5 -59t-145.5 -23.5 t-145.5 23.5t-132.5 59t-116.5 83.5t-97 90t-74.5 85.5t-49 63.5t-20 30zM120 600q7 -10 40.5 -58t56 -78.5t68 -77.5t87.5 -75t103 -49.5t125 -21.5t123.5 20t100.5 45.5t85.5 71.5t66.5 75.5t58 81.5t47 66q-1 1 -28.5 37.5t-42 55t-43.5 53t-57.5 63.5t-58.5 54 q49 -74 49 -163q0 -124 -88 -212t-212 -88t-212 88t-88 212q0 85 46 158q-102 -87 -226 -258zM377 656q49 -124 154 -191l105 105q-37 24 -75 72t-57 84l-20 36z" /> -<glyph unicode="" d="M-61 600l26 40q6 10 20 30t49 63.5t74.5 85.5t97 90t116.5 83.5t132.5 59t145.5 23.5q61 0 121 -17l37 142h148l-314 -1200h-148l37 143q-82 21 -165 71.5t-140 102t-109.5 112t-72 88.5t-29.5 43zM120 600q210 -282 393 -336l37 141q-107 18 -178.5 101.5t-71.5 193.5 q0 85 46 158q-102 -87 -226 -258zM377 656q49 -124 154 -191l47 47l23 87q-30 28 -59 69t-44 68l-14 26zM780 161l38 145q22 15 44.5 34t46 44t40.5 44t41 50.5t33.5 43.5t33 44t24.5 34q-97 127 -140 175l39 146q67 -54 131.5 -125.5t87.5 -103.5t36 -52l26 -40l-26 -40 q-7 -12 -25.5 -38t-63.5 -79.5t-95.5 -102.5t-124 -100t-146.5 -79z" /> -<glyph unicode="" d="M-97.5 34q13.5 -34 50.5 -34h1294q37 0 50.5 35.5t-7.5 67.5l-642 1056q-20 33 -48 36t-48 -29l-642 -1066q-21 -32 -7.5 -66zM155 200l445 723l445 -723h-345v100h-200v-100h-345zM500 600l100 -300l100 300v100h-200v-100z" /> -<glyph unicode="" d="M100 262v41q0 20 11 44.5t26 38.5l363 325v339q0 62 44 106t106 44t106 -44t44 -106v-339l363 -325q15 -14 26 -38.5t11 -44.5v-41q0 -20 -12 -26.5t-29 5.5l-359 249v-263q100 -91 100 -113v-64q0 -21 -13 -29t-32 1l-94 78h-222l-94 -78q-19 -9 -32 -1t-13 29v64 q0 22 100 113v263l-359 -249q-17 -12 -29 -5.5t-12 26.5z" /> -<glyph unicode="" d="M0 50q0 -20 14.5 -35t35.5 -15h1000q21 0 35.5 15t14.5 35v750h-1100v-750zM0 900h1100v150q0 21 -14.5 35.5t-35.5 14.5h-150v100h-100v-100h-500v100h-100v-100h-150q-21 0 -35.5 -14.5t-14.5 -35.5v-150zM100 100v100h100v-100h-100zM100 300v100h100v-100h-100z M100 500v100h100v-100h-100zM300 100v100h100v-100h-100zM300 300v100h100v-100h-100zM300 500v100h100v-100h-100zM500 100v100h100v-100h-100zM500 300v100h100v-100h-100zM500 500v100h100v-100h-100zM700 100v100h100v-100h-100zM700 300v100h100v-100h-100zM700 500 v100h100v-100h-100zM900 100v100h100v-100h-100zM900 300v100h100v-100h-100zM900 500v100h100v-100h-100z" /> -<glyph unicode="" d="M0 200v200h259l600 600h241v198l300 -295l-300 -300v197h-159l-600 -600h-341zM0 800h259l122 -122l141 142l-181 180h-341v-200zM678 381l141 142l122 -123h159v198l300 -295l-300 -300v197h-241z" /> -<glyph unicode="" d="M0 400v600q0 41 29.5 70.5t70.5 29.5h1000q41 0 70.5 -29.5t29.5 -70.5v-600q0 -41 -29.5 -70.5t-70.5 -29.5h-596l-304 -300v300h-100q-41 0 -70.5 29.5t-29.5 70.5z" /> -<glyph unicode="" d="M100 600v200h300v-250q0 -113 6 -145q17 -92 102 -117q39 -11 92 -11q37 0 66.5 5.5t50 15.5t36 24t24 31.5t14 37.5t7 42t2.5 45t0 47v25v250h300v-200q0 -42 -3 -83t-15 -104t-31.5 -116t-58 -109.5t-89 -96.5t-129 -65.5t-174.5 -25.5t-174.5 25.5t-129 65.5t-89 96.5 t-58 109.5t-31.5 116t-15 104t-3 83zM100 900v300h300v-300h-300zM800 900v300h300v-300h-300z" /> -<glyph unicode="" d="M-30 411l227 -227l352 353l353 -353l226 227l-578 579z" /> -<glyph unicode="" d="M70 797l580 -579l578 579l-226 227l-353 -353l-352 353z" /> -<glyph unicode="" d="M-198 700l299 283l300 -283h-203v-400h385l215 -200h-800v600h-196zM402 1000l215 -200h381v-400h-198l299 -283l299 283h-200v600h-796z" /> -<glyph unicode="" d="M18 939q-5 24 10 42q14 19 39 19h896l38 162q5 17 18.5 27.5t30.5 10.5h94q20 0 35 -14.5t15 -35.5t-15 -35.5t-35 -14.5h-54l-201 -961q-2 -4 -6 -10.5t-19 -17.5t-33 -11h-31v-50q0 -20 -14.5 -35t-35.5 -15t-35.5 15t-14.5 35v50h-300v-50q0 -20 -14.5 -35t-35.5 -15 t-35.5 15t-14.5 35v50h-50q-21 0 -35.5 15t-14.5 35q0 21 14.5 35.5t35.5 14.5h535l48 200h-633q-32 0 -54.5 21t-27.5 43z" /> -<glyph unicode="" d="M0 0v800h1200v-800h-1200zM0 900v100h200q0 41 29.5 70.5t70.5 29.5h300q41 0 70.5 -29.5t29.5 -70.5h500v-100h-1200z" /> -<glyph unicode="" d="M1 0l300 700h1200l-300 -700h-1200zM1 400v600h200q0 41 29.5 70.5t70.5 29.5h300q41 0 70.5 -29.5t29.5 -70.5h500v-200h-1000z" /> -<glyph unicode="" d="M302 300h198v600h-198l298 300l298 -300h-198v-600h198l-298 -300z" /> -<glyph unicode="" d="M0 600l300 298v-198h600v198l300 -298l-300 -297v197h-600v-197z" /> -<glyph unicode="" d="M0 100v100q0 41 29.5 70.5t70.5 29.5h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5zM31 400l172 739q5 22 23 41.5t38 19.5h672q19 0 37.5 -22.5t23.5 -45.5l172 -732h-1138zM800 100h100v100h-100v-100z M1000 100h100v100h-100v-100z" /> -<glyph unicode="" d="M-101 600v50q0 24 25 49t50 38l25 13v-250l-11 5.5t-24 14t-30 21.5t-24 27.5t-11 31.5zM99 500v250v5q0 13 0.5 18.5t2.5 13t8 10.5t15 3h200l675 250v-850l-675 200h-38l47 -276q2 -12 -3 -17.5t-11 -6t-21 -0.5h-8h-83q-20 0 -34.5 14t-18.5 35q-56 337 -56 351z M1100 200v850q0 21 14.5 35.5t35.5 14.5q20 0 35 -14.5t15 -35.5v-850q0 -20 -15 -35t-35 -15q-21 0 -35.5 15t-14.5 35z" /> -<glyph unicode="" d="M74 350q0 21 13.5 35.5t33.5 14.5h17l118 173l63 327q15 77 76 140t144 83l-18 32q-6 19 3 32t29 13h94q20 0 29 -10.5t3 -29.5l-18 -37q83 -19 144 -82.5t76 -140.5l63 -327l118 -173h17q20 0 33.5 -14.5t13.5 -35.5q0 -20 -13 -40t-31 -27q-22 -9 -63 -23t-167.5 -37 t-251.5 -23t-245.5 20.5t-178.5 41.5l-58 20q-18 7 -31 27.5t-13 40.5zM497 110q12 -49 40 -79.5t63 -30.5t63 30.5t39 79.5q-48 -6 -102 -6t-103 6z" /> -<glyph unicode="" d="M21 445l233 -45l-78 -224l224 78l45 -233l155 179l155 -179l45 233l224 -78l-78 224l234 45l-180 155l180 156l-234 44l78 225l-224 -78l-45 233l-155 -180l-155 180l-45 -233l-224 78l78 -225l-233 -44l179 -156z" /> -<glyph unicode="" d="M0 200h200v600h-200v-600zM300 275q0 -75 100 -75h61q123 -100 139 -100h250q46 0 83 57l238 344q29 31 29 74v100q0 44 -30.5 84.5t-69.5 40.5h-328q28 118 28 125v150q0 44 -30.5 84.5t-69.5 40.5h-50q-27 0 -51 -20t-38 -48l-96 -198l-145 -196q-20 -26 -20 -63v-400z M400 300v375l150 212l100 213h50v-175l-50 -225h450v-125l-250 -375h-214l-136 100h-100z" /> -<glyph unicode="" d="M0 400v600h200v-600h-200zM300 525v400q0 75 100 75h61q123 100 139 100h250q46 0 83 -57l238 -344q29 -31 29 -74v-100q0 -44 -30.5 -84.5t-69.5 -40.5h-328q28 -118 28 -125v-150q0 -44 -30.5 -84.5t-69.5 -40.5h-50q-27 0 -51 20t-38 48l-96 198l-145 196 q-20 26 -20 63zM400 525l150 -212l100 -213h50v175l-50 225h450v125l-250 375h-214l-136 -100h-100v-375z" /> -<glyph unicode="" d="M8 200v600h200v-600h-200zM308 275v525q0 17 14 35.5t28 28.5l14 9l362 230q14 6 25 6q17 0 29 -12l109 -112q14 -14 14 -34q0 -18 -11 -32l-85 -121h302q85 0 138.5 -38t53.5 -110t-54.5 -111t-138.5 -39h-107l-130 -339q-7 -22 -20.5 -41.5t-28.5 -19.5h-341 q-7 0 -90 81t-83 94zM408 289l100 -89h293l131 339q6 21 19.5 41t28.5 20h203q16 0 25 15t9 36q0 20 -9 34.5t-25 14.5h-457h-6.5h-7.5t-6.5 0.5t-6 1t-5 1.5t-5.5 2.5t-4 4t-4 5.5q-5 12 -5 20q0 14 10 27l147 183l-86 83l-339 -236v-503z" /> -<glyph unicode="" d="M-101 651q0 72 54 110t139 37h302l-85 121q-11 16 -11 32q0 21 14 34l109 113q13 12 29 12q11 0 25 -6l365 -230q7 -4 16.5 -10.5t26 -26t16.5 -36.5v-526q0 -13 -85.5 -93.5t-93.5 -80.5h-342q-15 0 -28.5 20t-19.5 41l-131 339h-106q-84 0 -139 39t-55 111zM-1 601h222 q15 0 28.5 -20.5t19.5 -40.5l131 -339h293l106 89v502l-342 237l-87 -83l145 -184q10 -11 10 -26q0 -11 -5 -20q-1 -3 -3.5 -5.5l-4 -4t-5 -2.5t-5.5 -1.5t-6.5 -1t-6.5 -0.5h-7.5h-6.5h-476v-100zM999 201v600h200v-600h-200z" /> -<glyph unicode="" d="M97 719l230 -363q4 -6 10.5 -15.5t26 -25t36.5 -15.5h525q13 0 94 83t81 90v342q0 15 -20 28.5t-41 19.5l-339 131v106q0 84 -39 139t-111 55t-110 -53.5t-38 -138.5v-302l-121 84q-15 12 -33.5 11.5t-32.5 -13.5l-112 -110q-22 -22 -6 -53zM172 739l83 86l183 -146 q22 -18 47 -5q3 1 5.5 3.5l4 4t2.5 5t1.5 5.5t1 6.5t0.5 6v7.5v7v456q0 22 25 31t50 -0.5t25 -30.5v-202q0 -16 20 -29.5t41 -19.5l339 -130v-294l-89 -100h-503zM400 0v200h600v-200h-600z" /> -<glyph unicode="" d="M1 585q-15 -31 7 -53l112 -110q13 -13 32 -13.5t34 10.5l121 85l-1 -302q0 -84 38.5 -138t110.5 -54t111 55t39 139v106l339 131q20 6 40.5 19.5t20.5 28.5v342q0 7 -81 90t-94 83h-525q-17 0 -35.5 -14t-28.5 -28l-10 -15zM76 565l237 339h503l89 -100v-294l-340 -130 q-20 -6 -40 -20t-20 -29v-202q0 -22 -25 -31t-50 0t-25 31v456v14.5t-1.5 11.5t-5 12t-9.5 7q-24 13 -46 -5l-184 -146zM305 1104v200h600v-200h-600z" /> -<glyph unicode="" d="M5 597q0 122 47.5 232.5t127.5 190.5t190.5 127.5t232.5 47.5q162 0 299.5 -80t217.5 -218t80 -300t-80 -299.5t-217.5 -217.5t-299.5 -80t-300 80t-218 217.5t-80 299.5zM300 500h300l-2 -194l402 294l-402 298v-197h-298v-201z" /> -<glyph unicode="" d="M0 597q0 122 47.5 232.5t127.5 190.5t190.5 127.5t231.5 47.5q122 0 232.5 -47.5t190.5 -127.5t127.5 -190.5t47.5 -232.5q0 -162 -80 -299.5t-218 -217.5t-300 -80t-299.5 80t-217.5 217.5t-80 299.5zM200 600l400 -294v194h302v201h-300v197z" /> -<glyph unicode="" d="M5 597q0 122 47.5 232.5t127.5 190.5t190.5 127.5t232.5 47.5q121 0 231.5 -47.5t190.5 -127.5t127.5 -190.5t47.5 -232.5q0 -162 -80 -299.5t-217.5 -217.5t-299.5 -80t-300 80t-218 217.5t-80 299.5zM300 600h200v-300h200v300h200l-300 400z" /> -<glyph unicode="" d="M5 597q0 122 47.5 232.5t127.5 190.5t190.5 127.5t232.5 47.5q121 0 231.5 -47.5t190.5 -127.5t127.5 -190.5t47.5 -232.5q0 -162 -80 -299.5t-217.5 -217.5t-299.5 -80t-300 80t-218 217.5t-80 299.5zM300 600l300 -400l300 400h-200v300h-200v-300h-200z" /> -<glyph unicode="" d="M5 597q0 122 47.5 232.5t127.5 190.5t190.5 127.5t232.5 47.5q121 0 231.5 -47.5t190.5 -127.5t127.5 -190.5t47.5 -232.5q0 -162 -80 -299.5t-217.5 -217.5t-299.5 -80t-300 80t-218 217.5t-80 299.5zM254 780q-8 -34 5.5 -93t7.5 -87q0 -9 17 -44t16 -60q12 0 23 -5.5 t23 -15t20 -13.5q20 -10 108 -42q22 -8 53 -31.5t59.5 -38.5t57.5 -11q8 -18 -15 -55.5t-20 -57.5q12 -21 22.5 -34.5t28 -27t36.5 -17.5q0 -6 -3 -15.5t-3.5 -14.5t4.5 -17q101 -2 221 111q31 30 47 48t34 49t21 62q-14 9 -37.5 9.5t-35.5 7.5q-14 7 -49 15t-52 19 q-9 0 -39.5 -0.5t-46.5 -1.5t-39 -6.5t-39 -16.5q-50 -35 -66 -12q-4 2 -3.5 25.5t0.5 25.5q-6 13 -26.5 17t-24.5 7q2 22 -2 41t-16.5 28t-38.5 -20q-23 -25 -42 4q-19 28 -8 58q8 16 22 22q6 -1 26 -1.5t33.5 -4.5t19.5 -13q12 -19 32 -37.5t34 -27.5l14 -8q0 3 9.5 39.5 t5.5 57.5q-4 23 14.5 44.5t22.5 31.5q5 14 10 35t8.5 31t15.5 22.5t34 21.5q-6 18 10 37q8 0 23.5 -1.5t24.5 -1.5t20.5 4.5t20.5 15.5q-10 23 -30.5 42.5t-38 30t-49 26.5t-43.5 23q11 41 1 44q31 -13 58.5 -14.5t39.5 3.5l11 4q6 36 -17 53.5t-64 28.5t-56 23 q-19 -3 -37 0q-15 -12 -36.5 -21t-34.5 -12t-44 -8t-39 -6q-15 -3 -46 0t-45 -3q-20 -6 -51.5 -25.5t-34.5 -34.5q-3 -11 6.5 -22.5t8.5 -18.5q-3 -34 -27.5 -91t-29.5 -79zM518 915q3 12 16 30.5t16 25.5q10 -10 18.5 -10t14 6t14.5 14.5t16 12.5q0 -18 8 -42.5t16.5 -44 t9.5 -23.5q-6 1 -39 5t-53.5 10t-36.5 16z" /> -<glyph unicode="" d="M0 164.5q0 21.5 15 37.5l600 599q-33 101 6 201.5t135 154.5q164 92 306 -9l-259 -138l145 -232l251 126q13 -175 -151 -267q-123 -70 -253 -23l-596 -596q-15 -16 -36.5 -16t-36.5 16l-111 110q-15 15 -15 36.5z" /> -<glyph unicode="" horiz-adv-x="1220" d="M0 196v100q0 41 29.5 70.5t70.5 29.5h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5zM0 596v100q0 41 29.5 70.5t70.5 29.5h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000 q-41 0 -70.5 29.5t-29.5 70.5zM0 996v100q0 41 29.5 70.5t70.5 29.5h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5zM600 596h500v100h-500v-100zM800 196h300v100h-300v-100zM900 996h200v100h-200v-100z" /> -<glyph unicode="" d="M100 1100v100h1000v-100h-1000zM150 1000h900l-350 -500v-300l-200 -200v500z" /> -<glyph unicode="" d="M0 200v200h1200v-200q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5zM0 500v400q0 41 29.5 70.5t70.5 29.5h300v100q0 41 29.5 70.5t70.5 29.5h200q41 0 70.5 -29.5t29.5 -70.5v-100h300q41 0 70.5 -29.5t29.5 -70.5v-400h-500v100h-200v-100h-500z M500 1000h200v100h-200v-100z" /> -<glyph unicode="" d="M0 0v400l129 -129l200 200l142 -142l-200 -200l129 -129h-400zM0 800l129 129l200 -200l142 142l-200 200l129 129h-400v-400zM729 329l142 142l200 -200l129 129v-400h-400l129 129zM729 871l200 200l-129 129h400v-400l-129 129l-200 -200z" /> -<glyph unicode="" d="M0 596q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM182 596q0 -172 121.5 -293t292.5 -121t292.5 121t121.5 293q0 171 -121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM291 655 q0 23 15.5 38.5t38.5 15.5t39 -16t16 -38q0 -23 -16 -39t-39 -16q-22 0 -38 16t-16 39zM400 850q0 22 16 38.5t39 16.5q22 0 38 -16t16 -39t-16 -39t-38 -16q-23 0 -39 16.5t-16 38.5zM513 609q0 32 21 56.5t52 29.5l122 126l1 1q-9 14 -9 28q0 22 16 38.5t39 16.5 q22 0 38 -16t16 -39t-16 -39t-38 -16q-16 0 -29 10l-55 -145q17 -22 17 -51q0 -36 -25.5 -61.5t-61.5 -25.5q-37 0 -62.5 25.5t-25.5 61.5zM800 655q0 22 16 38t39 16t38.5 -15.5t15.5 -38.5t-16 -39t-38 -16q-23 0 -39 16t-16 39z" /> -<glyph unicode="" d="M-40 375q-13 -95 35 -173q35 -57 94 -89t129 -32q63 0 119 28q33 16 65 40.5t52.5 45.5t59.5 64q40 44 57 61l394 394q35 35 47 84t-3 96q-27 87 -117 104q-20 2 -29 2q-46 0 -79.5 -17t-67.5 -51l-388 -396l-7 -7l69 -67l377 373q20 22 39 38q23 23 50 23q38 0 53 -36 q16 -39 -20 -75l-547 -547q-52 -52 -125 -52q-55 0 -100 33t-54 96q-5 35 2.5 66t31.5 63t42 50t56 54q24 21 44 41l348 348q52 52 82.5 79.5t84 54t107.5 26.5q25 0 48 -4q95 -17 154 -94.5t51 -175.5q-7 -101 -98 -192l-252 -249l-253 -256l7 -7l69 -60l517 511 q67 67 95 157t11 183q-16 87 -67 154t-130 103q-69 33 -152 33q-107 0 -197 -55q-40 -24 -111 -95l-512 -512q-68 -68 -81 -163z" /> -<glyph unicode="" d="M79 784q0 131 99 229.5t230 98.5q144 0 242 -129q103 129 245 129q130 0 227 -98.5t97 -229.5q0 -46 -17.5 -91t-61 -99t-77 -89.5t-104.5 -105.5q-197 -191 -293 -322l-17 -23l-16 23q-43 58 -100 122.5t-92 99.5t-101 100l-84.5 84.5t-68 74t-60 78t-33.5 70.5t-15 78z M250 784q0 -27 30.5 -70t61.5 -75.5t95 -94.5l22 -22q93 -90 190 -201q82 92 195 203l12 12q64 62 97.5 97t64.5 79t31 72q0 71 -48 119.5t-106 48.5q-73 0 -131 -83l-118 -171l-114 174q-51 80 -124 80q-59 0 -108.5 -49.5t-49.5 -118.5z" /> -<glyph unicode="" d="M57 353q0 -94 66 -160l141 -141q66 -66 159 -66q95 0 159 66l283 283q66 66 66 159t-66 159l-141 141q-12 12 -19 17l-105 -105l212 -212l-389 -389l-247 248l95 95l-18 18q-46 45 -75 101l-55 -55q-66 -66 -66 -159zM269 706q0 -93 66 -159l141 -141l19 -17l105 105 l-212 212l389 389l247 -247l-95 -96l18 -18q46 -46 77 -99l29 29q35 35 62.5 88t27.5 96q0 93 -66 159l-141 141q-66 66 -159 66q-95 0 -159 -66l-283 -283q-66 -64 -66 -159z" /> -<glyph unicode="" d="M200 100v953q0 21 30 46t81 48t129 38t163 15t162 -15t127 -38t79 -48t29 -46v-953q0 -41 -29.5 -70.5t-70.5 -29.5h-600q-41 0 -70.5 29.5t-29.5 70.5zM300 300h600v700h-600v-700zM496 150q0 -43 30.5 -73.5t73.5 -30.5t73.5 30.5t30.5 73.5t-30.5 73.5t-73.5 30.5 t-73.5 -30.5t-30.5 -73.5z" /> -<glyph unicode="" d="M0 0l303 380l207 208l-210 212h300l267 279l-35 36q-15 14 -15 35t15 35q14 15 35 15t35 -15l283 -282q15 -15 15 -36t-15 -35q-14 -15 -35 -15t-35 15l-36 35l-279 -267v-300l-212 210l-208 -207z" /> -<glyph unicode="" d="M295 433h139q5 -77 48.5 -126.5t117.5 -64.5v335l-27 7q-46 14 -79 26.5t-72 36t-62.5 52t-40 72.5t-16.5 99q0 92 44 159.5t109 101t144 40.5v78h100v-79q38 -4 72.5 -13.5t75.5 -31.5t71 -53.5t51.5 -84t24.5 -118.5h-159q-8 72 -35 109.5t-101 50.5v-307l64 -14 q34 -7 64 -16.5t70 -31.5t67.5 -52t47.5 -80.5t20 -112.5q0 -139 -89 -224t-244 -96v-77h-100v78q-152 17 -237 104q-40 40 -52.5 93.5t-15.5 139.5zM466 889q0 -29 8 -51t16.5 -34t29.5 -22.5t31 -13.5t38 -10q7 -2 11 -3v274q-61 -8 -97.5 -37.5t-36.5 -102.5zM700 237 q170 18 170 151q0 64 -44 99.5t-126 60.5v-311z" /> -<glyph unicode="" d="M100 600v100h166q-24 49 -44 104q-10 26 -14.5 55.5t-3 72.5t25 90t68.5 87q97 88 263 88q129 0 230 -89t101 -208h-153q0 52 -34 89.5t-74 51.5t-76 14q-37 0 -79 -14.5t-62 -35.5q-41 -44 -41 -101q0 -11 2.5 -24.5t5.5 -24t9.5 -26.5t10.5 -25t14 -27.5t14 -25.5 t15.5 -27t13.5 -24h242v-100h-197q8 -50 -2.5 -115t-31.5 -94q-41 -59 -99 -113q35 11 84 18t70 7q32 1 102 -16t104 -17q76 0 136 30l50 -147q-41 -25 -80.5 -36.5t-59 -13t-61.5 -1.5q-23 0 -128 33t-155 29q-39 -4 -82 -17t-66 -25l-24 -11l-55 145l16.5 11t15.5 10 t13.5 9.5t14.5 12t14.5 14t17.5 18.5q48 55 54 126.5t-30 142.5h-221z" /> -<glyph unicode="" d="M2 300l298 -300l298 300h-198v900h-200v-900h-198zM602 900l298 300l298 -300h-198v-900h-200v900h-198z" /> -<glyph unicode="" d="M2 300h198v900h200v-900h198l-298 -300zM700 0v200h100v-100h200v-100h-300zM700 400v100h300v-200h-99v-100h-100v100h99v100h-200zM700 700v500h300v-500h-100v100h-100v-100h-100zM801 900h100v200h-100v-200z" /> -<glyph unicode="" d="M2 300h198v900h200v-900h198l-298 -300zM700 0v500h300v-500h-100v100h-100v-100h-100zM700 700v200h100v-100h200v-100h-300zM700 1100v100h300v-200h-99v-100h-100v100h99v100h-200zM801 200h100v200h-100v-200z" /> -<glyph unicode="" d="M2 300l298 -300l298 300h-198v900h-200v-900h-198zM800 100v400h300v-500h-100v100h-200zM800 1100v100h200v-500h-100v400h-100zM901 200h100v200h-100v-200z" /> -<glyph unicode="" d="M2 300l298 -300l298 300h-198v900h-200v-900h-198zM800 400v100h200v-500h-100v400h-100zM800 800v400h300v-500h-100v100h-200zM901 900h100v200h-100v-200z" /> -<glyph unicode="" d="M2 300l298 -300l298 300h-198v900h-200v-900h-198zM700 100v200h500v-200h-500zM700 400v200h400v-200h-400zM700 700v200h300v-200h-300zM700 1000v200h200v-200h-200z" /> -<glyph unicode="" d="M2 300l298 -300l298 300h-198v900h-200v-900h-198zM700 100v200h200v-200h-200zM700 400v200h300v-200h-300zM700 700v200h400v-200h-400zM700 1000v200h500v-200h-500z" /> -<glyph unicode="" d="M0 400v300q0 165 117.5 282.5t282.5 117.5h300q162 0 281 -118.5t119 -281.5v-300q0 -165 -118.5 -282.5t-281.5 -117.5h-300q-165 0 -282.5 117.5t-117.5 282.5zM200 300q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5 h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500z" /> -<glyph unicode="" d="M0 400v300q0 163 119 281.5t281 118.5h300q165 0 282.5 -117.5t117.5 -282.5v-300q0 -165 -117.5 -282.5t-282.5 -117.5h-300q-163 0 -281.5 117.5t-118.5 282.5zM200 300q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5 h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500zM400 300l333 250l-333 250v-500z" /> -<glyph unicode="" d="M0 400v300q0 163 117.5 281.5t282.5 118.5h300q163 0 281.5 -119t118.5 -281v-300q0 -165 -117.5 -282.5t-282.5 -117.5h-300q-165 0 -282.5 117.5t-117.5 282.5zM200 300q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5 h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500zM300 700l250 -333l250 333h-500z" /> -<glyph unicode="" d="M0 400v300q0 165 117.5 282.5t282.5 117.5h300q165 0 282.5 -117.5t117.5 -282.5v-300q0 -162 -118.5 -281t-281.5 -119h-300q-165 0 -282.5 118.5t-117.5 281.5zM200 300q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5 h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500zM300 400h500l-250 333z" /> -<glyph unicode="" d="M0 400v300h300v200l400 -350l-400 -350v200h-300zM500 0v200h500q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5h-500v200h400q165 0 282.5 -117.5t117.5 -282.5v-300q0 -165 -117.5 -282.5t-282.5 -117.5h-400z" /> -<glyph unicode="" d="M216 519q10 -19 32 -19h302q-155 -438 -160 -458q-5 -21 4 -32l9 -8l9 -1q13 0 26 16l538 630q15 19 6 36q-8 18 -32 16h-300q1 4 78 219.5t79 227.5q2 17 -6 27l-8 8h-9q-16 0 -25 -15q-4 -5 -98.5 -111.5t-228 -257t-209.5 -238.5q-17 -19 -7 -40z" /> -<glyph unicode="" d="M0 400q0 -165 117.5 -282.5t282.5 -117.5h300q47 0 100 15v185h-500q-41 0 -70.5 29.5t-29.5 70.5v500q0 41 29.5 70.5t70.5 29.5h500v185q-14 4 -114 7.5t-193 5.5l-93 2q-165 0 -282.5 -117.5t-117.5 -282.5v-300zM600 400v300h300v200l400 -350l-400 -350v200h-300z " /> -<glyph unicode="" d="M0 400q0 -165 117.5 -282.5t282.5 -117.5h300q163 0 281.5 117.5t118.5 282.5v98l-78 73l-122 -123v-148q0 -41 -29.5 -70.5t-70.5 -29.5h-500q-41 0 -70.5 29.5t-29.5 70.5v500q0 41 29.5 70.5t70.5 29.5h156l118 122l-74 78h-100q-165 0 -282.5 -117.5t-117.5 -282.5 v-300zM496 709l353 342l-149 149h500v-500l-149 149l-342 -353z" /> -<glyph unicode="" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM406 600 q0 80 57 137t137 57t137 -57t57 -137t-57 -137t-137 -57t-137 57t-57 137z" /> -<glyph unicode="" d="M0 0v275q0 11 7 18t18 7h1048q11 0 19 -7.5t8 -17.5v-275h-1100zM100 800l445 -500l450 500h-295v400h-300v-400h-300zM900 150h100v50h-100v-50z" /> -<glyph unicode="" d="M0 0v275q0 11 7 18t18 7h1048q11 0 19 -7.5t8 -17.5v-275h-1100zM100 700h300v-300h300v300h295l-445 500zM900 150h100v50h-100v-50z" /> -<glyph unicode="" d="M0 0v275q0 11 7 18t18 7h1048q11 0 19 -7.5t8 -17.5v-275h-1100zM100 705l305 -305l596 596l-154 155l-442 -442l-150 151zM900 150h100v50h-100v-50z" /> -<glyph unicode="" d="M0 0v275q0 11 7 18t18 7h1048q11 0 19 -7.5t8 -17.5v-275h-1100zM100 988l97 -98l212 213l-97 97zM200 401h700v699l-250 -239l-149 149l-212 -212l149 -149zM900 150h100v50h-100v-50z" /> -<glyph unicode="" d="M0 0v275q0 11 7 18t18 7h1048q11 0 19 -7.5t8 -17.5v-275h-1100zM200 612l212 -212l98 97l-213 212zM300 1200l239 -250l-149 -149l212 -212l149 148l248 -237v700h-699zM900 150h100v50h-100v-50z" /> -<glyph unicode="" d="M23 415l1177 784v-1079l-475 272l-310 -393v416h-392zM494 210l672 938l-672 -712v-226z" /> -<glyph unicode="" d="M0 150v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100l200 -200v-850q0 -21 -15 -35.5t-35 -14.5h-150v400h-700v-400h-150q-21 0 -35.5 14.5t-14.5 35.5zM600 1000h100v200h-100v-200z" /> -<glyph unicode="" d="M0 150v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100l200 -200v-218l-276 -275l-120 120l-126 -127h-378v-400h-150q-21 0 -35.5 14.5t-14.5 35.5zM581 306l123 123l120 -120l353 352l123 -123l-475 -476zM600 1000h100v200h-100v-200z" /> -<glyph unicode="" d="M0 150v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100l200 -200v-269l-103 -103l-170 170l-298 -298h-329v-400h-150q-21 0 -35.5 14.5t-14.5 35.5zM600 1000h100v200h-100v-200zM700 133l170 170l-170 170l127 127l170 -170l170 170l127 -128l-170 -169l170 -170 l-127 -127l-170 170l-170 -170z" /> -<glyph unicode="" d="M0 150v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100l200 -200v-300h-400v-200h-500v-400h-150q-21 0 -35.5 14.5t-14.5 35.5zM600 300l300 -300l300 300h-200v300h-200v-300h-200zM600 1000v200h100v-200h-100z" /> -<glyph unicode="" d="M0 150v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100l200 -200v-402l-200 200l-298 -298h-402v-400h-150q-21 0 -35.5 14.5t-14.5 35.5zM600 300h200v-300h200v300h200l-300 300zM600 1000v200h100v-200h-100z" /> -<glyph unicode="" d="M0 250q0 -21 14.5 -35.5t35.5 -14.5h1100q21 0 35.5 14.5t14.5 35.5v550h-1200v-550zM0 900h1200v150q0 21 -14.5 35.5t-35.5 14.5h-1100q-21 0 -35.5 -14.5t-14.5 -35.5v-150zM100 300v200h400v-200h-400z" /> -<glyph unicode="" d="M0 400l300 298v-198h400v-200h-400v-198zM100 800v200h100v-200h-100zM300 800v200h100v-200h-100zM500 800v200h400v198l300 -298l-300 -298v198h-400zM800 300v200h100v-200h-100zM1000 300h100v200h-100v-200z" /> -<glyph unicode="" d="M100 700v400l50 100l50 -100v-300h100v300l50 100l50 -100v-300h100v300l50 100l50 -100v-400l-100 -203v-447q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v447zM800 597q0 -29 10.5 -55.5t25 -43t29 -28.5t25.5 -18l10 -5v-397q0 -21 14.5 -35.5 t35.5 -14.5h200q21 0 35.5 14.5t14.5 35.5v1106q0 31 -18 40.5t-44 -7.5l-276 -117q-25 -16 -43.5 -50.5t-18.5 -65.5v-359z" /> -<glyph unicode="" d="M100 0h400v56q-75 0 -87.5 6t-12.5 44v394h500v-394q0 -38 -12.5 -44t-87.5 -6v-56h400v56q-4 0 -11 0.5t-24 3t-30 7t-24 15t-11 24.5v888q0 22 25 34.5t50 13.5l25 2v56h-400v-56q75 0 87.5 -6t12.5 -44v-394h-500v394q0 38 12.5 44t87.5 6v56h-400v-56q4 0 11 -0.5 t24 -3t30 -7t24 -15t11 -24.5v-888q0 -22 -25 -34.5t-50 -13.5l-25 -2v-56z" /> -<glyph unicode="" d="M0 300q0 -41 29.5 -70.5t70.5 -29.5h300q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5h-300q-41 0 -70.5 -29.5t-29.5 -70.5v-500zM100 100h400l200 200h105l295 98v-298h-425l-100 -100h-375zM100 300v200h300v-200h-300zM100 600v200h300v-200h-300z M100 1000h400l200 -200v-98l295 98h105v200h-425l-100 100h-375zM700 402v163l400 133v-163z" /> -<glyph unicode="" d="M16.5 974.5q0.5 -21.5 16 -90t46.5 -140t104 -177.5t175 -208q103 -103 207.5 -176t180 -103.5t137 -47t92.5 -16.5l31 1l163 162q16 17 13 40.5t-22 37.5l-192 136q-19 14 -45 12t-42 -19l-119 -118q-143 103 -267 227q-126 126 -227 268l118 118q17 17 20 41.5 t-11 44.5l-139 194q-14 19 -36.5 22t-40.5 -14l-162 -162q-1 -11 -0.5 -32.5z" /> -<glyph unicode="" d="M0 50v212q0 20 10.5 45.5t24.5 39.5l365 303v50q0 4 1 10.5t12 22.5t30 28.5t60 23t97 10.5t97 -10t60 -23.5t30 -27.5t12 -24l1 -10v-50l365 -303q14 -14 24.5 -39.5t10.5 -45.5v-212q0 -21 -15 -35.5t-35 -14.5h-1100q-21 0 -35.5 14.5t-14.5 35.5zM0 712 q0 -21 14.5 -33.5t34.5 -8.5l202 33q20 4 34.5 21t14.5 38v146q141 24 300 24t300 -24v-146q0 -21 14.5 -38t34.5 -21l202 -33q20 -4 34.5 8.5t14.5 33.5v200q-6 8 -19 20.5t-63 45t-112 57t-171 45t-235 20.5q-92 0 -175 -10.5t-141.5 -27t-108.5 -36.5t-81.5 -40 t-53.5 -36.5t-31 -27.5l-9 -10v-200z" /> -<glyph unicode="" d="M100 0v100h1100v-100h-1100zM175 200h950l-125 150v250l100 100v400h-100v-200h-100v200h-200v-200h-100v200h-200v-200h-100v200h-100v-400l100 -100v-250z" /> -<glyph unicode="" d="M100 0h300v400q0 41 -29.5 70.5t-70.5 29.5h-100q-41 0 -70.5 -29.5t-29.5 -70.5v-400zM500 0v1000q0 41 29.5 70.5t70.5 29.5h100q41 0 70.5 -29.5t29.5 -70.5v-1000h-300zM900 0v700q0 41 29.5 70.5t70.5 29.5h100q41 0 70.5 -29.5t29.5 -70.5v-700h-300z" /> -<glyph unicode="" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 300h300v300h-200v100h200v100h-300v-300h200v-100h-200v-100zM600 300h200v100h100v300h-100v100h-200v-500 zM700 400v300h100v-300h-100z" /> -<glyph unicode="" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 300h100v200h100v-200h100v500h-100v-200h-100v200h-100v-500zM600 300h200v100h100v300h-100v100h-200v-500 zM700 400v300h100v-300h-100z" /> -<glyph unicode="" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 300h300v100h-200v300h200v100h-300v-500zM600 300h300v100h-200v300h200v100h-300v-500z" /> -<glyph unicode="" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 550l300 -150v300zM600 400l300 150l-300 150v-300z" /> -<glyph unicode="" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 300v500h700v-500h-700zM300 400h130q41 0 68 42t27 107t-28.5 108t-66.5 43h-130v-300zM575 549 q0 -65 27 -107t68 -42h130v300h-130q-38 0 -66.5 -43t-28.5 -108z" /> -<glyph unicode="" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 300h300v300h-200v100h200v100h-300v-300h200v-100h-200v-100zM601 300h100v100h-100v-100zM700 700h100 v-400h100v500h-200v-100z" /> -<glyph unicode="" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 300h300v400h-200v100h-100v-500zM301 400v200h100v-200h-100zM601 300h100v100h-100v-100zM700 700h100 v-400h100v500h-200v-100z" /> -<glyph unicode="" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 700v100h300v-300h-99v-100h-100v100h99v200h-200zM201 300v100h100v-100h-100zM601 300v100h100v-100h-100z M700 700v100h200v-500h-100v400h-100z" /> -<glyph unicode="" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM400 500v200 l100 100h300v-100h-300v-200h300v-100h-300z" /> -<glyph unicode="" d="M0 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM182 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM400 400v400h300 l100 -100v-100h-100v100h-200v-100h200v-100h-200v-100h-100zM700 400v100h100v-100h-100z" /> -<glyph unicode="" d="M-14 494q0 -80 56.5 -137t135.5 -57h222v300h400v-300h128q120 0 205 86t85 208q0 120 -85 206.5t-205 86.5q-46 0 -90 -14q-44 97 -134.5 156.5t-200.5 59.5q-152 0 -260 -107.5t-108 -260.5q0 -25 2 -37q-66 -14 -108.5 -67.5t-42.5 -122.5zM300 200h200v300h200v-300 h200l-300 -300z" /> -<glyph unicode="" d="M-14 494q0 -80 56.5 -137t135.5 -57h8l414 414l403 -403q94 26 154.5 104t60.5 178q0 121 -85 207.5t-205 86.5q-46 0 -90 -14q-44 97 -134.5 156.5t-200.5 59.5q-152 0 -260 -107.5t-108 -260.5q0 -25 2 -37q-66 -14 -108.5 -67.5t-42.5 -122.5zM300 200l300 300 l300 -300h-200v-300h-200v300h-200z" /> -<glyph unicode="" d="M100 200h400v-155l-75 -45h350l-75 45v155h400l-270 300h170l-270 300h170l-300 333l-300 -333h170l-270 -300h170z" /> -<glyph unicode="" d="M121 700q0 -53 28.5 -97t75.5 -65q-4 -16 -4 -38q0 -74 52.5 -126.5t126.5 -52.5q56 0 100 30v-306l-75 -45h350l-75 45v306q46 -30 100 -30q74 0 126.5 52.5t52.5 126.5q0 24 -9 55q50 32 79.5 83t29.5 112q0 90 -61.5 155.5t-150.5 71.5q-26 89 -99.5 145.5 t-167.5 56.5q-116 0 -197.5 -81.5t-81.5 -197.5q0 -4 1 -12t1 -11q-14 2 -23 2q-74 0 -126.5 -52.5t-52.5 -126.5z" /> +<glyph unicode=" " horiz-adv-x="325" /> +<glyph unicode="€" d="M744 1198q242 0 354 -189q60 -104 66 -209h-181q0 45 -17.5 82.5t-43.5 61.5t-58 40.5t-60.5 24t-51.5 7.5q-19 0 -40.5 -5.5t-49.5 -20.5t-53 -38t-49 -62.5t-39 -89.5h379l-100 -100h-300q-6 -50 -6 -100h406l-100 -100h-300q9 -74 33 -132t52.5 -91t61.5 -54.5t59 -29 t47 -7.5q22 0 50.5 7.5t60.5 24.5t58 41t43.5 61t17.5 80h174q-30 -171 -128 -278q-107 -117 -274 -117q-206 0 -324 158q-36 48 -69 133t-45 204h-217l100 100h112q1 47 6 100h-218l100 100h134q20 87 51 153.5t62 103.5q117 141 297 141z" /> +<glyph unicode="₽" d="M428 1200h350q67 0 120 -13t86 -31t57 -49.5t35 -56.5t17 -64.5t6.5 -60.5t0.5 -57v-16.5v-16.5q0 -36 -0.5 -57t-6.5 -61t-17 -65t-35 -57t-57 -50.5t-86 -31.5t-120 -13h-178l-2 -100h288q10 0 13 -6t-3 -14l-120 -160q-6 -8 -18 -14t-22 -6h-138v-175q0 -11 -5.5 -18 t-15.5 -7h-149q-10 0 -17.5 7.5t-7.5 17.5v175h-267q-10 0 -13 6t3 14l120 160q6 8 18 14t22 6h117v100h-267q-10 0 -13 6t3 14l120 160q6 8 18 14t22 6h117v475q0 10 7.5 17.5t17.5 7.5zM600 1000v-300h203q64 0 86.5 33t22.5 119q0 84 -22.5 116t-86.5 32h-203z" /> +<glyph unicode="−" d="M250 700h800q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-800q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5z" /> +<glyph unicode="⌛" d="M1000 1200v-150q0 -21 -14.5 -35.5t-35.5 -14.5h-50v-100q0 -91 -49.5 -165.5t-130.5 -109.5q81 -35 130.5 -109.5t49.5 -165.5v-150h50q21 0 35.5 -14.5t14.5 -35.5v-150h-800v150q0 21 14.5 35.5t35.5 14.5h50v150q0 91 49.5 165.5t130.5 109.5q-81 35 -130.5 109.5 t-49.5 165.5v100h-50q-21 0 -35.5 14.5t-14.5 35.5v150h800zM400 1000v-100q0 -60 32.5 -109.5t87.5 -73.5q28 -12 44 -37t16 -55t-16 -55t-44 -37q-55 -24 -87.5 -73.5t-32.5 -109.5v-150h400v150q0 60 -32.5 109.5t-87.5 73.5q-28 12 -44 37t-16 55t16 55t44 37 q55 24 87.5 73.5t32.5 109.5v100h-400z" /> +<glyph unicode="◼" horiz-adv-x="500" d="M0 0z" /> +<glyph unicode="☁" d="M503 1089q110 0 200.5 -59.5t134.5 -156.5q44 14 90 14q120 0 205 -86.5t85 -206.5q0 -121 -85 -207.5t-205 -86.5h-750q-79 0 -135.5 57t-56.5 137q0 69 42.5 122.5t108.5 67.5q-2 12 -2 37q0 153 108 260.5t260 107.5z" /> +<glyph unicode="⛺" d="M774 1193.5q16 -9.5 20.5 -27t-5.5 -33.5l-136 -187l467 -746h30q20 0 35 -18.5t15 -39.5v-42h-1200v42q0 21 15 39.5t35 18.5h30l468 746l-135 183q-10 16 -5.5 34t20.5 28t34 5.5t28 -20.5l111 -148l112 150q9 16 27 20.5t34 -5zM600 200h377l-182 112l-195 534v-646z " /> +<glyph unicode="✉" d="M25 1100h1150q10 0 12.5 -5t-5.5 -13l-564 -567q-8 -8 -18 -8t-18 8l-564 567q-8 8 -5.5 13t12.5 5zM18 882l264 -264q8 -8 8 -18t-8 -18l-264 -264q-8 -8 -13 -5.5t-5 12.5v550q0 10 5 12.5t13 -5.5zM918 618l264 264q8 8 13 5.5t5 -12.5v-550q0 -10 -5 -12.5t-13 5.5 l-264 264q-8 8 -8 18t8 18zM818 482l364 -364q8 -8 5.5 -13t-12.5 -5h-1150q-10 0 -12.5 5t5.5 13l364 364q8 8 18 8t18 -8l164 -164q8 -8 18 -8t18 8l164 164q8 8 18 8t18 -8z" /> +<glyph unicode="✏" d="M1011 1210q19 0 33 -13l153 -153q13 -14 13 -33t-13 -33l-99 -92l-214 214l95 96q13 14 32 14zM1013 800l-615 -614l-214 214l614 614zM317 96l-333 -112l110 335z" /> +<glyph unicode="" d="M700 650v-550h250q21 0 35.5 -14.5t14.5 -35.5v-50h-800v50q0 21 14.5 35.5t35.5 14.5h250v550l-500 550h1200z" /> +<glyph unicode="" d="M368 1017l645 163q39 15 63 0t24 -49v-831q0 -55 -41.5 -95.5t-111.5 -63.5q-79 -25 -147 -4.5t-86 75t25.5 111.5t122.5 82q72 24 138 8v521l-600 -155v-606q0 -42 -44 -90t-109 -69q-79 -26 -147 -5.5t-86 75.5t25.5 111.5t122.5 82.5q72 24 138 7v639q0 38 14.5 59 t53.5 34z" /> +<glyph unicode="" d="M500 1191q100 0 191 -39t156.5 -104.5t104.5 -156.5t39 -191l-1 -2l1 -5q0 -141 -78 -262l275 -274q23 -26 22.5 -44.5t-22.5 -42.5l-59 -58q-26 -20 -46.5 -20t-39.5 20l-275 274q-119 -77 -261 -77l-5 1l-2 -1q-100 0 -191 39t-156.5 104.5t-104.5 156.5t-39 191 t39 191t104.5 156.5t156.5 104.5t191 39zM500 1022q-88 0 -162 -43t-117 -117t-43 -162t43 -162t117 -117t162 -43t162 43t117 117t43 162t-43 162t-117 117t-162 43z" /> +<glyph unicode="" d="M649 949q48 68 109.5 104t121.5 38.5t118.5 -20t102.5 -64t71 -100.5t27 -123q0 -57 -33.5 -117.5t-94 -124.5t-126.5 -127.5t-150 -152.5t-146 -174q-62 85 -145.5 174t-150 152.5t-126.5 127.5t-93.5 124.5t-33.5 117.5q0 64 28 123t73 100.5t104 64t119 20 t120.5 -38.5t104.5 -104z" /> +<glyph unicode="" d="M407 800l131 353q7 19 17.5 19t17.5 -19l129 -353h421q21 0 24 -8.5t-14 -20.5l-342 -249l130 -401q7 -20 -0.5 -25.5t-24.5 6.5l-343 246l-342 -247q-17 -12 -24.5 -6.5t-0.5 25.5l130 400l-347 251q-17 12 -14 20.5t23 8.5h429z" /> +<glyph unicode="" d="M407 800l131 353q7 19 17.5 19t17.5 -19l129 -353h421q21 0 24 -8.5t-14 -20.5l-342 -249l130 -401q7 -20 -0.5 -25.5t-24.5 6.5l-343 246l-342 -247q-17 -12 -24.5 -6.5t-0.5 25.5l130 400l-347 251q-17 12 -14 20.5t23 8.5h429zM477 700h-240l197 -142l-74 -226 l193 139l195 -140l-74 229l192 140h-234l-78 211z" /> +<glyph unicode="" d="M600 1200q124 0 212 -88t88 -212v-250q0 -46 -31 -98t-69 -52v-75q0 -10 6 -21.5t15 -17.5l358 -230q9 -5 15 -16.5t6 -21.5v-93q0 -10 -7.5 -17.5t-17.5 -7.5h-1150q-10 0 -17.5 7.5t-7.5 17.5v93q0 10 6 21.5t15 16.5l358 230q9 6 15 17.5t6 21.5v75q-38 0 -69 52 t-31 98v250q0 124 88 212t212 88z" /> +<glyph unicode="" d="M25 1100h1150q10 0 17.5 -7.5t7.5 -17.5v-1050q0 -10 -7.5 -17.5t-17.5 -7.5h-1150q-10 0 -17.5 7.5t-7.5 17.5v1050q0 10 7.5 17.5t17.5 7.5zM100 1000v-100h100v100h-100zM875 1000h-550q-10 0 -17.5 -7.5t-7.5 -17.5v-350q0 -10 7.5 -17.5t17.5 -7.5h550 q10 0 17.5 7.5t7.5 17.5v350q0 10 -7.5 17.5t-17.5 7.5zM1000 1000v-100h100v100h-100zM100 800v-100h100v100h-100zM1000 800v-100h100v100h-100zM100 600v-100h100v100h-100zM1000 600v-100h100v100h-100zM875 500h-550q-10 0 -17.5 -7.5t-7.5 -17.5v-350q0 -10 7.5 -17.5 t17.5 -7.5h550q10 0 17.5 7.5t7.5 17.5v350q0 10 -7.5 17.5t-17.5 7.5zM100 400v-100h100v100h-100zM1000 400v-100h100v100h-100zM100 200v-100h100v100h-100zM1000 200v-100h100v100h-100z" /> +<glyph unicode="" d="M50 1100h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5zM650 1100h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5v400 q0 21 14.5 35.5t35.5 14.5zM50 500h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5zM650 500h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400 q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5z" /> +<glyph unicode="" d="M50 1100h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5zM450 1100h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200 q0 21 14.5 35.5t35.5 14.5zM850 1100h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5zM50 700h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200 q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5zM450 700h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5zM850 700h200q21 0 35.5 -14.5t14.5 -35.5v-200 q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5zM50 300h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5zM450 300h200 q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5zM850 300h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5 t35.5 14.5z" /> +<glyph unicode="" d="M50 1100h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5zM450 1100h700q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-700q-21 0 -35.5 14.5t-14.5 35.5v200 q0 21 14.5 35.5t35.5 14.5zM50 700h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5zM450 700h700q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-700 q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5zM50 300h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5zM450 300h700q21 0 35.5 -14.5t14.5 -35.5v-200 q0 -21 -14.5 -35.5t-35.5 -14.5h-700q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5z" /> +<glyph unicode="" d="M465 477l571 571q8 8 18 8t17 -8l177 -177q8 -7 8 -17t-8 -18l-783 -784q-7 -8 -17.5 -8t-17.5 8l-384 384q-8 8 -8 18t8 17l177 177q7 8 17 8t18 -8l171 -171q7 -7 18 -7t18 7z" /> +<glyph unicode="" d="M904 1083l178 -179q8 -8 8 -18.5t-8 -17.5l-267 -268l267 -268q8 -7 8 -17.5t-8 -18.5l-178 -178q-8 -8 -18.5 -8t-17.5 8l-268 267l-268 -267q-7 -8 -17.5 -8t-18.5 8l-178 178q-8 8 -8 18.5t8 17.5l267 268l-267 268q-8 7 -8 17.5t8 18.5l178 178q8 8 18.5 8t17.5 -8 l268 -267l268 268q7 7 17.5 7t18.5 -7z" /> +<glyph unicode="" d="M507 1177q98 0 187.5 -38.5t154.5 -103.5t103.5 -154.5t38.5 -187.5q0 -141 -78 -262l300 -299q8 -8 8 -18.5t-8 -18.5l-109 -108q-7 -8 -17.5 -8t-18.5 8l-300 299q-119 -77 -261 -77q-98 0 -188 38.5t-154.5 103t-103 154.5t-38.5 188t38.5 187.5t103 154.5 t154.5 103.5t188 38.5zM506.5 1023q-89.5 0 -165.5 -44t-120 -120.5t-44 -166t44 -165.5t120 -120t165.5 -44t166 44t120.5 120t44 165.5t-44 166t-120.5 120.5t-166 44zM425 900h150q10 0 17.5 -7.5t7.5 -17.5v-75h75q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5 t-17.5 -7.5h-75v-75q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5v75h-75q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5h75v75q0 10 7.5 17.5t17.5 7.5z" /> +<glyph unicode="" d="M507 1177q98 0 187.5 -38.5t154.5 -103.5t103.5 -154.5t38.5 -187.5q0 -141 -78 -262l300 -299q8 -8 8 -18.5t-8 -18.5l-109 -108q-7 -8 -17.5 -8t-18.5 8l-300 299q-119 -77 -261 -77q-98 0 -188 38.5t-154.5 103t-103 154.5t-38.5 188t38.5 187.5t103 154.5 t154.5 103.5t188 38.5zM506.5 1023q-89.5 0 -165.5 -44t-120 -120.5t-44 -166t44 -165.5t120 -120t165.5 -44t166 44t120.5 120t44 165.5t-44 166t-120.5 120.5t-166 44zM325 800h350q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-350q-10 0 -17.5 7.5 t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5z" /> +<glyph unicode="" d="M550 1200h100q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5zM800 975v166q167 -62 272 -209.5t105 -331.5q0 -117 -45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5 t-184.5 123t-123 184.5t-45.5 224q0 184 105 331.5t272 209.5v-166q-103 -55 -165 -155t-62 -220q0 -116 57 -214.5t155.5 -155.5t214.5 -57t214.5 57t155.5 155.5t57 214.5q0 120 -62 220t-165 155z" /> +<glyph unicode="" d="M1025 1200h150q10 0 17.5 -7.5t7.5 -17.5v-1150q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5v1150q0 10 7.5 17.5t17.5 7.5zM725 800h150q10 0 17.5 -7.5t7.5 -17.5v-750q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5v750 q0 10 7.5 17.5t17.5 7.5zM425 500h150q10 0 17.5 -7.5t7.5 -17.5v-450q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5v450q0 10 7.5 17.5t17.5 7.5zM125 300h150q10 0 17.5 -7.5t7.5 -17.5v-250q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5 v250q0 10 7.5 17.5t17.5 7.5z" /> +<glyph unicode="" d="M600 1174q33 0 74 -5l38 -152l5 -1q49 -14 94 -39l5 -2l134 80q61 -48 104 -105l-80 -134l3 -5q25 -44 39 -93l1 -6l152 -38q5 -43 5 -73q0 -34 -5 -74l-152 -38l-1 -6q-15 -49 -39 -93l-3 -5l80 -134q-48 -61 -104 -105l-134 81l-5 -3q-44 -25 -94 -39l-5 -2l-38 -151 q-43 -5 -74 -5q-33 0 -74 5l-38 151l-5 2q-49 14 -94 39l-5 3l-134 -81q-60 48 -104 105l80 134l-3 5q-25 45 -38 93l-2 6l-151 38q-6 42 -6 74q0 33 6 73l151 38l2 6q13 48 38 93l3 5l-80 134q47 61 105 105l133 -80l5 2q45 25 94 39l5 1l38 152q43 5 74 5zM600 815 q-89 0 -152 -63t-63 -151.5t63 -151.5t152 -63t152 63t63 151.5t-63 151.5t-152 63z" /> +<glyph unicode="" d="M500 1300h300q41 0 70.5 -29.5t29.5 -70.5v-100h275q10 0 17.5 -7.5t7.5 -17.5v-75h-1100v75q0 10 7.5 17.5t17.5 7.5h275v100q0 41 29.5 70.5t70.5 29.5zM500 1200v-100h300v100h-300zM1100 900v-800q0 -41 -29.5 -70.5t-70.5 -29.5h-700q-41 0 -70.5 29.5t-29.5 70.5 v800h900zM300 800v-700h100v700h-100zM500 800v-700h100v700h-100zM700 800v-700h100v700h-100zM900 800v-700h100v700h-100z" /> +<glyph unicode="" d="M18 618l620 608q8 7 18.5 7t17.5 -7l608 -608q8 -8 5.5 -13t-12.5 -5h-175v-575q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5v375h-300v-375q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5v575h-175q-10 0 -12.5 5t5.5 13z" /> +<glyph unicode="" d="M600 1200v-400q0 -41 29.5 -70.5t70.5 -29.5h300v-650q0 -21 -14.5 -35.5t-35.5 -14.5h-800q-21 0 -35.5 14.5t-14.5 35.5v1100q0 21 14.5 35.5t35.5 14.5h450zM1000 800h-250q-21 0 -35.5 14.5t-14.5 35.5v250z" /> +<glyph unicode="" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM600 1027q-116 0 -214.5 -57t-155.5 -155.5t-57 -214.5t57 -214.5 t155.5 -155.5t214.5 -57t214.5 57t155.5 155.5t57 214.5t-57 214.5t-155.5 155.5t-214.5 57zM525 900h50q10 0 17.5 -7.5t7.5 -17.5v-275h175q10 0 17.5 -7.5t7.5 -17.5v-50q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5v350q0 10 7.5 17.5t17.5 7.5z" /> +<glyph unicode="" d="M1300 0h-538l-41 400h-242l-41 -400h-538l431 1200h209l-21 -300h162l-20 300h208zM515 800l-27 -300h224l-27 300h-170z" /> +<glyph unicode="" d="M550 1200h200q21 0 35.5 -14.5t14.5 -35.5v-450h191q20 0 25.5 -11.5t-7.5 -27.5l-327 -400q-13 -16 -32 -16t-32 16l-327 400q-13 16 -7.5 27.5t25.5 11.5h191v450q0 21 14.5 35.5t35.5 14.5zM1125 400h50q10 0 17.5 -7.5t7.5 -17.5v-350q0 -10 -7.5 -17.5t-17.5 -7.5 h-1050q-10 0 -17.5 7.5t-7.5 17.5v350q0 10 7.5 17.5t17.5 7.5h50q10 0 17.5 -7.5t7.5 -17.5v-175h900v175q0 10 7.5 17.5t17.5 7.5z" /> +<glyph unicode="" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM600 1027q-116 0 -214.5 -57t-155.5 -155.5t-57 -214.5t57 -214.5 t155.5 -155.5t214.5 -57t214.5 57t155.5 155.5t57 214.5t-57 214.5t-155.5 155.5t-214.5 57zM525 900h150q10 0 17.5 -7.5t7.5 -17.5v-275h137q21 0 26 -11.5t-8 -27.5l-223 -275q-13 -16 -32 -16t-32 16l-223 275q-13 16 -8 27.5t26 11.5h137v275q0 10 7.5 17.5t17.5 7.5z " /> +<glyph unicode="" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM600 1027q-116 0 -214.5 -57t-155.5 -155.5t-57 -214.5t57 -214.5 t155.5 -155.5t214.5 -57t214.5 57t155.5 155.5t57 214.5t-57 214.5t-155.5 155.5t-214.5 57zM632 914l223 -275q13 -16 8 -27.5t-26 -11.5h-137v-275q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5v275h-137q-21 0 -26 11.5t8 27.5l223 275q13 16 32 16 t32 -16z" /> +<glyph unicode="" d="M225 1200h750q10 0 19.5 -7t12.5 -17l186 -652q7 -24 7 -49v-425q0 -12 -4 -27t-9 -17q-12 -6 -37 -6h-1100q-12 0 -27 4t-17 8q-6 13 -6 38l1 425q0 25 7 49l185 652q3 10 12.5 17t19.5 7zM878 1000h-556q-10 0 -19 -7t-11 -18l-87 -450q-2 -11 4 -18t16 -7h150 q10 0 19.5 -7t11.5 -17l38 -152q2 -10 11.5 -17t19.5 -7h250q10 0 19.5 7t11.5 17l38 152q2 10 11.5 17t19.5 7h150q10 0 16 7t4 18l-87 450q-2 11 -11 18t-19 7z" /> +<glyph unicode="" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM600 1027q-116 0 -214.5 -57t-155.5 -155.5t-57 -214.5t57 -214.5 t155.5 -155.5t214.5 -57t214.5 57t155.5 155.5t57 214.5t-57 214.5t-155.5 155.5t-214.5 57zM540 820l253 -190q17 -12 17 -30t-17 -30l-253 -190q-16 -12 -28 -6.5t-12 26.5v400q0 21 12 26.5t28 -6.5z" /> +<glyph unicode="" d="M947 1060l135 135q7 7 12.5 5t5.5 -13v-362q0 -10 -7.5 -17.5t-17.5 -7.5h-362q-11 0 -13 5.5t5 12.5l133 133q-109 76 -238 76q-116 0 -214.5 -57t-155.5 -155.5t-57 -214.5t57 -214.5t155.5 -155.5t214.5 -57t214.5 57t155.5 155.5t57 214.5h150q0 -117 -45.5 -224 t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5q192 0 347 -117z" /> +<glyph unicode="" d="M947 1060l135 135q7 7 12.5 5t5.5 -13v-361q0 -11 -7.5 -18.5t-18.5 -7.5h-361q-11 0 -13 5.5t5 12.5l134 134q-110 75 -239 75q-116 0 -214.5 -57t-155.5 -155.5t-57 -214.5h-150q0 117 45.5 224t123 184.5t184.5 123t224 45.5q192 0 347 -117zM1027 600h150 q0 -117 -45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5q-192 0 -348 118l-134 -134q-7 -8 -12.5 -5.5t-5.5 12.5v360q0 11 7.5 18.5t18.5 7.5h360q10 0 12.5 -5.5t-5.5 -12.5l-133 -133q110 -76 240 -76q116 0 214.5 57t155.5 155.5t57 214.5z" /> +<glyph unicode="" d="M125 1200h1050q10 0 17.5 -7.5t7.5 -17.5v-1150q0 -10 -7.5 -17.5t-17.5 -7.5h-1050q-10 0 -17.5 7.5t-7.5 17.5v1150q0 10 7.5 17.5t17.5 7.5zM1075 1000h-850q-10 0 -17.5 -7.5t-7.5 -17.5v-850q0 -10 7.5 -17.5t17.5 -7.5h850q10 0 17.5 7.5t7.5 17.5v850 q0 10 -7.5 17.5t-17.5 7.5zM325 900h50q10 0 17.5 -7.5t7.5 -17.5v-50q0 -10 -7.5 -17.5t-17.5 -7.5h-50q-10 0 -17.5 7.5t-7.5 17.5v50q0 10 7.5 17.5t17.5 7.5zM525 900h450q10 0 17.5 -7.5t7.5 -17.5v-50q0 -10 -7.5 -17.5t-17.5 -7.5h-450q-10 0 -17.5 7.5t-7.5 17.5v50 q0 10 7.5 17.5t17.5 7.5zM325 700h50q10 0 17.5 -7.5t7.5 -17.5v-50q0 -10 -7.5 -17.5t-17.5 -7.5h-50q-10 0 -17.5 7.5t-7.5 17.5v50q0 10 7.5 17.5t17.5 7.5zM525 700h450q10 0 17.5 -7.5t7.5 -17.5v-50q0 -10 -7.5 -17.5t-17.5 -7.5h-450q-10 0 -17.5 7.5t-7.5 17.5v50 q0 10 7.5 17.5t17.5 7.5zM325 500h50q10 0 17.5 -7.5t7.5 -17.5v-50q0 -10 -7.5 -17.5t-17.5 -7.5h-50q-10 0 -17.5 7.5t-7.5 17.5v50q0 10 7.5 17.5t17.5 7.5zM525 500h450q10 0 17.5 -7.5t7.5 -17.5v-50q0 -10 -7.5 -17.5t-17.5 -7.5h-450q-10 0 -17.5 7.5t-7.5 17.5v50 q0 10 7.5 17.5t17.5 7.5zM325 300h50q10 0 17.5 -7.5t7.5 -17.5v-50q0 -10 -7.5 -17.5t-17.5 -7.5h-50q-10 0 -17.5 7.5t-7.5 17.5v50q0 10 7.5 17.5t17.5 7.5zM525 300h450q10 0 17.5 -7.5t7.5 -17.5v-50q0 -10 -7.5 -17.5t-17.5 -7.5h-450q-10 0 -17.5 7.5t-7.5 17.5v50 q0 10 7.5 17.5t17.5 7.5z" /> +<glyph unicode="" d="M900 800v200q0 83 -58.5 141.5t-141.5 58.5h-300q-82 0 -141 -59t-59 -141v-200h-100q-41 0 -70.5 -29.5t-29.5 -70.5v-600q0 -41 29.5 -70.5t70.5 -29.5h900q41 0 70.5 29.5t29.5 70.5v600q0 41 -29.5 70.5t-70.5 29.5h-100zM400 800v150q0 21 15 35.5t35 14.5h200 q20 0 35 -14.5t15 -35.5v-150h-300z" /> +<glyph unicode="" d="M125 1100h50q10 0 17.5 -7.5t7.5 -17.5v-1075h-100v1075q0 10 7.5 17.5t17.5 7.5zM1075 1052q4 0 9 -2q16 -6 16 -23v-421q0 -6 -3 -12q-33 -59 -66.5 -99t-65.5 -58t-56.5 -24.5t-52.5 -6.5q-26 0 -57.5 6.5t-52.5 13.5t-60 21q-41 15 -63 22.5t-57.5 15t-65.5 7.5 q-85 0 -160 -57q-7 -5 -15 -5q-6 0 -11 3q-14 7 -14 22v438q22 55 82 98.5t119 46.5q23 2 43 0.5t43 -7t32.5 -8.5t38 -13t32.5 -11q41 -14 63.5 -21t57 -14t63.5 -7q103 0 183 87q7 8 18 8z" /> +<glyph unicode="" d="M600 1175q116 0 227 -49.5t192.5 -131t131 -192.5t49.5 -227v-300q0 -10 -7.5 -17.5t-17.5 -7.5h-50q-10 0 -17.5 7.5t-7.5 17.5v300q0 127 -70.5 231.5t-184.5 161.5t-245 57t-245 -57t-184.5 -161.5t-70.5 -231.5v-300q0 -10 -7.5 -17.5t-17.5 -7.5h-50 q-10 0 -17.5 7.5t-7.5 17.5v300q0 116 49.5 227t131 192.5t192.5 131t227 49.5zM220 500h160q8 0 14 -6t6 -14v-460q0 -8 -6 -14t-14 -6h-160q-8 0 -14 6t-6 14v460q0 8 6 14t14 6zM820 500h160q8 0 14 -6t6 -14v-460q0 -8 -6 -14t-14 -6h-160q-8 0 -14 6t-6 14v460 q0 8 6 14t14 6z" /> +<glyph unicode="" d="M321 814l258 172q9 6 15 2.5t6 -13.5v-750q0 -10 -6 -13.5t-15 2.5l-258 172q-21 14 -46 14h-250q-10 0 -17.5 7.5t-7.5 17.5v350q0 10 7.5 17.5t17.5 7.5h250q25 0 46 14zM900 668l120 120q7 7 17 7t17 -7l34 -34q7 -7 7 -17t-7 -17l-120 -120l120 -120q7 -7 7 -17 t-7 -17l-34 -34q-7 -7 -17 -7t-17 7l-120 119l-120 -119q-7 -7 -17 -7t-17 7l-34 34q-7 7 -7 17t7 17l119 120l-119 120q-7 7 -7 17t7 17l34 34q7 8 17 8t17 -8z" /> +<glyph unicode="" d="M321 814l258 172q9 6 15 2.5t6 -13.5v-750q0 -10 -6 -13.5t-15 2.5l-258 172q-21 14 -46 14h-250q-10 0 -17.5 7.5t-7.5 17.5v350q0 10 7.5 17.5t17.5 7.5h250q25 0 46 14zM766 900h4q10 -1 16 -10q96 -129 96 -290q0 -154 -90 -281q-6 -9 -17 -10l-3 -1q-9 0 -16 6 l-29 23q-7 7 -8.5 16.5t4.5 17.5q72 103 72 229q0 132 -78 238q-6 8 -4.5 18t9.5 17l29 22q7 5 15 5z" /> +<glyph unicode="" d="M967 1004h3q11 -1 17 -10q135 -179 135 -396q0 -105 -34 -206.5t-98 -185.5q-7 -9 -17 -10h-3q-9 0 -16 6l-42 34q-8 6 -9 16t5 18q111 150 111 328q0 90 -29.5 176t-84.5 157q-6 9 -5 19t10 16l42 33q7 5 15 5zM321 814l258 172q9 6 15 2.5t6 -13.5v-750q0 -10 -6 -13.5 t-15 2.5l-258 172q-21 14 -46 14h-250q-10 0 -17.5 7.5t-7.5 17.5v350q0 10 7.5 17.5t17.5 7.5h250q25 0 46 14zM766 900h4q10 -1 16 -10q96 -129 96 -290q0 -154 -90 -281q-6 -9 -17 -10l-3 -1q-9 0 -16 6l-29 23q-7 7 -8.5 16.5t4.5 17.5q72 103 72 229q0 132 -78 238 q-6 8 -4.5 18.5t9.5 16.5l29 22q7 5 15 5z" /> +<glyph unicode="" d="M500 900h100v-100h-100v-100h-400v-100h-100v600h500v-300zM1200 700h-200v-100h200v-200h-300v300h-200v300h-100v200h600v-500zM100 1100v-300h300v300h-300zM800 1100v-300h300v300h-300zM300 900h-100v100h100v-100zM1000 900h-100v100h100v-100zM300 500h200v-500 h-500v500h200v100h100v-100zM800 300h200v-100h-100v-100h-200v100h-100v100h100v200h-200v100h300v-300zM100 400v-300h300v300h-300zM300 200h-100v100h100v-100zM1200 200h-100v100h100v-100zM700 0h-100v100h100v-100zM1200 0h-300v100h300v-100z" /> +<glyph unicode="" d="M100 200h-100v1000h100v-1000zM300 200h-100v1000h100v-1000zM700 200h-200v1000h200v-1000zM900 200h-100v1000h100v-1000zM1200 200h-200v1000h200v-1000zM400 0h-300v100h300v-100zM600 0h-100v91h100v-91zM800 0h-100v91h100v-91zM1100 0h-200v91h200v-91z" /> +<glyph unicode="" d="M500 1200l682 -682q8 -8 8 -18t-8 -18l-464 -464q-8 -8 -18 -8t-18 8l-682 682l1 475q0 10 7.5 17.5t17.5 7.5h474zM319.5 1024.5q-29.5 29.5 -71 29.5t-71 -29.5t-29.5 -71.5t29.5 -71.5t71 -29.5t71 29.5t29.5 71.5t-29.5 71.5z" /> +<glyph unicode="" d="M500 1200l682 -682q8 -8 8 -18t-8 -18l-464 -464q-8 -8 -18 -8t-18 8l-682 682l1 475q0 10 7.5 17.5t17.5 7.5h474zM800 1200l682 -682q8 -8 8 -18t-8 -18l-464 -464q-8 -8 -18 -8t-18 8l-56 56l424 426l-700 700h150zM319.5 1024.5q-29.5 29.5 -71 29.5t-71 -29.5 t-29.5 -71.5t29.5 -71.5t71 -29.5t71 29.5t29.5 71.5t-29.5 71.5z" /> +<glyph unicode="" d="M300 1200h825q75 0 75 -75v-900q0 -25 -18 -43l-64 -64q-8 -8 -13 -5.5t-5 12.5v950q0 10 -7.5 17.5t-17.5 7.5h-700q-25 0 -43 -18l-64 -64q-8 -8 -5.5 -13t12.5 -5h700q10 0 17.5 -7.5t7.5 -17.5v-950q0 -10 -7.5 -17.5t-17.5 -7.5h-850q-10 0 -17.5 7.5t-7.5 17.5v975 q0 25 18 43l139 139q18 18 43 18z" /> +<glyph unicode="" d="M250 1200h800q21 0 35.5 -14.5t14.5 -35.5v-1150l-450 444l-450 -445v1151q0 21 14.5 35.5t35.5 14.5z" /> +<glyph unicode="" d="M822 1200h-444q-11 0 -19 -7.5t-9 -17.5l-78 -301q-7 -24 7 -45l57 -108q6 -9 17.5 -15t21.5 -6h450q10 0 21.5 6t17.5 15l62 108q14 21 7 45l-83 301q-1 10 -9 17.5t-19 7.5zM1175 800h-150q-10 0 -21 -6.5t-15 -15.5l-78 -156q-4 -9 -15 -15.5t-21 -6.5h-550 q-10 0 -21 6.5t-15 15.5l-78 156q-4 9 -15 15.5t-21 6.5h-150q-10 0 -17.5 -7.5t-7.5 -17.5v-650q0 -10 7.5 -17.5t17.5 -7.5h150q10 0 17.5 7.5t7.5 17.5v150q0 10 7.5 17.5t17.5 7.5h750q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 7.5 -17.5t17.5 -7.5h150q10 0 17.5 7.5 t7.5 17.5v650q0 10 -7.5 17.5t-17.5 7.5zM850 200h-500q-10 0 -19.5 -7t-11.5 -17l-38 -152q-2 -10 3.5 -17t15.5 -7h600q10 0 15.5 7t3.5 17l-38 152q-2 10 -11.5 17t-19.5 7z" /> +<glyph unicode="" d="M500 1100h200q56 0 102.5 -20.5t72.5 -50t44 -59t25 -50.5l6 -20h150q41 0 70.5 -29.5t29.5 -70.5v-600q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5v600q0 41 29.5 70.5t70.5 29.5h150q2 8 6.5 21.5t24 48t45 61t72 48t102.5 21.5zM900 800v-100 h100v100h-100zM600 730q-95 0 -162.5 -67.5t-67.5 -162.5t67.5 -162.5t162.5 -67.5t162.5 67.5t67.5 162.5t-67.5 162.5t-162.5 67.5zM600 603q43 0 73 -30t30 -73t-30 -73t-73 -30t-73 30t-30 73t30 73t73 30z" /> +<glyph unicode="" d="M681 1199l385 -998q20 -50 60 -92q18 -19 36.5 -29.5t27.5 -11.5l10 -2v-66h-417v66q53 0 75 43.5t5 88.5l-82 222h-391q-58 -145 -92 -234q-11 -34 -6.5 -57t25.5 -37t46 -20t55 -6v-66h-365v66q56 24 84 52q12 12 25 30.5t20 31.5l7 13l399 1006h93zM416 521h340 l-162 457z" /> +<glyph unicode="" d="M753 641q5 -1 14.5 -4.5t36 -15.5t50.5 -26.5t53.5 -40t50.5 -54.5t35.5 -70t14.5 -87q0 -67 -27.5 -125.5t-71.5 -97.5t-98.5 -66.5t-108.5 -40.5t-102 -13h-500v89q41 7 70.5 32.5t29.5 65.5v827q0 24 -0.5 34t-3.5 24t-8.5 19.5t-17 13.5t-28 12.5t-42.5 11.5v71 l471 -1q57 0 115.5 -20.5t108 -57t80.5 -94t31 -124.5q0 -51 -15.5 -96.5t-38 -74.5t-45 -50.5t-38.5 -30.5zM400 700h139q78 0 130.5 48.5t52.5 122.5q0 41 -8.5 70.5t-29.5 55.5t-62.5 39.5t-103.5 13.5h-118v-350zM400 200h216q80 0 121 50.5t41 130.5q0 90 -62.5 154.5 t-156.5 64.5h-159v-400z" /> +<glyph unicode="" d="M877 1200l2 -57q-83 -19 -116 -45.5t-40 -66.5l-132 -839q-9 -49 13 -69t96 -26v-97h-500v97q186 16 200 98l173 832q3 17 3 30t-1.5 22.5t-9 17.5t-13.5 12.5t-21.5 10t-26 8.5t-33.5 10q-13 3 -19 5v57h425z" /> +<glyph unicode="" d="M1300 900h-50q0 21 -4 37t-9.5 26.5t-18 17.5t-22 11t-28.5 5.5t-31 2t-37 0.5h-200v-850q0 -22 25 -34.5t50 -13.5l25 -2v-100h-400v100q4 0 11 0.5t24 3t30 7t24 15t11 24.5v850h-200q-25 0 -37 -0.5t-31 -2t-28.5 -5.5t-22 -11t-18 -17.5t-9.5 -26.5t-4 -37h-50v300 h1000v-300zM175 1000h-75v-800h75l-125 -167l-125 167h75v800h-75l125 167z" /> +<glyph unicode="" d="M1100 900h-50q0 21 -4 37t-9.5 26.5t-18 17.5t-22 11t-28.5 5.5t-31 2t-37 0.5h-200v-650q0 -22 25 -34.5t50 -13.5l25 -2v-100h-400v100q4 0 11 0.5t24 3t30 7t24 15t11 24.5v650h-200q-25 0 -37 -0.5t-31 -2t-28.5 -5.5t-22 -11t-18 -17.5t-9.5 -26.5t-4 -37h-50v300 h1000v-300zM1167 50l-167 -125v75h-800v-75l-167 125l167 125v-75h800v75z" /> +<glyph unicode="" d="M50 1100h600q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-600q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM50 800h1000q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1000q-21 0 -35.5 14.5t-14.5 35.5v100 q0 21 14.5 35.5t35.5 14.5zM50 500h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-800q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM50 200h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1100 q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5z" /> +<glyph unicode="" d="M250 1100h700q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-700q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM50 800h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1100q-21 0 -35.5 14.5t-14.5 35.5v100 q0 21 14.5 35.5t35.5 14.5zM250 500h700q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-700q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM50 200h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1100 q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5z" /> +<glyph unicode="" d="M500 950v100q0 21 14.5 35.5t35.5 14.5h600q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-600q-21 0 -35.5 14.5t-14.5 35.5zM100 650v100q0 21 14.5 35.5t35.5 14.5h1000q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1000 q-21 0 -35.5 14.5t-14.5 35.5zM300 350v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-800q-21 0 -35.5 14.5t-14.5 35.5zM0 50v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100 q0 -21 -14.5 -35.5t-35.5 -14.5h-1100q-21 0 -35.5 14.5t-14.5 35.5z" /> +<glyph unicode="" d="M50 1100h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1100q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM50 800h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1100q-21 0 -35.5 14.5t-14.5 35.5v100 q0 21 14.5 35.5t35.5 14.5zM50 500h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1100q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM50 200h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1100 q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5z" /> +<glyph unicode="" d="M50 1100h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM350 1100h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-800q-21 0 -35.5 14.5t-14.5 35.5v100 q0 21 14.5 35.5t35.5 14.5zM50 800h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM350 800h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-800 q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM50 500h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM350 500h800q21 0 35.5 -14.5t14.5 -35.5v-100 q0 -21 -14.5 -35.5t-35.5 -14.5h-800q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM50 200h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM350 200h800 q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-800q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5z" /> +<glyph unicode="" d="M400 0h-100v1100h100v-1100zM550 1100h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM550 800h500q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-500 q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM267 550l-167 -125v75h-200v100h200v75zM550 500h300q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-300q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM550 200h600 q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-600q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5z" /> +<glyph unicode="" d="M50 1100h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM900 0h-100v1100h100v-1100zM50 800h500q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-500 q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM1100 600h200v-100h-200v-75l-167 125l167 125v-75zM50 500h300q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-300q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM50 200h600 q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-600q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5z" /> +<glyph unicode="" d="M75 1000h750q31 0 53 -22t22 -53v-650q0 -31 -22 -53t-53 -22h-750q-31 0 -53 22t-22 53v650q0 31 22 53t53 22zM1200 300l-300 300l300 300v-600z" /> +<glyph unicode="" d="M44 1100h1112q18 0 31 -13t13 -31v-1012q0 -18 -13 -31t-31 -13h-1112q-18 0 -31 13t-13 31v1012q0 18 13 31t31 13zM100 1000v-737l247 182l298 -131l-74 156l293 318l236 -288v500h-1000zM342 884q56 0 95 -39t39 -94.5t-39 -95t-95 -39.5t-95 39.5t-39 95t39 94.5 t95 39z" /> +<glyph unicode="" d="M648 1169q117 0 216 -60t156.5 -161t57.5 -218q0 -115 -70 -258q-69 -109 -158 -225.5t-143 -179.5l-54 -62q-9 8 -25.5 24.5t-63.5 67.5t-91 103t-98.5 128t-95.5 148q-60 132 -60 249q0 88 34 169.5t91.5 142t137 96.5t166.5 36zM652.5 974q-91.5 0 -156.5 -65 t-65 -157t65 -156.5t156.5 -64.5t156.5 64.5t65 156.5t-65 157t-156.5 65z" /> +<glyph unicode="" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM600 173v854q-116 0 -214.5 -57t-155.5 -155.5t-57 -214.5t57 -214.5 t155.5 -155.5t214.5 -57z" /> +<glyph unicode="" d="M554 1295q21 -72 57.5 -143.5t76 -130t83 -118t82.5 -117t70 -116t49.5 -126t18.5 -136.5q0 -71 -25.5 -135t-68.5 -111t-99 -82t-118.5 -54t-125.5 -23q-84 5 -161.5 34t-139.5 78.5t-99 125t-37 164.5q0 69 18 136.5t49.5 126.5t69.5 116.5t81.5 117.5t83.5 119 t76.5 131t58.5 143zM344 710q-23 -33 -43.5 -70.5t-40.5 -102.5t-17 -123q1 -37 14.5 -69.5t30 -52t41 -37t38.5 -24.5t33 -15q21 -7 32 -1t13 22l6 34q2 10 -2.5 22t-13.5 19q-5 4 -14 12t-29.5 40.5t-32.5 73.5q-26 89 6 271q2 11 -6 11q-8 1 -15 -10z" /> +<glyph unicode="" d="M1000 1013l108 115q2 1 5 2t13 2t20.5 -1t25 -9.5t28.5 -21.5q22 -22 27 -43t0 -32l-6 -10l-108 -115zM350 1100h400q50 0 105 -13l-187 -187h-368q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v182l200 200v-332 q0 -165 -93.5 -257.5t-256.5 -92.5h-400q-165 0 -257.5 92.5t-92.5 257.5v400q0 165 92.5 257.5t257.5 92.5zM1009 803l-362 -362l-161 -50l55 170l355 355z" /> +<glyph unicode="" d="M350 1100h361q-164 -146 -216 -200h-195q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5l200 153v-103q0 -165 -92.5 -257.5t-257.5 -92.5h-400q-165 0 -257.5 92.5t-92.5 257.5v400q0 165 92.5 257.5t257.5 92.5z M824 1073l339 -301q8 -7 8 -17.5t-8 -17.5l-340 -306q-7 -6 -12.5 -4t-6.5 11v203q-26 1 -54.5 0t-78.5 -7.5t-92 -17.5t-86 -35t-70 -57q10 59 33 108t51.5 81.5t65 58.5t68.5 40.5t67 24.5t56 13.5t40 4.5v210q1 10 6.5 12.5t13.5 -4.5z" /> +<glyph unicode="" d="M350 1100h350q60 0 127 -23l-178 -177h-349q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v69l200 200v-219q0 -165 -92.5 -257.5t-257.5 -92.5h-400q-165 0 -257.5 92.5t-92.5 257.5v400q0 165 92.5 257.5t257.5 92.5z M643 639l395 395q7 7 17.5 7t17.5 -7l101 -101q7 -7 7 -17.5t-7 -17.5l-531 -532q-7 -7 -17.5 -7t-17.5 7l-248 248q-7 7 -7 17.5t7 17.5l101 101q7 7 17.5 7t17.5 -7l111 -111q8 -7 18 -7t18 7z" /> +<glyph unicode="" d="M318 918l264 264q8 8 18 8t18 -8l260 -264q7 -8 4.5 -13t-12.5 -5h-170v-200h200v173q0 10 5 12t13 -5l264 -260q8 -7 8 -17.5t-8 -17.5l-264 -265q-8 -7 -13 -5t-5 12v173h-200v-200h170q10 0 12.5 -5t-4.5 -13l-260 -264q-8 -8 -18 -8t-18 8l-264 264q-8 8 -5.5 13 t12.5 5h175v200h-200v-173q0 -10 -5 -12t-13 5l-264 265q-8 7 -8 17.5t8 17.5l264 260q8 7 13 5t5 -12v-173h200v200h-175q-10 0 -12.5 5t5.5 13z" /> +<glyph unicode="" d="M250 1100h100q21 0 35.5 -14.5t14.5 -35.5v-438l464 453q15 14 25.5 10t10.5 -25v-1000q0 -21 -10.5 -25t-25.5 10l-464 453v-438q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v1000q0 21 14.5 35.5t35.5 14.5z" /> +<glyph unicode="" d="M50 1100h100q21 0 35.5 -14.5t14.5 -35.5v-438l464 453q15 14 25.5 10t10.5 -25v-438l464 453q15 14 25.5 10t10.5 -25v-1000q0 -21 -10.5 -25t-25.5 10l-464 453v-438q0 -21 -10.5 -25t-25.5 10l-464 453v-438q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5 t-14.5 35.5v1000q0 21 14.5 35.5t35.5 14.5z" /> +<glyph unicode="" d="M1200 1050v-1000q0 -21 -10.5 -25t-25.5 10l-464 453v-438q0 -21 -10.5 -25t-25.5 10l-492 480q-15 14 -15 35t15 35l492 480q15 14 25.5 10t10.5 -25v-438l464 453q15 14 25.5 10t10.5 -25z" /> +<glyph unicode="" d="M243 1074l814 -498q18 -11 18 -26t-18 -26l-814 -498q-18 -11 -30.5 -4t-12.5 28v1000q0 21 12.5 28t30.5 -4z" /> +<glyph unicode="" d="M250 1000h200q21 0 35.5 -14.5t14.5 -35.5v-800q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v800q0 21 14.5 35.5t35.5 14.5zM650 1000h200q21 0 35.5 -14.5t14.5 -35.5v-800q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v800 q0 21 14.5 35.5t35.5 14.5z" /> +<glyph unicode="" d="M1100 950v-800q0 -21 -14.5 -35.5t-35.5 -14.5h-800q-21 0 -35.5 14.5t-14.5 35.5v800q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5z" /> +<glyph unicode="" d="M500 612v438q0 21 10.5 25t25.5 -10l492 -480q15 -14 15 -35t-15 -35l-492 -480q-15 -14 -25.5 -10t-10.5 25v438l-464 -453q-15 -14 -25.5 -10t-10.5 25v1000q0 21 10.5 25t25.5 -10z" /> +<glyph unicode="" d="M1048 1102l100 1q20 0 35 -14.5t15 -35.5l5 -1000q0 -21 -14.5 -35.5t-35.5 -14.5l-100 -1q-21 0 -35.5 14.5t-14.5 35.5l-2 437l-463 -454q-14 -15 -24.5 -10.5t-10.5 25.5l-2 437l-462 -455q-15 -14 -25.5 -9.5t-10.5 24.5l-5 1000q0 21 10.5 25.5t25.5 -10.5l466 -450 l-2 438q0 20 10.5 24.5t25.5 -9.5l466 -451l-2 438q0 21 14.5 35.5t35.5 14.5z" /> +<glyph unicode="" d="M850 1100h100q21 0 35.5 -14.5t14.5 -35.5v-1000q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v438l-464 -453q-15 -14 -25.5 -10t-10.5 25v1000q0 21 10.5 25t25.5 -10l464 -453v438q0 21 14.5 35.5t35.5 14.5z" /> +<glyph unicode="" d="M686 1081l501 -540q15 -15 10.5 -26t-26.5 -11h-1042q-22 0 -26.5 11t10.5 26l501 540q15 15 36 15t36 -15zM150 400h1000q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1000q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5z" /> +<glyph unicode="" d="M885 900l-352 -353l352 -353l-197 -198l-552 552l552 550z" /> +<glyph unicode="" d="M1064 547l-551 -551l-198 198l353 353l-353 353l198 198z" /> +<glyph unicode="" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM650 900h-100q-21 0 -35.5 -14.5t-14.5 -35.5v-150h-150 q-21 0 -35.5 -14.5t-14.5 -35.5v-100q0 -21 14.5 -35.5t35.5 -14.5h150v-150q0 -21 14.5 -35.5t35.5 -14.5h100q21 0 35.5 14.5t14.5 35.5v150h150q21 0 35.5 14.5t14.5 35.5v100q0 21 -14.5 35.5t-35.5 14.5h-150v150q0 21 -14.5 35.5t-35.5 14.5z" /> +<glyph unicode="" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM850 700h-500q-21 0 -35.5 -14.5t-14.5 -35.5v-100q0 -21 14.5 -35.5 t35.5 -14.5h500q21 0 35.5 14.5t14.5 35.5v100q0 21 -14.5 35.5t-35.5 14.5z" /> +<glyph unicode="" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM741.5 913q-12.5 0 -21.5 -9l-120 -120l-120 120q-9 9 -21.5 9 t-21.5 -9l-141 -141q-9 -9 -9 -21.5t9 -21.5l120 -120l-120 -120q-9 -9 -9 -21.5t9 -21.5l141 -141q9 -9 21.5 -9t21.5 9l120 120l120 -120q9 -9 21.5 -9t21.5 9l141 141q9 9 9 21.5t-9 21.5l-120 120l120 120q9 9 9 21.5t-9 21.5l-141 141q-9 9 -21.5 9z" /> +<glyph unicode="" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM546 623l-84 85q-7 7 -17.5 7t-18.5 -7l-139 -139q-7 -8 -7 -18t7 -18 l242 -241q7 -8 17.5 -8t17.5 8l375 375q7 7 7 17.5t-7 18.5l-139 139q-7 7 -17.5 7t-17.5 -7z" /> +<glyph unicode="" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM588 941q-29 0 -59 -5.5t-63 -20.5t-58 -38.5t-41.5 -63t-16.5 -89.5 q0 -25 20 -25h131q30 -5 35 11q6 20 20.5 28t45.5 8q20 0 31.5 -10.5t11.5 -28.5q0 -23 -7 -34t-26 -18q-1 0 -13.5 -4t-19.5 -7.5t-20 -10.5t-22 -17t-18.5 -24t-15.5 -35t-8 -46q-1 -8 5.5 -16.5t20.5 -8.5h173q7 0 22 8t35 28t37.5 48t29.5 74t12 100q0 47 -17 83 t-42.5 57t-59.5 34.5t-64 18t-59 4.5zM675 400h-150q-10 0 -17.5 -7.5t-7.5 -17.5v-150q0 -10 7.5 -17.5t17.5 -7.5h150q10 0 17.5 7.5t7.5 17.5v150q0 10 -7.5 17.5t-17.5 7.5z" /> +<glyph unicode="" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM675 1000h-150q-10 0 -17.5 -7.5t-7.5 -17.5v-150q0 -10 7.5 -17.5 t17.5 -7.5h150q10 0 17.5 7.5t7.5 17.5v150q0 10 -7.5 17.5t-17.5 7.5zM675 700h-250q-10 0 -17.5 -7.5t-7.5 -17.5v-50q0 -10 7.5 -17.5t17.5 -7.5h75v-200h-75q-10 0 -17.5 -7.5t-7.5 -17.5v-50q0 -10 7.5 -17.5t17.5 -7.5h350q10 0 17.5 7.5t7.5 17.5v50q0 10 -7.5 17.5 t-17.5 7.5h-75v275q0 10 -7.5 17.5t-17.5 7.5z" /> +<glyph unicode="" d="M525 1200h150q10 0 17.5 -7.5t7.5 -17.5v-194q103 -27 178.5 -102.5t102.5 -178.5h194q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-194q-27 -103 -102.5 -178.5t-178.5 -102.5v-194q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5v194 q-103 27 -178.5 102.5t-102.5 178.5h-194q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5h194q27 103 102.5 178.5t178.5 102.5v194q0 10 7.5 17.5t17.5 7.5zM700 893v-168q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5v168q-68 -23 -119 -74 t-74 -119h168q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-168q23 -68 74 -119t119 -74v168q0 10 7.5 17.5t17.5 7.5h150q10 0 17.5 -7.5t7.5 -17.5v-168q68 23 119 74t74 119h-168q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5h168 q-23 68 -74 119t-119 74z" /> +<glyph unicode="" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM600 1027q-116 0 -214.5 -57t-155.5 -155.5t-57 -214.5t57 -214.5 t155.5 -155.5t214.5 -57t214.5 57t155.5 155.5t57 214.5t-57 214.5t-155.5 155.5t-214.5 57zM759 823l64 -64q7 -7 7 -17.5t-7 -17.5l-124 -124l124 -124q7 -7 7 -17.5t-7 -17.5l-64 -64q-7 -7 -17.5 -7t-17.5 7l-124 124l-124 -124q-7 -7 -17.5 -7t-17.5 7l-64 64 q-7 7 -7 17.5t7 17.5l124 124l-124 124q-7 7 -7 17.5t7 17.5l64 64q7 7 17.5 7t17.5 -7l124 -124l124 124q7 7 17.5 7t17.5 -7z" /> +<glyph unicode="" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM600 1027q-116 0 -214.5 -57t-155.5 -155.5t-57 -214.5t57 -214.5 t155.5 -155.5t214.5 -57t214.5 57t155.5 155.5t57 214.5t-57 214.5t-155.5 155.5t-214.5 57zM782 788l106 -106q7 -7 7 -17.5t-7 -17.5l-320 -321q-8 -7 -18 -7t-18 7l-202 203q-8 7 -8 17.5t8 17.5l106 106q7 8 17.5 8t17.5 -8l79 -79l197 197q7 7 17.5 7t17.5 -7z" /> +<glyph unicode="" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM600 1027q-116 0 -214.5 -57t-155.5 -155.5t-57 -214.5q0 -120 65 -225 l587 587q-105 65 -225 65zM965 819l-584 -584q104 -62 219 -62q116 0 214.5 57t155.5 155.5t57 214.5q0 115 -62 219z" /> +<glyph unicode="" d="M39 582l522 427q16 13 27.5 8t11.5 -26v-291h550q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-550v-291q0 -21 -11.5 -26t-27.5 8l-522 427q-16 13 -16 32t16 32z" /> +<glyph unicode="" d="M639 1009l522 -427q16 -13 16 -32t-16 -32l-522 -427q-16 -13 -27.5 -8t-11.5 26v291h-550q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5h550v291q0 21 11.5 26t27.5 -8z" /> +<glyph unicode="" d="M682 1161l427 -522q13 -16 8 -27.5t-26 -11.5h-291v-550q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v550h-291q-21 0 -26 11.5t8 27.5l427 522q13 16 32 16t32 -16z" /> +<glyph unicode="" d="M550 1200h200q21 0 35.5 -14.5t14.5 -35.5v-550h291q21 0 26 -11.5t-8 -27.5l-427 -522q-13 -16 -32 -16t-32 16l-427 522q-13 16 -8 27.5t26 11.5h291v550q0 21 14.5 35.5t35.5 14.5z" /> +<glyph unicode="" d="M639 1109l522 -427q16 -13 16 -32t-16 -32l-522 -427q-16 -13 -27.5 -8t-11.5 26v291q-94 -2 -182 -20t-170.5 -52t-147 -92.5t-100.5 -135.5q5 105 27 193.5t67.5 167t113 135t167 91.5t225.5 42v262q0 21 11.5 26t27.5 -8z" /> +<glyph unicode="" d="M850 1200h300q21 0 35.5 -14.5t14.5 -35.5v-300q0 -21 -10.5 -25t-24.5 10l-94 94l-249 -249q-8 -7 -18 -7t-18 7l-106 106q-7 8 -7 18t7 18l249 249l-94 94q-14 14 -10 24.5t25 10.5zM350 0h-300q-21 0 -35.5 14.5t-14.5 35.5v300q0 21 10.5 25t24.5 -10l94 -94l249 249 q8 7 18 7t18 -7l106 -106q7 -8 7 -18t-7 -18l-249 -249l94 -94q14 -14 10 -24.5t-25 -10.5z" /> +<glyph unicode="" d="M1014 1120l106 -106q7 -8 7 -18t-7 -18l-249 -249l94 -94q14 -14 10 -24.5t-25 -10.5h-300q-21 0 -35.5 14.5t-14.5 35.5v300q0 21 10.5 25t24.5 -10l94 -94l249 249q8 7 18 7t18 -7zM250 600h300q21 0 35.5 -14.5t14.5 -35.5v-300q0 -21 -10.5 -25t-24.5 10l-94 94 l-249 -249q-8 -7 -18 -7t-18 7l-106 106q-7 8 -7 18t7 18l249 249l-94 94q-14 14 -10 24.5t25 10.5z" /> +<glyph unicode="" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM704 900h-208q-20 0 -32 -14.5t-8 -34.5l58 -302q4 -20 21.5 -34.5 t37.5 -14.5h54q20 0 37.5 14.5t21.5 34.5l58 302q4 20 -8 34.5t-32 14.5zM675 400h-150q-10 0 -17.5 -7.5t-7.5 -17.5v-150q0 -10 7.5 -17.5t17.5 -7.5h150q10 0 17.5 7.5t7.5 17.5v150q0 10 -7.5 17.5t-17.5 7.5z" /> +<glyph unicode="" d="M260 1200q9 0 19 -2t15 -4l5 -2q22 -10 44 -23l196 -118q21 -13 36 -24q29 -21 37 -12q11 13 49 35l196 118q22 13 45 23q17 7 38 7q23 0 47 -16.5t37 -33.5l13 -16q14 -21 18 -45l25 -123l8 -44q1 -9 8.5 -14.5t17.5 -5.5h61q10 0 17.5 -7.5t7.5 -17.5v-50 q0 -10 -7.5 -17.5t-17.5 -7.5h-50q-10 0 -17.5 -7.5t-7.5 -17.5v-175h-400v300h-200v-300h-400v175q0 10 -7.5 17.5t-17.5 7.5h-50q-10 0 -17.5 7.5t-7.5 17.5v50q0 10 7.5 17.5t17.5 7.5h61q11 0 18 3t7 8q0 4 9 52l25 128q5 25 19 45q2 3 5 7t13.5 15t21.5 19.5t26.5 15.5 t29.5 7zM915 1079l-166 -162q-7 -7 -5 -12t12 -5h219q10 0 15 7t2 17l-51 149q-3 10 -11 12t-15 -6zM463 917l-177 157q-8 7 -16 5t-11 -12l-51 -143q-3 -10 2 -17t15 -7h231q11 0 12.5 5t-5.5 12zM500 0h-375q-10 0 -17.5 7.5t-7.5 17.5v375h400v-400zM1100 400v-375 q0 -10 -7.5 -17.5t-17.5 -7.5h-375v400h400z" /> +<glyph unicode="" d="M1165 1190q8 3 21 -6.5t13 -17.5q-2 -178 -24.5 -323.5t-55.5 -245.5t-87 -174.5t-102.5 -118.5t-118 -68.5t-118.5 -33t-120 -4.5t-105 9.5t-90 16.5q-61 12 -78 11q-4 1 -12.5 0t-34 -14.5t-52.5 -40.5l-153 -153q-26 -24 -37 -14.5t-11 43.5q0 64 42 102q8 8 50.5 45 t66.5 58q19 17 35 47t13 61q-9 55 -10 102.5t7 111t37 130t78 129.5q39 51 80 88t89.5 63.5t94.5 45t113.5 36t129 31t157.5 37t182 47.5zM1116 1098q-8 9 -22.5 -3t-45.5 -50q-38 -47 -119 -103.5t-142 -89.5l-62 -33q-56 -30 -102 -57t-104 -68t-102.5 -80.5t-85.5 -91 t-64 -104.5q-24 -56 -31 -86t2 -32t31.5 17.5t55.5 59.5q25 30 94 75.5t125.5 77.5t147.5 81q70 37 118.5 69t102 79.5t99 111t86.5 148.5q22 50 24 60t-6 19z" /> +<glyph unicode="" d="M653 1231q-39 -67 -54.5 -131t-10.5 -114.5t24.5 -96.5t47.5 -80t63.5 -62.5t68.5 -46.5t65 -30q-4 7 -17.5 35t-18.5 39.5t-17 39.5t-17 43t-13 42t-9.5 44.5t-2 42t4 43t13.5 39t23 38.5q96 -42 165 -107.5t105 -138t52 -156t13 -159t-19 -149.5q-13 -55 -44 -106.5 t-68 -87t-78.5 -64.5t-72.5 -45t-53 -22q-72 -22 -127 -11q-31 6 -13 19q6 3 17 7q13 5 32.5 21t41 44t38.5 63.5t21.5 81.5t-6.5 94.5t-50 107t-104 115.5q10 -104 -0.5 -189t-37 -140.5t-65 -93t-84 -52t-93.5 -11t-95 24.5q-80 36 -131.5 114t-53.5 171q-2 23 0 49.5 t4.5 52.5t13.5 56t27.5 60t46 64.5t69.5 68.5q-8 -53 -5 -102.5t17.5 -90t34 -68.5t44.5 -39t49 -2q31 13 38.5 36t-4.5 55t-29 64.5t-36 75t-26 75.5q-15 85 2 161.5t53.5 128.5t85.5 92.5t93.5 61t81.5 25.5z" /> +<glyph unicode="" d="M600 1094q82 0 160.5 -22.5t140 -59t116.5 -82.5t94.5 -95t68 -95t42.5 -82.5t14 -57.5t-14 -57.5t-43 -82.5t-68.5 -95t-94.5 -95t-116.5 -82.5t-140 -59t-159.5 -22.5t-159.5 22.5t-140 59t-116.5 82.5t-94.5 95t-68.5 95t-43 82.5t-14 57.5t14 57.5t42.5 82.5t68 95 t94.5 95t116.5 82.5t140 59t160.5 22.5zM888 829q-15 15 -18 12t5 -22q25 -57 25 -119q0 -124 -88 -212t-212 -88t-212 88t-88 212q0 59 23 114q8 19 4.5 22t-17.5 -12q-70 -69 -160 -184q-13 -16 -15 -40.5t9 -42.5q22 -36 47 -71t70 -82t92.5 -81t113 -58.5t133.5 -24.5 t133.5 24t113 58.5t92.5 81.5t70 81.5t47 70.5q11 18 9 42.5t-14 41.5q-90 117 -163 189zM448 727l-35 -36q-15 -15 -19.5 -38.5t4.5 -41.5q37 -68 93 -116q16 -13 38.5 -11t36.5 17l35 34q14 15 12.5 33.5t-16.5 33.5q-44 44 -89 117q-11 18 -28 20t-32 -12z" /> +<glyph unicode="" d="M592 0h-148l31 120q-91 20 -175.5 68.5t-143.5 106.5t-103.5 119t-66.5 110t-22 76q0 21 14 57.5t42.5 82.5t68 95t94.5 95t116.5 82.5t140 59t160.5 22.5q61 0 126 -15l32 121h148zM944 770l47 181q108 -85 176.5 -192t68.5 -159q0 -26 -19.5 -71t-59.5 -102t-93 -112 t-129 -104.5t-158 -75.5l46 173q77 49 136 117t97 131q11 18 9 42.5t-14 41.5q-54 70 -107 130zM310 824q-70 -69 -160 -184q-13 -16 -15 -40.5t9 -42.5q18 -30 39 -60t57 -70.5t74 -73t90 -61t105 -41.5l41 154q-107 18 -178.5 101.5t-71.5 193.5q0 59 23 114q8 19 4.5 22 t-17.5 -12zM448 727l-35 -36q-15 -15 -19.5 -38.5t4.5 -41.5q37 -68 93 -116q16 -13 38.5 -11t36.5 17l12 11l22 86l-3 4q-44 44 -89 117q-11 18 -28 20t-32 -12z" /> +<glyph unicode="" d="M-90 100l642 1066q20 31 48 28.5t48 -35.5l642 -1056q21 -32 7.5 -67.5t-50.5 -35.5h-1294q-37 0 -50.5 34t7.5 66zM155 200h345v75q0 10 7.5 17.5t17.5 7.5h150q10 0 17.5 -7.5t7.5 -17.5v-75h345l-445 723zM496 700h208q20 0 32 -14.5t8 -34.5l-58 -252 q-4 -20 -21.5 -34.5t-37.5 -14.5h-54q-20 0 -37.5 14.5t-21.5 34.5l-58 252q-4 20 8 34.5t32 14.5z" /> +<glyph unicode="" d="M650 1200q62 0 106 -44t44 -106v-339l363 -325q15 -14 26 -38.5t11 -44.5v-41q0 -20 -12 -26.5t-29 5.5l-359 249v-263q100 -93 100 -113v-64q0 -21 -13 -29t-32 1l-205 128l-205 -128q-19 -9 -32 -1t-13 29v64q0 20 100 113v263l-359 -249q-17 -12 -29 -5.5t-12 26.5v41 q0 20 11 44.5t26 38.5l363 325v339q0 62 44 106t106 44z" /> +<glyph unicode="" d="M850 1200h100q21 0 35.5 -14.5t14.5 -35.5v-50h50q21 0 35.5 -14.5t14.5 -35.5v-150h-1100v150q0 21 14.5 35.5t35.5 14.5h50v50q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-50h500v50q0 21 14.5 35.5t35.5 14.5zM1100 800v-750q0 -21 -14.5 -35.5 t-35.5 -14.5h-1000q-21 0 -35.5 14.5t-14.5 35.5v750h1100zM100 600v-100h100v100h-100zM300 600v-100h100v100h-100zM500 600v-100h100v100h-100zM700 600v-100h100v100h-100zM900 600v-100h100v100h-100zM100 400v-100h100v100h-100zM300 400v-100h100v100h-100zM500 400 v-100h100v100h-100zM700 400v-100h100v100h-100zM900 400v-100h100v100h-100zM100 200v-100h100v100h-100zM300 200v-100h100v100h-100zM500 200v-100h100v100h-100zM700 200v-100h100v100h-100zM900 200v-100h100v100h-100z" /> +<glyph unicode="" d="M1135 1165l249 -230q15 -14 15 -35t-15 -35l-249 -230q-14 -14 -24.5 -10t-10.5 25v150h-159l-600 -600h-291q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5h209l600 600h241v150q0 21 10.5 25t24.5 -10zM522 819l-141 -141l-122 122h-209q-21 0 -35.5 14.5 t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5h291zM1135 565l249 -230q15 -14 15 -35t-15 -35l-249 -230q-14 -14 -24.5 -10t-10.5 25v150h-241l-181 181l141 141l122 -122h159v150q0 21 10.5 25t24.5 -10z" /> +<glyph unicode="" d="M100 1100h1000q41 0 70.5 -29.5t29.5 -70.5v-600q0 -41 -29.5 -70.5t-70.5 -29.5h-596l-304 -300v300h-100q-41 0 -70.5 29.5t-29.5 70.5v600q0 41 29.5 70.5t70.5 29.5z" /> +<glyph unicode="" d="M150 1200h200q21 0 35.5 -14.5t14.5 -35.5v-250h-300v250q0 21 14.5 35.5t35.5 14.5zM850 1200h200q21 0 35.5 -14.5t14.5 -35.5v-250h-300v250q0 21 14.5 35.5t35.5 14.5zM1100 800v-300q0 -41 -3 -77.5t-15 -89.5t-32 -96t-58 -89t-89 -77t-129 -51t-174 -20t-174 20 t-129 51t-89 77t-58 89t-32 96t-15 89.5t-3 77.5v300h300v-250v-27v-42.5t1.5 -41t5 -38t10 -35t16.5 -30t25.5 -24.5t35 -19t46.5 -12t60 -4t60 4.5t46.5 12.5t35 19.5t25 25.5t17 30.5t10 35t5 38t2 40.5t-0.5 42v25v250h300z" /> +<glyph unicode="" d="M1100 411l-198 -199l-353 353l-353 -353l-197 199l551 551z" /> +<glyph unicode="" d="M1101 789l-550 -551l-551 551l198 199l353 -353l353 353z" /> +<glyph unicode="" d="M404 1000h746q21 0 35.5 -14.5t14.5 -35.5v-551h150q21 0 25 -10.5t-10 -24.5l-230 -249q-14 -15 -35 -15t-35 15l-230 249q-14 14 -10 24.5t25 10.5h150v401h-381zM135 984l230 -249q14 -14 10 -24.5t-25 -10.5h-150v-400h385l215 -200h-750q-21 0 -35.5 14.5 t-14.5 35.5v550h-150q-21 0 -25 10.5t10 24.5l230 249q14 15 35 15t35 -15z" /> +<glyph unicode="" d="M56 1200h94q17 0 31 -11t18 -27l38 -162h896q24 0 39 -18.5t10 -42.5l-100 -475q-5 -21 -27 -42.5t-55 -21.5h-633l48 -200h535q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-50v-50q0 -21 -14.5 -35.5t-35.5 -14.5t-35.5 14.5t-14.5 35.5v50h-300v-50 q0 -21 -14.5 -35.5t-35.5 -14.5t-35.5 14.5t-14.5 35.5v50h-31q-18 0 -32.5 10t-20.5 19l-5 10l-201 961h-54q-20 0 -35 14.5t-15 35.5t15 35.5t35 14.5z" /> +<glyph unicode="" d="M1200 1000v-100h-1200v100h200q0 41 29.5 70.5t70.5 29.5h300q41 0 70.5 -29.5t29.5 -70.5h500zM0 800h1200v-800h-1200v800z" /> +<glyph unicode="" d="M200 800l-200 -400v600h200q0 41 29.5 70.5t70.5 29.5h300q42 0 71 -29.5t29 -70.5h500v-200h-1000zM1500 700l-300 -700h-1200l300 700h1200z" /> +<glyph unicode="" d="M635 1184l230 -249q14 -14 10 -24.5t-25 -10.5h-150v-601h150q21 0 25 -10.5t-10 -24.5l-230 -249q-14 -15 -35 -15t-35 15l-230 249q-14 14 -10 24.5t25 10.5h150v601h-150q-21 0 -25 10.5t10 24.5l230 249q14 15 35 15t35 -15z" /> +<glyph unicode="" d="M936 864l249 -229q14 -15 14 -35.5t-14 -35.5l-249 -229q-15 -15 -25.5 -10.5t-10.5 24.5v151h-600v-151q0 -20 -10.5 -24.5t-25.5 10.5l-249 229q-14 15 -14 35.5t14 35.5l249 229q15 15 25.5 10.5t10.5 -25.5v-149h600v149q0 21 10.5 25.5t25.5 -10.5z" /> +<glyph unicode="" d="M1169 400l-172 732q-5 23 -23 45.5t-38 22.5h-672q-20 0 -38 -20t-23 -41l-172 -739h1138zM1100 300h-1000q-41 0 -70.5 -29.5t-29.5 -70.5v-100q0 -41 29.5 -70.5t70.5 -29.5h1000q41 0 70.5 29.5t29.5 70.5v100q0 41 -29.5 70.5t-70.5 29.5zM800 100v100h100v-100h-100 zM1000 100v100h100v-100h-100z" /> +<glyph unicode="" d="M1150 1100q21 0 35.5 -14.5t14.5 -35.5v-850q0 -21 -14.5 -35.5t-35.5 -14.5t-35.5 14.5t-14.5 35.5v850q0 21 14.5 35.5t35.5 14.5zM1000 200l-675 200h-38l47 -276q3 -16 -5.5 -20t-29.5 -4h-7h-84q-20 0 -34.5 14t-18.5 35q-55 337 -55 351v250v6q0 16 1 23.5t6.5 14 t17.5 6.5h200l675 250v-850zM0 750v-250q-4 0 -11 0.5t-24 6t-30 15t-24 30t-11 48.5v50q0 26 10.5 46t25 30t29 16t25.5 7z" /> +<glyph unicode="" d="M553 1200h94q20 0 29 -10.5t3 -29.5l-18 -37q83 -19 144 -82.5t76 -140.5l63 -327l118 -173h17q19 0 33 -14.5t14 -35t-13 -40.5t-31 -27q-8 -4 -23 -9.5t-65 -19.5t-103 -25t-132.5 -20t-158.5 -9q-57 0 -115 5t-104 12t-88.5 15.5t-73.5 17.5t-54.5 16t-35.5 12l-11 4 q-18 8 -31 28t-13 40.5t14 35t33 14.5h17l118 173l63 327q15 77 76 140t144 83l-18 32q-6 19 3.5 32t28.5 13zM498 110q50 -6 102 -6q53 0 102 6q-12 -49 -39.5 -79.5t-62.5 -30.5t-63 30.5t-39 79.5z" /> +<glyph unicode="" d="M800 946l224 78l-78 -224l234 -45l-180 -155l180 -155l-234 -45l78 -224l-224 78l-45 -234l-155 180l-155 -180l-45 234l-224 -78l78 224l-234 45l180 155l-180 155l234 45l-78 224l224 -78l45 234l155 -180l155 180z" /> +<glyph unicode="" d="M650 1200h50q40 0 70 -40.5t30 -84.5v-150l-28 -125h328q40 0 70 -40.5t30 -84.5v-100q0 -45 -29 -74l-238 -344q-16 -24 -38 -40.5t-45 -16.5h-250q-7 0 -42 25t-66 50l-31 25h-61q-45 0 -72.5 18t-27.5 57v400q0 36 20 63l145 196l96 198q13 28 37.5 48t51.5 20z M650 1100l-100 -212l-150 -213v-375h100l136 -100h214l250 375v125h-450l50 225v175h-50zM50 800h100q21 0 35.5 -14.5t14.5 -35.5v-500q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v500q0 21 14.5 35.5t35.5 14.5z" /> +<glyph unicode="" d="M600 1100h250q23 0 45 -16.5t38 -40.5l238 -344q29 -29 29 -74v-100q0 -44 -30 -84.5t-70 -40.5h-328q28 -118 28 -125v-150q0 -44 -30 -84.5t-70 -40.5h-50q-27 0 -51.5 20t-37.5 48l-96 198l-145 196q-20 27 -20 63v400q0 39 27.5 57t72.5 18h61q124 100 139 100z M50 1000h100q21 0 35.5 -14.5t14.5 -35.5v-500q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v500q0 21 14.5 35.5t35.5 14.5zM636 1000l-136 -100h-100v-375l150 -213l100 -212h50v175l-50 225h450v125l-250 375h-214z" /> +<glyph unicode="" d="M356 873l363 230q31 16 53 -6l110 -112q13 -13 13.5 -32t-11.5 -34l-84 -121h302q84 0 138 -38t54 -110t-55 -111t-139 -39h-106l-131 -339q-6 -21 -19.5 -41t-28.5 -20h-342q-7 0 -90 81t-83 94v525q0 17 14 35.5t28 28.5zM400 792v-503l100 -89h293l131 339 q6 21 19.5 41t28.5 20h203q21 0 30.5 25t0.5 50t-31 25h-456h-7h-6h-5.5t-6 0.5t-5 1.5t-5 2t-4 2.5t-4 4t-2.5 4.5q-12 25 5 47l146 183l-86 83zM50 800h100q21 0 35.5 -14.5t14.5 -35.5v-500q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v500 q0 21 14.5 35.5t35.5 14.5z" /> +<glyph unicode="" d="M475 1103l366 -230q2 -1 6 -3.5t14 -10.5t18 -16.5t14.5 -20t6.5 -22.5v-525q0 -13 -86 -94t-93 -81h-342q-15 0 -28.5 20t-19.5 41l-131 339h-106q-85 0 -139.5 39t-54.5 111t54 110t138 38h302l-85 121q-11 15 -10.5 34t13.5 32l110 112q22 22 53 6zM370 945l146 -183 q17 -22 5 -47q-2 -2 -3.5 -4.5t-4 -4t-4 -2.5t-5 -2t-5 -1.5t-6 -0.5h-6h-6.5h-6h-475v-100h221q15 0 29 -20t20 -41l130 -339h294l106 89v503l-342 236zM1050 800h100q21 0 35.5 -14.5t14.5 -35.5v-500q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5 v500q0 21 14.5 35.5t35.5 14.5z" /> +<glyph unicode="" d="M550 1294q72 0 111 -55t39 -139v-106l339 -131q21 -6 41 -19.5t20 -28.5v-342q0 -7 -81 -90t-94 -83h-525q-17 0 -35.5 14t-28.5 28l-9 14l-230 363q-16 31 6 53l112 110q13 13 32 13.5t34 -11.5l121 -84v302q0 84 38 138t110 54zM600 972v203q0 21 -25 30.5t-50 0.5 t-25 -31v-456v-7v-6v-5.5t-0.5 -6t-1.5 -5t-2 -5t-2.5 -4t-4 -4t-4.5 -2.5q-25 -12 -47 5l-183 146l-83 -86l236 -339h503l89 100v293l-339 131q-21 6 -41 19.5t-20 28.5zM450 200h500q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-500 q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5z" /> +<glyph unicode="" d="M350 1100h500q21 0 35.5 14.5t14.5 35.5v100q0 21 -14.5 35.5t-35.5 14.5h-500q-21 0 -35.5 -14.5t-14.5 -35.5v-100q0 -21 14.5 -35.5t35.5 -14.5zM600 306v-106q0 -84 -39 -139t-111 -55t-110 54t-38 138v302l-121 -84q-15 -12 -34 -11.5t-32 13.5l-112 110 q-22 22 -6 53l230 363q1 2 3.5 6t10.5 13.5t16.5 17t20 13.5t22.5 6h525q13 0 94 -83t81 -90v-342q0 -15 -20 -28.5t-41 -19.5zM308 900l-236 -339l83 -86l183 146q22 17 47 5q2 -1 4.5 -2.5t4 -4t2.5 -4t2 -5t1.5 -5t0.5 -6v-5.5v-6v-7v-456q0 -22 25 -31t50 0.5t25 30.5 v203q0 15 20 28.5t41 19.5l339 131v293l-89 100h-503z" /> +<glyph unicode="" d="M600 1178q118 0 225 -45.5t184.5 -123t123 -184.5t45.5 -225t-45.5 -225t-123 -184.5t-184.5 -123t-225 -45.5t-225 45.5t-184.5 123t-123 184.5t-45.5 225t45.5 225t123 184.5t184.5 123t225 45.5zM914 632l-275 223q-16 13 -27.5 8t-11.5 -26v-137h-275 q-10 0 -17.5 -7.5t-7.5 -17.5v-150q0 -10 7.5 -17.5t17.5 -7.5h275v-137q0 -21 11.5 -26t27.5 8l275 223q16 13 16 32t-16 32z" /> +<glyph unicode="" d="M600 1178q118 0 225 -45.5t184.5 -123t123 -184.5t45.5 -225t-45.5 -225t-123 -184.5t-184.5 -123t-225 -45.5t-225 45.5t-184.5 123t-123 184.5t-45.5 225t45.5 225t123 184.5t184.5 123t225 45.5zM561 855l-275 -223q-16 -13 -16 -32t16 -32l275 -223q16 -13 27.5 -8 t11.5 26v137h275q10 0 17.5 7.5t7.5 17.5v150q0 10 -7.5 17.5t-17.5 7.5h-275v137q0 21 -11.5 26t-27.5 -8z" /> +<glyph unicode="" d="M600 1178q118 0 225 -45.5t184.5 -123t123 -184.5t45.5 -225t-45.5 -225t-123 -184.5t-184.5 -123t-225 -45.5t-225 45.5t-184.5 123t-123 184.5t-45.5 225t45.5 225t123 184.5t184.5 123t225 45.5zM855 639l-223 275q-13 16 -32 16t-32 -16l-223 -275q-13 -16 -8 -27.5 t26 -11.5h137v-275q0 -10 7.5 -17.5t17.5 -7.5h150q10 0 17.5 7.5t7.5 17.5v275h137q21 0 26 11.5t-8 27.5z" /> +<glyph unicode="" d="M600 1178q118 0 225 -45.5t184.5 -123t123 -184.5t45.5 -225t-45.5 -225t-123 -184.5t-184.5 -123t-225 -45.5t-225 45.5t-184.5 123t-123 184.5t-45.5 225t45.5 225t123 184.5t184.5 123t225 45.5zM675 900h-150q-10 0 -17.5 -7.5t-7.5 -17.5v-275h-137q-21 0 -26 -11.5 t8 -27.5l223 -275q13 -16 32 -16t32 16l223 275q13 16 8 27.5t-26 11.5h-137v275q0 10 -7.5 17.5t-17.5 7.5z" /> +<glyph unicode="" d="M600 1176q116 0 222.5 -46t184 -123.5t123.5 -184t46 -222.5t-46 -222.5t-123.5 -184t-184 -123.5t-222.5 -46t-222.5 46t-184 123.5t-123.5 184t-46 222.5t46 222.5t123.5 184t184 123.5t222.5 46zM627 1101q-15 -12 -36.5 -20.5t-35.5 -12t-43 -8t-39 -6.5 q-15 -3 -45.5 0t-45.5 -2q-20 -7 -51.5 -26.5t-34.5 -34.5q-3 -11 6.5 -22.5t8.5 -18.5q-3 -34 -27.5 -91t-29.5 -79q-9 -34 5 -93t8 -87q0 -9 17 -44.5t16 -59.5q12 0 23 -5t23.5 -15t19.5 -14q16 -8 33 -15t40.5 -15t34.5 -12q21 -9 52.5 -32t60 -38t57.5 -11 q7 -15 -3 -34t-22.5 -40t-9.5 -38q13 -21 23 -34.5t27.5 -27.5t36.5 -18q0 -7 -3.5 -16t-3.5 -14t5 -17q104 -2 221 112q30 29 46.5 47t34.5 49t21 63q-13 8 -37 8.5t-36 7.5q-15 7 -49.5 15t-51.5 19q-18 0 -41 -0.5t-43 -1.5t-42 -6.5t-38 -16.5q-51 -35 -66 -12 q-4 1 -3.5 25.5t0.5 25.5q-6 13 -26.5 17.5t-24.5 6.5q1 15 -0.5 30.5t-7 28t-18.5 11.5t-31 -21q-23 -25 -42 4q-19 28 -8 58q6 16 22 22q6 -1 26 -1.5t33.5 -4t19.5 -13.5q7 -12 18 -24t21.5 -20.5t20 -15t15.5 -10.5l5 -3q2 12 7.5 30.5t8 34.5t-0.5 32q-3 18 3.5 29 t18 22.5t15.5 24.5q6 14 10.5 35t8 31t15.5 22.5t34 22.5q-6 18 10 36q8 0 24 -1.5t24.5 -1.5t20 4.5t20.5 15.5q-10 23 -31 42.5t-37.5 29.5t-49 27t-43.5 23q0 1 2 8t3 11.5t1.5 10.5t-1 9.5t-4.5 4.5q31 -13 58.5 -14.5t38.5 2.5l12 5q5 28 -9.5 46t-36.5 24t-50 15 t-41 20q-18 -4 -37 0zM613 994q0 -17 8 -42t17 -45t9 -23q-8 1 -39.5 5.5t-52.5 10t-37 16.5q3 11 16 29.5t16 25.5q10 -10 19 -10t14 6t13.5 14.5t16.5 12.5z" /> +<glyph unicode="" d="M756 1157q164 92 306 -9l-259 -138l145 -232l251 126q6 -89 -34 -156.5t-117 -110.5q-60 -34 -127 -39.5t-126 16.5l-596 -596q-15 -16 -36.5 -16t-36.5 16l-111 110q-15 15 -15 36.5t15 37.5l600 599q-34 101 5.5 201.5t135.5 154.5z" /> +<glyph unicode="" horiz-adv-x="1220" d="M100 1196h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5v100q0 41 29.5 70.5t70.5 29.5zM1100 1096h-200v-100h200v100zM100 796h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000 q-41 0 -70.5 29.5t-29.5 70.5v100q0 41 29.5 70.5t70.5 29.5zM1100 696h-500v-100h500v100zM100 396h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5v100q0 41 29.5 70.5t70.5 29.5zM1100 296h-300v-100h300v100z " /> +<glyph unicode="" d="M150 1200h900q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-900q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5zM700 500v-300l-200 -200v500l-350 500h900z" /> +<glyph unicode="" d="M500 1200h200q41 0 70.5 -29.5t29.5 -70.5v-100h300q41 0 70.5 -29.5t29.5 -70.5v-400h-500v100h-200v-100h-500v400q0 41 29.5 70.5t70.5 29.5h300v100q0 41 29.5 70.5t70.5 29.5zM500 1100v-100h200v100h-200zM1200 400v-200q0 -41 -29.5 -70.5t-70.5 -29.5h-1000 q-41 0 -70.5 29.5t-29.5 70.5v200h1200z" /> +<glyph unicode="" d="M50 1200h300q21 0 25 -10.5t-10 -24.5l-94 -94l199 -199q7 -8 7 -18t-7 -18l-106 -106q-8 -7 -18 -7t-18 7l-199 199l-94 -94q-14 -14 -24.5 -10t-10.5 25v300q0 21 14.5 35.5t35.5 14.5zM850 1200h300q21 0 35.5 -14.5t14.5 -35.5v-300q0 -21 -10.5 -25t-24.5 10l-94 94 l-199 -199q-8 -7 -18 -7t-18 7l-106 106q-7 8 -7 18t7 18l199 199l-94 94q-14 14 -10 24.5t25 10.5zM364 470l106 -106q7 -8 7 -18t-7 -18l-199 -199l94 -94q14 -14 10 -24.5t-25 -10.5h-300q-21 0 -35.5 14.5t-14.5 35.5v300q0 21 10.5 25t24.5 -10l94 -94l199 199 q8 7 18 7t18 -7zM1071 271l94 94q14 14 24.5 10t10.5 -25v-300q0 -21 -14.5 -35.5t-35.5 -14.5h-300q-21 0 -25 10.5t10 24.5l94 94l-199 199q-7 8 -7 18t7 18l106 106q8 7 18 7t18 -7z" /> +<glyph unicode="" d="M596 1192q121 0 231.5 -47.5t190 -127t127 -190t47.5 -231.5t-47.5 -231.5t-127 -190.5t-190 -127t-231.5 -47t-231.5 47t-190.5 127t-127 190.5t-47 231.5t47 231.5t127 190t190.5 127t231.5 47.5zM596 1010q-112 0 -207.5 -55.5t-151 -151t-55.5 -207.5t55.5 -207.5 t151 -151t207.5 -55.5t207.5 55.5t151 151t55.5 207.5t-55.5 207.5t-151 151t-207.5 55.5zM454.5 905q22.5 0 38.5 -16t16 -38.5t-16 -39t-38.5 -16.5t-38.5 16.5t-16 39t16 38.5t38.5 16zM754.5 905q22.5 0 38.5 -16t16 -38.5t-16 -39t-38 -16.5q-14 0 -29 10l-55 -145 q17 -23 17 -51q0 -36 -25.5 -61.5t-61.5 -25.5t-61.5 25.5t-25.5 61.5q0 32 20.5 56.5t51.5 29.5l122 126l1 1q-9 14 -9 28q0 23 16 39t38.5 16zM345.5 709q22.5 0 38.5 -16t16 -38.5t-16 -38.5t-38.5 -16t-38.5 16t-16 38.5t16 38.5t38.5 16zM854.5 709q22.5 0 38.5 -16 t16 -38.5t-16 -38.5t-38.5 -16t-38.5 16t-16 38.5t16 38.5t38.5 16z" /> +<glyph unicode="" d="M546 173l469 470q91 91 99 192q7 98 -52 175.5t-154 94.5q-22 4 -47 4q-34 0 -66.5 -10t-56.5 -23t-55.5 -38t-48 -41.5t-48.5 -47.5q-376 -375 -391 -390q-30 -27 -45 -41.5t-37.5 -41t-32 -46.5t-16 -47.5t-1.5 -56.5q9 -62 53.5 -95t99.5 -33q74 0 125 51l548 548 q36 36 20 75q-7 16 -21.5 26t-32.5 10q-26 0 -50 -23q-13 -12 -39 -38l-341 -338q-15 -15 -35.5 -15.5t-34.5 13.5t-14 34.5t14 34.5q327 333 361 367q35 35 67.5 51.5t78.5 16.5q14 0 29 -1q44 -8 74.5 -35.5t43.5 -68.5q14 -47 2 -96.5t-47 -84.5q-12 -11 -32 -32 t-79.5 -81t-114.5 -115t-124.5 -123.5t-123 -119.5t-96.5 -89t-57 -45q-56 -27 -120 -27q-70 0 -129 32t-93 89q-48 78 -35 173t81 163l511 511q71 72 111 96q91 55 198 55q80 0 152 -33q78 -36 129.5 -103t66.5 -154q17 -93 -11 -183.5t-94 -156.5l-482 -476 q-15 -15 -36 -16t-37 14t-17.5 34t14.5 35z" /> +<glyph unicode="" d="M649 949q48 68 109.5 104t121.5 38.5t118.5 -20t102.5 -64t71 -100.5t27 -123q0 -57 -33.5 -117.5t-94 -124.5t-126.5 -127.5t-150 -152.5t-146 -174q-62 85 -145.5 174t-150 152.5t-126.5 127.5t-93.5 124.5t-33.5 117.5q0 64 28 123t73 100.5t104 64t119 20 t120.5 -38.5t104.5 -104zM896 972q-33 0 -64.5 -19t-56.5 -46t-47.5 -53.5t-43.5 -45.5t-37.5 -19t-36 19t-40 45.5t-43 53.5t-54 46t-65.5 19q-67 0 -122.5 -55.5t-55.5 -132.5q0 -23 13.5 -51t46 -65t57.5 -63t76 -75l22 -22q15 -14 44 -44t50.5 -51t46 -44t41 -35t23 -12 t23.5 12t42.5 36t46 44t52.5 52t44 43q4 4 12 13q43 41 63.5 62t52 55t46 55t26 46t11.5 44q0 79 -53 133.5t-120 54.5z" /> +<glyph unicode="" d="M776.5 1214q93.5 0 159.5 -66l141 -141q66 -66 66 -160q0 -42 -28 -95.5t-62 -87.5l-29 -29q-31 53 -77 99l-18 18l95 95l-247 248l-389 -389l212 -212l-105 -106l-19 18l-141 141q-66 66 -66 159t66 159l283 283q65 66 158.5 66zM600 706l105 105q10 -8 19 -17l141 -141 q66 -66 66 -159t-66 -159l-283 -283q-66 -66 -159 -66t-159 66l-141 141q-66 66 -66 159.5t66 159.5l55 55q29 -55 75 -102l18 -17l-95 -95l247 -248l389 389z" /> +<glyph unicode="" d="M603 1200q85 0 162 -15t127 -38t79 -48t29 -46v-953q0 -41 -29.5 -70.5t-70.5 -29.5h-600q-41 0 -70.5 29.5t-29.5 70.5v953q0 21 30 46.5t81 48t129 37.5t163 15zM300 1000v-700h600v700h-600zM600 254q-43 0 -73.5 -30.5t-30.5 -73.5t30.5 -73.5t73.5 -30.5t73.5 30.5 t30.5 73.5t-30.5 73.5t-73.5 30.5z" /> +<glyph unicode="" d="M902 1185l283 -282q15 -15 15 -36t-14.5 -35.5t-35.5 -14.5t-35 15l-36 35l-279 -267v-300l-212 210l-308 -307l-280 -203l203 280l307 308l-210 212h300l267 279l-35 36q-15 14 -15 35t14.5 35.5t35.5 14.5t35 -15z" /> +<glyph unicode="" d="M700 1248v-78q38 -5 72.5 -14.5t75.5 -31.5t71 -53.5t52 -84t24 -118.5h-159q-4 36 -10.5 59t-21 45t-40 35.5t-64.5 20.5v-307l64 -13q34 -7 64 -16.5t70 -32t67.5 -52.5t47.5 -80t20 -112q0 -139 -89 -224t-244 -97v-77h-100v79q-150 16 -237 103q-40 40 -52.5 93.5 t-15.5 139.5h139q5 -77 48.5 -126t117.5 -65v335l-27 8q-46 14 -79 26.5t-72 36t-63 52t-40 72.5t-16 98q0 70 25 126t67.5 92t94.5 57t110 27v77h100zM600 754v274q-29 -4 -50 -11t-42 -21.5t-31.5 -41.5t-10.5 -65q0 -29 7 -50.5t16.5 -34t28.5 -22.5t31.5 -14t37.5 -10 q9 -3 13 -4zM700 547v-310q22 2 42.5 6.5t45 15.5t41.5 27t29 42t12 59.5t-12.5 59.5t-38 44.5t-53 31t-66.5 24.5z" /> +<glyph unicode="" d="M561 1197q84 0 160.5 -40t123.5 -109.5t47 -147.5h-153q0 40 -19.5 71.5t-49.5 48.5t-59.5 26t-55.5 9q-37 0 -79 -14.5t-62 -35.5q-41 -44 -41 -101q0 -26 13.5 -63t26.5 -61t37 -66q6 -9 9 -14h241v-100h-197q8 -50 -2.5 -115t-31.5 -95q-45 -62 -99 -112 q34 10 83 17.5t71 7.5q32 1 102 -16t104 -17q83 0 136 30l50 -147q-31 -19 -58 -30.5t-55 -15.5t-42 -4.5t-46 -0.5q-23 0 -76 17t-111 32.5t-96 11.5q-39 -3 -82 -16t-67 -25l-23 -11l-55 145q4 3 16 11t15.5 10.5t13 9t15.5 12t14.5 14t17.5 18.5q48 55 54 126.5 t-30 142.5h-221v100h166q-23 47 -44 104q-7 20 -12 41.5t-6 55.5t6 66.5t29.5 70.5t58.5 71q97 88 263 88z" /> +<glyph unicode="" d="M400 300h150q21 0 25 -11t-10 -25l-230 -250q-14 -15 -35 -15t-35 15l-230 250q-14 14 -10 25t25 11h150v900h200v-900zM935 1184l230 -249q14 -14 10 -24.5t-25 -10.5h-150v-900h-200v900h-150q-21 0 -25 10.5t10 24.5l230 249q14 15 35 15t35 -15z" /> +<glyph unicode="" d="M1000 700h-100v100h-100v-100h-100v500h300v-500zM400 300h150q21 0 25 -11t-10 -25l-230 -250q-14 -15 -35 -15t-35 15l-230 250q-14 14 -10 25t25 11h150v900h200v-900zM801 1100v-200h100v200h-100zM1000 350l-200 -250h200v-100h-300v150l200 250h-200v100h300v-150z " /> +<glyph unicode="" d="M400 300h150q21 0 25 -11t-10 -25l-230 -250q-14 -15 -35 -15t-35 15l-230 250q-14 14 -10 25t25 11h150v900h200v-900zM1000 1050l-200 -250h200v-100h-300v150l200 250h-200v100h300v-150zM1000 0h-100v100h-100v-100h-100v500h300v-500zM801 400v-200h100v200h-100z " /> +<glyph unicode="" d="M400 300h150q21 0 25 -11t-10 -25l-230 -250q-14 -15 -35 -15t-35 15l-230 250q-14 14 -10 25t25 11h150v900h200v-900zM1000 700h-100v400h-100v100h200v-500zM1100 0h-100v100h-200v400h300v-500zM901 400v-200h100v200h-100z" /> +<glyph unicode="" d="M400 300h150q21 0 25 -11t-10 -25l-230 -250q-14 -15 -35 -15t-35 15l-230 250q-14 14 -10 25t25 11h150v900h200v-900zM1100 700h-100v100h-200v400h300v-500zM901 1100v-200h100v200h-100zM1000 0h-100v400h-100v100h200v-500z" /> +<glyph unicode="" d="M400 300h150q21 0 25 -11t-10 -25l-230 -250q-14 -15 -35 -15t-35 15l-230 250q-14 14 -10 25t25 11h150v900h200v-900zM900 1000h-200v200h200v-200zM1000 700h-300v200h300v-200zM1100 400h-400v200h400v-200zM1200 100h-500v200h500v-200z" /> +<glyph unicode="" d="M400 300h150q21 0 25 -11t-10 -25l-230 -250q-14 -15 -35 -15t-35 15l-230 250q-14 14 -10 25t25 11h150v900h200v-900zM1200 1000h-500v200h500v-200zM1100 700h-400v200h400v-200zM1000 400h-300v200h300v-200zM900 100h-200v200h200v-200z" /> +<glyph unicode="" d="M350 1100h400q162 0 256 -93.5t94 -256.5v-400q0 -165 -93.5 -257.5t-256.5 -92.5h-400q-165 0 -257.5 92.5t-92.5 257.5v400q0 165 92.5 257.5t257.5 92.5zM800 900h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5 v500q0 41 -29.5 70.5t-70.5 29.5z" /> +<glyph unicode="" d="M350 1100h400q165 0 257.5 -92.5t92.5 -257.5v-400q0 -165 -92.5 -257.5t-257.5 -92.5h-400q-163 0 -256.5 92.5t-93.5 257.5v400q0 163 94 256.5t256 93.5zM800 900h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5 v500q0 41 -29.5 70.5t-70.5 29.5zM440 770l253 -190q17 -12 17 -30t-17 -30l-253 -190q-16 -12 -28 -6.5t-12 26.5v400q0 21 12 26.5t28 -6.5z" /> +<glyph unicode="" d="M350 1100h400q163 0 256.5 -94t93.5 -256v-400q0 -165 -92.5 -257.5t-257.5 -92.5h-400q-165 0 -257.5 92.5t-92.5 257.5v400q0 163 92.5 256.5t257.5 93.5zM800 900h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5 v500q0 41 -29.5 70.5t-70.5 29.5zM350 700h400q21 0 26.5 -12t-6.5 -28l-190 -253q-12 -17 -30 -17t-30 17l-190 253q-12 16 -6.5 28t26.5 12z" /> +<glyph unicode="" d="M350 1100h400q165 0 257.5 -92.5t92.5 -257.5v-400q0 -163 -92.5 -256.5t-257.5 -93.5h-400q-163 0 -256.5 94t-93.5 256v400q0 165 92.5 257.5t257.5 92.5zM800 900h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5 v500q0 41 -29.5 70.5t-70.5 29.5zM580 693l190 -253q12 -16 6.5 -28t-26.5 -12h-400q-21 0 -26.5 12t6.5 28l190 253q12 17 30 17t30 -17z" /> +<glyph unicode="" d="M550 1100h400q165 0 257.5 -92.5t92.5 -257.5v-400q0 -165 -92.5 -257.5t-257.5 -92.5h-400q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5h450q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5h-450q-21 0 -35.5 14.5t-14.5 35.5v100 q0 21 14.5 35.5t35.5 14.5zM338 867l324 -284q16 -14 16 -33t-16 -33l-324 -284q-16 -14 -27 -9t-11 26v150h-250q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5h250v150q0 21 11 26t27 -9z" /> +<glyph unicode="" d="M793 1182l9 -9q8 -10 5 -27q-3 -11 -79 -225.5t-78 -221.5l300 1q24 0 32.5 -17.5t-5.5 -35.5q-1 0 -133.5 -155t-267 -312.5t-138.5 -162.5q-12 -15 -26 -15h-9l-9 8q-9 11 -4 32q2 9 42 123.5t79 224.5l39 110h-302q-23 0 -31 19q-10 21 6 41q75 86 209.5 237.5 t228 257t98.5 111.5q9 16 25 16h9z" /> +<glyph unicode="" d="M350 1100h400q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-450q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h450q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-165 0 -257.5 92.5t-92.5 257.5v400 q0 165 92.5 257.5t257.5 92.5zM938 867l324 -284q16 -14 16 -33t-16 -33l-324 -284q-16 -14 -27 -9t-11 26v150h-250q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5h250v150q0 21 11 26t27 -9z" /> +<glyph unicode="" d="M750 1200h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -10.5 -25t-24.5 10l-109 109l-312 -312q-15 -15 -35.5 -15t-35.5 15l-141 141q-15 15 -15 35.5t15 35.5l312 312l-109 109q-14 14 -10 24.5t25 10.5zM456 900h-156q-41 0 -70.5 -29.5t-29.5 -70.5v-500 q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v148l200 200v-298q0 -165 -93.5 -257.5t-256.5 -92.5h-400q-165 0 -257.5 92.5t-92.5 257.5v400q0 165 92.5 257.5t257.5 92.5h300z" /> +<glyph unicode="" d="M600 1186q119 0 227.5 -46.5t187 -125t125 -187t46.5 -227.5t-46.5 -227.5t-125 -187t-187 -125t-227.5 -46.5t-227.5 46.5t-187 125t-125 187t-46.5 227.5t46.5 227.5t125 187t187 125t227.5 46.5zM600 1022q-115 0 -212 -56.5t-153.5 -153.5t-56.5 -212t56.5 -212 t153.5 -153.5t212 -56.5t212 56.5t153.5 153.5t56.5 212t-56.5 212t-153.5 153.5t-212 56.5zM600 794q80 0 137 -57t57 -137t-57 -137t-137 -57t-137 57t-57 137t57 137t137 57z" /> +<glyph unicode="" d="M450 1200h200q21 0 35.5 -14.5t14.5 -35.5v-350h245q20 0 25 -11t-9 -26l-383 -426q-14 -15 -33.5 -15t-32.5 15l-379 426q-13 15 -8.5 26t25.5 11h250v350q0 21 14.5 35.5t35.5 14.5zM50 300h1000q21 0 35.5 -14.5t14.5 -35.5v-250h-1100v250q0 21 14.5 35.5t35.5 14.5z M900 200v-50h100v50h-100z" /> +<glyph unicode="" d="M583 1182l378 -435q14 -15 9 -31t-26 -16h-244v-250q0 -20 -17 -35t-39 -15h-200q-20 0 -32 14.5t-12 35.5v250h-250q-20 0 -25.5 16.5t8.5 31.5l383 431q14 16 33.5 17t33.5 -14zM50 300h1000q21 0 35.5 -14.5t14.5 -35.5v-250h-1100v250q0 21 14.5 35.5t35.5 14.5z M900 200v-50h100v50h-100z" /> +<glyph unicode="" d="M396 723l369 369q7 7 17.5 7t17.5 -7l139 -139q7 -8 7 -18.5t-7 -17.5l-525 -525q-7 -8 -17.5 -8t-17.5 8l-292 291q-7 8 -7 18t7 18l139 139q8 7 18.5 7t17.5 -7zM50 300h1000q21 0 35.5 -14.5t14.5 -35.5v-250h-1100v250q0 21 14.5 35.5t35.5 14.5zM900 200v-50h100v50 h-100z" /> +<glyph unicode="" d="M135 1023l142 142q14 14 35 14t35 -14l77 -77l-212 -212l-77 76q-14 15 -14 36t14 35zM655 855l210 210q14 14 24.5 10t10.5 -25l-2 -599q-1 -20 -15.5 -35t-35.5 -15l-597 -1q-21 0 -25 10.5t10 24.5l208 208l-154 155l212 212zM50 300h1000q21 0 35.5 -14.5t14.5 -35.5 v-250h-1100v250q0 21 14.5 35.5t35.5 14.5zM900 200v-50h100v50h-100z" /> +<glyph unicode="" d="M350 1200l599 -2q20 -1 35 -15.5t15 -35.5l1 -597q0 -21 -10.5 -25t-24.5 10l-208 208l-155 -154l-212 212l155 154l-210 210q-14 14 -10 24.5t25 10.5zM524 512l-76 -77q-15 -14 -36 -14t-35 14l-142 142q-14 14 -14 35t14 35l77 77zM50 300h1000q21 0 35.5 -14.5 t14.5 -35.5v-250h-1100v250q0 21 14.5 35.5t35.5 14.5zM900 200v-50h100v50h-100z" /> +<glyph unicode="" d="M1200 103l-483 276l-314 -399v423h-399l1196 796v-1096zM483 424v-230l683 953z" /> +<glyph unicode="" d="M1100 1000v-850q0 -21 -14.5 -35.5t-35.5 -14.5h-150v400h-700v-400h-150q-21 0 -35.5 14.5t-14.5 35.5v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100zM700 1000h-100v200h100v-200z" /> +<glyph unicode="" d="M1100 1000l-2 -149l-299 -299l-95 95q-9 9 -21.5 9t-21.5 -9l-149 -147h-312v-400h-150q-21 0 -35.5 14.5t-14.5 35.5v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100zM700 1000h-100v200h100v-200zM1132 638l106 -106q7 -7 7 -17.5t-7 -17.5l-420 -421q-8 -7 -18 -7 t-18 7l-202 203q-8 7 -8 17.5t8 17.5l106 106q7 8 17.5 8t17.5 -8l79 -79l297 297q7 7 17.5 7t17.5 -7z" /> +<glyph unicode="" d="M1100 1000v-269l-103 -103l-134 134q-15 15 -33.5 16.5t-34.5 -12.5l-266 -266h-329v-400h-150q-21 0 -35.5 14.5t-14.5 35.5v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100zM700 1000h-100v200h100v-200zM1202 572l70 -70q15 -15 15 -35.5t-15 -35.5l-131 -131 l131 -131q15 -15 15 -35.5t-15 -35.5l-70 -70q-15 -15 -35.5 -15t-35.5 15l-131 131l-131 -131q-15 -15 -35.5 -15t-35.5 15l-70 70q-15 15 -15 35.5t15 35.5l131 131l-131 131q-15 15 -15 35.5t15 35.5l70 70q15 15 35.5 15t35.5 -15l131 -131l131 131q15 15 35.5 15 t35.5 -15z" /> +<glyph unicode="" d="M1100 1000v-300h-350q-21 0 -35.5 -14.5t-14.5 -35.5v-150h-500v-400h-150q-21 0 -35.5 14.5t-14.5 35.5v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100zM700 1000h-100v200h100v-200zM850 600h100q21 0 35.5 -14.5t14.5 -35.5v-250h150q21 0 25 -10.5t-10 -24.5 l-230 -230q-14 -14 -35 -14t-35 14l-230 230q-14 14 -10 24.5t25 10.5h150v250q0 21 14.5 35.5t35.5 14.5z" /> +<glyph unicode="" d="M1100 1000v-400l-165 165q-14 15 -35 15t-35 -15l-263 -265h-402v-400h-150q-21 0 -35.5 14.5t-14.5 35.5v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100zM700 1000h-100v200h100v-200zM935 565l230 -229q14 -15 10 -25.5t-25 -10.5h-150v-250q0 -20 -14.5 -35 t-35.5 -15h-100q-21 0 -35.5 15t-14.5 35v250h-150q-21 0 -25 10.5t10 25.5l230 229q14 15 35 15t35 -15z" /> +<glyph unicode="" d="M50 1100h1100q21 0 35.5 -14.5t14.5 -35.5v-150h-1200v150q0 21 14.5 35.5t35.5 14.5zM1200 800v-550q0 -21 -14.5 -35.5t-35.5 -14.5h-1100q-21 0 -35.5 14.5t-14.5 35.5v550h1200zM100 500v-200h400v200h-400z" /> +<glyph unicode="" d="M935 1165l248 -230q14 -14 14 -35t-14 -35l-248 -230q-14 -14 -24.5 -10t-10.5 25v150h-400v200h400v150q0 21 10.5 25t24.5 -10zM200 800h-50q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5h50v-200zM400 800h-100v200h100v-200zM18 435l247 230 q14 14 24.5 10t10.5 -25v-150h400v-200h-400v-150q0 -21 -10.5 -25t-24.5 10l-247 230q-15 14 -15 35t15 35zM900 300h-100v200h100v-200zM1000 500h51q20 0 34.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-34.5 -14.5h-51v200z" /> +<glyph unicode="" d="M862 1073l276 116q25 18 43.5 8t18.5 -41v-1106q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v397q-4 1 -11 5t-24 17.5t-30 29t-24 42t-11 56.5v359q0 31 18.5 65t43.5 52zM550 1200q22 0 34.5 -12.5t14.5 -24.5l1 -13v-450q0 -28 -10.5 -59.5 t-25 -56t-29 -45t-25.5 -31.5l-10 -11v-447q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v447q-4 4 -11 11.5t-24 30.5t-30 46t-24 55t-11 60v450q0 2 0.5 5.5t4 12t8.5 15t14.5 12t22.5 5.5q20 0 32.5 -12.5t14.5 -24.5l3 -13v-350h100v350v5.5t2.5 12 t7 15t15 12t25.5 5.5q23 0 35.5 -12.5t13.5 -24.5l1 -13v-350h100v350q0 2 0.5 5.5t3 12t7 15t15 12t24.5 5.5z" /> +<glyph unicode="" d="M1200 1100v-56q-4 0 -11 -0.5t-24 -3t-30 -7.5t-24 -15t-11 -24v-888q0 -22 25 -34.5t50 -13.5l25 -2v-56h-400v56q75 0 87.5 6.5t12.5 43.5v394h-500v-394q0 -37 12.5 -43.5t87.5 -6.5v-56h-400v56q4 0 11 0.5t24 3t30 7.5t24 15t11 24v888q0 22 -25 34.5t-50 13.5 l-25 2v56h400v-56q-75 0 -87.5 -6.5t-12.5 -43.5v-394h500v394q0 37 -12.5 43.5t-87.5 6.5v56h400z" /> +<glyph unicode="" d="M675 1000h375q21 0 35.5 -14.5t14.5 -35.5v-150h-105l-295 -98v98l-200 200h-400l100 100h375zM100 900h300q41 0 70.5 -29.5t29.5 -70.5v-500q0 -41 -29.5 -70.5t-70.5 -29.5h-300q-41 0 -70.5 29.5t-29.5 70.5v500q0 41 29.5 70.5t70.5 29.5zM100 800v-200h300v200 h-300zM1100 535l-400 -133v163l400 133v-163zM100 500v-200h300v200h-300zM1100 398v-248q0 -21 -14.5 -35.5t-35.5 -14.5h-375l-100 -100h-375l-100 100h400l200 200h105z" /> +<glyph unicode="" d="M17 1007l162 162q17 17 40 14t37 -22l139 -194q14 -20 11 -44.5t-20 -41.5l-119 -118q102 -142 228 -268t267 -227l119 118q17 17 42.5 19t44.5 -12l192 -136q19 -14 22.5 -37.5t-13.5 -40.5l-163 -162q-3 -1 -9.5 -1t-29.5 2t-47.5 6t-62.5 14.5t-77.5 26.5t-90 42.5 t-101.5 60t-111 83t-119 108.5q-74 74 -133.5 150.5t-94.5 138.5t-60 119.5t-34.5 100t-15 74.5t-4.5 48z" /> +<glyph unicode="" d="M600 1100q92 0 175 -10.5t141.5 -27t108.5 -36.5t81.5 -40t53.5 -37t31 -27l9 -10v-200q0 -21 -14.5 -33t-34.5 -9l-202 34q-20 3 -34.5 20t-14.5 38v146q-141 24 -300 24t-300 -24v-146q0 -21 -14.5 -38t-34.5 -20l-202 -34q-20 -3 -34.5 9t-14.5 33v200q3 4 9.5 10.5 t31 26t54 37.5t80.5 39.5t109 37.5t141 26.5t175 10.5zM600 795q56 0 97 -9.5t60 -23.5t30 -28t12 -24l1 -10v-50l365 -303q14 -15 24.5 -40t10.5 -45v-212q0 -21 -14.5 -35.5t-35.5 -14.5h-1100q-21 0 -35.5 14.5t-14.5 35.5v212q0 20 10.5 45t24.5 40l365 303v50 q0 4 1 10.5t12 23t30 29t60 22.5t97 10z" /> +<glyph unicode="" d="M1100 700l-200 -200h-600l-200 200v500h200v-200h200v200h200v-200h200v200h200v-500zM250 400h700q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-12l137 -100h-950l137 100h-12q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5zM50 100h1100q21 0 35.5 -14.5 t14.5 -35.5v-50h-1200v50q0 21 14.5 35.5t35.5 14.5z" /> +<glyph unicode="" d="M700 1100h-100q-41 0 -70.5 -29.5t-29.5 -70.5v-1000h300v1000q0 41 -29.5 70.5t-70.5 29.5zM1100 800h-100q-41 0 -70.5 -29.5t-29.5 -70.5v-700h300v700q0 41 -29.5 70.5t-70.5 29.5zM400 0h-300v400q0 41 29.5 70.5t70.5 29.5h100q41 0 70.5 -29.5t29.5 -70.5v-400z " /> +<glyph unicode="" d="M200 1100h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88zM100 900v-700h900v700h-900zM500 700h-200v-100h200v-300h-300v100h200v100h-200v300h300v-100zM900 700v-300l-100 -100h-200v500h200z M700 700v-300h100v300h-100z" /> +<glyph unicode="" d="M200 1100h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88zM100 900v-700h900v700h-900zM500 300h-100v200h-100v-200h-100v500h100v-200h100v200h100v-500zM900 700v-300l-100 -100h-200v500h200z M700 700v-300h100v300h-100z" /> +<glyph unicode="" d="M200 1100h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88zM100 900v-700h900v700h-900zM500 700h-200v-300h200v-100h-300v500h300v-100zM900 700h-200v-300h200v-100h-300v500h300v-100z" /> +<glyph unicode="" d="M200 1100h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88zM100 900v-700h900v700h-900zM500 400l-300 150l300 150v-300zM900 550l-300 -150v300z" /> +<glyph unicode="" d="M200 1100h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88zM100 900v-700h900v700h-900zM900 300h-700v500h700v-500zM800 700h-130q-38 0 -66.5 -43t-28.5 -108t27 -107t68 -42h130v300zM300 700v-300 h130q41 0 68 42t27 107t-28.5 108t-66.5 43h-130z" /> +<glyph unicode="" d="M200 1100h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88zM100 900v-700h900v700h-900zM500 700h-200v-100h200v-300h-300v100h200v100h-200v300h300v-100zM900 300h-100v400h-100v100h200v-500z M700 300h-100v100h100v-100z" /> +<glyph unicode="" d="M200 1100h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88zM100 900v-700h900v700h-900zM300 700h200v-400h-300v500h100v-100zM900 300h-100v400h-100v100h200v-500zM300 600v-200h100v200h-100z M700 300h-100v100h100v-100z" /> +<glyph unicode="" d="M200 1100h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88zM100 900v-700h900v700h-900zM500 500l-199 -200h-100v50l199 200v150h-200v100h300v-300zM900 300h-100v400h-100v100h200v-500zM701 300h-100 v100h100v-100z" /> +<glyph unicode="" d="M600 1191q120 0 229.5 -47t188.5 -126t126 -188.5t47 -229.5t-47 -229.5t-126 -188.5t-188.5 -126t-229.5 -47t-229.5 47t-188.5 126t-126 188.5t-47 229.5t47 229.5t126 188.5t188.5 126t229.5 47zM600 1021q-114 0 -211 -56.5t-153.5 -153.5t-56.5 -211t56.5 -211 t153.5 -153.5t211 -56.5t211 56.5t153.5 153.5t56.5 211t-56.5 211t-153.5 153.5t-211 56.5zM800 700h-300v-200h300v-100h-300l-100 100v200l100 100h300v-100z" /> +<glyph unicode="" d="M600 1191q120 0 229.5 -47t188.5 -126t126 -188.5t47 -229.5t-47 -229.5t-126 -188.5t-188.5 -126t-229.5 -47t-229.5 47t-188.5 126t-126 188.5t-47 229.5t47 229.5t126 188.5t188.5 126t229.5 47zM600 1021q-114 0 -211 -56.5t-153.5 -153.5t-56.5 -211t56.5 -211 t153.5 -153.5t211 -56.5t211 56.5t153.5 153.5t56.5 211t-56.5 211t-153.5 153.5t-211 56.5zM800 700v-100l-50 -50l100 -100v-50h-100l-100 100h-150v-100h-100v400h300zM500 700v-100h200v100h-200z" /> +<glyph unicode="" d="M503 1089q110 0 200.5 -59.5t134.5 -156.5q44 14 90 14q120 0 205 -86.5t85 -207t-85 -207t-205 -86.5h-128v250q0 21 -14.5 35.5t-35.5 14.5h-300q-21 0 -35.5 -14.5t-14.5 -35.5v-250h-222q-80 0 -136 57.5t-56 136.5q0 69 43 122.5t108 67.5q-2 19 -2 37q0 100 49 185 t134 134t185 49zM525 500h150q10 0 17.5 -7.5t7.5 -17.5v-275h137q21 0 26 -11.5t-8 -27.5l-223 -244q-13 -16 -32 -16t-32 16l-223 244q-13 16 -8 27.5t26 11.5h137v275q0 10 7.5 17.5t17.5 7.5z" /> +<glyph unicode="" d="M502 1089q110 0 201 -59.5t135 -156.5q43 15 89 15q121 0 206 -86.5t86 -206.5q0 -99 -60 -181t-150 -110l-378 360q-13 16 -31.5 16t-31.5 -16l-381 -365h-9q-79 0 -135.5 57.5t-56.5 136.5q0 69 43 122.5t108 67.5q-2 19 -2 38q0 100 49 184.5t133.5 134t184.5 49.5z M632 467l223 -228q13 -16 8 -27.5t-26 -11.5h-137v-275q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5v275h-137q-21 0 -26 11.5t8 27.5q199 204 223 228q19 19 31.5 19t32.5 -19z" /> +<glyph unicode="" d="M700 100v100h400l-270 300h170l-270 300h170l-300 333l-300 -333h170l-270 -300h170l-270 -300h400v-100h-50q-21 0 -35.5 -14.5t-14.5 -35.5v-50h400v50q0 21 -14.5 35.5t-35.5 14.5h-50z" /> +<glyph unicode="" d="M600 1179q94 0 167.5 -56.5t99.5 -145.5q89 -6 150.5 -71.5t61.5 -155.5q0 -61 -29.5 -112.5t-79.5 -82.5q9 -29 9 -55q0 -74 -52.5 -126.5t-126.5 -52.5q-55 0 -100 30v-251q21 0 35.5 -14.5t14.5 -35.5v-50h-300v50q0 21 14.5 35.5t35.5 14.5v251q-45 -30 -100 -30 q-74 0 -126.5 52.5t-52.5 126.5q0 18 4 38q-47 21 -75.5 65t-28.5 97q0 74 52.5 126.5t126.5 52.5q5 0 23 -2q0 2 -1 10t-1 13q0 116 81.5 197.5t197.5 81.5z" /> +<glyph unicode="" d="M1010 1010q111 -111 150.5 -260.5t0 -299t-150.5 -260.5q-83 -83 -191.5 -126.5t-218.5 -43.5t-218.5 43.5t-191.5 126.5q-111 111 -150.5 260.5t0 299t150.5 260.5q83 83 191.5 126.5t218.5 43.5t218.5 -43.5t191.5 -126.5zM476 1065q-4 0 -8 -1q-121 -34 -209.5 -122.5 t-122.5 -209.5q-4 -12 2.5 -23t18.5 -14l36 -9q3 -1 7 -1q23 0 29 22q27 96 98 166q70 71 166 98q11 3 17.5 13.5t3.5 22.5l-9 35q-3 13 -14 19q-7 4 -15 4zM512 920q-4 0 -9 -2q-80 -24 -138.5 -82.5t-82.5 -138.5q-4 -13 2 -24t19 -14l34 -9q4 -1 8 -1q22 0 28 21 q18 58 58.5 98.5t97.5 58.5q12 3 18 13.5t3 21.5l-9 35q-3 12 -14 19q-7 4 -15 4zM719.5 719.5q-49.5 49.5 -119.5 49.5t-119.5 -49.5t-49.5 -119.5t49.5 -119.5t119.5 -49.5t119.5 49.5t49.5 119.5t-49.5 119.5zM855 551q-22 0 -28 -21q-18 -58 -58.5 -98.5t-98.5 -57.5 q-11 -4 -17 -14.5t-3 -21.5l9 -35q3 -12 14 -19q7 -4 15 -4q4 0 9 2q80 24 138.5 82.5t82.5 138.5q4 13 -2.5 24t-18.5 14l-34 9q-4 1 -8 1zM1000 515q-23 0 -29 -22q-27 -96 -98 -166q-70 -71 -166 -98q-11 -3 -17.5 -13.5t-3.5 -22.5l9 -35q3 -13 14 -19q7 -4 15 -4 q4 0 8 1q121 34 209.5 122.5t122.5 209.5q4 12 -2.5 23t-18.5 14l-36 9q-3 1 -7 1z" /> +<glyph unicode="" d="M700 800h300v-380h-180v200h-340v-200h-380v755q0 10 7.5 17.5t17.5 7.5h575v-400zM1000 900h-200v200zM700 300h162l-212 -212l-212 212h162v200h100v-200zM520 0h-395q-10 0 -17.5 7.5t-7.5 17.5v395zM1000 220v-195q0 -10 -7.5 -17.5t-17.5 -7.5h-195z" /> +<glyph unicode="" d="M700 800h300v-520l-350 350l-550 -550v1095q0 10 7.5 17.5t17.5 7.5h575v-400zM1000 900h-200v200zM862 200h-162v-200h-100v200h-162l212 212zM480 0h-355q-10 0 -17.5 7.5t-7.5 17.5v55h380v-80zM1000 80v-55q0 -10 -7.5 -17.5t-17.5 -7.5h-155v80h180z" /> +<glyph unicode="" d="M1162 800h-162v-200h100l100 -100h-300v300h-162l212 212zM200 800h200q27 0 40 -2t29.5 -10.5t23.5 -30t7 -57.5h300v-100h-600l-200 -350v450h100q0 36 7 57.5t23.5 30t29.5 10.5t40 2zM800 400h240l-240 -400h-800l300 500h500v-100z" /> +<glyph unicode="" d="M650 1100h100q21 0 35.5 -14.5t14.5 -35.5v-50h50q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-300q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5h50v50q0 21 14.5 35.5t35.5 14.5zM1000 850v150q41 0 70.5 -29.5t29.5 -70.5v-800 q0 -41 -29.5 -70.5t-70.5 -29.5h-600q-1 0 -20 4l246 246l-326 326v324q0 41 29.5 70.5t70.5 29.5v-150q0 -62 44 -106t106 -44h300q62 0 106 44t44 106zM412 250l-212 -212v162h-200v100h200v162z" /> +<glyph unicode="" d="M450 1100h100q21 0 35.5 -14.5t14.5 -35.5v-50h50q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-300q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5h50v50q0 21 14.5 35.5t35.5 14.5zM800 850v150q41 0 70.5 -29.5t29.5 -70.5v-500 h-200v-300h200q0 -36 -7 -57.5t-23.5 -30t-29.5 -10.5t-40 -2h-600q-41 0 -70.5 29.5t-29.5 70.5v800q0 41 29.5 70.5t70.5 29.5v-150q0 -62 44 -106t106 -44h300q62 0 106 44t44 106zM1212 250l-212 -212v162h-200v100h200v162z" /> +<glyph unicode="" d="M658 1197l637 -1104q23 -38 7 -65.5t-60 -27.5h-1276q-44 0 -60 27.5t7 65.5l637 1104q22 39 54 39t54 -39zM704 800h-208q-20 0 -32 -14.5t-8 -34.5l58 -302q4 -20 21.5 -34.5t37.5 -14.5h54q20 0 37.5 14.5t21.5 34.5l58 302q4 20 -8 34.5t-32 14.5zM500 300v-100h200 v100h-200z" /> +<glyph unicode="" d="M425 1100h250q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5zM425 800h250q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5 t17.5 7.5zM825 800h250q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5zM25 500h250q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5v150 q0 10 7.5 17.5t17.5 7.5zM425 500h250q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5zM825 500h250q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5 v150q0 10 7.5 17.5t17.5 7.5zM25 200h250q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5zM425 200h250q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5 t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5zM825 200h250q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5z" /> +<glyph unicode="" d="M700 1200h100v-200h-100v-100h350q62 0 86.5 -39.5t-3.5 -94.5l-66 -132q-41 -83 -81 -134h-772q-40 51 -81 134l-66 132q-28 55 -3.5 94.5t86.5 39.5h350v100h-100v200h100v100h200v-100zM250 400h700q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-12l137 -100 h-950l138 100h-13q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5zM50 100h1100q21 0 35.5 -14.5t14.5 -35.5v-50h-1200v50q0 21 14.5 35.5t35.5 14.5z" /> +<glyph unicode="" d="M600 1300q40 0 68.5 -29.5t28.5 -70.5h-194q0 41 28.5 70.5t68.5 29.5zM443 1100h314q18 -37 18 -75q0 -8 -3 -25h328q41 0 44.5 -16.5t-30.5 -38.5l-175 -145h-678l-178 145q-34 22 -29 38.5t46 16.5h328q-3 17 -3 25q0 38 18 75zM250 700h700q21 0 35.5 -14.5 t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-150v-200l275 -200h-950l275 200v200h-150q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5zM50 100h1100q21 0 35.5 -14.5t14.5 -35.5v-50h-1200v50q0 21 14.5 35.5t35.5 14.5z" /> +<glyph unicode="" d="M600 1181q75 0 128 -53t53 -128t-53 -128t-128 -53t-128 53t-53 128t53 128t128 53zM602 798h46q34 0 55.5 -28.5t21.5 -86.5q0 -76 39 -183h-324q39 107 39 183q0 58 21.5 86.5t56.5 28.5h45zM250 400h700q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-13 l138 -100h-950l137 100h-12q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5zM50 100h1100q21 0 35.5 -14.5t14.5 -35.5v-50h-1200v50q0 21 14.5 35.5t35.5 14.5z" /> +<glyph unicode="" d="M600 1300q47 0 92.5 -53.5t71 -123t25.5 -123.5q0 -78 -55.5 -133.5t-133.5 -55.5t-133.5 55.5t-55.5 133.5q0 62 34 143l144 -143l111 111l-163 163q34 26 63 26zM602 798h46q34 0 55.5 -28.5t21.5 -86.5q0 -76 39 -183h-324q39 107 39 183q0 58 21.5 86.5t56.5 28.5h45 zM250 400h700q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-13l138 -100h-950l137 100h-12q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5zM50 100h1100q21 0 35.5 -14.5t14.5 -35.5v-50h-1200v50q0 21 14.5 35.5t35.5 14.5z" /> +<glyph unicode="" d="M600 1200l300 -161v-139h-300q0 -57 18.5 -108t50 -91.5t63 -72t70 -67.5t57.5 -61h-530q-60 83 -90.5 177.5t-30.5 178.5t33 164.5t87.5 139.5t126 96.5t145.5 41.5v-98zM250 400h700q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-13l138 -100h-950l137 100 h-12q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5zM50 100h1100q21 0 35.5 -14.5t14.5 -35.5v-50h-1200v50q0 21 14.5 35.5t35.5 14.5z" /> +<glyph unicode="" d="M600 1300q41 0 70.5 -29.5t29.5 -70.5v-78q46 -26 73 -72t27 -100v-50h-400v50q0 54 27 100t73 72v78q0 41 29.5 70.5t70.5 29.5zM400 800h400q54 0 100 -27t72 -73h-172v-100h200v-100h-200v-100h200v-100h-200v-100h200q0 -83 -58.5 -141.5t-141.5 -58.5h-400 q-83 0 -141.5 58.5t-58.5 141.5v400q0 83 58.5 141.5t141.5 58.5z" /> +<glyph unicode="" d="M150 1100h900q21 0 35.5 -14.5t14.5 -35.5v-500q0 -21 -14.5 -35.5t-35.5 -14.5h-900q-21 0 -35.5 14.5t-14.5 35.5v500q0 21 14.5 35.5t35.5 14.5zM125 400h950q10 0 17.5 -7.5t7.5 -17.5v-50q0 -10 -7.5 -17.5t-17.5 -7.5h-283l224 -224q13 -13 13 -31.5t-13 -32 t-31.5 -13.5t-31.5 13l-88 88h-524l-87 -88q-13 -13 -32 -13t-32 13.5t-13 32t13 31.5l224 224h-289q-10 0 -17.5 7.5t-7.5 17.5v50q0 10 7.5 17.5t17.5 7.5zM541 300l-100 -100h324l-100 100h-124z" /> +<glyph unicode="" d="M200 1100h800q83 0 141.5 -58.5t58.5 -141.5v-200h-100q0 41 -29.5 70.5t-70.5 29.5h-250q-41 0 -70.5 -29.5t-29.5 -70.5h-100q0 41 -29.5 70.5t-70.5 29.5h-250q-41 0 -70.5 -29.5t-29.5 -70.5h-100v200q0 83 58.5 141.5t141.5 58.5zM100 600h1000q41 0 70.5 -29.5 t29.5 -70.5v-300h-1200v300q0 41 29.5 70.5t70.5 29.5zM300 100v-50q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v50h200zM1100 100v-50q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v50h200z" /> +<glyph unicode="" d="M480 1165l682 -683q31 -31 31 -75.5t-31 -75.5l-131 -131h-481l-517 518q-32 31 -32 75.5t32 75.5l295 296q31 31 75.5 31t76.5 -31zM108 794l342 -342l303 304l-341 341zM250 100h800q21 0 35.5 -14.5t14.5 -35.5v-50h-900v50q0 21 14.5 35.5t35.5 14.5z" /> +<glyph unicode="" d="M1057 647l-189 506q-8 19 -27.5 33t-40.5 14h-400q-21 0 -40.5 -14t-27.5 -33l-189 -506q-8 -19 1.5 -33t30.5 -14h625v-150q0 -21 14.5 -35.5t35.5 -14.5t35.5 14.5t14.5 35.5v150h125q21 0 30.5 14t1.5 33zM897 0h-595v50q0 21 14.5 35.5t35.5 14.5h50v50 q0 21 14.5 35.5t35.5 14.5h48v300h200v-300h47q21 0 35.5 -14.5t14.5 -35.5v-50h50q21 0 35.5 -14.5t14.5 -35.5v-50z" /> +<glyph unicode="" d="M900 800h300v-575q0 -10 -7.5 -17.5t-17.5 -7.5h-375v591l-300 300v84q0 10 7.5 17.5t17.5 7.5h375v-400zM1200 900h-200v200zM400 600h300v-575q0 -10 -7.5 -17.5t-17.5 -7.5h-650q-10 0 -17.5 7.5t-7.5 17.5v950q0 10 7.5 17.5t17.5 7.5h375v-400zM700 700h-200v200z " /> +<glyph unicode="" d="M484 1095h195q75 0 146 -32.5t124 -86t89.5 -122.5t48.5 -142q18 -14 35 -20q31 -10 64.5 6.5t43.5 48.5q10 34 -15 71q-19 27 -9 43q5 8 12.5 11t19 -1t23.5 -16q41 -44 39 -105q-3 -63 -46 -106.5t-104 -43.5h-62q-7 -55 -35 -117t-56 -100l-39 -234q-3 -20 -20 -34.5 t-38 -14.5h-100q-21 0 -33 14.5t-9 34.5l12 70q-49 -14 -91 -14h-195q-24 0 -65 8l-11 -64q-3 -20 -20 -34.5t-38 -14.5h-100q-21 0 -33 14.5t-9 34.5l26 157q-84 74 -128 175l-159 53q-19 7 -33 26t-14 40v50q0 21 14.5 35.5t35.5 14.5h124q11 87 56 166l-111 95 q-16 14 -12.5 23.5t24.5 9.5h203q116 101 250 101zM675 1000h-250q-10 0 -17.5 -7.5t-7.5 -17.5v-50q0 -10 7.5 -17.5t17.5 -7.5h250q10 0 17.5 7.5t7.5 17.5v50q0 10 -7.5 17.5t-17.5 7.5z" /> +<glyph unicode="" d="M641 900l423 247q19 8 42 2.5t37 -21.5l32 -38q14 -15 12.5 -36t-17.5 -34l-139 -120h-390zM50 1100h106q67 0 103 -17t66 -71l102 -212h823q21 0 35.5 -14.5t14.5 -35.5v-50q0 -21 -14 -40t-33 -26l-737 -132q-23 -4 -40 6t-26 25q-42 67 -100 67h-300q-62 0 -106 44 t-44 106v200q0 62 44 106t106 44zM173 928h-80q-19 0 -28 -14t-9 -35v-56q0 -51 42 -51h134q16 0 21.5 8t5.5 24q0 11 -16 45t-27 51q-18 28 -43 28zM550 727q-32 0 -54.5 -22.5t-22.5 -54.5t22.5 -54.5t54.5 -22.5t54.5 22.5t22.5 54.5t-22.5 54.5t-54.5 22.5zM130 389 l152 130q18 19 34 24t31 -3.5t24.5 -17.5t25.5 -28q28 -35 50.5 -51t48.5 -13l63 5l48 -179q13 -61 -3.5 -97.5t-67.5 -79.5l-80 -69q-47 -40 -109 -35.5t-103 51.5l-130 151q-40 47 -35.5 109.5t51.5 102.5zM380 377l-102 -88q-31 -27 2 -65l37 -43q13 -15 27.5 -19.5 t31.5 6.5l61 53q19 16 14 49q-2 20 -12 56t-17 45q-11 12 -19 14t-23 -8z" /> +<glyph unicode="" d="M625 1200h150q10 0 17.5 -7.5t7.5 -17.5v-109q79 -33 131 -87.5t53 -128.5q1 -46 -15 -84.5t-39 -61t-46 -38t-39 -21.5l-17 -6q6 0 15 -1.5t35 -9t50 -17.5t53 -30t50 -45t35.5 -64t14.5 -84q0 -59 -11.5 -105.5t-28.5 -76.5t-44 -51t-49.5 -31.5t-54.5 -16t-49.5 -6.5 t-43.5 -1v-75q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5v75h-100v-75q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5v75h-175q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5h75v600h-75q-10 0 -17.5 7.5t-7.5 17.5v150 q0 10 7.5 17.5t17.5 7.5h175v75q0 10 7.5 17.5t17.5 7.5h150q10 0 17.5 -7.5t7.5 -17.5v-75h100v75q0 10 7.5 17.5t17.5 7.5zM400 900v-200h263q28 0 48.5 10.5t30 25t15 29t5.5 25.5l1 10q0 4 -0.5 11t-6 24t-15 30t-30 24t-48.5 11h-263zM400 500v-200h363q28 0 48.5 10.5 t30 25t15 29t5.5 25.5l1 10q0 4 -0.5 11t-6 24t-15 30t-30 24t-48.5 11h-363z" /> +<glyph unicode="" d="M212 1198h780q86 0 147 -61t61 -147v-416q0 -51 -18 -142.5t-36 -157.5l-18 -66q-29 -87 -93.5 -146.5t-146.5 -59.5h-572q-82 0 -147 59t-93 147q-8 28 -20 73t-32 143.5t-20 149.5v416q0 86 61 147t147 61zM600 1045q-70 0 -132.5 -11.5t-105.5 -30.5t-78.5 -41.5 t-57 -45t-36 -41t-20.5 -30.5l-6 -12l156 -243h560l156 243q-2 5 -6 12.5t-20 29.5t-36.5 42t-57 44.5t-79 42t-105 29.5t-132.5 12zM762 703h-157l195 261z" /> +<glyph unicode="" d="M475 1300h150q103 0 189 -86t86 -189v-500q0 -41 -42 -83t-83 -42h-450q-41 0 -83 42t-42 83v500q0 103 86 189t189 86zM700 300v-225q0 -21 -27 -48t-48 -27h-150q-21 0 -48 27t-27 48v225h300z" /> +<glyph unicode="" d="M475 1300h96q0 -150 89.5 -239.5t239.5 -89.5v-446q0 -41 -42 -83t-83 -42h-450q-41 0 -83 42t-42 83v500q0 103 86 189t189 86zM700 300v-225q0 -21 -27 -48t-48 -27h-150q-21 0 -48 27t-27 48v225h300z" /> +<glyph unicode="" d="M1294 767l-638 -283l-378 170l-78 -60v-224l100 -150v-199l-150 148l-150 -149v200l100 150v250q0 4 -0.5 10.5t0 9.5t1 8t3 8t6.5 6l47 40l-147 65l642 283zM1000 380l-350 -166l-350 166v147l350 -165l350 165v-147z" /> +<glyph unicode="" d="M250 800q62 0 106 -44t44 -106t-44 -106t-106 -44t-106 44t-44 106t44 106t106 44zM650 800q62 0 106 -44t44 -106t-44 -106t-106 -44t-106 44t-44 106t44 106t106 44zM1050 800q62 0 106 -44t44 -106t-44 -106t-106 -44t-106 44t-44 106t44 106t106 44z" /> +<glyph unicode="" d="M550 1100q62 0 106 -44t44 -106t-44 -106t-106 -44t-106 44t-44 106t44 106t106 44zM550 700q62 0 106 -44t44 -106t-44 -106t-106 -44t-106 44t-44 106t44 106t106 44zM550 300q62 0 106 -44t44 -106t-44 -106t-106 -44t-106 44t-44 106t44 106t106 44z" /> +<glyph unicode="" d="M125 1100h950q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-950q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5zM125 700h950q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-950q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5 t17.5 7.5zM125 300h950q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-950q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5z" /> +<glyph unicode="" d="M350 1200h500q162 0 256 -93.5t94 -256.5v-500q0 -165 -93.5 -257.5t-256.5 -92.5h-500q-165 0 -257.5 92.5t-92.5 257.5v500q0 165 92.5 257.5t257.5 92.5zM900 1000h-600q-41 0 -70.5 -29.5t-29.5 -70.5v-600q0 -41 29.5 -70.5t70.5 -29.5h600q41 0 70.5 29.5 t29.5 70.5v600q0 41 -29.5 70.5t-70.5 29.5zM350 900h500q21 0 35.5 -14.5t14.5 -35.5v-300q0 -21 -14.5 -35.5t-35.5 -14.5h-500q-21 0 -35.5 14.5t-14.5 35.5v300q0 21 14.5 35.5t35.5 14.5zM400 800v-200h400v200h-400z" /> +<glyph unicode="" d="M150 1100h1000q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-50v-200h50q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-50v-200h50q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-50v-200h50q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5 t-35.5 -14.5h-1000q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5h50v200h-50q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5h50v200h-50q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5h50v200h-50q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5z" /> +<glyph unicode="" d="M650 1187q87 -67 118.5 -156t0 -178t-118.5 -155q-87 66 -118.5 155t0 178t118.5 156zM300 800q124 0 212 -88t88 -212q-124 0 -212 88t-88 212zM1000 800q0 -124 -88 -212t-212 -88q0 124 88 212t212 88zM300 500q124 0 212 -88t88 -212q-124 0 -212 88t-88 212z M1000 500q0 -124 -88 -212t-212 -88q0 124 88 212t212 88zM700 199v-144q0 -21 -14.5 -35.5t-35.5 -14.5t-35.5 14.5t-14.5 35.5v142q40 -4 43 -4q17 0 57 6z" /> +<glyph unicode="" d="M745 878l69 19q25 6 45 -12l298 -295q11 -11 15 -26.5t-2 -30.5q-5 -14 -18 -23.5t-28 -9.5h-8q1 0 1 -13q0 -29 -2 -56t-8.5 -62t-20 -63t-33 -53t-51 -39t-72.5 -14h-146q-184 0 -184 288q0 24 10 47q-20 4 -62 4t-63 -4q11 -24 11 -47q0 -288 -184 -288h-142 q-48 0 -84.5 21t-56 51t-32 71.5t-16 75t-3.5 68.5q0 13 2 13h-7q-15 0 -27.5 9.5t-18.5 23.5q-6 15 -2 30.5t15 25.5l298 296q20 18 46 11l76 -19q20 -5 30.5 -22.5t5.5 -37.5t-22.5 -31t-37.5 -5l-51 12l-182 -193h891l-182 193l-44 -12q-20 -5 -37.5 6t-22.5 31t6 37.5 t31 22.5z" /> +<glyph unicode="" d="M1200 900h-50q0 21 -4 37t-9.5 26.5t-18 17.5t-22 11t-28.5 5.5t-31 2t-37 0.5h-200v-850q0 -22 25 -34.5t50 -13.5l25 -2v-100h-400v100q4 0 11 0.5t24 3t30 7t24 15t11 24.5v850h-200q-25 0 -37 -0.5t-31 -2t-28.5 -5.5t-22 -11t-18 -17.5t-9.5 -26.5t-4 -37h-50v300 h1000v-300zM500 450h-25q0 15 -4 24.5t-9 14.5t-17 7.5t-20 3t-25 0.5h-100v-425q0 -11 12.5 -17.5t25.5 -7.5h12v-50h-200v50q50 0 50 25v425h-100q-17 0 -25 -0.5t-20 -3t-17 -7.5t-9 -14.5t-4 -24.5h-25v150h500v-150z" /> +<glyph unicode="" d="M1000 300v50q-25 0 -55 32q-14 14 -25 31t-16 27l-4 11l-289 747h-69l-300 -754q-18 -35 -39 -56q-9 -9 -24.5 -18.5t-26.5 -14.5l-11 -5v-50h273v50q-49 0 -78.5 21.5t-11.5 67.5l69 176h293l61 -166q13 -34 -3.5 -66.5t-55.5 -32.5v-50h312zM412 691l134 342l121 -342 h-255zM1100 150v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1000q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5h1000q21 0 35.5 -14.5t14.5 -35.5z" /> +<glyph unicode="" d="M50 1200h1100q21 0 35.5 -14.5t14.5 -35.5v-1100q0 -21 -14.5 -35.5t-35.5 -14.5h-1100q-21 0 -35.5 14.5t-14.5 35.5v1100q0 21 14.5 35.5t35.5 14.5zM611 1118h-70q-13 0 -18 -12l-299 -753q-17 -32 -35 -51q-18 -18 -56 -34q-12 -5 -12 -18v-50q0 -8 5.5 -14t14.5 -6 h273q8 0 14 6t6 14v50q0 8 -6 14t-14 6q-55 0 -71 23q-10 14 0 39l63 163h266l57 -153q11 -31 -6 -55q-12 -17 -36 -17q-8 0 -14 -6t-6 -14v-50q0 -8 6 -14t14 -6h313q8 0 14 6t6 14v50q0 7 -5.5 13t-13.5 7q-17 0 -42 25q-25 27 -40 63h-1l-288 748q-5 12 -19 12zM639 611 h-197l103 264z" /> +<glyph unicode="" d="M1200 1100h-1200v100h1200v-100zM50 1000h400q21 0 35.5 -14.5t14.5 -35.5v-900q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5v900q0 21 14.5 35.5t35.5 14.5zM650 1000h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400 q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5zM700 900v-300h300v300h-300z" /> +<glyph unicode="" d="M50 1200h400q21 0 35.5 -14.5t14.5 -35.5v-900q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5v900q0 21 14.5 35.5t35.5 14.5zM650 700h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5v400 q0 21 14.5 35.5t35.5 14.5zM700 600v-300h300v300h-300zM1200 0h-1200v100h1200v-100z" /> +<glyph unicode="" d="M50 1000h400q21 0 35.5 -14.5t14.5 -35.5v-350h100v150q0 21 14.5 35.5t35.5 14.5h400q21 0 35.5 -14.5t14.5 -35.5v-150h100v-100h-100v-150q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5v150h-100v-350q0 -21 -14.5 -35.5t-35.5 -14.5h-400 q-21 0 -35.5 14.5t-14.5 35.5v800q0 21 14.5 35.5t35.5 14.5zM700 700v-300h300v300h-300z" /> +<glyph unicode="" d="M100 0h-100v1200h100v-1200zM250 1100h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5zM300 1000v-300h300v300h-300zM250 500h900q21 0 35.5 -14.5t14.5 -35.5v-400 q0 -21 -14.5 -35.5t-35.5 -14.5h-900q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5z" /> +<glyph unicode="" d="M600 1100h150q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-150v-100h450q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-900q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5h350v100h-150q-21 0 -35.5 14.5 t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5h150v100h100v-100zM400 1000v-300h300v300h-300z" /> +<glyph unicode="" d="M1200 0h-100v1200h100v-1200zM550 1100h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5zM600 1000v-300h300v300h-300zM50 500h900q21 0 35.5 -14.5t14.5 -35.5v-400 q0 -21 -14.5 -35.5t-35.5 -14.5h-900q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5z" /> +<glyph unicode="" d="M865 565l-494 -494q-23 -23 -41 -23q-14 0 -22 13.5t-8 38.5v1000q0 25 8 38.5t22 13.5q18 0 41 -23l494 -494q14 -14 14 -35t-14 -35z" /> +<glyph unicode="" d="M335 635l494 494q29 29 50 20.5t21 -49.5v-1000q0 -41 -21 -49.5t-50 20.5l-494 494q-14 14 -14 35t14 35z" /> +<glyph unicode="" d="M100 900h1000q41 0 49.5 -21t-20.5 -50l-494 -494q-14 -14 -35 -14t-35 14l-494 494q-29 29 -20.5 50t49.5 21z" /> +<glyph unicode="" d="M635 865l494 -494q29 -29 20.5 -50t-49.5 -21h-1000q-41 0 -49.5 21t20.5 50l494 494q14 14 35 14t35 -14z" /> +<glyph unicode="" d="M700 741v-182l-692 -323v221l413 193l-413 193v221zM1200 0h-800v200h800v-200z" /> +<glyph unicode="" d="M1200 900h-200v-100h200v-100h-300v300h200v100h-200v100h300v-300zM0 700h50q0 21 4 37t9.5 26.5t18 17.5t22 11t28.5 5.5t31 2t37 0.5h100v-550q0 -22 -25 -34.5t-50 -13.5l-25 -2v-100h400v100q-4 0 -11 0.5t-24 3t-30 7t-24 15t-11 24.5v550h100q25 0 37 -0.5t31 -2 t28.5 -5.5t22 -11t18 -17.5t9.5 -26.5t4 -37h50v300h-800v-300z" /> +<glyph unicode="" d="M800 700h-50q0 21 -4 37t-9.5 26.5t-18 17.5t-22 11t-28.5 5.5t-31 2t-37 0.5h-100v-550q0 -22 25 -34.5t50 -14.5l25 -1v-100h-400v100q4 0 11 0.5t24 3t30 7t24 15t11 24.5v550h-100q-25 0 -37 -0.5t-31 -2t-28.5 -5.5t-22 -11t-18 -17.5t-9.5 -26.5t-4 -37h-50v300 h800v-300zM1100 200h-200v-100h200v-100h-300v300h200v100h-200v100h300v-300z" /> +<glyph unicode="" d="M701 1098h160q16 0 21 -11t-7 -23l-464 -464l464 -464q12 -12 7 -23t-21 -11h-160q-13 0 -23 9l-471 471q-7 8 -7 18t7 18l471 471q10 9 23 9z" /> +<glyph unicode="" d="M339 1098h160q13 0 23 -9l471 -471q7 -8 7 -18t-7 -18l-471 -471q-10 -9 -23 -9h-160q-16 0 -21 11t7 23l464 464l-464 464q-12 12 -7 23t21 11z" /> +<glyph unicode="" d="M1087 882q11 -5 11 -21v-160q0 -13 -9 -23l-471 -471q-8 -7 -18 -7t-18 7l-471 471q-9 10 -9 23v160q0 16 11 21t23 -7l464 -464l464 464q12 12 23 7z" /> +<glyph unicode="" d="M618 993l471 -471q9 -10 9 -23v-160q0 -16 -11 -21t-23 7l-464 464l-464 -464q-12 -12 -23 -7t-11 21v160q0 13 9 23l471 471q8 7 18 7t18 -7z" /> +<glyph unicode="" d="M1000 1200q0 -124 -88 -212t-212 -88q0 124 88 212t212 88zM450 1000h100q21 0 40 -14t26 -33l79 -194q5 1 16 3q34 6 54 9.5t60 7t65.5 1t61 -10t56.5 -23t42.5 -42t29 -64t5 -92t-19.5 -121.5q-1 -7 -3 -19.5t-11 -50t-20.5 -73t-32.5 -81.5t-46.5 -83t-64 -70 t-82.5 -50q-13 -5 -42 -5t-65.5 2.5t-47.5 2.5q-14 0 -49.5 -3.5t-63 -3.5t-43.5 7q-57 25 -104.5 78.5t-75 111.5t-46.5 112t-26 90l-7 35q-15 63 -18 115t4.5 88.5t26 64t39.5 43.5t52 25.5t58.5 13t62.5 2t59.5 -4.5t55.5 -8l-147 192q-12 18 -5.5 30t27.5 12z" /> +<glyph unicode="🔑" d="M250 1200h600q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-150v-500l-255 -178q-19 -9 -32 -1t-13 29v650h-150q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5zM400 1100v-100h300v100h-300z" /> +<glyph unicode="🚪" d="M250 1200h750q39 0 69.5 -40.5t30.5 -84.5v-933l-700 -117v950l600 125h-700v-1000h-100v1025q0 23 15.5 49t34.5 26zM500 525v-100l100 20v100z" /> </font> </defs></svg>
\ No newline at end of file diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/fonts/glyphicons-halflings-regular.ttf b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/fonts/glyphicons-halflings-regular.ttf Binary files differindex a498ef4e7..1413fc609 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/fonts/glyphicons-halflings-regular.ttf +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/fonts/glyphicons-halflings-regular.ttf diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/fonts/glyphicons-halflings-regular.woff b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/fonts/glyphicons-halflings-regular.woff Binary files differindex d83c539b8..9e612858f 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/fonts/glyphicons-halflings-regular.woff +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/fonts/glyphicons-halflings-regular.woff diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/fonts/glyphicons-halflings-regular.woff2 b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/fonts/glyphicons-halflings-regular.woff2 Binary files differnew file mode 100644 index 000000000..64539b54c --- /dev/null +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/fonts/glyphicons-halflings-regular.woff2 diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/html/layer_deps_modal.html b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/html/layer_deps_modal.html index e1dba4358..e843d8d85 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/html/layer_deps_modal.html +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/html/layer_deps_modal.html @@ -1,17 +1,21 @@ -<div id="dependencies-modal" class="modal hide fade" tabindex="-1" role="dialog" aria-hidden="false"> - <form id="dependencies-modal-form"> - <div class="modal-header"> - <button type="button" class="close" data-dismiss="modal" aria-hidden="true">x</button> - <h3><span id="title"></span> dependencies</h3> - </div> - <div class="modal-body"> - <p id="body-text"> <strong id="layer-name"></strong> depends on some layers that are not added to your project. Select the ones you want to add:</p> - <ul class="unstyled" id="dependencies-list"> - </ul> - </div> - <div class="modal-footer"> - <button class="btn btn-primary" type="submit">Add layers</button> - <button class="btn" type="reset" data-dismiss="modal">Cancel</button> - </div> - </form> -</div> +<div id="dependencies-modal" class="modal fade" tabindex="-1" role="dialog" aria-hidden="false"> + <div class="modal-dialog"> + <div class="modal-content"> + <form id="dependencies-modal-form"> + <div class="modal-header"> + <button type="button" class="close" data-dismiss="modal" aria-hidden="true">x</button> + <h3><span id="title"></span> dependencies</h3> + </div> + <div class="modal-body"> + <p id="body-text"> <strong id="layer-name"></strong> depends on some layers that are not added to your project. Select the ones you want to add:</p> + <ul class="list-unstyled" id="dependencies-list"> + </ul> + </div> + <div class="modal-footer"> + <button class="btn btn-primary" type="submit">Add layers</button> + <button class="btn btn-link" type="reset" data-dismiss="modal">Cancel</button> + </div> + </form> + </div><!-- /.modal-content --> + </div><!-- /.modal-dialog --> +</div><!-- /.modal --> diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/img/glyphicons-halflings-white.png b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/img/glyphicons-halflings-white.png Binary files differdeleted file mode 100644 index 3bf6484a2..000000000 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/img/glyphicons-halflings-white.png +++ /dev/null diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/img/glyphicons-halflings.png b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/img/glyphicons-halflings.png Binary files differdeleted file mode 100644 index a99699932..000000000 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/img/glyphicons-halflings.png +++ /dev/null diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/bootstrap.js b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/bootstrap.js new file mode 100644 index 000000000..d47d640fe --- /dev/null +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/bootstrap.js @@ -0,0 +1,2363 @@ +/*! + * Bootstrap v3.3.6 (http://getbootstrap.com) + * Copyright 2011-2016 Twitter, Inc. + * Licensed under the MIT license + */ + +if (typeof jQuery === 'undefined') { + throw new Error('Bootstrap\'s JavaScript requires jQuery') +} + ++function ($) { + 'use strict'; + var version = $.fn.jquery.split(' ')[0].split('.') + if ((version[0] < 2 && version[1] < 9) || (version[0] == 1 && version[1] == 9 && version[2] < 1) || (version[0] > 2)) { + throw new Error('Bootstrap\'s JavaScript requires jQuery version 1.9.1 or higher, but lower than version 3') + } +}(jQuery); + +/* ======================================================================== + * Bootstrap: transition.js v3.3.6 + * http://getbootstrap.com/javascript/#transitions + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // CSS TRANSITION SUPPORT (Shoutout: http://www.modernizr.com/) + // ============================================================ + + function transitionEnd() { + var el = document.createElement('bootstrap') + + var transEndEventNames = { + WebkitTransition : 'webkitTransitionEnd', + MozTransition : 'transitionend', + OTransition : 'oTransitionEnd otransitionend', + transition : 'transitionend' + } + + for (var name in transEndEventNames) { + if (el.style[name] !== undefined) { + return { end: transEndEventNames[name] } + } + } + + return false // explicit for ie8 ( ._.) + } + + // http://blog.alexmaccaw.com/css-transitions + $.fn.emulateTransitionEnd = function (duration) { + var called = false + var $el = this + $(this).one('bsTransitionEnd', function () { called = true }) + var callback = function () { if (!called) $($el).trigger($.support.transition.end) } + setTimeout(callback, duration) + return this + } + + $(function () { + $.support.transition = transitionEnd() + + if (!$.support.transition) return + + $.event.special.bsTransitionEnd = { + bindType: $.support.transition.end, + delegateType: $.support.transition.end, + handle: function (e) { + if ($(e.target).is(this)) return e.handleObj.handler.apply(this, arguments) + } + } + }) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: alert.js v3.3.6 + * http://getbootstrap.com/javascript/#alerts + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // ALERT CLASS DEFINITION + // ====================== + + var dismiss = '[data-dismiss="alert"]' + var Alert = function (el) { + $(el).on('click', dismiss, this.close) + } + + Alert.VERSION = '3.3.6' + + Alert.TRANSITION_DURATION = 150 + + Alert.prototype.close = function (e) { + var $this = $(this) + var selector = $this.attr('data-target') + + if (!selector) { + selector = $this.attr('href') + selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7 + } + + var $parent = $(selector) + + if (e) e.preventDefault() + + if (!$parent.length) { + $parent = $this.closest('.alert') + } + + $parent.trigger(e = $.Event('close.bs.alert')) + + if (e.isDefaultPrevented()) return + + $parent.removeClass('in') + + function removeElement() { + // detach from parent, fire event then clean up data + $parent.detach().trigger('closed.bs.alert').remove() + } + + $.support.transition && $parent.hasClass('fade') ? + $parent + .one('bsTransitionEnd', removeElement) + .emulateTransitionEnd(Alert.TRANSITION_DURATION) : + removeElement() + } + + + // ALERT PLUGIN DEFINITION + // ======================= + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.alert') + + if (!data) $this.data('bs.alert', (data = new Alert(this))) + if (typeof option == 'string') data[option].call($this) + }) + } + + var old = $.fn.alert + + $.fn.alert = Plugin + $.fn.alert.Constructor = Alert + + + // ALERT NO CONFLICT + // ================= + + $.fn.alert.noConflict = function () { + $.fn.alert = old + return this + } + + + // ALERT DATA-API + // ============== + + $(document).on('click.bs.alert.data-api', dismiss, Alert.prototype.close) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: button.js v3.3.6 + * http://getbootstrap.com/javascript/#buttons + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // BUTTON PUBLIC CLASS DEFINITION + // ============================== + + var Button = function (element, options) { + this.$element = $(element) + this.options = $.extend({}, Button.DEFAULTS, options) + this.isLoading = false + } + + Button.VERSION = '3.3.6' + + Button.DEFAULTS = { + loadingText: 'loading...' + } + + Button.prototype.setState = function (state) { + var d = 'disabled' + var $el = this.$element + var val = $el.is('input') ? 'val' : 'html' + var data = $el.data() + + state += 'Text' + + if (data.resetText == null) $el.data('resetText', $el[val]()) + + // push to event loop to allow forms to submit + setTimeout($.proxy(function () { + $el[val](data[state] == null ? this.options[state] : data[state]) + + if (state == 'loadingText') { + this.isLoading = true + $el.addClass(d).attr(d, d) + } else if (this.isLoading) { + this.isLoading = false + $el.removeClass(d).removeAttr(d) + } + }, this), 0) + } + + Button.prototype.toggle = function () { + var changed = true + var $parent = this.$element.closest('[data-toggle="buttons"]') + + if ($parent.length) { + var $input = this.$element.find('input') + if ($input.prop('type') == 'radio') { + if ($input.prop('checked')) changed = false + $parent.find('.active').removeClass('active') + this.$element.addClass('active') + } else if ($input.prop('type') == 'checkbox') { + if (($input.prop('checked')) !== this.$element.hasClass('active')) changed = false + this.$element.toggleClass('active') + } + $input.prop('checked', this.$element.hasClass('active')) + if (changed) $input.trigger('change') + } else { + this.$element.attr('aria-pressed', !this.$element.hasClass('active')) + this.$element.toggleClass('active') + } + } + + + // BUTTON PLUGIN DEFINITION + // ======================== + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.button') + var options = typeof option == 'object' && option + + if (!data) $this.data('bs.button', (data = new Button(this, options))) + + if (option == 'toggle') data.toggle() + else if (option) data.setState(option) + }) + } + + var old = $.fn.button + + $.fn.button = Plugin + $.fn.button.Constructor = Button + + + // BUTTON NO CONFLICT + // ================== + + $.fn.button.noConflict = function () { + $.fn.button = old + return this + } + + + // BUTTON DATA-API + // =============== + + $(document) + .on('click.bs.button.data-api', '[data-toggle^="button"]', function (e) { + var $btn = $(e.target) + if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn') + Plugin.call($btn, 'toggle') + if (!($(e.target).is('input[type="radio"]') || $(e.target).is('input[type="checkbox"]'))) e.preventDefault() + }) + .on('focus.bs.button.data-api blur.bs.button.data-api', '[data-toggle^="button"]', function (e) { + $(e.target).closest('.btn').toggleClass('focus', /^focus(in)?$/.test(e.type)) + }) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: carousel.js v3.3.6 + * http://getbootstrap.com/javascript/#carousel + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // CAROUSEL CLASS DEFINITION + // ========================= + + var Carousel = function (element, options) { + this.$element = $(element) + this.$indicators = this.$element.find('.carousel-indicators') + this.options = options + this.paused = null + this.sliding = null + this.interval = null + this.$active = null + this.$items = null + + this.options.keyboard && this.$element.on('keydown.bs.carousel', $.proxy(this.keydown, this)) + + this.options.pause == 'hover' && !('ontouchstart' in document.documentElement) && this.$element + .on('mouseenter.bs.carousel', $.proxy(this.pause, this)) + .on('mouseleave.bs.carousel', $.proxy(this.cycle, this)) + } + + Carousel.VERSION = '3.3.6' + + Carousel.TRANSITION_DURATION = 600 + + Carousel.DEFAULTS = { + interval: 5000, + pause: 'hover', + wrap: true, + keyboard: true + } + + Carousel.prototype.keydown = function (e) { + if (/input|textarea/i.test(e.target.tagName)) return + switch (e.which) { + case 37: this.prev(); break + case 39: this.next(); break + default: return + } + + e.preventDefault() + } + + Carousel.prototype.cycle = function (e) { + e || (this.paused = false) + + this.interval && clearInterval(this.interval) + + this.options.interval + && !this.paused + && (this.interval = setInterval($.proxy(this.next, this), this.options.interval)) + + return this + } + + Carousel.prototype.getItemIndex = function (item) { + this.$items = item.parent().children('.item') + return this.$items.index(item || this.$active) + } + + Carousel.prototype.getItemForDirection = function (direction, active) { + var activeIndex = this.getItemIndex(active) + var willWrap = (direction == 'prev' && activeIndex === 0) + || (direction == 'next' && activeIndex == (this.$items.length - 1)) + if (willWrap && !this.options.wrap) return active + var delta = direction == 'prev' ? -1 : 1 + var itemIndex = (activeIndex + delta) % this.$items.length + return this.$items.eq(itemIndex) + } + + Carousel.prototype.to = function (pos) { + var that = this + var activeIndex = this.getItemIndex(this.$active = this.$element.find('.item.active')) + + if (pos > (this.$items.length - 1) || pos < 0) return + + if (this.sliding) return this.$element.one('slid.bs.carousel', function () { that.to(pos) }) // yes, "slid" + if (activeIndex == pos) return this.pause().cycle() + + return this.slide(pos > activeIndex ? 'next' : 'prev', this.$items.eq(pos)) + } + + Carousel.prototype.pause = function (e) { + e || (this.paused = true) + + if (this.$element.find('.next, .prev').length && $.support.transition) { + this.$element.trigger($.support.transition.end) + this.cycle(true) + } + + this.interval = clearInterval(this.interval) + + return this + } + + Carousel.prototype.next = function () { + if (this.sliding) return + return this.slide('next') + } + + Carousel.prototype.prev = function () { + if (this.sliding) return + return this.slide('prev') + } + + Carousel.prototype.slide = function (type, next) { + var $active = this.$element.find('.item.active') + var $next = next || this.getItemForDirection(type, $active) + var isCycling = this.interval + var direction = type == 'next' ? 'left' : 'right' + var that = this + + if ($next.hasClass('active')) return (this.sliding = false) + + var relatedTarget = $next[0] + var slideEvent = $.Event('slide.bs.carousel', { + relatedTarget: relatedTarget, + direction: direction + }) + this.$element.trigger(slideEvent) + if (slideEvent.isDefaultPrevented()) return + + this.sliding = true + + isCycling && this.pause() + + if (this.$indicators.length) { + this.$indicators.find('.active').removeClass('active') + var $nextIndicator = $(this.$indicators.children()[this.getItemIndex($next)]) + $nextIndicator && $nextIndicator.addClass('active') + } + + var slidEvent = $.Event('slid.bs.carousel', { relatedTarget: relatedTarget, direction: direction }) // yes, "slid" + if ($.support.transition && this.$element.hasClass('slide')) { + $next.addClass(type) + $next[0].offsetWidth // force reflow + $active.addClass(direction) + $next.addClass(direction) + $active + .one('bsTransitionEnd', function () { + $next.removeClass([type, direction].join(' ')).addClass('active') + $active.removeClass(['active', direction].join(' ')) + that.sliding = false + setTimeout(function () { + that.$element.trigger(slidEvent) + }, 0) + }) + .emulateTransitionEnd(Carousel.TRANSITION_DURATION) + } else { + $active.removeClass('active') + $next.addClass('active') + this.sliding = false + this.$element.trigger(slidEvent) + } + + isCycling && this.cycle() + + return this + } + + + // CAROUSEL PLUGIN DEFINITION + // ========================== + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.carousel') + var options = $.extend({}, Carousel.DEFAULTS, $this.data(), typeof option == 'object' && option) + var action = typeof option == 'string' ? option : options.slide + + if (!data) $this.data('bs.carousel', (data = new Carousel(this, options))) + if (typeof option == 'number') data.to(option) + else if (action) data[action]() + else if (options.interval) data.pause().cycle() + }) + } + + var old = $.fn.carousel + + $.fn.carousel = Plugin + $.fn.carousel.Constructor = Carousel + + + // CAROUSEL NO CONFLICT + // ==================== + + $.fn.carousel.noConflict = function () { + $.fn.carousel = old + return this + } + + + // CAROUSEL DATA-API + // ================= + + var clickHandler = function (e) { + var href + var $this = $(this) + var $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) // strip for ie7 + if (!$target.hasClass('carousel')) return + var options = $.extend({}, $target.data(), $this.data()) + var slideIndex = $this.attr('data-slide-to') + if (slideIndex) options.interval = false + + Plugin.call($target, options) + + if (slideIndex) { + $target.data('bs.carousel').to(slideIndex) + } + + e.preventDefault() + } + + $(document) + .on('click.bs.carousel.data-api', '[data-slide]', clickHandler) + .on('click.bs.carousel.data-api', '[data-slide-to]', clickHandler) + + $(window).on('load', function () { + $('[data-ride="carousel"]').each(function () { + var $carousel = $(this) + Plugin.call($carousel, $carousel.data()) + }) + }) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: collapse.js v3.3.6 + * http://getbootstrap.com/javascript/#collapse + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // COLLAPSE PUBLIC CLASS DEFINITION + // ================================ + + var Collapse = function (element, options) { + this.$element = $(element) + this.options = $.extend({}, Collapse.DEFAULTS, options) + this.$trigger = $('[data-toggle="collapse"][href="#' + element.id + '"],' + + '[data-toggle="collapse"][data-target="#' + element.id + '"]') + this.transitioning = null + + if (this.options.parent) { + this.$parent = this.getParent() + } else { + this.addAriaAndCollapsedClass(this.$element, this.$trigger) + } + + if (this.options.toggle) this.toggle() + } + + Collapse.VERSION = '3.3.6' + + Collapse.TRANSITION_DURATION = 350 + + Collapse.DEFAULTS = { + toggle: true + } + + Collapse.prototype.dimension = function () { + var hasWidth = this.$element.hasClass('width') + return hasWidth ? 'width' : 'height' + } + + Collapse.prototype.show = function () { + if (this.transitioning || this.$element.hasClass('in')) return + + var activesData + var actives = this.$parent && this.$parent.children('.panel').children('.in, .collapsing') + + if (actives && actives.length) { + activesData = actives.data('bs.collapse') + if (activesData && activesData.transitioning) return + } + + var startEvent = $.Event('show.bs.collapse') + this.$element.trigger(startEvent) + if (startEvent.isDefaultPrevented()) return + + if (actives && actives.length) { + Plugin.call(actives, 'hide') + activesData || actives.data('bs.collapse', null) + } + + var dimension = this.dimension() + + this.$element + .removeClass('collapse') + .addClass('collapsing')[dimension](0) + .attr('aria-expanded', true) + + this.$trigger + .removeClass('collapsed') + .attr('aria-expanded', true) + + this.transitioning = 1 + + var complete = function () { + this.$element + .removeClass('collapsing') + .addClass('collapse in')[dimension]('') + this.transitioning = 0 + this.$element + .trigger('shown.bs.collapse') + } + + if (!$.support.transition) return complete.call(this) + + var scrollSize = $.camelCase(['scroll', dimension].join('-')) + + this.$element + .one('bsTransitionEnd', $.proxy(complete, this)) + .emulateTransitionEnd(Collapse.TRANSITION_DURATION)[dimension](this.$element[0][scrollSize]) + } + + Collapse.prototype.hide = function () { + if (this.transitioning || !this.$element.hasClass('in')) return + + var startEvent = $.Event('hide.bs.collapse') + this.$element.trigger(startEvent) + if (startEvent.isDefaultPrevented()) return + + var dimension = this.dimension() + + this.$element[dimension](this.$element[dimension]())[0].offsetHeight + + this.$element + .addClass('collapsing') + .removeClass('collapse in') + .attr('aria-expanded', false) + + this.$trigger + .addClass('collapsed') + .attr('aria-expanded', false) + + this.transitioning = 1 + + var complete = function () { + this.transitioning = 0 + this.$element + .removeClass('collapsing') + .addClass('collapse') + .trigger('hidden.bs.collapse') + } + + if (!$.support.transition) return complete.call(this) + + this.$element + [dimension](0) + .one('bsTransitionEnd', $.proxy(complete, this)) + .emulateTransitionEnd(Collapse.TRANSITION_DURATION) + } + + Collapse.prototype.toggle = function () { + this[this.$element.hasClass('in') ? 'hide' : 'show']() + } + + Collapse.prototype.getParent = function () { + return $(this.options.parent) + .find('[data-toggle="collapse"][data-parent="' + this.options.parent + '"]') + .each($.proxy(function (i, element) { + var $element = $(element) + this.addAriaAndCollapsedClass(getTargetFromTrigger($element), $element) + }, this)) + .end() + } + + Collapse.prototype.addAriaAndCollapsedClass = function ($element, $trigger) { + var isOpen = $element.hasClass('in') + + $element.attr('aria-expanded', isOpen) + $trigger + .toggleClass('collapsed', !isOpen) + .attr('aria-expanded', isOpen) + } + + function getTargetFromTrigger($trigger) { + var href + var target = $trigger.attr('data-target') + || (href = $trigger.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') // strip for ie7 + + return $(target) + } + + + // COLLAPSE PLUGIN DEFINITION + // ========================== + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.collapse') + var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option == 'object' && option) + + if (!data && options.toggle && /show|hide/.test(option)) options.toggle = false + if (!data) $this.data('bs.collapse', (data = new Collapse(this, options))) + if (typeof option == 'string') data[option]() + }) + } + + var old = $.fn.collapse + + $.fn.collapse = Plugin + $.fn.collapse.Constructor = Collapse + + + // COLLAPSE NO CONFLICT + // ==================== + + $.fn.collapse.noConflict = function () { + $.fn.collapse = old + return this + } + + + // COLLAPSE DATA-API + // ================= + + $(document).on('click.bs.collapse.data-api', '[data-toggle="collapse"]', function (e) { + var $this = $(this) + + if (!$this.attr('data-target')) e.preventDefault() + + var $target = getTargetFromTrigger($this) + var data = $target.data('bs.collapse') + var option = data ? 'toggle' : $this.data() + + Plugin.call($target, option) + }) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: dropdown.js v3.3.6 + * http://getbootstrap.com/javascript/#dropdowns + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // DROPDOWN CLASS DEFINITION + // ========================= + + var backdrop = '.dropdown-backdrop' + var toggle = '[data-toggle="dropdown"]' + var Dropdown = function (element) { + $(element).on('click.bs.dropdown', this.toggle) + } + + Dropdown.VERSION = '3.3.6' + + function getParent($this) { + var selector = $this.attr('data-target') + + if (!selector) { + selector = $this.attr('href') + selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7 + } + + var $parent = selector && $(selector) + + return $parent && $parent.length ? $parent : $this.parent() + } + + function clearMenus(e) { + if (e && e.which === 3) return + $(backdrop).remove() + $(toggle).each(function () { + var $this = $(this) + var $parent = getParent($this) + var relatedTarget = { relatedTarget: this } + + if (!$parent.hasClass('open')) return + + if (e && e.type == 'click' && /input|textarea/i.test(e.target.tagName) && $.contains($parent[0], e.target)) return + + $parent.trigger(e = $.Event('hide.bs.dropdown', relatedTarget)) + + if (e.isDefaultPrevented()) return + + $this.attr('aria-expanded', 'false') + $parent.removeClass('open').trigger($.Event('hidden.bs.dropdown', relatedTarget)) + }) + } + + Dropdown.prototype.toggle = function (e) { + var $this = $(this) + + if ($this.is('.disabled, :disabled')) return + + var $parent = getParent($this) + var isActive = $parent.hasClass('open') + + clearMenus() + + if (!isActive) { + if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) { + // if mobile we use a backdrop because click events don't delegate + $(document.createElement('div')) + .addClass('dropdown-backdrop') + .insertAfter($(this)) + .on('click', clearMenus) + } + + var relatedTarget = { relatedTarget: this } + $parent.trigger(e = $.Event('show.bs.dropdown', relatedTarget)) + + if (e.isDefaultPrevented()) return + + $this + .trigger('focus') + .attr('aria-expanded', 'true') + + $parent + .toggleClass('open') + .trigger($.Event('shown.bs.dropdown', relatedTarget)) + } + + return false + } + + Dropdown.prototype.keydown = function (e) { + if (!/(38|40|27|32)/.test(e.which) || /input|textarea/i.test(e.target.tagName)) return + + var $this = $(this) + + e.preventDefault() + e.stopPropagation() + + if ($this.is('.disabled, :disabled')) return + + var $parent = getParent($this) + var isActive = $parent.hasClass('open') + + if (!isActive && e.which != 27 || isActive && e.which == 27) { + if (e.which == 27) $parent.find(toggle).trigger('focus') + return $this.trigger('click') + } + + var desc = ' li:not(.disabled):visible a' + var $items = $parent.find('.dropdown-menu' + desc) + + if (!$items.length) return + + var index = $items.index(e.target) + + if (e.which == 38 && index > 0) index-- // up + if (e.which == 40 && index < $items.length - 1) index++ // down + if (!~index) index = 0 + + $items.eq(index).trigger('focus') + } + + + // DROPDOWN PLUGIN DEFINITION + // ========================== + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.dropdown') + + if (!data) $this.data('bs.dropdown', (data = new Dropdown(this))) + if (typeof option == 'string') data[option].call($this) + }) + } + + var old = $.fn.dropdown + + $.fn.dropdown = Plugin + $.fn.dropdown.Constructor = Dropdown + + + // DROPDOWN NO CONFLICT + // ==================== + + $.fn.dropdown.noConflict = function () { + $.fn.dropdown = old + return this + } + + + // APPLY TO STANDARD DROPDOWN ELEMENTS + // =================================== + + $(document) + .on('click.bs.dropdown.data-api', clearMenus) + .on('click.bs.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() }) + .on('click.bs.dropdown.data-api', toggle, Dropdown.prototype.toggle) + .on('keydown.bs.dropdown.data-api', toggle, Dropdown.prototype.keydown) + .on('keydown.bs.dropdown.data-api', '.dropdown-menu', Dropdown.prototype.keydown) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: modal.js v3.3.6 + * http://getbootstrap.com/javascript/#modals + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // MODAL CLASS DEFINITION + // ====================== + + var Modal = function (element, options) { + this.options = options + this.$body = $(document.body) + this.$element = $(element) + this.$dialog = this.$element.find('.modal-dialog') + this.$backdrop = null + this.isShown = null + this.originalBodyPad = null + this.scrollbarWidth = 0 + this.ignoreBackdropClick = false + + if (this.options.remote) { + this.$element + .find('.modal-content') + .load(this.options.remote, $.proxy(function () { + this.$element.trigger('loaded.bs.modal') + }, this)) + } + } + + Modal.VERSION = '3.3.6' + + Modal.TRANSITION_DURATION = 300 + Modal.BACKDROP_TRANSITION_DURATION = 150 + + Modal.DEFAULTS = { + backdrop: true, + keyboard: true, + show: true + } + + Modal.prototype.toggle = function (_relatedTarget) { + return this.isShown ? this.hide() : this.show(_relatedTarget) + } + + Modal.prototype.show = function (_relatedTarget) { + var that = this + var e = $.Event('show.bs.modal', { relatedTarget: _relatedTarget }) + + this.$element.trigger(e) + + if (this.isShown || e.isDefaultPrevented()) return + + this.isShown = true + + this.checkScrollbar() + this.setScrollbar() + this.$body.addClass('modal-open') + + this.escape() + this.resize() + + this.$element.on('click.dismiss.bs.modal', '[data-dismiss="modal"]', $.proxy(this.hide, this)) + + this.$dialog.on('mousedown.dismiss.bs.modal', function () { + that.$element.one('mouseup.dismiss.bs.modal', function (e) { + if ($(e.target).is(that.$element)) that.ignoreBackdropClick = true + }) + }) + + this.backdrop(function () { + var transition = $.support.transition && that.$element.hasClass('fade') + + if (!that.$element.parent().length) { + that.$element.appendTo(that.$body) // don't move modals dom position + } + + that.$element + .show() + .scrollTop(0) + + that.adjustDialog() + + if (transition) { + that.$element[0].offsetWidth // force reflow + } + + that.$element.addClass('in') + + that.enforceFocus() + + var e = $.Event('shown.bs.modal', { relatedTarget: _relatedTarget }) + + transition ? + that.$dialog // wait for modal to slide in + .one('bsTransitionEnd', function () { + that.$element.trigger('focus').trigger(e) + }) + .emulateTransitionEnd(Modal.TRANSITION_DURATION) : + that.$element.trigger('focus').trigger(e) + }) + } + + Modal.prototype.hide = function (e) { + if (e) e.preventDefault() + + e = $.Event('hide.bs.modal') + + this.$element.trigger(e) + + if (!this.isShown || e.isDefaultPrevented()) return + + this.isShown = false + + this.escape() + this.resize() + + $(document).off('focusin.bs.modal') + + this.$element + .removeClass('in') + .off('click.dismiss.bs.modal') + .off('mouseup.dismiss.bs.modal') + + this.$dialog.off('mousedown.dismiss.bs.modal') + + $.support.transition && this.$element.hasClass('fade') ? + this.$element + .one('bsTransitionEnd', $.proxy(this.hideModal, this)) + .emulateTransitionEnd(Modal.TRANSITION_DURATION) : + this.hideModal() + } + + Modal.prototype.enforceFocus = function () { + $(document) + .off('focusin.bs.modal') // guard against infinite focus loop + .on('focusin.bs.modal', $.proxy(function (e) { + if (this.$element[0] !== e.target && !this.$element.has(e.target).length) { + this.$element.trigger('focus') + } + }, this)) + } + + Modal.prototype.escape = function () { + if (this.isShown && this.options.keyboard) { + this.$element.on('keydown.dismiss.bs.modal', $.proxy(function (e) { + e.which == 27 && this.hide() + }, this)) + } else if (!this.isShown) { + this.$element.off('keydown.dismiss.bs.modal') + } + } + + Modal.prototype.resize = function () { + if (this.isShown) { + $(window).on('resize.bs.modal', $.proxy(this.handleUpdate, this)) + } else { + $(window).off('resize.bs.modal') + } + } + + Modal.prototype.hideModal = function () { + var that = this + this.$element.hide() + this.backdrop(function () { + that.$body.removeClass('modal-open') + that.resetAdjustments() + that.resetScrollbar() + that.$element.trigger('hidden.bs.modal') + }) + } + + Modal.prototype.removeBackdrop = function () { + this.$backdrop && this.$backdrop.remove() + this.$backdrop = null + } + + Modal.prototype.backdrop = function (callback) { + var that = this + var animate = this.$element.hasClass('fade') ? 'fade' : '' + + if (this.isShown && this.options.backdrop) { + var doAnimate = $.support.transition && animate + + this.$backdrop = $(document.createElement('div')) + .addClass('modal-backdrop ' + animate) + .appendTo(this.$body) + + this.$element.on('click.dismiss.bs.modal', $.proxy(function (e) { + if (this.ignoreBackdropClick) { + this.ignoreBackdropClick = false + return + } + if (e.target !== e.currentTarget) return + this.options.backdrop == 'static' + ? this.$element[0].focus() + : this.hide() + }, this)) + + if (doAnimate) this.$backdrop[0].offsetWidth // force reflow + + this.$backdrop.addClass('in') + + if (!callback) return + + doAnimate ? + this.$backdrop + .one('bsTransitionEnd', callback) + .emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) : + callback() + + } else if (!this.isShown && this.$backdrop) { + this.$backdrop.removeClass('in') + + var callbackRemove = function () { + that.removeBackdrop() + callback && callback() + } + $.support.transition && this.$element.hasClass('fade') ? + this.$backdrop + .one('bsTransitionEnd', callbackRemove) + .emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) : + callbackRemove() + + } else if (callback) { + callback() + } + } + + // these following methods are used to handle overflowing modals + + Modal.prototype.handleUpdate = function () { + this.adjustDialog() + } + + Modal.prototype.adjustDialog = function () { + var modalIsOverflowing = this.$element[0].scrollHeight > document.documentElement.clientHeight + + this.$element.css({ + paddingLeft: !this.bodyIsOverflowing && modalIsOverflowing ? this.scrollbarWidth : '', + paddingRight: this.bodyIsOverflowing && !modalIsOverflowing ? this.scrollbarWidth : '' + }) + } + + Modal.prototype.resetAdjustments = function () { + this.$element.css({ + paddingLeft: '', + paddingRight: '' + }) + } + + Modal.prototype.checkScrollbar = function () { + var fullWindowWidth = window.innerWidth + if (!fullWindowWidth) { // workaround for missing window.innerWidth in IE8 + var documentElementRect = document.documentElement.getBoundingClientRect() + fullWindowWidth = documentElementRect.right - Math.abs(documentElementRect.left) + } + this.bodyIsOverflowing = document.body.clientWidth < fullWindowWidth + this.scrollbarWidth = this.measureScrollbar() + } + + Modal.prototype.setScrollbar = function () { + var bodyPad = parseInt((this.$body.css('padding-right') || 0), 10) + this.originalBodyPad = document.body.style.paddingRight || '' + if (this.bodyIsOverflowing) this.$body.css('padding-right', bodyPad + this.scrollbarWidth) + } + + Modal.prototype.resetScrollbar = function () { + this.$body.css('padding-right', this.originalBodyPad) + } + + Modal.prototype.measureScrollbar = function () { // thx walsh + var scrollDiv = document.createElement('div') + scrollDiv.className = 'modal-scrollbar-measure' + this.$body.append(scrollDiv) + var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth + this.$body[0].removeChild(scrollDiv) + return scrollbarWidth + } + + + // MODAL PLUGIN DEFINITION + // ======================= + + function Plugin(option, _relatedTarget) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.modal') + var options = $.extend({}, Modal.DEFAULTS, $this.data(), typeof option == 'object' && option) + + if (!data) $this.data('bs.modal', (data = new Modal(this, options))) + if (typeof option == 'string') data[option](_relatedTarget) + else if (options.show) data.show(_relatedTarget) + }) + } + + var old = $.fn.modal + + $.fn.modal = Plugin + $.fn.modal.Constructor = Modal + + + // MODAL NO CONFLICT + // ================= + + $.fn.modal.noConflict = function () { + $.fn.modal = old + return this + } + + + // MODAL DATA-API + // ============== + + $(document).on('click.bs.modal.data-api', '[data-toggle="modal"]', function (e) { + var $this = $(this) + var href = $this.attr('href') + var $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\s]+$)/, ''))) // strip for ie7 + var option = $target.data('bs.modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data()) + + if ($this.is('a')) e.preventDefault() + + $target.one('show.bs.modal', function (showEvent) { + if (showEvent.isDefaultPrevented()) return // only register focus restorer if modal will actually get shown + $target.one('hidden.bs.modal', function () { + $this.is(':visible') && $this.trigger('focus') + }) + }) + Plugin.call($target, option, this) + }) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: tooltip.js v3.3.6 + * http://getbootstrap.com/javascript/#tooltip + * Inspired by the original jQuery.tipsy by Jason Frame + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // TOOLTIP PUBLIC CLASS DEFINITION + // =============================== + + var Tooltip = function (element, options) { + this.type = null + this.options = null + this.enabled = null + this.timeout = null + this.hoverState = null + this.$element = null + this.inState = null + + this.init('tooltip', element, options) + } + + Tooltip.VERSION = '3.3.6' + + Tooltip.TRANSITION_DURATION = 150 + + Tooltip.DEFAULTS = { + animation: true, + placement: 'top', + selector: false, + template: '<div class="tooltip" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>', + trigger: 'hover focus', + title: '', + delay: 0, + html: false, + container: false, + viewport: { + selector: 'body', + padding: 0 + } + } + + Tooltip.prototype.init = function (type, element, options) { + this.enabled = true + this.type = type + this.$element = $(element) + this.options = this.getOptions(options) + this.$viewport = this.options.viewport && $($.isFunction(this.options.viewport) ? this.options.viewport.call(this, this.$element) : (this.options.viewport.selector || this.options.viewport)) + this.inState = { click: false, hover: false, focus: false } + + if (this.$element[0] instanceof document.constructor && !this.options.selector) { + throw new Error('`selector` option must be specified when initializing ' + this.type + ' on the window.document object!') + } + + var triggers = this.options.trigger.split(' ') + + for (var i = triggers.length; i--;) { + var trigger = triggers[i] + + if (trigger == 'click') { + this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this)) + } else if (trigger != 'manual') { + var eventIn = trigger == 'hover' ? 'mouseenter' : 'focusin' + var eventOut = trigger == 'hover' ? 'mouseleave' : 'focusout' + + this.$element.on(eventIn + '.' + this.type, this.options.selector, $.proxy(this.enter, this)) + this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this)) + } + } + + this.options.selector ? + (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) : + this.fixTitle() + } + + Tooltip.prototype.getDefaults = function () { + return Tooltip.DEFAULTS + } + + Tooltip.prototype.getOptions = function (options) { + options = $.extend({}, this.getDefaults(), this.$element.data(), options) + + if (options.delay && typeof options.delay == 'number') { + options.delay = { + show: options.delay, + hide: options.delay + } + } + + return options + } + + Tooltip.prototype.getDelegateOptions = function () { + var options = {} + var defaults = this.getDefaults() + + this._options && $.each(this._options, function (key, value) { + if (defaults[key] != value) options[key] = value + }) + + return options + } + + Tooltip.prototype.enter = function (obj) { + var self = obj instanceof this.constructor ? + obj : $(obj.currentTarget).data('bs.' + this.type) + + if (!self) { + self = new this.constructor(obj.currentTarget, this.getDelegateOptions()) + $(obj.currentTarget).data('bs.' + this.type, self) + } + + if (obj instanceof $.Event) { + self.inState[obj.type == 'focusin' ? 'focus' : 'hover'] = true + } + + if (self.tip().hasClass('in') || self.hoverState == 'in') { + self.hoverState = 'in' + return + } + + clearTimeout(self.timeout) + + self.hoverState = 'in' + + if (!self.options.delay || !self.options.delay.show) return self.show() + + self.timeout = setTimeout(function () { + if (self.hoverState == 'in') self.show() + }, self.options.delay.show) + } + + Tooltip.prototype.isInStateTrue = function () { + for (var key in this.inState) { + if (this.inState[key]) return true + } + + return false + } + + Tooltip.prototype.leave = function (obj) { + var self = obj instanceof this.constructor ? + obj : $(obj.currentTarget).data('bs.' + this.type) + + if (!self) { + self = new this.constructor(obj.currentTarget, this.getDelegateOptions()) + $(obj.currentTarget).data('bs.' + this.type, self) + } + + if (obj instanceof $.Event) { + self.inState[obj.type == 'focusout' ? 'focus' : 'hover'] = false + } + + if (self.isInStateTrue()) return + + clearTimeout(self.timeout) + + self.hoverState = 'out' + + if (!self.options.delay || !self.options.delay.hide) return self.hide() + + self.timeout = setTimeout(function () { + if (self.hoverState == 'out') self.hide() + }, self.options.delay.hide) + } + + Tooltip.prototype.show = function () { + var e = $.Event('show.bs.' + this.type) + + if (this.hasContent() && this.enabled) { + this.$element.trigger(e) + + var inDom = $.contains(this.$element[0].ownerDocument.documentElement, this.$element[0]) + if (e.isDefaultPrevented() || !inDom) return + var that = this + + var $tip = this.tip() + + var tipId = this.getUID(this.type) + + this.setContent() + $tip.attr('id', tipId) + this.$element.attr('aria-describedby', tipId) + + if (this.options.animation) $tip.addClass('fade') + + var placement = typeof this.options.placement == 'function' ? + this.options.placement.call(this, $tip[0], this.$element[0]) : + this.options.placement + + var autoToken = /\s?auto?\s?/i + var autoPlace = autoToken.test(placement) + if (autoPlace) placement = placement.replace(autoToken, '') || 'top' + + $tip + .detach() + .css({ top: 0, left: 0, display: 'block' }) + .addClass(placement) + .data('bs.' + this.type, this) + + this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element) + this.$element.trigger('inserted.bs.' + this.type) + + var pos = this.getPosition() + var actualWidth = $tip[0].offsetWidth + var actualHeight = $tip[0].offsetHeight + + if (autoPlace) { + var orgPlacement = placement + var viewportDim = this.getPosition(this.$viewport) + + placement = placement == 'bottom' && pos.bottom + actualHeight > viewportDim.bottom ? 'top' : + placement == 'top' && pos.top - actualHeight < viewportDim.top ? 'bottom' : + placement == 'right' && pos.right + actualWidth > viewportDim.width ? 'left' : + placement == 'left' && pos.left - actualWidth < viewportDim.left ? 'right' : + placement + + $tip + .removeClass(orgPlacement) + .addClass(placement) + } + + var calculatedOffset = this.getCalculatedOffset(placement, pos, actualWidth, actualHeight) + + this.applyPlacement(calculatedOffset, placement) + + var complete = function () { + var prevHoverState = that.hoverState + that.$element.trigger('shown.bs.' + that.type) + that.hoverState = null + + if (prevHoverState == 'out') that.leave(that) + } + + $.support.transition && this.$tip.hasClass('fade') ? + $tip + .one('bsTransitionEnd', complete) + .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) : + complete() + } + } + + Tooltip.prototype.applyPlacement = function (offset, placement) { + var $tip = this.tip() + var width = $tip[0].offsetWidth + var height = $tip[0].offsetHeight + + // manually read margins because getBoundingClientRect includes difference + var marginTop = parseInt($tip.css('margin-top'), 10) + var marginLeft = parseInt($tip.css('margin-left'), 10) + + // we must check for NaN for ie 8/9 + if (isNaN(marginTop)) marginTop = 0 + if (isNaN(marginLeft)) marginLeft = 0 + + offset.top += marginTop + offset.left += marginLeft + + // $.fn.offset doesn't round pixel values + // so we use setOffset directly with our own function B-0 + $.offset.setOffset($tip[0], $.extend({ + using: function (props) { + $tip.css({ + top: Math.round(props.top), + left: Math.round(props.left) + }) + } + }, offset), 0) + + $tip.addClass('in') + + // check to see if placing tip in new offset caused the tip to resize itself + var actualWidth = $tip[0].offsetWidth + var actualHeight = $tip[0].offsetHeight + + if (placement == 'top' && actualHeight != height) { + offset.top = offset.top + height - actualHeight + } + + var delta = this.getViewportAdjustedDelta(placement, offset, actualWidth, actualHeight) + + if (delta.left) offset.left += delta.left + else offset.top += delta.top + + var isVertical = /top|bottom/.test(placement) + var arrowDelta = isVertical ? delta.left * 2 - width + actualWidth : delta.top * 2 - height + actualHeight + var arrowOffsetPosition = isVertical ? 'offsetWidth' : 'offsetHeight' + + $tip.offset(offset) + this.replaceArrow(arrowDelta, $tip[0][arrowOffsetPosition], isVertical) + } + + Tooltip.prototype.replaceArrow = function (delta, dimension, isVertical) { + this.arrow() + .css(isVertical ? 'left' : 'top', 50 * (1 - delta / dimension) + '%') + .css(isVertical ? 'top' : 'left', '') + } + + Tooltip.prototype.setContent = function () { + var $tip = this.tip() + var title = this.getTitle() + + $tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title) + $tip.removeClass('fade in top bottom left right') + } + + Tooltip.prototype.hide = function (callback) { + var that = this + var $tip = $(this.$tip) + var e = $.Event('hide.bs.' + this.type) + + function complete() { + if (that.hoverState != 'in') $tip.detach() + that.$element + .removeAttr('aria-describedby') + .trigger('hidden.bs.' + that.type) + callback && callback() + } + + this.$element.trigger(e) + + if (e.isDefaultPrevented()) return + + $tip.removeClass('in') + + $.support.transition && $tip.hasClass('fade') ? + $tip + .one('bsTransitionEnd', complete) + .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) : + complete() + + this.hoverState = null + + return this + } + + Tooltip.prototype.fixTitle = function () { + var $e = this.$element + if ($e.attr('title') || typeof $e.attr('data-original-title') != 'string') { + $e.attr('data-original-title', $e.attr('title') || '').attr('title', '') + } + } + + Tooltip.prototype.hasContent = function () { + return this.getTitle() + } + + Tooltip.prototype.getPosition = function ($element) { + $element = $element || this.$element + + var el = $element[0] + var isBody = el.tagName == 'BODY' + + var elRect = el.getBoundingClientRect() + if (elRect.width == null) { + // width and height are missing in IE8, so compute them manually; see https://github.com/twbs/bootstrap/issues/14093 + elRect = $.extend({}, elRect, { width: elRect.right - elRect.left, height: elRect.bottom - elRect.top }) + } + var elOffset = isBody ? { top: 0, left: 0 } : $element.offset() + var scroll = { scroll: isBody ? document.documentElement.scrollTop || document.body.scrollTop : $element.scrollTop() } + var outerDims = isBody ? { width: $(window).width(), height: $(window).height() } : null + + return $.extend({}, elRect, scroll, outerDims, elOffset) + } + + Tooltip.prototype.getCalculatedOffset = function (placement, pos, actualWidth, actualHeight) { + return placement == 'bottom' ? { top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2 } : + placement == 'top' ? { top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2 } : + placement == 'left' ? { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth } : + /* placement == 'right' */ { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width } + + } + + Tooltip.prototype.getViewportAdjustedDelta = function (placement, pos, actualWidth, actualHeight) { + var delta = { top: 0, left: 0 } + if (!this.$viewport) return delta + + var viewportPadding = this.options.viewport && this.options.viewport.padding || 0 + var viewportDimensions = this.getPosition(this.$viewport) + + if (/right|left/.test(placement)) { + var topEdgeOffset = pos.top - viewportPadding - viewportDimensions.scroll + var bottomEdgeOffset = pos.top + viewportPadding - viewportDimensions.scroll + actualHeight + if (topEdgeOffset < viewportDimensions.top) { // top overflow + delta.top = viewportDimensions.top - topEdgeOffset + } else if (bottomEdgeOffset > viewportDimensions.top + viewportDimensions.height) { // bottom overflow + delta.top = viewportDimensions.top + viewportDimensions.height - bottomEdgeOffset + } + } else { + var leftEdgeOffset = pos.left - viewportPadding + var rightEdgeOffset = pos.left + viewportPadding + actualWidth + if (leftEdgeOffset < viewportDimensions.left) { // left overflow + delta.left = viewportDimensions.left - leftEdgeOffset + } else if (rightEdgeOffset > viewportDimensions.right) { // right overflow + delta.left = viewportDimensions.left + viewportDimensions.width - rightEdgeOffset + } + } + + return delta + } + + Tooltip.prototype.getTitle = function () { + var title + var $e = this.$element + var o = this.options + + title = $e.attr('data-original-title') + || (typeof o.title == 'function' ? o.title.call($e[0]) : o.title) + + return title + } + + Tooltip.prototype.getUID = function (prefix) { + do prefix += ~~(Math.random() * 1000000) + while (document.getElementById(prefix)) + return prefix + } + + Tooltip.prototype.tip = function () { + if (!this.$tip) { + this.$tip = $(this.options.template) + if (this.$tip.length != 1) { + throw new Error(this.type + ' `template` option must consist of exactly 1 top-level element!') + } + } + return this.$tip + } + + Tooltip.prototype.arrow = function () { + return (this.$arrow = this.$arrow || this.tip().find('.tooltip-arrow')) + } + + Tooltip.prototype.enable = function () { + this.enabled = true + } + + Tooltip.prototype.disable = function () { + this.enabled = false + } + + Tooltip.prototype.toggleEnabled = function () { + this.enabled = !this.enabled + } + + Tooltip.prototype.toggle = function (e) { + var self = this + if (e) { + self = $(e.currentTarget).data('bs.' + this.type) + if (!self) { + self = new this.constructor(e.currentTarget, this.getDelegateOptions()) + $(e.currentTarget).data('bs.' + this.type, self) + } + } + + if (e) { + self.inState.click = !self.inState.click + if (self.isInStateTrue()) self.enter(self) + else self.leave(self) + } else { + self.tip().hasClass('in') ? self.leave(self) : self.enter(self) + } + } + + Tooltip.prototype.destroy = function () { + var that = this + clearTimeout(this.timeout) + this.hide(function () { + that.$element.off('.' + that.type).removeData('bs.' + that.type) + if (that.$tip) { + that.$tip.detach() + } + that.$tip = null + that.$arrow = null + that.$viewport = null + }) + } + + + // TOOLTIP PLUGIN DEFINITION + // ========================= + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.tooltip') + var options = typeof option == 'object' && option + + if (!data && /destroy|hide/.test(option)) return + if (!data) $this.data('bs.tooltip', (data = new Tooltip(this, options))) + if (typeof option == 'string') data[option]() + }) + } + + var old = $.fn.tooltip + + $.fn.tooltip = Plugin + $.fn.tooltip.Constructor = Tooltip + + + // TOOLTIP NO CONFLICT + // =================== + + $.fn.tooltip.noConflict = function () { + $.fn.tooltip = old + return this + } + +}(jQuery); + +/* ======================================================================== + * Bootstrap: popover.js v3.3.6 + * http://getbootstrap.com/javascript/#popovers + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // POPOVER PUBLIC CLASS DEFINITION + // =============================== + + var Popover = function (element, options) { + this.init('popover', element, options) + } + + if (!$.fn.tooltip) throw new Error('Popover requires tooltip.js') + + Popover.VERSION = '3.3.6' + + Popover.DEFAULTS = $.extend({}, $.fn.tooltip.Constructor.DEFAULTS, { + placement: 'right', + trigger: 'click', + content: '', + template: '<div class="popover" role="tooltip"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>' + }) + + + // NOTE: POPOVER EXTENDS tooltip.js + // ================================ + + Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype) + + Popover.prototype.constructor = Popover + + Popover.prototype.getDefaults = function () { + return Popover.DEFAULTS + } + + Popover.prototype.setContent = function () { + var $tip = this.tip() + var title = this.getTitle() + var content = this.getContent() + + $tip.find('.popover-title')[this.options.html ? 'html' : 'text'](title) + $tip.find('.popover-content').children().detach().end()[ // we use append for html objects to maintain js events + this.options.html ? (typeof content == 'string' ? 'html' : 'append') : 'text' + ](content) + + $tip.removeClass('fade top bottom left right in') + + // IE8 doesn't accept hiding via the `:empty` pseudo selector, we have to do + // this manually by checking the contents. + if (!$tip.find('.popover-title').html()) $tip.find('.popover-title').hide() + } + + Popover.prototype.hasContent = function () { + return this.getTitle() || this.getContent() + } + + Popover.prototype.getContent = function () { + var $e = this.$element + var o = this.options + + return $e.attr('data-content') + || (typeof o.content == 'function' ? + o.content.call($e[0]) : + o.content) + } + + Popover.prototype.arrow = function () { + return (this.$arrow = this.$arrow || this.tip().find('.arrow')) + } + + + // POPOVER PLUGIN DEFINITION + // ========================= + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.popover') + var options = typeof option == 'object' && option + + if (!data && /destroy|hide/.test(option)) return + if (!data) $this.data('bs.popover', (data = new Popover(this, options))) + if (typeof option == 'string') data[option]() + }) + } + + var old = $.fn.popover + + $.fn.popover = Plugin + $.fn.popover.Constructor = Popover + + + // POPOVER NO CONFLICT + // =================== + + $.fn.popover.noConflict = function () { + $.fn.popover = old + return this + } + +}(jQuery); + +/* ======================================================================== + * Bootstrap: scrollspy.js v3.3.6 + * http://getbootstrap.com/javascript/#scrollspy + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // SCROLLSPY CLASS DEFINITION + // ========================== + + function ScrollSpy(element, options) { + this.$body = $(document.body) + this.$scrollElement = $(element).is(document.body) ? $(window) : $(element) + this.options = $.extend({}, ScrollSpy.DEFAULTS, options) + this.selector = (this.options.target || '') + ' .nav li > a' + this.offsets = [] + this.targets = [] + this.activeTarget = null + this.scrollHeight = 0 + + this.$scrollElement.on('scroll.bs.scrollspy', $.proxy(this.process, this)) + this.refresh() + this.process() + } + + ScrollSpy.VERSION = '3.3.6' + + ScrollSpy.DEFAULTS = { + offset: 10 + } + + ScrollSpy.prototype.getScrollHeight = function () { + return this.$scrollElement[0].scrollHeight || Math.max(this.$body[0].scrollHeight, document.documentElement.scrollHeight) + } + + ScrollSpy.prototype.refresh = function () { + var that = this + var offsetMethod = 'offset' + var offsetBase = 0 + + this.offsets = [] + this.targets = [] + this.scrollHeight = this.getScrollHeight() + + if (!$.isWindow(this.$scrollElement[0])) { + offsetMethod = 'position' + offsetBase = this.$scrollElement.scrollTop() + } + + this.$body + .find(this.selector) + .map(function () { + var $el = $(this) + var href = $el.data('target') || $el.attr('href') + var $href = /^#./.test(href) && $(href) + + return ($href + && $href.length + && $href.is(':visible') + && [[$href[offsetMethod]().top + offsetBase, href]]) || null + }) + .sort(function (a, b) { return a[0] - b[0] }) + .each(function () { + that.offsets.push(this[0]) + that.targets.push(this[1]) + }) + } + + ScrollSpy.prototype.process = function () { + var scrollTop = this.$scrollElement.scrollTop() + this.options.offset + var scrollHeight = this.getScrollHeight() + var maxScroll = this.options.offset + scrollHeight - this.$scrollElement.height() + var offsets = this.offsets + var targets = this.targets + var activeTarget = this.activeTarget + var i + + if (this.scrollHeight != scrollHeight) { + this.refresh() + } + + if (scrollTop >= maxScroll) { + return activeTarget != (i = targets[targets.length - 1]) && this.activate(i) + } + + if (activeTarget && scrollTop < offsets[0]) { + this.activeTarget = null + return this.clear() + } + + for (i = offsets.length; i--;) { + activeTarget != targets[i] + && scrollTop >= offsets[i] + && (offsets[i + 1] === undefined || scrollTop < offsets[i + 1]) + && this.activate(targets[i]) + } + } + + ScrollSpy.prototype.activate = function (target) { + this.activeTarget = target + + this.clear() + + var selector = this.selector + + '[data-target="' + target + '"],' + + this.selector + '[href="' + target + '"]' + + var active = $(selector) + .parents('li') + .addClass('active') + + if (active.parent('.dropdown-menu').length) { + active = active + .closest('li.dropdown') + .addClass('active') + } + + active.trigger('activate.bs.scrollspy') + } + + ScrollSpy.prototype.clear = function () { + $(this.selector) + .parentsUntil(this.options.target, '.active') + .removeClass('active') + } + + + // SCROLLSPY PLUGIN DEFINITION + // =========================== + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.scrollspy') + var options = typeof option == 'object' && option + + if (!data) $this.data('bs.scrollspy', (data = new ScrollSpy(this, options))) + if (typeof option == 'string') data[option]() + }) + } + + var old = $.fn.scrollspy + + $.fn.scrollspy = Plugin + $.fn.scrollspy.Constructor = ScrollSpy + + + // SCROLLSPY NO CONFLICT + // ===================== + + $.fn.scrollspy.noConflict = function () { + $.fn.scrollspy = old + return this + } + + + // SCROLLSPY DATA-API + // ================== + + $(window).on('load.bs.scrollspy.data-api', function () { + $('[data-spy="scroll"]').each(function () { + var $spy = $(this) + Plugin.call($spy, $spy.data()) + }) + }) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: tab.js v3.3.6 + * http://getbootstrap.com/javascript/#tabs + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // TAB CLASS DEFINITION + // ==================== + + var Tab = function (element) { + // jscs:disable requireDollarBeforejQueryAssignment + this.element = $(element) + // jscs:enable requireDollarBeforejQueryAssignment + } + + Tab.VERSION = '3.3.6' + + Tab.TRANSITION_DURATION = 150 + + Tab.prototype.show = function () { + var $this = this.element + var $ul = $this.closest('ul:not(.dropdown-menu)') + var selector = $this.data('target') + + if (!selector) { + selector = $this.attr('href') + selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7 + } + + if ($this.parent('li').hasClass('active')) return + + var $previous = $ul.find('.active:last a') + var hideEvent = $.Event('hide.bs.tab', { + relatedTarget: $this[0] + }) + var showEvent = $.Event('show.bs.tab', { + relatedTarget: $previous[0] + }) + + $previous.trigger(hideEvent) + $this.trigger(showEvent) + + if (showEvent.isDefaultPrevented() || hideEvent.isDefaultPrevented()) return + + var $target = $(selector) + + this.activate($this.closest('li'), $ul) + this.activate($target, $target.parent(), function () { + $previous.trigger({ + type: 'hidden.bs.tab', + relatedTarget: $this[0] + }) + $this.trigger({ + type: 'shown.bs.tab', + relatedTarget: $previous[0] + }) + }) + } + + Tab.prototype.activate = function (element, container, callback) { + var $active = container.find('> .active') + var transition = callback + && $.support.transition + && ($active.length && $active.hasClass('fade') || !!container.find('> .fade').length) + + function next() { + $active + .removeClass('active') + .find('> .dropdown-menu > .active') + .removeClass('active') + .end() + .find('[data-toggle="tab"]') + .attr('aria-expanded', false) + + element + .addClass('active') + .find('[data-toggle="tab"]') + .attr('aria-expanded', true) + + if (transition) { + element[0].offsetWidth // reflow for transition + element.addClass('in') + } else { + element.removeClass('fade') + } + + if (element.parent('.dropdown-menu').length) { + element + .closest('li.dropdown') + .addClass('active') + .end() + .find('[data-toggle="tab"]') + .attr('aria-expanded', true) + } + + callback && callback() + } + + $active.length && transition ? + $active + .one('bsTransitionEnd', next) + .emulateTransitionEnd(Tab.TRANSITION_DURATION) : + next() + + $active.removeClass('in') + } + + + // TAB PLUGIN DEFINITION + // ===================== + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.tab') + + if (!data) $this.data('bs.tab', (data = new Tab(this))) + if (typeof option == 'string') data[option]() + }) + } + + var old = $.fn.tab + + $.fn.tab = Plugin + $.fn.tab.Constructor = Tab + + + // TAB NO CONFLICT + // =============== + + $.fn.tab.noConflict = function () { + $.fn.tab = old + return this + } + + + // TAB DATA-API + // ============ + + var clickHandler = function (e) { + e.preventDefault() + Plugin.call($(this), 'show') + } + + $(document) + .on('click.bs.tab.data-api', '[data-toggle="tab"]', clickHandler) + .on('click.bs.tab.data-api', '[data-toggle="pill"]', clickHandler) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: affix.js v3.3.6 + * http://getbootstrap.com/javascript/#affix + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // AFFIX CLASS DEFINITION + // ====================== + + var Affix = function (element, options) { + this.options = $.extend({}, Affix.DEFAULTS, options) + + this.$target = $(this.options.target) + .on('scroll.bs.affix.data-api', $.proxy(this.checkPosition, this)) + .on('click.bs.affix.data-api', $.proxy(this.checkPositionWithEventLoop, this)) + + this.$element = $(element) + this.affixed = null + this.unpin = null + this.pinnedOffset = null + + this.checkPosition() + } + + Affix.VERSION = '3.3.6' + + Affix.RESET = 'affix affix-top affix-bottom' + + Affix.DEFAULTS = { + offset: 0, + target: window + } + + Affix.prototype.getState = function (scrollHeight, height, offsetTop, offsetBottom) { + var scrollTop = this.$target.scrollTop() + var position = this.$element.offset() + var targetHeight = this.$target.height() + + if (offsetTop != null && this.affixed == 'top') return scrollTop < offsetTop ? 'top' : false + + if (this.affixed == 'bottom') { + if (offsetTop != null) return (scrollTop + this.unpin <= position.top) ? false : 'bottom' + return (scrollTop + targetHeight <= scrollHeight - offsetBottom) ? false : 'bottom' + } + + var initializing = this.affixed == null + var colliderTop = initializing ? scrollTop : position.top + var colliderHeight = initializing ? targetHeight : height + + if (offsetTop != null && scrollTop <= offsetTop) return 'top' + if (offsetBottom != null && (colliderTop + colliderHeight >= scrollHeight - offsetBottom)) return 'bottom' + + return false + } + + Affix.prototype.getPinnedOffset = function () { + if (this.pinnedOffset) return this.pinnedOffset + this.$element.removeClass(Affix.RESET).addClass('affix') + var scrollTop = this.$target.scrollTop() + var position = this.$element.offset() + return (this.pinnedOffset = position.top - scrollTop) + } + + Affix.prototype.checkPositionWithEventLoop = function () { + setTimeout($.proxy(this.checkPosition, this), 1) + } + + Affix.prototype.checkPosition = function () { + if (!this.$element.is(':visible')) return + + var height = this.$element.height() + var offset = this.options.offset + var offsetTop = offset.top + var offsetBottom = offset.bottom + var scrollHeight = Math.max($(document).height(), $(document.body).height()) + + if (typeof offset != 'object') offsetBottom = offsetTop = offset + if (typeof offsetTop == 'function') offsetTop = offset.top(this.$element) + if (typeof offsetBottom == 'function') offsetBottom = offset.bottom(this.$element) + + var affix = this.getState(scrollHeight, height, offsetTop, offsetBottom) + + if (this.affixed != affix) { + if (this.unpin != null) this.$element.css('top', '') + + var affixType = 'affix' + (affix ? '-' + affix : '') + var e = $.Event(affixType + '.bs.affix') + + this.$element.trigger(e) + + if (e.isDefaultPrevented()) return + + this.affixed = affix + this.unpin = affix == 'bottom' ? this.getPinnedOffset() : null + + this.$element + .removeClass(Affix.RESET) + .addClass(affixType) + .trigger(affixType.replace('affix', 'affixed') + '.bs.affix') + } + + if (affix == 'bottom') { + this.$element.offset({ + top: scrollHeight - height - offsetBottom + }) + } + } + + + // AFFIX PLUGIN DEFINITION + // ======================= + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.affix') + var options = typeof option == 'object' && option + + if (!data) $this.data('bs.affix', (data = new Affix(this, options))) + if (typeof option == 'string') data[option]() + }) + } + + var old = $.fn.affix + + $.fn.affix = Plugin + $.fn.affix.Constructor = Affix + + + // AFFIX NO CONFLICT + // ================= + + $.fn.affix.noConflict = function () { + $.fn.affix = old + return this + } + + + // AFFIX DATA-API + // ============== + + $(window).on('load', function () { + $('[data-spy="affix"]').each(function () { + var $spy = $(this) + var data = $spy.data() + + data.offset = data.offset || {} + + if (data.offsetBottom != null) data.offset.bottom = data.offsetBottom + if (data.offsetTop != null) data.offset.top = data.offsetTop + + Plugin.call($spy, data) + }) + }) + +}(jQuery); diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/bootstrap.min.js b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/bootstrap.min.js index 848258d38..c4a924160 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/bootstrap.min.js +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/bootstrap.min.js @@ -1,6 +1,7 @@ /*! -* Bootstrap.js by @fat & @mdo -* Copyright 2013 Twitter, Inc. -* http://www.apache.org/licenses/LICENSE-2.0.txt -*/ -!function(e){"use strict";e(function(){e.support.transition=function(){var e=function(){var e=document.createElement("bootstrap"),t={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"},n;for(n in t)if(e.style[n]!==undefined)return t[n]}();return e&&{end:e}}()})}(window.jQuery),!function(e){"use strict";var t='[data-dismiss="alert"]',n=function(n){e(n).on("click",t,this.close)};n.prototype.close=function(t){function s(){i.trigger("closed").remove()}var n=e(this),r=n.attr("data-target"),i;r||(r=n.attr("href"),r=r&&r.replace(/.*(?=#[^\s]*$)/,"")),i=e(r),t&&t.preventDefault(),i.length||(i=n.hasClass("alert")?n:n.parent()),i.trigger(t=e.Event("close"));if(t.isDefaultPrevented())return;i.removeClass("in"),e.support.transition&&i.hasClass("fade")?i.on(e.support.transition.end,s):s()};var r=e.fn.alert;e.fn.alert=function(t){return this.each(function(){var r=e(this),i=r.data("alert");i||r.data("alert",i=new n(this)),typeof t=="string"&&i[t].call(r)})},e.fn.alert.Constructor=n,e.fn.alert.noConflict=function(){return e.fn.alert=r,this},e(document).on("click.alert.data-api",t,n.prototype.close)}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.button.defaults,n)};t.prototype.setState=function(e){var t="disabled",n=this.$element,r=n.data(),i=n.is("input")?"val":"html";e+="Text",r.resetText||n.data("resetText",n[i]()),n[i](r[e]||this.options[e]),setTimeout(function(){e=="loadingText"?n.addClass(t).attr(t,t):n.removeClass(t).removeAttr(t)},0)},t.prototype.toggle=function(){var e=this.$element.closest('[data-toggle="buttons-radio"]');e&&e.find(".active").removeClass("active"),this.$element.toggleClass("active")};var n=e.fn.button;e.fn.button=function(n){return this.each(function(){var r=e(this),i=r.data("button"),s=typeof n=="object"&&n;i||r.data("button",i=new t(this,s)),n=="toggle"?i.toggle():n&&i.setState(n)})},e.fn.button.defaults={loadingText:"loading..."},e.fn.button.Constructor=t,e.fn.button.noConflict=function(){return e.fn.button=n,this},e(document).on("click.button.data-api","[data-toggle^=button]",function(t){var n=e(t.target);n.hasClass("btn")||(n=n.closest(".btn")),n.button("toggle")})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.$indicators=this.$element.find(".carousel-indicators"),this.options=n,this.options.pause=="hover"&&this.$element.on("mouseenter",e.proxy(this.pause,this)).on("mouseleave",e.proxy(this.cycle,this))};t.prototype={cycle:function(t){return t||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(e.proxy(this.next,this),this.options.interval)),this},getActiveIndex:function(){return this.$active=this.$element.find(".item.active"),this.$items=this.$active.parent().children(),this.$items.index(this.$active)},to:function(t){var n=this.getActiveIndex(),r=this;if(t>this.$items.length-1||t<0)return;return this.sliding?this.$element.one("slid",function(){r.to(t)}):n==t?this.pause().cycle():this.slide(t>n?"next":"prev",e(this.$items[t]))},pause:function(t){return t||(this.paused=!0),this.$element.find(".next, .prev").length&&e.support.transition.end&&(this.$element.trigger(e.support.transition.end),this.cycle(!0)),clearInterval(this.interval),this.interval=null,this},next:function(){if(this.sliding)return;return this.slide("next")},prev:function(){if(this.sliding)return;return this.slide("prev")},slide:function(t,n){var r=this.$element.find(".item.active"),i=n||r[t](),s=this.interval,o=t=="next"?"left":"right",u=t=="next"?"first":"last",a=this,f;this.sliding=!0,s&&this.pause(),i=i.length?i:this.$element.find(".item")[u](),f=e.Event("slide",{relatedTarget:i[0],direction:o});if(i.hasClass("active"))return;this.$indicators.length&&(this.$indicators.find(".active").removeClass("active"),this.$element.one("slid",function(){var t=e(a.$indicators.children()[a.getActiveIndex()]);t&&t.addClass("active")}));if(e.support.transition&&this.$element.hasClass("slide")){this.$element.trigger(f);if(f.isDefaultPrevented())return;i.addClass(t),i[0].offsetWidth,r.addClass(o),i.addClass(o),this.$element.one(e.support.transition.end,function(){i.removeClass([t,o].join(" ")).addClass("active"),r.removeClass(["active",o].join(" ")),a.sliding=!1,setTimeout(function(){a.$element.trigger("slid")},0)})}else{this.$element.trigger(f);if(f.isDefaultPrevented())return;r.removeClass("active"),i.addClass("active"),this.sliding=!1,this.$element.trigger("slid")}return s&&this.cycle(),this}};var n=e.fn.carousel;e.fn.carousel=function(n){return this.each(function(){var r=e(this),i=r.data("carousel"),s=e.extend({},e.fn.carousel.defaults,typeof n=="object"&&n),o=typeof n=="string"?n:s.slide;i||r.data("carousel",i=new t(this,s)),typeof n=="number"?i.to(n):o?i[o]():s.interval&&i.pause().cycle()})},e.fn.carousel.defaults={interval:5e3,pause:"hover"},e.fn.carousel.Constructor=t,e.fn.carousel.noConflict=function(){return e.fn.carousel=n,this},e(document).on("click.carousel.data-api","[data-slide], [data-slide-to]",function(t){var n=e(this),r,i=e(n.attr("data-target")||(r=n.attr("href"))&&r.replace(/.*(?=#[^\s]+$)/,"")),s=e.extend({},i.data(),n.data()),o;i.carousel(s),(o=n.attr("data-slide-to"))&&i.data("carousel").pause().to(o).cycle(),t.preventDefault()})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.collapse.defaults,n),this.options.parent&&(this.$parent=e(this.options.parent)),this.options.toggle&&this.toggle()};t.prototype={constructor:t,dimension:function(){var e=this.$element.hasClass("width");return e?"width":"height"},show:function(){var t,n,r,i;if(this.transitioning||this.$element.hasClass("in"))return;t=this.dimension(),n=e.camelCase(["scroll",t].join("-")),r=this.$parent&&this.$parent.find("> .accordion-group > .in");if(r&&r.length){i=r.data("collapse");if(i&&i.transitioning)return;r.collapse("hide"),i||r.data("collapse",null)}this.$element[t](0),this.transition("addClass",e.Event("show"),"shown"),e.support.transition&&this.$element[t](this.$element[0][n])},hide:function(){var t;if(this.transitioning||!this.$element.hasClass("in"))return;t=this.dimension(),this.reset(this.$element[t]()),this.transition("removeClass",e.Event("hide"),"hidden"),this.$element[t](0)},reset:function(e){var t=this.dimension();return this.$element.removeClass("collapse")[t](e||"auto")[0].offsetWidth,this.$element[e!==null?"addClass":"removeClass"]("collapse"),this},transition:function(t,n,r){var i=this,s=function(){n.type=="show"&&i.reset(),i.transitioning=0,i.$element.trigger(r)};this.$element.trigger(n);if(n.isDefaultPrevented())return;this.transitioning=1,this.$element[t]("in"),e.support.transition&&this.$element.hasClass("collapse")?this.$element.one(e.support.transition.end,s):s()},toggle:function(){this[this.$element.hasClass("in")?"hide":"show"]()}};var n=e.fn.collapse;e.fn.collapse=function(n){return this.each(function(){var r=e(this),i=r.data("collapse"),s=e.extend({},e.fn.collapse.defaults,r.data(),typeof n=="object"&&n);i||r.data("collapse",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.collapse.defaults={toggle:!0},e.fn.collapse.Constructor=t,e.fn.collapse.noConflict=function(){return e.fn.collapse=n,this},e(document).on("click.collapse.data-api","[data-toggle=collapse]",function(t){var n=e(this),r,i=n.attr("data-target")||t.preventDefault()||(r=n.attr("href"))&&r.replace(/.*(?=#[^\s]+$)/,""),s=e(i).data("collapse")?"toggle":n.data();n[e(i).hasClass("in")?"addClass":"removeClass"]("collapsed"),e(i).collapse(s)})}(window.jQuery),!function(e){"use strict";function r(){e(".dropdown-backdrop").remove(),e(t).each(function(){i(e(this)).removeClass("open")})}function i(t){var n=t.attr("data-target"),r;n||(n=t.attr("href"),n=n&&/#/.test(n)&&n.replace(/.*(?=#[^\s]*$)/,"")),r=n&&e(n);if(!r||!r.length)r=t.parent();return r}var t="[data-toggle=dropdown]",n=function(t){var n=e(t).on("click.dropdown.data-api",this.toggle);e("html").on("click.dropdown.data-api",function(){n.parent().removeClass("open")})};n.prototype={constructor:n,toggle:function(t){var n=e(this),s,o;if(n.is(".disabled, :disabled"))return;return s=i(n),o=s.hasClass("open"),r(),o||("ontouchstart"in document.documentElement&&e('<div class="dropdown-backdrop"/>').insertBefore(e(this)).on("click",r),s.toggleClass("open")),n.focus(),!1},keydown:function(n){var r,s,o,u,a,f;if(!/(38|40|27)/.test(n.keyCode))return;r=e(this),n.preventDefault(),n.stopPropagation();if(r.is(".disabled, :disabled"))return;u=i(r),a=u.hasClass("open");if(!a||a&&n.keyCode==27)return n.which==27&&u.find(t).focus(),r.click();s=e("[role=menu] li:not(.divider):visible a",u);if(!s.length)return;f=s.index(s.filter(":focus")),n.keyCode==38&&f>0&&f--,n.keyCode==40&&f<s.length-1&&f++,~f||(f=0),s.eq(f).focus()}};var s=e.fn.dropdown;e.fn.dropdown=function(t){return this.each(function(){var r=e(this),i=r.data("dropdown");i||r.data("dropdown",i=new n(this)),typeof t=="string"&&i[t].call(r)})},e.fn.dropdown.Constructor=n,e.fn.dropdown.noConflict=function(){return e.fn.dropdown=s,this},e(document).on("click.dropdown.data-api",r).on("click.dropdown.data-api",".dropdown form",function(e){e.stopPropagation()}).on("click.dropdown.data-api",t,n.prototype.toggle).on("keydown.dropdown.data-api",t+", [role=menu]",n.prototype.keydown)}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.options=n,this.$element=e(t).delegate('[data-dismiss="modal"]',"click.dismiss.modal",e.proxy(this.hide,this)),this.options.remote&&this.$element.find(".modal-body").load(this.options.remote)};t.prototype={constructor:t,toggle:function(){return this[this.isShown?"hide":"show"]()},show:function(){var t=this,n=e.Event("show");this.$element.trigger(n);if(this.isShown||n.isDefaultPrevented())return;this.isShown=!0,this.escape(),this.backdrop(function(){var n=e.support.transition&&t.$element.hasClass("fade");t.$element.parent().length||t.$element.appendTo(document.body),t.$element.show(),n&&t.$element[0].offsetWidth,t.$element.addClass("in").attr("aria-hidden",!1),t.enforceFocus(),n?t.$element.one(e.support.transition.end,function(){t.$element.focus().trigger("shown")}):t.$element.focus().trigger("shown")})},hide:function(t){t&&t.preventDefault();var n=this;t=e.Event("hide"),this.$element.trigger(t);if(!this.isShown||t.isDefaultPrevented())return;this.isShown=!1,this.escape(),e(document).off("focusin.modal"),this.$element.removeClass("in").attr("aria-hidden",!0),e.support.transition&&this.$element.hasClass("fade")?this.hideWithTransition():this.hideModal()},enforceFocus:function(){var t=this;e(document).on("focusin.modal",function(e){t.$element[0]!==e.target&&!t.$element.has(e.target).length&&t.$element.focus()})},escape:function(){var e=this;this.isShown&&this.options.keyboard?this.$element.on("keyup.dismiss.modal",function(t){t.which==27&&e.hide()}):this.isShown||this.$element.off("keyup.dismiss.modal")},hideWithTransition:function(){var t=this,n=setTimeout(function(){t.$element.off(e.support.transition.end),t.hideModal()},500);this.$element.one(e.support.transition.end,function(){clearTimeout(n),t.hideModal()})},hideModal:function(){var e=this;this.$element.hide(),this.backdrop(function(){e.removeBackdrop(),e.$element.trigger("hidden")})},removeBackdrop:function(){this.$backdrop&&this.$backdrop.remove(),this.$backdrop=null},backdrop:function(t){var n=this,r=this.$element.hasClass("fade")?"fade":"";if(this.isShown&&this.options.backdrop){var i=e.support.transition&&r;this.$backdrop=e('<div class="modal-backdrop '+r+'" />').appendTo(document.body),this.$backdrop.click(this.options.backdrop=="static"?e.proxy(this.$element[0].focus,this.$element[0]):e.proxy(this.hide,this)),i&&this.$backdrop[0].offsetWidth,this.$backdrop.addClass("in");if(!t)return;i?this.$backdrop.one(e.support.transition.end,t):t()}else!this.isShown&&this.$backdrop?(this.$backdrop.removeClass("in"),e.support.transition&&this.$element.hasClass("fade")?this.$backdrop.one(e.support.transition.end,t):t()):t&&t()}};var n=e.fn.modal;e.fn.modal=function(n){return this.each(function(){var r=e(this),i=r.data("modal"),s=e.extend({},e.fn.modal.defaults,r.data(),typeof n=="object"&&n);i||r.data("modal",i=new t(this,s)),typeof n=="string"?i[n]():s.show&&i.show()})},e.fn.modal.defaults={backdrop:!0,keyboard:!0,show:!0},e.fn.modal.Constructor=t,e.fn.modal.noConflict=function(){return e.fn.modal=n,this},e(document).on("click.modal.data-api",'[data-toggle="modal"]',function(t){var n=e(this),r=n.attr("href"),i=e(n.attr("data-target")||r&&r.replace(/.*(?=#[^\s]+$)/,"")),s=i.data("modal")?"toggle":e.extend({remote:!/#/.test(r)&&r},i.data(),n.data());t.preventDefault(),i.modal(s).one("hide",function(){n.focus()})})}(window.jQuery),!function(e){"use strict";var t=function(e,t){this.init("tooltip",e,t)};t.prototype={constructor:t,init:function(t,n,r){var i,s,o,u,a;this.type=t,this.$element=e(n),this.options=this.getOptions(r),this.enabled=!0,o=this.options.trigger.split(" ");for(a=o.length;a--;)u=o[a],u=="click"?this.$element.on("click."+this.type,this.options.selector,e.proxy(this.toggle,this)):u!="manual"&&(i=u=="hover"?"mouseenter":"focus",s=u=="hover"?"mouseleave":"blur",this.$element.on(i+"."+this.type,this.options.selector,e.proxy(this.enter,this)),this.$element.on(s+"."+this.type,this.options.selector,e.proxy(this.leave,this)));this.options.selector?this._options=e.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},getOptions:function(t){return t=e.extend({},e.fn[this.type].defaults,this.$element.data(),t),t.delay&&typeof t.delay=="number"&&(t.delay={show:t.delay,hide:t.delay}),t},enter:function(t){var n=e.fn[this.type].defaults,r={},i;this._options&&e.each(this._options,function(e,t){n[e]!=t&&(r[e]=t)},this),i=e(t.currentTarget)[this.type](r).data(this.type);if(!i.options.delay||!i.options.delay.show)return i.show();clearTimeout(this.timeout),i.hoverState="in",this.timeout=setTimeout(function(){i.hoverState=="in"&&i.show()},i.options.delay.show)},leave:function(t){var n=e(t.currentTarget)[this.type](this._options).data(this.type);this.timeout&&clearTimeout(this.timeout);if(!n.options.delay||!n.options.delay.hide)return n.hide();n.hoverState="out",this.timeout=setTimeout(function(){n.hoverState=="out"&&n.hide()},n.options.delay.hide)},show:function(){var t,n,r,i,s,o,u=e.Event("show");if(this.hasContent()&&this.enabled){this.$element.trigger(u);if(u.isDefaultPrevented())return;t=this.tip(),this.setContent(),this.options.animation&&t.addClass("fade"),s=typeof this.options.placement=="function"?this.options.placement.call(this,t[0],this.$element[0]):this.options.placement,t.detach().css({top:0,left:0,display:"block"}),this.options.container?t.appendTo(this.options.container):t.insertAfter(this.$element),n=this.getPosition(),r=t[0].offsetWidth,i=t[0].offsetHeight;switch(s){case"bottom":o={top:n.top+n.height,left:n.left+n.width/2-r/2};break;case"top":o={top:n.top-i,left:n.left+n.width/2-r/2};break;case"left":o={top:n.top+n.height/2-i/2,left:n.left-r};break;case"right":o={top:n.top+n.height/2-i/2,left:n.left+n.width}}this.applyPlacement(o,s),this.$element.trigger("shown")}},applyPlacement:function(e,t){var n=this.tip(),r=n[0].offsetWidth,i=n[0].offsetHeight,s,o,u,a;n.offset(e).addClass(t).addClass("in"),s=n[0].offsetWidth,o=n[0].offsetHeight,t=="top"&&o!=i&&(e.top=e.top+i-o,a=!0),t=="bottom"||t=="top"?(u=0,e.left<0&&(u=e.left*-2,e.left=0,n.offset(e),s=n[0].offsetWidth,o=n[0].offsetHeight),this.replaceArrow(u-r+s,s,"left")):this.replaceArrow(o-i,o,"top"),a&&n.offset(e)},replaceArrow:function(e,t,n){this.arrow().css(n,e?50*(1-e/t)+"%":"")},setContent:function(){var e=this.tip(),t=this.getTitle();e.find(".tooltip-inner")[this.options.html?"html":"text"](t),e.removeClass("fade in top bottom left right")},hide:function(){function i(){var t=setTimeout(function(){n.off(e.support.transition.end).detach()},500);n.one(e.support.transition.end,function(){clearTimeout(t),n.detach()})}var t=this,n=this.tip(),r=e.Event("hide");this.$element.trigger(r);if(r.isDefaultPrevented())return;return n.removeClass("in"),e.support.transition&&this.$tip.hasClass("fade")?i():n.detach(),this.$element.trigger("hidden"),this},fixTitle:function(){var e=this.$element;(e.attr("title")||typeof e.attr("data-original-title")!="string")&&e.attr("data-original-title",e.attr("title")||"").attr("title","")},hasContent:function(){return this.getTitle()},getPosition:function(){var t=this.$element[0];return e.extend({},typeof t.getBoundingClientRect=="function"?t.getBoundingClientRect():{width:t.offsetWidth,height:t.offsetHeight},this.$element.offset())},getTitle:function(){var e,t=this.$element,n=this.options;return e=t.attr("data-original-title")||(typeof n.title=="function"?n.title.call(t[0]):n.title),e},tip:function(){return this.$tip=this.$tip||e(this.options.template)},arrow:function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},validate:function(){this.$element[0].parentNode||(this.hide(),this.$element=null,this.options=null)},enable:function(){this.enabled=!0},disable:function(){this.enabled=!1},toggleEnabled:function(){this.enabled=!this.enabled},toggle:function(t){var n=t?e(t.currentTarget)[this.type](this._options).data(this.type):this;n.tip().hasClass("in")?n.hide():n.show()},destroy:function(){this.hide().$element.off("."+this.type).removeData(this.type)}};var n=e.fn.tooltip;e.fn.tooltip=function(n){return this.each(function(){var r=e(this),i=r.data("tooltip"),s=typeof n=="object"&&n;i||r.data("tooltip",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.tooltip.Constructor=t,e.fn.tooltip.defaults={animation:!0,placement:"top",selector:!1,template:'<div class="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',trigger:"hover focus",title:"",delay:0,html:!1,container:!1},e.fn.tooltip.noConflict=function(){return e.fn.tooltip=n,this}}(window.jQuery),!function(e){"use strict";var t=function(e,t){this.init("popover",e,t)};t.prototype=e.extend({},e.fn.tooltip.Constructor.prototype,{constructor:t,setContent:function(){var e=this.tip(),t=this.getTitle(),n=this.getContent();e.find(".popover-title")[this.options.html?"html":"text"](t),e.find(".popover-content")[this.options.html?"html":"text"](n),e.removeClass("fade top bottom left right in")},hasContent:function(){return this.getTitle()||this.getContent()},getContent:function(){var e,t=this.$element,n=this.options;return e=(typeof n.content=="function"?n.content.call(t[0]):n.content)||t.attr("data-content"),e},tip:function(){return this.$tip||(this.$tip=e(this.options.template)),this.$tip},destroy:function(){this.hide().$element.off("."+this.type).removeData(this.type)}});var n=e.fn.popover;e.fn.popover=function(n){return this.each(function(){var r=e(this),i=r.data("popover"),s=typeof n=="object"&&n;i||r.data("popover",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.popover.Constructor=t,e.fn.popover.defaults=e.extend({},e.fn.tooltip.defaults,{placement:"right",trigger:"click",content:"",template:'<div class="popover"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'}),e.fn.popover.noConflict=function(){return e.fn.popover=n,this}}(window.jQuery),!function(e){"use strict";function t(t,n){var r=e.proxy(this.process,this),i=e(t).is("body")?e(window):e(t),s;this.options=e.extend({},e.fn.scrollspy.defaults,n),this.$scrollElement=i.on("scroll.scroll-spy.data-api",r),this.selector=(this.options.target||(s=e(t).attr("href"))&&s.replace(/.*(?=#[^\s]+$)/,"")||"")+" .nav li > a",this.$body=e("body"),this.refresh(),this.process()}t.prototype={constructor:t,refresh:function(){var t=this,n;this.offsets=e([]),this.targets=e([]),n=this.$body.find(this.selector).map(function(){var n=e(this),r=n.data("target")||n.attr("href"),i=/^#\w/.test(r)&&e(r);return i&&i.length&&[[i.position().top+(!e.isWindow(t.$scrollElement.get(0))&&t.$scrollElement.scrollTop()),r]]||null}).sort(function(e,t){return e[0]-t[0]}).each(function(){t.offsets.push(this[0]),t.targets.push(this[1])})},process:function(){var e=this.$scrollElement.scrollTop()+this.options.offset,t=this.$scrollElement[0].scrollHeight||this.$body[0].scrollHeight,n=t-this.$scrollElement.height(),r=this.offsets,i=this.targets,s=this.activeTarget,o;if(e>=n)return s!=(o=i.last()[0])&&this.activate(o);for(o=r.length;o--;)s!=i[o]&&e>=r[o]&&(!r[o+1]||e<=r[o+1])&&this.activate(i[o])},activate:function(t){var n,r;this.activeTarget=t,e(this.selector).parent(".active").removeClass("active"),r=this.selector+'[data-target="'+t+'"],'+this.selector+'[href="'+t+'"]',n=e(r).parent("li").addClass("active"),n.parent(".dropdown-menu").length&&(n=n.closest("li.dropdown").addClass("active")),n.trigger("activate")}};var n=e.fn.scrollspy;e.fn.scrollspy=function(n){return this.each(function(){var r=e(this),i=r.data("scrollspy"),s=typeof n=="object"&&n;i||r.data("scrollspy",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.scrollspy.Constructor=t,e.fn.scrollspy.defaults={offset:10},e.fn.scrollspy.noConflict=function(){return e.fn.scrollspy=n,this},e(window).on("load",function(){e('[data-spy="scroll"]').each(function(){var t=e(this);t.scrollspy(t.data())})})}(window.jQuery),!function(e){"use strict";var t=function(t){this.element=e(t)};t.prototype={constructor:t,show:function(){var t=this.element,n=t.closest("ul:not(.dropdown-menu)"),r=t.attr("data-target"),i,s,o;r||(r=t.attr("href"),r=r&&r.replace(/.*(?=#[^\s]*$)/,""));if(t.parent("li").hasClass("active"))return;i=n.find(".active:last a")[0],o=e.Event("show",{relatedTarget:i}),t.trigger(o);if(o.isDefaultPrevented())return;s=e(r),this.activate(t.parent("li"),n),this.activate(s,s.parent(),function(){t.trigger({type:"shown",relatedTarget:i})})},activate:function(t,n,r){function o(){i.removeClass("active").find("> .dropdown-menu > .active").removeClass("active"),t.addClass("active"),s?(t[0].offsetWidth,t.addClass("in")):t.removeClass("fade"),t.parent(".dropdown-menu")&&t.closest("li.dropdown").addClass("active"),r&&r()}var i=n.find("> .active"),s=r&&e.support.transition&&i.hasClass("fade");s?i.one(e.support.transition.end,o):o(),i.removeClass("in")}};var n=e.fn.tab;e.fn.tab=function(n){return this.each(function(){var r=e(this),i=r.data("tab");i||r.data("tab",i=new t(this)),typeof n=="string"&&i[n]()})},e.fn.tab.Constructor=t,e.fn.tab.noConflict=function(){return e.fn.tab=n,this},e(document).on("click.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"]',function(t){t.preventDefault(),e(this).tab("show")})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.typeahead.defaults,n),this.matcher=this.options.matcher||this.matcher,this.sorter=this.options.sorter||this.sorter,this.highlighter=this.options.highlighter||this.highlighter,this.updater=this.options.updater||this.updater,this.source=this.options.source,this.$menu=e(this.options.menu),this.shown=!1,this.listen()};t.prototype={constructor:t,select:function(){var e=this.$menu.find(".active").attr("data-value");return this.$element.val(this.updater(e)).change(),this.hide()},updater:function(e){return e},show:function(){var t=e.extend({},this.$element.position(),{height:this.$element[0].offsetHeight});return this.$menu.insertAfter(this.$element).css({top:t.top+t.height,left:t.left}).show(),this.shown=!0,this},hide:function(){return this.$menu.hide(),this.shown=!1,this},lookup:function(t){var n;return this.query=this.$element.val(),!this.query||this.query.length<this.options.minLength?this.shown?this.hide():this:(n=e.isFunction(this.source)?this.source(this.query,e.proxy(this.process,this)):this.source,n?this.process(n):this)},process:function(t){var n=this;return t=e.grep(t,function(e){return n.matcher(e)}),t=this.sorter(t),t.length?this.render(t.slice(0,this.options.items)).show():this.shown?this.hide():this},matcher:function(e){return~e.toLowerCase().indexOf(this.query.toLowerCase())},sorter:function(e){var t=[],n=[],r=[],i;while(i=e.shift())i.toLowerCase().indexOf(this.query.toLowerCase())?~i.indexOf(this.query)?n.push(i):r.push(i):t.push(i);return t.concat(n,r)},highlighter:function(e){var t=this.query.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g,"\\$&");return e.replace(new RegExp("("+t+")","ig"),function(e,t){return"<strong>"+t+"</strong>"})},render:function(t){var n=this;return t=e(t).map(function(t,r){return t=e(n.options.item).attr("data-value",r),t.find("a").html(n.highlighter(r)),t[0]}),t.first().addClass("active"),this.$menu.html(t),this},next:function(t){var n=this.$menu.find(".active").removeClass("active"),r=n.next();r.length||(r=e(this.$menu.find("li")[0])),r.addClass("active")},prev:function(e){var t=this.$menu.find(".active").removeClass("active"),n=t.prev();n.length||(n=this.$menu.find("li").last()),n.addClass("active")},listen:function(){this.$element.on("focus",e.proxy(this.focus,this)).on("blur",e.proxy(this.blur,this)).on("keypress",e.proxy(this.keypress,this)).on("keyup",e.proxy(this.keyup,this)),this.eventSupported("keydown")&&this.$element.on("keydown",e.proxy(this.keydown,this)),this.$menu.on("click",e.proxy(this.click,this)).on("mouseenter","li",e.proxy(this.mouseenter,this)).on("mouseleave","li",e.proxy(this.mouseleave,this))},eventSupported:function(e){var t=e in this.$element;return t||(this.$element.setAttribute(e,"return;"),t=typeof this.$element[e]=="function"),t},move:function(e){if(!this.shown)return;switch(e.keyCode){case 9:case 13:case 27:e.preventDefault();break;case 38:e.preventDefault(),this.prev();break;case 40:e.preventDefault(),this.next()}e.stopPropagation()},keydown:function(t){this.suppressKeyPressRepeat=~e.inArray(t.keyCode,[40,38,9,13,27]),this.move(t)},keypress:function(e){if(this.suppressKeyPressRepeat)return;this.move(e)},keyup:function(e){switch(e.keyCode){case 40:case 38:case 16:case 17:case 18:break;case 9:case 13:if(!this.shown)return;this.select();break;case 27:if(!this.shown)return;this.hide();break;default:this.lookup()}e.stopPropagation(),e.preventDefault()},focus:function(e){this.focused=!0},blur:function(e){this.focused=!1,!this.mousedover&&this.shown&&this.hide()},click:function(e){e.stopPropagation(),e.preventDefault(),this.select(),this.$element.focus()},mouseenter:function(t){this.mousedover=!0,this.$menu.find(".active").removeClass("active"),e(t.currentTarget).addClass("active")},mouseleave:function(e){this.mousedover=!1,!this.focused&&this.shown&&this.hide()}};var n=e.fn.typeahead;e.fn.typeahead=function(n){return this.each(function(){var r=e(this),i=r.data("typeahead"),s=typeof n=="object"&&n;i||r.data("typeahead",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.typeahead.defaults={source:[],items:8,menu:'<ul class="typeahead dropdown-menu"></ul>',item:'<li><a href="#"></a></li>',minLength:1},e.fn.typeahead.Constructor=t,e.fn.typeahead.noConflict=function(){return e.fn.typeahead=n,this},e(document).on("focus.typeahead.data-api",'[data-provide="typeahead"]',function(t){var n=e(this);if(n.data("typeahead"))return;n.typeahead(n.data())})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.options=e.extend({},e.fn.affix.defaults,n),this.$window=e(window).on("scroll.affix.data-api",e.proxy(this.checkPosition,this)).on("click.affix.data-api",e.proxy(function(){setTimeout(e.proxy(this.checkPosition,this),1)},this)),this.$element=e(t),this.checkPosition()};t.prototype.checkPosition=function(){if(!this.$element.is(":visible"))return;var t=e(document).height(),n=this.$window.scrollTop(),r=this.$element.offset(),i=this.options.offset,s=i.bottom,o=i.top,u="affix affix-top affix-bottom",a;typeof i!="object"&&(s=o=i),typeof o=="function"&&(o=i.top()),typeof s=="function"&&(s=i.bottom()),a=this.unpin!=null&&n+this.unpin<=r.top?!1:s!=null&&r.top+this.$element.height()>=t-s?"bottom":o!=null&&n<=o?"top":!1;if(this.affixed===a)return;this.affixed=a,this.unpin=a=="bottom"?r.top-n:null,this.$element.removeClass(u).addClass("affix"+(a?"-"+a:""))};var n=e.fn.affix;e.fn.affix=function(n){return this.each(function(){var r=e(this),i=r.data("affix"),s=typeof n=="object"&&n;i||r.data("affix",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.affix.Constructor=t,e.fn.affix.defaults={offset:0},e.fn.affix.noConflict=function(){return e.fn.affix=n,this},e(window).on("load",function(){e('[data-spy="affix"]').each(function(){var t=e(this),n=t.data();n.offset=n.offset||{},n.offsetBottom&&(n.offset.bottom=n.offsetBottom),n.offsetTop&&(n.offset.top=n.offsetTop),t.affix(n)})})}(window.jQuery);
\ No newline at end of file + * Bootstrap v3.3.6 (http://getbootstrap.com) + * Copyright 2011-2016 Twitter, Inc. + * Licensed under the MIT license + */ +if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1||b[0]>2)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher, but lower than version 3")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){return a(b.target).is(this)?b.handleObj.handler.apply(this,arguments):void 0}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.6",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a(f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.6",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")?(c.prop("checked")&&(a=!1),b.find(".active").removeClass("active"),this.$element.addClass("active")):"checkbox"==c.prop("type")&&(c.prop("checked")!==this.$element.hasClass("active")&&(a=!1),this.$element.toggleClass("active")),c.prop("checked",this.$element.hasClass("active")),a&&c.trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active")),this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target);d.hasClass("btn")||(d=d.closest(".btn")),b.call(d,"toggle"),a(c.target).is('input[type="radio"]')||a(c.target).is('input[type="checkbox"]')||c.preventDefault()}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=null,this.sliding=null,this.interval=null,this.$active=null,this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.6",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){if(!/input|textarea/i.test(a.target.tagName)){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()}},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c=this.getItemIndex(b),d="prev"==a&&0===c||"next"==a&&c==this.$items.length-1;if(d&&!this.options.wrap)return b;var e="prev"==a?-1:1,f=(c+e)%this.$items.length;return this.$items.eq(f)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));return a>this.$items.length-1||0>a?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){return this.sliding?void 0:this.slide("next")},c.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i=this;if(f.hasClass("active"))return this.sliding=!1;var j=f[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:h});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(f)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offsetWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(m)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&/show|hide/.test(b)&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a('[data-toggle="collapse"][href="#'+b.id+'"],[data-toggle="collapse"][data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.6",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expanded",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":e.data();c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function c(c){c&&3===c.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=b(d),f={relatedTarget:this};e.hasClass("open")&&(c&&"click"==c.type&&/input|textarea/i.test(c.target.tagName)&&a.contains(e[0],c.target)||(e.trigger(c=a.Event("hide.bs.dropdown",f)),c.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger(a.Event("hidden.bs.dropdown",f)))))}))}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.6",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=b(e),g=f.hasClass("open");if(c(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a(document.createElement("div")).addClass("dropdown-backdrop").insertAfter(a(this)).on("click",c);var h={relatedTarget:this};if(f.trigger(d=a.Event("show.bs.dropdown",h)),d.isDefaultPrevented())return;e.trigger("focus").attr("aria-expanded","true"),f.toggleClass("open").trigger(a.Event("shown.bs.dropdown",h))}return!1}},g.prototype.keydown=function(c){if(/(38|40|27|32)/.test(c.which)&&!/input|textarea/i.test(c.target.tagName)){var d=a(this);if(c.preventDefault(),c.stopPropagation(),!d.is(".disabled, :disabled")){var e=b(d),g=e.hasClass("open");if(!g&&27!=c.which||g&&27==c.which)return 27==c.which&&e.find(f).trigger("focus"),d.trigger("click");var h=" li:not(.disabled):visible a",i=e.find(".dropdown-menu"+h);if(i.length){var j=i.index(c.target);38==c.which&&j>0&&j--,40==c.which&&j<i.length-1&&j++,~j||(j=0),i.eq(j).trigger("focus")}}}};var h=a.fn.dropdown;a.fn.dropdown=d,a.fn.dropdown.Constructor=g,a.fn.dropdown.noConflict=function(){return a.fn.dropdown=h,this},a(document).on("click.bs.dropdown.data-api",c).on("click.bs.dropdown.data-api",".dropdown form",function(a){a.stopPropagation()}).on("click.bs.dropdown.data-api",f,g.prototype.toggle).on("keydown.bs.dropdown.data-api",f,g.prototype.keydown).on("keydown.bs.dropdown.data-api",".dropdown-menu",g.prototype.keydown)}(jQuery),+function(a){"use strict";function b(b,d){return this.each(function(){var e=a(this),f=e.data("bs.modal"),g=a.extend({},c.DEFAULTS,e.data(),"object"==typeof b&&b);f||e.data("bs.modal",f=new c(this,g)),"string"==typeof b?f[b](d):g.show&&f.show(d)})}var c=function(b,c){this.options=c,this.$body=a(document.body),this.$element=a(b),this.$dialog=this.$element.find(".modal-dialog"),this.$backdrop=null,this.isShown=null,this.originalBodyPad=null,this.scrollbarWidth=0,this.ignoreBackdropClick=!1,this.options.remote&&this.$element.find(".modal-content").load(this.options.remote,a.proxy(function(){this.$element.trigger("loaded.bs.modal")},this))};c.VERSION="3.3.6",c.TRANSITION_DURATION=300,c.BACKDROP_TRANSITION_DURATION=150,c.DEFAULTS={backdrop:!0,keyboard:!0,show:!0},c.prototype.toggle=function(a){return this.isShown?this.hide():this.show(a)},c.prototype.show=function(b){var d=this,e=a.Event("show.bs.modal",{relatedTarget:b});this.$element.trigger(e),this.isShown||e.isDefaultPrevented()||(this.isShown=!0,this.checkScrollbar(),this.setScrollbar(),this.$body.addClass("modal-open"),this.escape(),this.resize(),this.$element.on("click.dismiss.bs.modal",'[data-dismiss="modal"]',a.proxy(this.hide,this)),this.$dialog.on("mousedown.dismiss.bs.modal",function(){d.$element.one("mouseup.dismiss.bs.modal",function(b){a(b.target).is(d.$element)&&(d.ignoreBackdropClick=!0)})}),this.backdrop(function(){var e=a.support.transition&&d.$element.hasClass("fade");d.$element.parent().length||d.$element.appendTo(d.$body),d.$element.show().scrollTop(0),d.adjustDialog(),e&&d.$element[0].offsetWidth,d.$element.addClass("in"),d.enforceFocus();var f=a.Event("shown.bs.modal",{relatedTarget:b});e?d.$dialog.one("bsTransitionEnd",function(){d.$element.trigger("focus").trigger(f)}).emulateTransitionEnd(c.TRANSITION_DURATION):d.$element.trigger("focus").trigger(f)}))},c.prototype.hide=function(b){b&&b.preventDefault(),b=a.Event("hide.bs.modal"),this.$element.trigger(b),this.isShown&&!b.isDefaultPrevented()&&(this.isShown=!1,this.escape(),this.resize(),a(document).off("focusin.bs.modal"),this.$element.removeClass("in").off("click.dismiss.bs.modal").off("mouseup.dismiss.bs.modal"),this.$dialog.off("mousedown.dismiss.bs.modal"),a.support.transition&&this.$element.hasClass("fade")?this.$element.one("bsTransitionEnd",a.proxy(this.hideModal,this)).emulateTransitionEnd(c.TRANSITION_DURATION):this.hideModal())},c.prototype.enforceFocus=function(){a(document).off("focusin.bs.modal").on("focusin.bs.modal",a.proxy(function(a){this.$element[0]===a.target||this.$element.has(a.target).length||this.$element.trigger("focus")},this))},c.prototype.escape=function(){this.isShown&&this.options.keyboard?this.$element.on("keydown.dismiss.bs.modal",a.proxy(function(a){27==a.which&&this.hide()},this)):this.isShown||this.$element.off("keydown.dismiss.bs.modal")},c.prototype.resize=function(){this.isShown?a(window).on("resize.bs.modal",a.proxy(this.handleUpdate,this)):a(window).off("resize.bs.modal")},c.prototype.hideModal=function(){var a=this;this.$element.hide(),this.backdrop(function(){a.$body.removeClass("modal-open"),a.resetAdjustments(),a.resetScrollbar(),a.$element.trigger("hidden.bs.modal")})},c.prototype.removeBackdrop=function(){this.$backdrop&&this.$backdrop.remove(),this.$backdrop=null},c.prototype.backdrop=function(b){var d=this,e=this.$element.hasClass("fade")?"fade":"";if(this.isShown&&this.options.backdrop){var f=a.support.transition&&e;if(this.$backdrop=a(document.createElement("div")).addClass("modal-backdrop "+e).appendTo(this.$body),this.$element.on("click.dismiss.bs.modal",a.proxy(function(a){return this.ignoreBackdropClick?void(this.ignoreBackdropClick=!1):void(a.target===a.currentTarget&&("static"==this.options.backdrop?this.$element[0].focus():this.hide()))},this)),f&&this.$backdrop[0].offsetWidth,this.$backdrop.addClass("in"),!b)return;f?this.$backdrop.one("bsTransitionEnd",b).emulateTransitionEnd(c.BACKDROP_TRANSITION_DURATION):b()}else if(!this.isShown&&this.$backdrop){this.$backdrop.removeClass("in");var g=function(){d.removeBackdrop(),b&&b()};a.support.transition&&this.$element.hasClass("fade")?this.$backdrop.one("bsTransitionEnd",g).emulateTransitionEnd(c.BACKDROP_TRANSITION_DURATION):g()}else b&&b()},c.prototype.handleUpdate=function(){this.adjustDialog()},c.prototype.adjustDialog=function(){var a=this.$element[0].scrollHeight>document.documentElement.clientHeight;this.$element.css({paddingLeft:!this.bodyIsOverflowing&&a?this.scrollbarWidth:"",paddingRight:this.bodyIsOverflowing&&!a?this.scrollbarWidth:""})},c.prototype.resetAdjustments=function(){this.$element.css({paddingLeft:"",paddingRight:""})},c.prototype.checkScrollbar=function(){var a=window.innerWidth;if(!a){var b=document.documentElement.getBoundingClientRect();a=b.right-Math.abs(b.left)}this.bodyIsOverflowing=document.body.clientWidth<a,this.scrollbarWidth=this.measureScrollbar()},c.prototype.setScrollbar=function(){var a=parseInt(this.$body.css("padding-right")||0,10);this.originalBodyPad=document.body.style.paddingRight||"",this.bodyIsOverflowing&&this.$body.css("padding-right",a+this.scrollbarWidth)},c.prototype.resetScrollbar=function(){this.$body.css("padding-right",this.originalBodyPad)},c.prototype.measureScrollbar=function(){var a=document.createElement("div");a.className="modal-scrollbar-measure",this.$body.append(a);var b=a.offsetWidth-a.clientWidth;return this.$body[0].removeChild(a),b};var d=a.fn.modal;a.fn.modal=b,a.fn.modal.Constructor=c,a.fn.modal.noConflict=function(){return a.fn.modal=d,this},a(document).on("click.bs.modal.data-api",'[data-toggle="modal"]',function(c){var d=a(this),e=d.attr("href"),f=a(d.attr("data-target")||e&&e.replace(/.*(?=#[^\s]+$)/,"")),g=f.data("bs.modal")?"toggle":a.extend({remote:!/#/.test(e)&&e},f.data(),d.data());d.is("a")&&c.preventDefault(),f.one("show.bs.modal",function(a){a.isDefaultPrevented()||f.one("hidden.bs.modal",function(){d.is(":visible")&&d.trigger("focus")})}),b.call(f,g,this)})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.tooltip"),f="object"==typeof b&&b;!e&&/destroy|hide/.test(b)||(e||d.data("bs.tooltip",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.type=null,this.options=null,this.enabled=null,this.timeout=null,this.hoverState=null,this.$element=null,this.inState=null,this.init("tooltip",a,b)};c.VERSION="3.3.6",c.TRANSITION_DURATION=150,c.DEFAULTS={animation:!0,placement:"top",selector:!1,template:'<div class="tooltip" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},c.prototype.init=function(b,c,d){if(this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.$viewport=this.options.viewport&&a(a.isFunction(this.options.viewport)?this.options.viewport.call(this,this.$element):this.options.viewport.selector||this.options.viewport),this.inState={click:!1,hover:!1,focus:!1},this.$element[0]instanceof document.constructor&&!this.options.selector)throw new Error("`selector` option must be specified when initializing "+this.type+" on the window.document object!");for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},c.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},c.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusin"==b.type?"focus":"hover"]=!0),c.tip().hasClass("in")||"in"==c.hoverState?void(c.hoverState="in"):(clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show())},c.prototype.isInStateTrue=function(){for(var a in this.inState)if(this.inState[a])return!0;return!1},c.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusout"==b.type?"focus":"hover"]=!1),c.isInStateTrue()?void 0:(clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide())},c.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(b);var d=a.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(b.isDefaultPrevented()||!d)return;var e=this,f=this.tip(),g=this.getUID(this.type);this.setContent(),f.attr("id",g),this.$element.attr("aria-describedby",g),this.options.animation&&f.addClass("fade");var h="function"==typeof this.options.placement?this.options.placement.call(this,f[0],this.$element[0]):this.options.placement,i=/\s?auto?\s?/i,j=i.test(h);j&&(h=h.replace(i,"")||"top"),f.detach().css({top:0,left:0,display:"block"}).addClass(h).data("bs."+this.type,this),this.options.container?f.appendTo(this.options.container):f.insertAfter(this.$element),this.$element.trigger("inserted.bs."+this.type);var k=this.getPosition(),l=f[0].offsetWidth,m=f[0].offsetHeight;if(j){var n=h,o=this.getPosition(this.$viewport);h="bottom"==h&&k.bottom+m>o.bottom?"top":"top"==h&&k.top-m<o.top?"bottom":"right"==h&&k.right+l>o.width?"left":"left"==h&&k.left-l<o.left?"right":h,f.removeClass(n).addClass(h)}var p=this.getCalculatedOffset(h,k,l,m);this.applyPlacement(p,h);var q=function(){var a=e.hoverState;e.$element.trigger("shown.bs."+e.type),e.hoverState=null,"out"==a&&e.leave(e)};a.support.transition&&this.$tip.hasClass("fade")?f.one("bsTransitionEnd",q).emulateTransitionEnd(c.TRANSITION_DURATION):q()}},c.prototype.applyPlacement=function(b,c){var d=this.tip(),e=d[0].offsetWidth,f=d[0].offsetHeight,g=parseInt(d.css("margin-top"),10),h=parseInt(d.css("margin-left"),10);isNaN(g)&&(g=0),isNaN(h)&&(h=0),b.top+=g,b.left+=h,a.offset.setOffset(d[0],a.extend({using:function(a){d.css({top:Math.round(a.top),left:Math.round(a.left)})}},b),0),d.addClass("in");var i=d[0].offsetWidth,j=d[0].offsetHeight;"top"==c&&j!=f&&(b.top=b.top+f-j);var k=this.getViewportAdjustedDelta(c,b,i,j);k.left?b.left+=k.left:b.top+=k.top;var l=/top|bottom/.test(c),m=l?2*k.left-e+i:2*k.top-f+j,n=l?"offsetWidth":"offsetHeight";d.offset(b),this.replaceArrow(m,d[0][n],l)},c.prototype.replaceArrow=function(a,b,c){this.arrow().css(c?"left":"top",50*(1-a/b)+"%").css(c?"top":"left","")},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle();a.find(".tooltip-inner")[this.options.html?"html":"text"](b),a.removeClass("fade in top bottom left right")},c.prototype.hide=function(b){function d(){"in"!=e.hoverState&&f.detach(),e.$element.removeAttr("aria-describedby").trigger("hidden.bs."+e.type),b&&b()}var e=this,f=a(this.$tip),g=a.Event("hide.bs."+this.type);return this.$element.trigger(g),g.isDefaultPrevented()?void 0:(f.removeClass("in"),a.support.transition&&f.hasClass("fade")?f.one("bsTransitionEnd",d).emulateTransitionEnd(c.TRANSITION_DURATION):d(),this.hoverState=null,this)},c.prototype.fixTitle=function(){var a=this.$element;(a.attr("title")||"string"!=typeof a.attr("data-original-title"))&&a.attr("data-original-title",a.attr("title")||"").attr("title","")},c.prototype.hasContent=function(){return this.getTitle()},c.prototype.getPosition=function(b){b=b||this.$element;var c=b[0],d="BODY"==c.tagName,e=c.getBoundingClientRect();null==e.width&&(e=a.extend({},e,{width:e.right-e.left,height:e.bottom-e.top}));var f=d?{top:0,left:0}:b.offset(),g={scroll:d?document.documentElement.scrollTop||document.body.scrollTop:b.scrollTop()},h=d?{width:a(window).width(),height:a(window).height()}:null;return a.extend({},e,g,h,f)},c.prototype.getCalculatedOffset=function(a,b,c,d){return"bottom"==a?{top:b.top+b.height,left:b.left+b.width/2-c/2}:"top"==a?{top:b.top-d,left:b.left+b.width/2-c/2}:"left"==a?{top:b.top+b.height/2-d/2,left:b.left-c}:{top:b.top+b.height/2-d/2,left:b.left+b.width}},c.prototype.getViewportAdjustedDelta=function(a,b,c,d){var e={top:0,left:0};if(!this.$viewport)return e;var f=this.options.viewport&&this.options.viewport.padding||0,g=this.getPosition(this.$viewport);if(/right|left/.test(a)){var h=b.top-f-g.scroll,i=b.top+f-g.scroll+d;h<g.top?e.top=g.top-h:i>g.top+g.height&&(e.top=g.top+g.height-i)}else{var j=b.left-f,k=b.left+f+c;j<g.left?e.left=g.left-j:k>g.right&&(e.left=g.left+g.width-k)}return e},c.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},c.prototype.getUID=function(a){do a+=~~(1e6*Math.random());while(document.getElementById(a));return a},c.prototype.tip=function(){if(!this.$tip&&(this.$tip=a(this.options.template),1!=this.$tip.length))throw new Error(this.type+" `template` option must consist of exactly 1 top-level element!");return this.$tip},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},c.prototype.enable=function(){this.enabled=!0},c.prototype.disable=function(){this.enabled=!1},c.prototype.toggleEnabled=function(){this.enabled=!this.enabled},c.prototype.toggle=function(b){var c=this;b&&(c=a(b.currentTarget).data("bs."+this.type),c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c))),b?(c.inState.click=!c.inState.click,c.isInStateTrue()?c.enter(c):c.leave(c)):c.tip().hasClass("in")?c.leave(c):c.enter(c)},c.prototype.destroy=function(){var a=this;clearTimeout(this.timeout),this.hide(function(){a.$element.off("."+a.type).removeData("bs."+a.type),a.$tip&&a.$tip.detach(),a.$tip=null,a.$arrow=null,a.$viewport=null})};var d=a.fn.tooltip;a.fn.tooltip=b,a.fn.tooltip.Constructor=c,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=d,this}}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof b&&b;!e&&/destroy|hide/.test(b)||(e||d.data("bs.popover",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");c.VERSION="3.3.6",c.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:'<div class="popover" role="tooltip"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'}),c.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),c.prototype.constructor=c,c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content").children().detach().end()[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},c.prototype.hasContent=function(){return this.getTitle()||this.getContent()},c.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")};var d=a.fn.popover;a.fn.popover=b,a.fn.popover.Constructor=c,a.fn.popover.noConflict=function(){return a.fn.popover=d,this}}(jQuery),+function(a){"use strict";function b(c,d){this.$body=a(document.body),this.$scrollElement=a(a(c).is(document.body)?window:c),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",a.proxy(this.process,this)),this.refresh(),this.process()}function c(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})}b.VERSION="3.3.6",b.DEFAULTS={offset:10},b.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},b.prototype.refresh=function(){var b=this,c="offset",d=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),a.isWindow(this.$scrollElement[0])||(c="position",d=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var b=a(this),e=b.data("target")||b.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[c]().top+d,e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){b.offsets.push(this[0]),b.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.getScrollHeight(),d=this.options.offset+c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(this.scrollHeight!=c&&this.refresh(),b>=d)return g!=(a=f[f.length-1])&&this.activate(a);if(g&&b<e[0])return this.activeTarget=null,this.clear();for(a=e.length;a--;)g!=f[a]&&b>=e[a]&&(void 0===e[a+1]||b<e[a+1])&&this.activate(f[a])},b.prototype.activate=function(b){this.activeTarget=b,this.clear();var c=this.selector+'[data-target="'+b+'"],'+this.selector+'[href="'+b+'"]',d=a(c).parents("li").addClass("active");d.parent(".dropdown-menu").length&&(d=d.closest("li.dropdown").addClass("active")), +d.trigger("activate.bs.scrollspy")},b.prototype.clear=function(){a(this.selector).parentsUntil(this.options.target,".active").removeClass("active")};var d=a.fn.scrollspy;a.fn.scrollspy=c,a.fn.scrollspy.Constructor=b,a.fn.scrollspy.noConflict=function(){return a.fn.scrollspy=d,this},a(window).on("load.bs.scrollspy.data-api",function(){a('[data-spy="scroll"]').each(function(){var b=a(this);c.call(b,b.data())})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.tab");e||d.data("bs.tab",e=new c(this)),"string"==typeof b&&e[b]()})}var c=function(b){this.element=a(b)};c.VERSION="3.3.6",c.TRANSITION_DURATION=150,c.prototype.show=function(){var b=this.element,c=b.closest("ul:not(.dropdown-menu)"),d=b.data("target");if(d||(d=b.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,"")),!b.parent("li").hasClass("active")){var e=c.find(".active:last a"),f=a.Event("hide.bs.tab",{relatedTarget:b[0]}),g=a.Event("show.bs.tab",{relatedTarget:e[0]});if(e.trigger(f),b.trigger(g),!g.isDefaultPrevented()&&!f.isDefaultPrevented()){var h=a(d);this.activate(b.closest("li"),c),this.activate(h,h.parent(),function(){e.trigger({type:"hidden.bs.tab",relatedTarget:b[0]}),b.trigger({type:"shown.bs.tab",relatedTarget:e[0]})})}}},c.prototype.activate=function(b,d,e){function f(){g.removeClass("active").find("> .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),b.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),h?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu").length&&b.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),e&&e()}var g=d.find("> .active"),h=e&&a.support.transition&&(g.length&&g.hasClass("fade")||!!d.find("> .fade").length);g.length&&h?g.one("bsTransitionEnd",f).emulateTransitionEnd(c.TRANSITION_DURATION):f(),g.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this};var e=function(c){c.preventDefault(),b.call(a(this),"show")};a(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',e).on("click.bs.tab.data-api",'[data-toggle="pill"]',e)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=null,this.unpin=null,this.pinnedOffset=null,this.checkPosition()};c.VERSION="3.3.6",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getState=function(a,b,c,d){var e=this.$target.scrollTop(),f=this.$element.offset(),g=this.$target.height();if(null!=c&&"top"==this.affixed)return c>e?"top":!1;if("bottom"==this.affixed)return null!=c?e+this.unpin<=f.top?!1:"bottom":a-d>=e+g?!1:"bottom";var h=null==this.affixed,i=h?e:f.top,j=h?g:b;return null!=c&&c>=e?"top":null!=d&&i+j>=a-d?"bottom":!1},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=this.$element.height(),d=this.options.offset,e=d.top,f=d.bottom,g=Math.max(a(document).height(),a(document.body).height());"object"!=typeof d&&(f=e=d),"function"==typeof e&&(e=d.top(this.$element)),"function"==typeof f&&(f=d.bottom(this.$element));var h=this.getState(g,b,e,f);if(this.affixed!=h){null!=this.unpin&&this.$element.css("top","");var i="affix"+(h?"-"+h:""),j=a.Event(i+".bs.affix");if(this.$element.trigger(j),j.isDefaultPrevented())return;this.affixed=h,this.unpin="bottom"==h?this.getPinnedOffset():null,this.$element.removeClass(c.RESET).addClass(i).trigger(i.replace("affix","affixed")+".bs.affix")}"bottom"==h&&this.$element.offset({top:g-b-f})}};var d=a.fn.affix;a.fn.affix=b,a.fn.affix.Constructor=c,a.fn.affix.noConflict=function(){return a.fn.affix=d,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var c=a(this),d=c.data();d.offset=d.offset||{},null!=d.offsetBottom&&(d.offset.bottom=d.offsetBottom),null!=d.offsetTop&&(d.offset.top=d.offsetTop),b.call(c,d)})})}(jQuery);
\ No newline at end of file diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/customrecipe.js b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/customrecipe.js index 1c0ef9e37..9ea960288 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/customrecipe.js +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/customrecipe.js @@ -158,7 +158,7 @@ function customRecipePageInit(ctx) { msg += " <strong>" + dep.name + "</strong>"; /* Add any cells currently in view to the list of cells which get - * an inline notification inside them and which change add/rm state + * an list-inline notification inside them and which change add/rm state */ depBtnCell = $("#package-btn-cell-" + dep.pk); btnCell = btnCell.add(depBtnCell); @@ -208,7 +208,7 @@ function customRecipePageInit(ctx) { } /* Add any cells currently in view to the list of cells which get - * an inline notification inside them and which change add/rm state + * an list-inline notification inside them and which change add/rm state */ depBtnCell = $("#package-btn-cell-" + dep.pk); btnCell = btnCell.add(depBtnCell); @@ -281,4 +281,36 @@ function customRecipePageInit(ctx) { window.location.replace(libtoaster.ctx.projectBuildsUrl); }); }); + + $("#delete-custom-recipe-confirmed").click(function(e){ + e.preventDefault(); + libtoaster.disableAjaxLoadingTimer(); + $(this).find('[data-role="submit-state"]').hide(); + $(this).find('[data-role="loading-state"]').show(); + $(this).attr("disabled", "disabled"); + + $.ajax({ + type: 'DELETE', + url: ctx.recipe.xhrCustomRecipeUrl, + headers: { 'X-CSRFToken' : $.cookie('csrftoken')}, + success: function (data) { + if (data.error !== "ok") { + console.warn(data.error); + } else { + var msg = $('<span>You have deleted <strong>1</strong> custom image: <strong id="deleted-custom-image-name"></strong></span>'); + msg.find("#deleted-custom-image-name").text(ctx.recipe.name); + + libtoaster.setNotification("custom-image-recipe-deleted", + msg.html()); + + window.location.replace(data.gotoUrl); + } + }, + error: function (data) { + console.warn(data); + } + }); + }); + + } diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/importlayer.js b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/importlayer.js index 5a59799bc..30dc28280 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/importlayer.js +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/importlayer.js @@ -10,45 +10,31 @@ function importLayerPageInit (ctx) { var layerDepInput = $("#layer-dependency"); var layerNameCtrl = $("#layer-name-ctrl"); var duplicatedLayerName = $("#duplicated-layer-name-hint"); + var localDirPath = $("#local-dir-path"); var layerDeps = {}; var layerDepsDeps = {}; var currentLayerDepSelection; var validLayerName = /^(\w|-)+$/; - libtoaster.makeTypeahead(layerDepInput, libtoaster.ctx.layersTypeAheadUrl, { include_added: "true" }, function(item){ + libtoaster.makeTypeahead(layerDepInput, + libtoaster.ctx.layersTypeAheadUrl, + { include_added: "true" }, function(item){ currentLayerDepSelection = item; + layerDepBtn.removeAttr("disabled"); }); - // choices available in the typeahead - var layerDepsChoices = {}; - - // when the typeahead choices change, store an array of the available layer - // choices locally, to use for enabling/disabling the "Add layer" button - layerDepInput.on("typeahead-choices-change", function (event, data) { - layerDepsChoices = {}; - - if (data.choices) { - data.choices.forEach(function (item) { - layerDepsChoices[item.name] = item; - }); - } + layerDepInput.on("typeahead:select", function(event, data){ + currentLayerDepSelection = data; }); + // Disable local dir repo when page is loaded. + $('#local-dir').hide(); + // disable the "Add layer" button when the layer input typeahead is empty // or not in the typeahead choices - layerDepInput.on("input change", function () { - // get the choices from the typeahead - var choice = layerDepsChoices[$(this).val()]; - - if (choice) { - layerDepBtn.removeAttr("disabled"); - currentLayerDepSelection = choice; - } - else { - layerDepBtn.attr("disabled", "disabled"); - currentLayerDepSelection = undefined; - } + layerDepInput.on("input change", function(){ + layerDepBtn.attr("disabled","disabled"); }); /* We automatically add "openembedded-core" layer for convenience as a @@ -70,7 +56,7 @@ function importLayerPageInit (ctx) { layerDeps[currentLayerDepSelection.id] = currentLayerDepSelection; /* Make a list item for the new layer dependency */ - var newLayerDep = $("<li><a></a><span class=\"icon-trash\" data-toggle=\"tooltip\" title=\"Delete\"></span></li>"); + var newLayerDep = $("<li><a></a><span class=\"glyphicon glyphicon-trash\" data-toggle=\"tooltip\" title=\"Remove\"></span></li>"); newLayerDep.data('layer-id', currentLayerDepSelection.id); newLayerDep.children("span").tooltip(); @@ -91,7 +77,8 @@ function importLayerPageInit (ctx) { $("#layer-deps-list").append(newLayerDep); - libtoaster.getLayerDepsForProject(currentLayerDepSelection.layerdetailurl, function (data){ + libtoaster.getLayerDepsForProject(currentLayerDepSelection.layerdetailurl, + function (data){ /* These are the dependencies of the layer added as a dependency */ if (data.list.length > 0) { currentLayerDepSelection.url = currentLayerDepSelection.layerdetailurl; @@ -105,7 +92,8 @@ function importLayerPageInit (ctx) { }, null); }); - importAndAddBtn.click(function(){ + importAndAddBtn.click(function(e){ + e.preventDefault(); /* This is a list of the names from layerDeps for the layer deps * modal dialog body */ @@ -145,7 +133,9 @@ function importLayerPageInit (ctx) { var body = "<strong>"+layer.name+"</strong>'s dependencies ("+ depNames.join(", ")+"</span>) require some layers that are not added to your project. Select the ones you want to add:</p>"; - showLayerDepsModal(layer, depDepsArray, title, body, false, function(layerObsList){ + showLayerDepsModal(layer, + depDepsArray, + title, body, false, function(layerObsList){ /* Add the accepted layer dependencies' ids to the allDeps array */ for (var key in layerObsList){ allDeps.push(layerObsList[key].id); @@ -167,8 +157,16 @@ function importLayerPageInit (ctx) { dir_path: $("#layer-subdir").val(), project_id: libtoaster.ctx.projectId, layer_deps: layerDepsCsv, + local_source_dir: $('#local-dir-path').val(), }; + if ($('input[name=repo]:checked').val() == "git") { + layerData.local_source_dir = ""; + } else { + layerData.vcs_url = ""; + layerData.git_ref = ""; + } + $.ajax({ type: "POST", url: ctx.xhrImportLayerUrl, @@ -178,9 +176,8 @@ function importLayerPageInit (ctx) { if (data.error != "ok") { console.log(data.error); } else { - /* Success layer import now go to the project page */ - $.cookie('layer-imported-alert', JSON.stringify(data), { path: '/'}); - window.location.replace(libtoaster.ctx.projectPageUrl+'?notify=layer-imported'); + createImportedNotification(data); + window.location.replace(libtoaster.ctx.projectPageUrl); } }, error: function (data) { @@ -191,6 +188,30 @@ function importLayerPageInit (ctx) { } }); + /* Layer imported notification */ + function createImportedNotification(imported){ + var message = "Layer imported"; + + if (imported.deps_added.length === 0) { + message = "You have imported <strong><a class=\"alert-link\" href=\""+imported.imported_layer.layerdetailurl+"\">"+imported.imported_layer.name+"</a></strong> and added it to your project."; + } else { + + var links = "<a href=\""+imported.imported_layer.layerdetailurl+"\">"+imported.imported_layer.name+"</a>, "; + + imported.deps_added.map (function(item, index){ + links +='<a href="'+item.layerdetailurl+'">'+item.name+'</a>'; + /*If we're at the last element we don't want the trailing comma */ + if (imported.deps_added[index+1] !== undefined) + links += ', '; + }); + + /* Length + 1 here to do deps + the imported layer */ + message = 'You have imported <strong><a href="'+imported.imported_layer.layerdetailurl+'">'+imported.imported_layer.name+'</a></strong> and added <strong>'+(imported.deps_added.length+1)+'</strong> layers to your project: <strong>'+links+'</strong>'; + } + + libtoaster.setNotification("layer-imported", message); + } + function enable_import_btn(enabled) { var importAndAddHint = $("#import-and-add-hint"); @@ -207,25 +228,48 @@ function importLayerPageInit (ctx) { function check_form() { var valid = false; var inputs = $("input:required"); + var inputStr = inputs.val().split(""); - for (var i=0; i<inputs.length; i++){ - if (!(valid = inputs[i].value)){ + for (var i=0; i<inputs.val().length; i++){ + if (!(valid = inputStr[i])){ enable_import_btn(false); break; } } - if (valid) - enable_import_btn(true); + if (valid) { + if ($("#local-dir-radio").prop("checked") && + localDirPath.val().length > 0) { + enable_import_btn(true); + } + + if ($("#git-repo-radio").prop("checked") && + vcsURLInput.val().length > 0 && gitRefInput.val().length > 0) { + enable_import_btn(true); + } + } + + if (inputs.val().length == 0) + enable_import_btn(false); } function layerExistsError(layer){ var dupLayerInfo = $("#duplicate-layer-info"); - dupLayerInfo.find(".dup-layer-name").text(layer.name); - dupLayerInfo.find(".dup-layer-link").attr("href", layer.layerdetailurl); - dupLayerInfo.find("#dup-layer-vcs-url").text(layer.vcs_url); - dupLayerInfo.find("#dup-layer-revision").text(layer.vcs_reference); + if (layer.local_source_dir) { + $("#git-layer-dup").hide(); + $("#local-layer-dup").fadeIn(); + dupLayerInfo.find(".dup-layer-name").text(layer.name); + dupLayerInfo.find(".dup-layer-link").attr("href", layer.layerdetailurl); + dupLayerInfo.find("#dup-local-source-dir-name").text(layer.local_source_dir); + } else { + $("#git-layer-dup").fadeIn(); + $("#local-layer-dup").hide(); + dupLayerInfo.find(".dup-layer-name").text(layer.name); + dupLayerInfo.find(".dup-layer-link").attr("href", layer.layerdetailurl); + dupLayerInfo.find("#dup-layer-vcs-url").text(layer.vcs_url); + dupLayerInfo.find("#dup-layer-revision").text(layer.vcs_reference); + } $(".fields-apart-from-layer-name").fadeOut(function(){ dupLayerInfo.fadeIn(); @@ -233,13 +277,13 @@ function importLayerPageInit (ctx) { } layerNameInput.on('blur', function() { - if (!$(this).val()){ - return; - } - var name = $(this).val(); + if (!$(this).val()){ + return; + } + var name = $(this).val(); - /* Check if the layer name exists */ - $.getJSON(libtoaster.ctx.layersTypeAheadUrl, + /* Check if the layer name exists */ + $.getJSON(libtoaster.ctx.layersTypeAheadUrl, { include_added: "true" , search: name, format: "json" }, function(layer) { if (layer.results.length > 0) { @@ -262,7 +306,7 @@ function importLayerPageInit (ctx) { layerNameInput.on('input', function() { if ($(this).val() && !validLayerName.test($(this).val())){ - layerNameCtrl.addClass("error") + layerNameCtrl.addClass("has-error") $("#invalid-layer-name-hint").show(); enable_import_btn(false); return; @@ -270,16 +314,19 @@ function importLayerPageInit (ctx) { if ($("#duplicate-layer-info").css("display") != "None"){ $("#duplicate-layer-info").fadeOut(function(){ - $(".fields-apart-from-layer-name").show(); - }); + $(".fields-apart-from-layer-name").show(); + radioDisplay(); + }); - } + } + + radioDisplay(); /* Don't remove the error class if we're displaying the error for another * reason. */ if (!duplicatedLayerName.is(":visible")) - layerNameCtrl.removeClass("error") + layerNameCtrl.removeClass("has-error") $("#invalid-layer-name-hint").hide(); check_form(); @@ -300,4 +347,72 @@ function importLayerPageInit (ctx) { } }); + function radioDisplay() { + if ($('input[name=repo]:checked').val() == "local") { + $('#git-repo').hide(); + $('#import-git-layer-and-add-hint').hide(); + $('#local-dir').fadeIn(); + $('#import-local-dir-and-add-hint').fadeIn(); + } else { + $('#local-dir').hide(); + $('#import-local-dir-and-add-hint').hide(); + $('#git-repo').fadeIn(); + $('#import-git-layer-and-add-hint').fadeIn(); + } + } + + $('input:radio[name="repo"]').change(function() { + radioDisplay(); + if ($("#local-dir-radio").prop("checked")) { + if (localDirPath.val().length > 0) { + enable_import_btn(true); + } else { + enable_import_btn(false); + } + } + if ($("#git-repo-radio").prop("checked")) { + if (vcsURLInput.val().length > 0 && gitRefInput.val().length > 0) { + enable_import_btn(true); + } else { + enable_import_btn(false); + } + } + }); + + localDirPath.on('input', function(){ + if ($(this).val().trim().length == 0) { + $('#import-and-add-btn').attr("disabled","disabled"); + $('#local-dir').addClass('has-error'); + $('#hintError-dir-abs-path').show(); + $('#hintError-dir-path-starts-with-slash').show(); + } else { + var input = $(this); + var reBeginWithSlash = /^\//; + var reCheckVariable = /^\$/; + var re = /([ <>\\|":\.%\?\*]+)/; + + var invalidDir = re.test(input.val()); + var invalidSlash = reBeginWithSlash.test(input.val()); + var invalidVar = reCheckVariable.test(input.val()); + + if (!invalidSlash && !invalidVar) { + $('#local-dir').addClass('has-error'); + $('#import-and-add-btn').attr("disabled","disabled"); + $('#hintError-dir-abs-path').show(); + $('#hintError-dir-path-starts-with-slash').show(); + } else if (invalidDir) { + $('#local-dir').addClass('has-error'); + $('#import-and-add-btn').attr("disabled","disabled"); + $('#hintError-dir-path').show(); + } else { + $('#local-dir').removeClass('has-error'); + if (layerNameInput.val().length > 0) { + $('#import-and-add-btn').removeAttr("disabled"); + } + $('#hintError-dir-abs-path').hide(); + $('#hintError-dir-path-starts-with-slash').hide(); + $('#hintError-dir-path').hide(); + } + } + }); } diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/jsrender.min.js b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/jsrender.min.js new file mode 100644 index 000000000..87cac4eb3 --- /dev/null +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/jsrender.min.js @@ -0,0 +1,4 @@ +/*! JsRender v0.9.78 (Beta): http://jsviews.com/#jsrender */ +/*! **VERSION FOR WEB** (For NODE.JS see http://jsviews.com/download/jsrender-node.js) */ +!function(e,t){var n=t.jQuery;"object"==typeof exports?module.exports=n?e(t,n):function(n){if(n&&!n.fn)throw"Provide jQuery or null";return e(t,n)}:"function"==typeof define&&define.amd?define(function(){return e(t)}):e(t,!1)}(function(e,t){"use strict";function n(e,t){return function(){var n,r=this,i=r.base;return r.base=e,n=t.apply(r,arguments),r.base=i,n}}function r(e,t){return te(t)&&(t=n(e?e._d?e:n(s,e):s,t),t._d=1),t}function i(e,t){for(var n in t.props)Re.test(n)&&(e[n]=r(e[n],t.props[n]))}function o(e){return e}function s(){return""}function a(e){try{throw console.log("JsRender dbg breakpoint: "+e),"dbg breakpoint"}catch(t){}return this.base?this.baseApply(arguments):e}function d(e){this.name=(t.link?"JsViews":"JsRender")+" Error",this.message=e||this.name}function u(e,t){for(var n in t)e[n]=t[n];return e}function l(e,t,n){return e?(de.delimiters=[e,t,ve=n?n.charAt(0):ve],pe=e.charAt(0),ce=e.charAt(1),fe=t.charAt(0),ge=t.charAt(1),e="\\"+pe+"(\\"+ve+")?\\"+ce,t="\\"+fe+"\\"+ge,G="(?:(\\w+(?=[\\/\\s\\"+fe+"]))|(\\w+)?(:)|(>)|(\\*))\\s*((?:[^\\"+fe+"]|\\"+fe+"(?!\\"+ge+"))*?)",ae.rTag="(?:"+G+")",G=new RegExp("(?:"+e+G+"(\\/)?|\\"+pe+"(\\"+ve+")?\\"+ce+"(?:(?:\\/(\\w+))\\s*|!--[\\s\\S]*?--))"+t,"g"),W=new RegExp("<.*>|([^\\\\]|^)[{}]|"+e+".*"+t),le):de.delimiters}function p(e,t){t||e===!0||(t=e,e=void 0);var n,r,i,o,s=this,a=!t||"root"===t;if(e){if(o=t&&s.type===t&&s,!o)if(n=s.views,s._.useKey){for(r in n)if(o=t?n[r].get(e,t):n[r])break}else for(r=0,i=n.length;!o&&i>r;r++)o=t?n[r].get(e,t):n[r]}else if(a)for(;s.parent;)o=s,s=s.parent;else for(;s&&!o;)o=s.type===t?s:void 0,s=s.parent;return o}function c(){var e=this.get("item");return e?e.index:void 0}function f(){return this.index}function g(t){var n,r=this,i=r.linkCtx,o=(r.ctx||{})[t];return void 0===o&&i&&i.ctx&&(o=i.ctx[t]),void 0===o&&(o=oe[t]),o&&te(o)&&!o._wrp&&(n=function(){return o.apply(this&&this!==e?this:r,arguments)},n._wrp=r,u(n,o)),n||o}function v(e){return e&&(e.fn?e:this.getRsc("templates",e)||re(e))}function h(e,t,n,r){var o,s,a="number"==typeof n&&t.tmpl.bnds[n-1],d=t.linkCtx;return void 0!==r?n=r={props:{},args:[r]}:a&&(n=a(t.data,t,ae)),s=n.args[0],(e||a)&&(o=d&&d.tag,o||(o=u(new ae._tg,{_:{inline:!d,bnd:a,unlinked:!0},tagName:":",cvt:e,flow:!0,tagCtx:n}),d&&(d.tag=o,o.linkCtx=d),n.ctx=L(n.ctx,(d?d.view:t).ctx)),o._er=r&&s,i(o,n),n.view=t,o.ctx=n.ctx||o.ctx||{},n.ctx=void 0,s=o.cvtArgs("true"!==e&&e)[0],s=a&&t._.onRender?t._.onRender(s,t,o):s),void 0!=s?s:""}function m(e){var t=this,n=t.tagCtx,r=n.view,i=n.args;return e=e||t.convert,e=e&&(""+e===e?r.getRsc("converters",e)||S("Unknown converter: '"+e+"'"):e),i=i.length||n.index?e?i.slice():i:[r.data],e&&(e.depends&&(t.depends=ae.getDeps(t.depends,t,e.depends,e)),i[0]=e.apply(t,i)),i}function w(e,t){for(var n,r,i=this;void 0===n&&i;)r=i.tmpl&&i.tmpl[e],n=r&&r[t],i=i.parent;return n||Y[e][t]}function x(e,t,n,r,o,s){t=t||X;var a,d,u,l,p,c,f,g,v,h,m,w,x,b,_,y,k,j,C,A="",T=t.linkCtx||0,V=t.ctx,R=n||t.tmpl,M="number"==typeof r&&t.tmpl.bnds[r-1];for("tag"===e._is?(a=e,e=a.tagName,r=a.tagCtxs,u=a.template):(d=t.getRsc("tags",e)||S("Unknown tag: {{"+e+"}} "),u=d.template),void 0!==s?(A+=s,r=s=[{props:{},args:[]}]):M&&(r=M(t.data,t,ae)),g=r.length,f=0;g>f;f++)h=r[f],(!T||!T.tag||f&&!T.tag._.inline||a._er)&&((w=R.tmpls&&h.tmpl)&&(w=h.content=R.tmpls[w-1]),h.index=f,h.tmpl=w,h.render=N,h.view=t,h.ctx=L(h.ctx,V)),(n=h.props.tmpl)&&(h.tmpl=t.getTmpl(n)),a||(a=new d._ctr,x=!!a.init,a.parent=c=V&&V.tag,a.tagCtxs=r,C=a.dataMap,T&&(a._.inline=!1,T.tag=a,a.linkCtx=T),(a._.bnd=M||T.fn)?a._.arrVws={}:a.dataBoundOnly&&S("{^{"+e+"}} tag must be data-bound")),r=a.tagCtxs,C=a.dataMap,h.tag=a,C&&r&&(h.map=r[f].map),a.flow||(m=h.ctx=h.ctx||{},l=a.parents=m.parentTags=V&&L(m.parentTags,V.parentTags)||{},c&&(l[c.tagName]=c),l[a.tagName]=m.tag=a);if(!(a._er=s)){for(i(a,r[0]),a.rendering={},f=0;g>f;f++)h=a.tagCtx=r[f],k=h.props,y=a.cvtArgs(),(b=k.dataMap||C)&&(y.length||k.dataMap)&&(_=h.map,_&&_.src===y[0]&&!o||(_&&_.src&&_.unmap(),_=h.map=b.map(y[0],k,void 0,!a._.bnd)),y=[_.tgt]),a.ctx=h.ctx,f||(x&&(j=a.template,a.init(h,T,a.ctx),x=void 0),T&&(T.attr=a.attr=T.attr||a.attr),p=a.attr,a._.noVws=p&&p!==Ee),v=void 0,a.render&&(v=a.render.apply(a,y)),y.length||(y=[t]),void 0===v&&(v=h.render(y[0],!0)||(o?void 0:"")),A=A?A+(v||""):v;a.rendering=void 0}return a.tagCtx=r[0],a.ctx=a.tagCtx.ctx,a._.noVws&&a._.inline&&(A="text"===p?ie.html(A):""),M&&t._.onRender?t._.onRender(A,t,a):A}function b(e,t,n,r,i,o,s,a){var d,u,l,p=this,f="array"===t;p.content=a,p.views=f?[]:{},p.parent=n,p.type=t||"top",p.data=r,p.tmpl=i,l=p._={key:0,useKey:f?0:1,id:""+$e++,onRender:s,bnds:{}},p.linked=!!s,n?(d=n.views,u=n._,u.useKey?(d[l.key="_"+u.useKey++]=p,p.index=Ue,p.getIndex=c):d.length===(l.key=p.index=o)?d.push(p):d.splice(o,0,p),p.ctx=e||n.ctx):p.ctx=e}function _(e){var t,n,r,i,o,s,a;for(t in Oe)if(o=Oe[t],(s=o.compile)&&(n=e[t+"s"]))for(r in n)i=n[r]=s(r,n[r],e,0),i._is=t,i&&(a=ae.onStore[t])&&a(r,i,s)}function y(e,t,n){function i(){var t=this;t._={inline:!0,unlinked:!0},t.tagName=e}var o,s,a,d=new ae._tg;if(te(t)?t={depends:t.depends,render:t}:""+t===t&&(t={template:t}),s=t.baseTag){t.flow=!!t.flow,t.baseTag=s=""+s===s?n&&n.tags[s]||se[s]:s,d=u(d,s);for(a in t)d[a]=r(s[a],t[a])}else d=u(d,t);return void 0!==(o=d.template)&&(d.template=""+o===o?re[o]||re(o):o),d.init!==!1&&((i.prototype=d).constructor=d._ctr=i),n&&(d._parentTmpl=n),d}function k(e){return this.base.apply(this,e)}function j(e,n,r,i){function o(n){var o,a;if(""+n===n||n.nodeType>0&&(s=n)){if(!s)if(/^\.\/[^\\:*?"<>]*$/.test(n))(a=re[e=e||n])?n=a:s=document.getElementById(n);else if(t.fn&&!W.test(n))try{s=t(document).find(n)[0]}catch(d){}s&&(i?n=s.innerHTML:(o=s.getAttribute(Se),o?o!==Ie?(n=re[o],delete re[o]):t.fn&&(n=t.data(s)[Ie]):(e=e||(t.fn?Ie:n),n=j(e,s.innerHTML,r,i)),n.tmplName=e=e||o,e!==Ie&&(re[e]=n),s.setAttribute(Se,e),t.fn&&t.data(s,Ie,n))),s=void 0}else n.fn||(n=void 0);return n}var s,a,d=n=n||"";return 0===i&&(i=void 0,d=o(d)),i=i||(n.markup?n:{}),i.tmplName=e,r&&(i._parentTmpl=r),!d&&n.markup&&(d=o(n.markup))&&d.fn&&(d=d.markup),void 0!==d?(d.fn||n.fn?d.fn&&(a=d):(n=V(d,i),U(d.replace(ke,"\\$&"),n)),a||(_(i),a=u(function(){return n.render.apply(n,arguments)},n)),e&&!r&&e!==Ie&&(qe[e]=a),a):void 0}function C(e,n){return t.isFunction(e)?e.call(n):e}function A(e){var t,n=[],r=e.length;for(t=0;r>t;t++)n.push(e[t].unmap());return n}function T(e,n){function r(e){l.apply(this,e)}function i(){return new r(arguments)}function o(e,t){var n,r,i,o,s,a=c.length;for(n=0;a>n;n++)o=c[n],r=void 0,o+""!==o&&(r=o,o=r.getter),void 0===(s=e[o])&&r&&void 0!==(i=r.defaultVal)&&(s=C(i,e)),t(s,r&&p[r.type],o)}function s(n){n=n+""===n?JSON.parse(n):n;var r,i,s,u=n,l=[];if(t.isArray(n)){for(n=n||[],i=n.length,r=0;i>r;r++)l.push(this.map(n[r]));return l._is=e,l.unmap=d,l.merge=a,l}if(n){o(n,function(e,t){t&&(e=t.map(e)),l.push(e)}),u=this.apply(this,l);for(s in n)s===ee||b[s]||(u[s]=n[s])}return u}function a(e){e=e+""===e?JSON.parse(e):e;var n,r,s,a,d,u,l,p,c,f,v=this;if(t.isArray(v)){for(p={},f=[],s=e.length,a=v.length,n=0;s>n;n++){for(c=e[n],l=!1,r=0;a>r&&!l;r++)p[r]||(u=v[r],g&&(p[r]=l=g+""===g?c[g]&&(b[g]?u[g]():u[g])===c[g]:g(u,c)));l?(u.merge(c),f.push(u)):f.push(i.map(c))}return void(x?x(v).refresh(f,!0):v.splice.apply(v,[0,v.length].concat(f)))}o(e,function(e,t,n){t?v[n]().merge(e):v[n](e)});for(d in e)d===ee||b[d]||(v[d]=e[d])}function d(){var e,n,r,i,o,s,a=this;if(t.isArray(a))return A(a);for(e={},i=c.length,r=0;i>r;r++)n=c[r],o=void 0,n+""!==n&&(o=n,n=o.getter),s=a[n](),e[n]=o&&s&&p[o.type]?t.isArray(s)?A(s):s.unmap():s;for(n in a)"_is"===n||b[n]||n===ee||"_"===n.charAt(0)&&b[n.slice(1)]||t.isFunction(a[n])||(e[n]=a[n]);return e}var u,l,p=this,c=n.getters,f=n.extend,g=n.id,v=t.extend({_is:e||"unnamed",unmap:d,merge:a},f),h="",m="",w=c?c.length:0,x=t.observable,b={};for(r.prototype=v,u=0;w>u;u++)!function(e){e=e.getter||e,b[e]=u+1;var t="_"+e;h+=(h?",":"")+e,m+="this."+t+" = "+e+";\n",v[e]=v[e]||function(n){return arguments.length?void(x?x(this).setProperty(e,n):this[t]=n):this[t]},x&&(v[e].set=v[e].set||function(e){this[t]=e})}(c[u]);return l=new Function(h,m.slice(0,-1)),l.prototype=v,v.constructor=l,i.map=s,i.getters=c,i.extend=f,i.id=g,i}function V(e,n){var r,i=ue._wm||{},o=u({tmpls:[],links:{},bnds:[],_is:"template",render:N},n);return o.markup=e,n.htmlTag||(r=Ae.exec(e),o.htmlTag=r?r[1].toLowerCase():""),r=i[o.htmlTag],r&&r!==i.div&&(o.markup=t.trim(o.markup)),o}function R(e,t){function n(i,o,s){var a,d,u,l;if(i&&typeof i===Fe&&!i.nodeType&&!i.markup&&!i.getTgt&&!("viewModel"===e&&i.getters||i.extend)){for(u in i)n(u,i[u],o);return o||Y}return void 0===o&&(o=i,i=void 0),i&&""+i!==i&&(s=o,o=i,i=void 0),l=s?"viewModel"===e?s:s[r]=s[r]||{}:n,d=t.compile,null===o?i&&delete l[i]:(o=d?d.call(l,i,o,s,0):o,i&&(l[i]=o)),d&&o&&(o._is=e),o&&(a=ae.onStore[e])&&a(i,o,d),o}var r=e+"s";Y[r]=n}function M(e){le[e]=function(t){return arguments.length?(de[e]=t,le):de[e]}}function $(e){function t(t,n){this.tgt=e.getTgt(t,n)}return te(e)&&(e={getTgt:e}),e.baseMap&&(e=u(u({},e.baseMap),e)),e.map=function(e,n){return new t(e,n)},e}function N(e,t,n,r,i,o){var s,a,d,u,l,p,c,f,g=r,v="";if(t===!0?(n=t,t=void 0):typeof t!==Fe&&(t=void 0),(d=this.tag)?(l=this,g=g||l.view,u=g.getTmpl(d.template||l.tmpl),arguments.length||(e=g)):u=this,u){if(!g&&e&&"view"===e._is&&(g=e),g&&e===g&&(e=g.data),p=!g,me=me||p,g||((t=t||{}).root=e),!me||ue.useViews||u.useViews||g&&g!==X)v=E(u,e,t,n,g,i,o,d);else{if(g?(c=g.data,f=g.index,g.index=Ue):(g=X,g.data=e,g.ctx=t),ne(e)&&!n)for(s=0,a=e.length;a>s;s++)g.index=s,g.data=e[s],v+=u.fn(e[s],g,ae);else g.data=e,v+=u.fn(e,g,ae);g.data=c,g.index=f}p&&(me=void 0)}return v}function E(e,t,n,r,i,o,s,a){function d(e){_=u({},n),_[x]=e}var l,p,c,f,g,v,h,m,w,x,_,y,k="";if(a&&(w=a.tagName,y=a.tagCtx,n=n?L(n,a.ctx):a.ctx,e===i.content?h=e!==i.ctx._wrp?i.ctx._wrp:void 0:e!==y.content?e===a.template?(h=y.tmpl,n._wrp=y.content):h=y.content||i.content:h=i.content,y.props.link===!1&&(n=n||{},n.link=!1),(x=y.props.itemVar)&&("~"!==x.charAt(0)&&I("Use itemVar='~myItem'"),x=x.slice(1))),i&&(s=s||i._.onRender,n=L(n,i.ctx)),o===!0&&(v=!0,o=0),s&&(n&&n.link===!1||a&&a._.noVws)&&(s=void 0),m=s,s===!0&&(m=void 0,s=i._.onRender),n=e.helpers?L(e.helpers,n):n,_=n,ne(t)&&!r)for(c=v?i:void 0!==o&&i||new b(n,"array",i,t,e,o,s),i&&i._.useKey&&(c._.bnd=!a||a._.bnd&&a),x&&(c.it=x),x=c.it,l=0,p=t.length;p>l;l++)x&&d(t[l]),f=new b(_,"item",c,t[l],e,(o||0)+l,s,h),g=e.fn(t[l],f,ae),k+=c._.onRender?c._.onRender(g,f):g;else x&&d(t),c=v?i:new b(_,w||"data",i,t,e,o,s,h),a&&!a.flow&&(c.tag=a),k+=e.fn(t,c,ae);return m?m(k,c):k}function F(e,t,n){var r=void 0!==n?te(n)?n.call(t.data,e,t):n||"":"{Error: "+e.message+"}";return de.onError&&void 0!==(n=de.onError.call(t.data,e,n&&r,t))&&(r=n),t&&!t.linkCtx?ie.html(r):r}function S(e){throw new ae.Err(e)}function I(e){S("Syntax error\n"+e)}function U(e,t,n,r,i){function o(t){t-=v,t&&m.push(e.substr(v,t).replace(_e,"\\n"))}function s(t,n){t&&(t+="}}",I((n?"{{"+n+"}} block has {{/"+t+" without {{"+t:"Unmatched or missing {{/"+t)+", in template:\n"+e))}function a(a,d,u,c,g,x,b,_,y,k,j,C){(b&&d||y&&!u||_&&":"===_.slice(-1)||k)&&I(a),x&&(g=":",c=Ee),y=y||n&&!i;var A=(d||n)&&[[]],T="",V="",R="",M="",$="",N="",E="",F="",S=!y&&!g;u=u||(_=_||"#data",g),o(C),v=C+a.length,b?f&&m.push(["*","\n"+_.replace(/^:/,"ret+= ").replace(ye,"$1")+";\n"]):u?("else"===u&&(Ce.test(_)&&I('for "{{else if expr}}" use "{{else expr}}"'),A=w[7]&&[[]],w[8]=e.substring(w[8],C),w=h.pop(),m=w[2],S=!0),_&&O(_.replace(_e," "),A,t).replace(je,function(e,t,n,r,i,o,s,a){return r="'"+i+"':",s?(V+=o+",",M+="'"+a+"',"):n?(R+=r+o+",",N+=r+"'"+a+"',"):t?E+=o:("trigger"===i&&(F+=o),T+=r+o+",",$+=r+"'"+a+"',",p=p||Re.test(i)),""}).slice(0,-1),A&&A[0]&&A.pop(),l=[u,c||!!r||p||"",S&&[],J(M||(":"===u?"'#data',":""),$,N),J(V||(":"===u?"data,":""),T,R),E,F,A||0],m.push(l),S&&(h.push(w),w=l,w[8]=v)):j&&(s(j!==w[0]&&"else"!==w[0]&&j,w[0]),w[8]=e.substring(w[8],C),w=h.pop()),s(!w&&j),m=w[2]}var d,u,l,p,c,f=de.allowCode||t&&t.allowCode||le.allowCode===!0,g=[],v=0,h=[],m=g,w=[,,g];if(f&&(t.allowCode=f),n&&(void 0!==r&&(e=e.slice(0,-r.length-2)+ge),e=pe+e+ge),s(h[0]&&h[0][2].pop()[0]),e.replace(G,a),o(e.length),(v=g[g.length-1])&&s(""+v!==v&&+v[8]===v[8]&&v[0]),n){for(u=B(g,e,n),c=[],d=g.length;d--;)c.unshift(g[d][7]);q(u,c)}else u=B(g,t);return u}function q(e,t){var n,r,i=0,o=t.length;for(e.deps=[];o>i;i++){r=t[i];for(n in r)"_jsvto"!==n&&r[n].length&&(e.deps=e.deps.concat(r[n]))}e.paths=r}function J(e,t,n){return[e.slice(0,-1),t.slice(0,-1),n.slice(0,-1)]}function K(e,t){return"\n "+(t?t+":{":"")+"args:["+e[0]+"]"+(e[1]||!t?",\n props:{"+e[1]+"}":"")+(e[2]?",\n ctx:{"+e[2]+"}":"")}function O(e,t,n){function r(r,m,w,x,b,_,y,k,j,C,A,T,V,R,M,$,N,E,F,S){function q(e,n,r,s,a,d,p,c){var f="."===r;if(r&&(b=b.slice(n.length),/^\.?constructor$/.test(c||b)&&I(e),f||(e=(s?'view.hlp("'+s+'")':a?"view":"data")+(c?(d?"."+d:s?"":a?"":"."+r)+(p||""):(c=s?"":a?d||"":r,"")),e+=c?"."+c:"",e=n+("view.data"===e.slice(0,9)?e.slice(5):e)),u)){if(O="linkTo"===i?o=t._jsvto=t._jsvto||[]:l.bd,B=f&&O[O.length-1]){if(B._jsv){for(;B.sb;)B=B.sb;B.bnd&&(b="^"+b.slice(1)),B.sb=b,B.bnd=B.bnd||"^"===b.charAt(0)}}else O.push(b);h[g]=F+(f?1:0)}return e}x=u&&x,x&&!k&&(b=x+b),_=_||"",w=w||m||T,b=b||j,C=C||N||"";var J,K,O,B,L,Q=")";if("["===C&&(C="[j._sq(",Q=")]"),!y||d||a){if(u&&$&&!d&&!a&&(!i||s||o)&&(J=h[g-1],S.length-1>F-(J||0))){if(J=S.slice(J,F+r.length),K!==!0)if(O=o||p[g-1].bd,B=O[O.length-1],B&&B.prm){for(;B.sb&&B.sb.prm;)B=B.sb;L=B.sb={path:B.sb,bnd:B.bnd}}else O.push(L={path:O.pop()});$=ce+":"+J+" onerror=''"+fe,K=f[$],K||(f[$]=!0,f[$]=K=U($,n,!0)),K!==!0&&L&&(L._jsv=K,L.prm=l.bd,L.bnd=L.bnd||L.path&&L.path.indexOf("^")>=0)}return d?(d=!V,d?r:T+'"'):a?(a=!R,a?r:T+'"'):(w?(h[g]=F++,l=p[++g]={bd:[]},w):"")+(E?g?"":(c=S.slice(c,F),(i?(i=s=o=!1,"\b"):"\b,")+c+(c=F+r.length,u&&t.push(l.bd=[]),"\b")):k?(g&&I(e),u&&t.pop(),i=b,s=x,c=F+r.length,x&&(u=l.bd=t[i]=[]),b+":"):b?b.split("^").join(".").replace(xe,q)+(C?(l=p[++g]={bd:[]},v[g]=Q,C):_):_?_:M?(M=v[g]||M,v[g]=!1,l=p[--g],M+(C?(l=p[++g],v[g]=Q,C):"")):A?(v[g]||I(e),","):m?"":(d=V,a=R,'"'))}I(e)}var i,o,s,a,d,u=t&&t[0],l={bd:u},p={0:l},c=0,f=n?n.links:u&&(u.links=u.links||{}),g=0,v={},h={},m=(e+(n?" ":"")).replace(be,r);return!g&&m||I(e)}function B(e,t,n){var r,i,o,s,a,d,u,l,p,c,f,g,v,h,m,w,x,b,_,y,k,j,C,A,T,R,M,$,N,E,F=0,S=ue.useViews||t.useViews||t.tags||t.templates||t.helpers||t.converters,U="",J={},O=e.length;for(""+t===t?(b=n?'data-link="'+t.replace(_e," ").slice(1,-1)+'"':t,t=0):(b=t.tmplName||"unnamed",t.allowCode&&(J.allowCode=!0),t.debug&&(J.debug=!0),f=t.bnds,x=t.tmpls),r=0;O>r;r++)if(i=e[r],""+i===i)U+='\n+"'+i+'"';else if(o=i[0],"*"===o)U+=";\n"+i[1]+"\nret=ret";else{if(s=i[1],k=!n&&i[2],a=K(i[3],"params")+"},"+K(v=i[4]),$=i[5],E=i[6],j=i[8]&&i[8].replace(ye,"$1"),(T="else"===o)?g&&g.push(i[7]):(F=0,f&&(g=i[7])&&(g=[g],F=f.push(1))),S=S||v[1]||v[2]||g||/view.(?!index)/.test(v[0]),(R=":"===o)?s&&(o=s===Ee?">":s+o):(k&&(_=V(j,J),_.tmplName=b+"/"+o,_.useViews=_.useViews||S,B(k,_),S=_.useViews,x.push(_)),T||(y=o,S=S||o&&(!se[o]||!se[o].flow),A=U,U=""),C=e[r+1],C=C&&"else"===C[0]),N=$?";\ntry{\nret+=":"\n+",h="",m="",R&&(g||E||s&&s!==Ee)){if(M=new Function("data,view,j,u"," // "+b+" "+F+" "+o+"\nreturn {"+a+"};"),M._er=$,M._tag=o,n)return M;q(M,g),w='c("'+s+'",view,',c=!0,h=w+F+",",m=")"}if(U+=R?(n?($?"try{\n":"")+"return ":N)+(c?(c=void 0,S=p=!0,w+(g?(f[F-1]=M,F):"{"+a+"}")+")"):">"===o?(u=!0,"h("+v[0]+")"):(l=!0,"((v="+v[0]+")!=null?v:"+(n?"null)":'"")'))):(d=!0,"\n{view:view,tmpl:"+(k?x.length:"0")+","+a+"},"),y&&!C){if(U="["+U.slice(0,-1)+"]",w='t("'+y+'",view,this,',n||g){if(U=new Function("data,view,j,u"," // "+b+" "+F+" "+y+"\nreturn "+U+";"),U._er=$,U._tag=y,g&&q(f[F-1]=U,g),n)return U;h=w+F+",undefined,",m=")"}U=A+N+w+(F||U)+")",g=0,y=0}$&&(S=!0,U+=";\n}catch(e){ret"+(n?"urn ":"+=")+h+"j._err(e,view,"+$+")"+m+";}"+(n?"":"ret=ret"))}U="// "+b+"\nvar v"+(d?",t=j._tag":"")+(p?",c=j._cnvt":"")+(u?",h=j._html":"")+(n?";\n":',ret=""\n')+(J.debug?"debugger;":"")+U+(n?"\n":";\nreturn ret;"),de.debugMode!==!1&&(U="try {\n"+U+"\n}catch(e){\nreturn j._err(e, view);\n}");try{U=new Function("data,view,j,u",U)}catch(L){I("Compiled template code:\n\n"+U+'\n: "'+L.message+'"')}return t&&(t.fn=U,t.useViews=!!S),U}function L(e,t){return e&&e!==t?t?u(u({},t),e):e:t&&u({},t)}function Q(e){return Ne[e]||(Ne[e]="&#"+e.charCodeAt(0)+";")}function H(e){var t,n,r=[];if(typeof e===Fe)for(t in e)n=e[t],t===ee||te(n)||r.push({key:t,prop:n});return r}function P(e,n,r){var i=this.jquery&&(this[0]||S('Unknown template: "'+this.selector+'"')),o=i.getAttribute(Se);return N.call(o?t.data(i)[Ie]:re(i),e,n,r)}function D(e){return void 0!=e?Ve.test(e)&&(""+e).replace(Me,Q)||e:""}var Z=t===!1;t=t&&t.fn?t:e.jQuery;var z,G,W,X,Y,ee,te,ne,re,ie,oe,se,ae,de,ue,le,pe,ce,fe,ge,ve,he,me,we="v0.9.78",xe=/^(!*?)(?:null|true|false|\d[\d.]*|([\w$]+|\.|~([\w$]+)|#(view|([\w$]+))?)([\w$.^]*?)(?:[.[^]([\w$]+)\]?)?)$/g,be=/(\()(?=\s*\()|(?:([([])\s*)?(?:(\^?)(!*?[#~]?[\w$.^]+)?\s*((\+\+|--)|\+|-|&&|\|\||===|!==|==|!=|<=|>=|[<>%*:?\/]|(=))\s*|(!*?[#~]?[\w$.^]+)([([])?)|(,\s*)|(\(?)\\?(?:(')|("))|(?:\s*(([)\]])(?=\s*[.^]|\s*$|[^([])|[)\]])([([]?))|(\s+)/g,_e=/[ \t]*(\r\n|\n|\r)/g,ye=/\\(['"])/g,ke=/['"\\]/g,je=/(?:\x08|^)(onerror:)?(?:(~?)(([\w$_\.]+):)?([^\x08]+))\x08(,)?([^\x08]+)/gi,Ce=/^if\s/,Ae=/<(\w+)[>\s]/,Te=/[\x00`><"'&=]/g,Ve=/[\x00`><\"'&=]/,Re=/^on[A-Z]|^convert(Back)?$/,Me=Te,$e=0,Ne={"&":"&","<":"<",">":">","\x00":"�","'":"'",'"':""","`":"`","=":"="},Ee="html",Fe="object",Se="data-jsv-tmpl",Ie="jsvTmpl",Ue="For #index in nested block use #getIndex().",qe={},Je=e.jsrender,Ke=Je&&t&&!t.render,Oe={template:{compile:j},tag:{compile:y},viewModel:{compile:T},helper:{},converter:{}};if(Y={jsviews:we,sub:{View:b,Err:d,tmplFn:U,parse:O,extend:u,extendCtx:L,syntaxErr:I,onStore:{},addSetting:M,settings:{allowCode:!1},advSet:s,_ths:i,_tg:function(){},_cnvt:h,_tag:x,_er:S,_err:F,_html:D,_sq:function(e){return"constructor"===e&&I(""),e}},settings:{delimiters:l,advanced:function(e){return e?(u(ue,e),ae.advSet(),le):ue}},map:$},(d.prototype=new Error).constructor=d,c.depends=function(){return[this.get("item"),"index"]},f.depends="index",b.prototype={get:p,getIndex:f,getRsc:w,getTmpl:v,hlp:g,_is:"view"},ae=Y.sub,le=Y.settings,!(Je||t&&t.render)){for(z in Oe)R(z,Oe[z]);ie=Y.converters,oe=Y.helpers,se=Y.tags,ae._tg.prototype={baseApply:k,cvtArgs:m},X=ae.topView=new b,t?(t.fn.render=P,ee=t.expando,t.observable&&(u(ae,t.views.sub),Y.map=t.views.map)):(t={},Z&&(e.jsrender=t),t.renderFile=t.__express=t.compile=function(){throw"Node.js: use npm jsrender, or jsrender-node.js"},t.isFunction=function(e){return"function"==typeof e},t.isArray=Array.isArray||function(e){return"[object Array]"==={}.toString.call(e)},ae._jq=function(e){e!==t&&(u(e,t),t=e,t.fn.render=P,delete t.jsrender,ee=t.expando)},t.jsrender=we),de=ae.settings,de.allowCode=!1,te=t.isFunction,ne=t.isArray,t.render=qe,t.views=Y,t.templates=re=Y.templates;for(he in de)M(he);(le.debugMode=function(e){return void 0===e?de.debugMode:(de.debugMode=e,de.onError=e+""===e?new Function("","return '"+e+"';"):te(e)?e:void 0,le)})(!1),ue=de.advanced={useViews:!1,_jsv:!1},se({"if":{render:function(e){var t=this,n=t.tagCtx,r=t.rendering.done||!e&&(arguments.length||!n.index)?"":(t.rendering.done=!0,t.selected=n.index,n.render(n.view,!0));return r},flow:!0},"for":{render:function(e){var t,n=!arguments.length,r=this,i=r.tagCtx,o="",s=0;return r.rendering.done||(t=n?i.view.data:e,void 0!==t&&(o+=i.render(t,n),s+=ne(t)?t.length:1),(r.rendering.done=s)&&(r.selected=i.index)),o},flow:!0},props:{baseTag:"for",dataMap:$(H),flow:!0},include:{flow:!0},"*":{render:o,flow:!0},":*":{render:o,flow:!0},dbg:oe.dbg=ie.dbg=a}),ie({html:D,attr:D,url:function(e){return void 0!=e?encodeURI(""+e):null===e?e:""}})}return de=ae.settings,le.delimiters("{{","}}","^"),Ke&&Je.views.sub._jq(t),t||Je},window); +//# sourceMappingURL=jsrender.min.js.map diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/layerBtn.js b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/layerBtn.js index 259271df3..9f9eda1e1 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/layerBtn.js +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/layerBtn.js @@ -55,8 +55,8 @@ function layerBtnsInit() { }); }); - $(".build-recipe-btn").unbind('click'); - $(".build-recipe-btn").click(function(e){ + $("td .build-recipe-btn").unbind('click'); + $("td .build-recipe-btn").click(function(e){ e.preventDefault(); var recipe = $(this).data('recipe-name'); diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/layerDepsModal.js b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/layerDepsModal.js index 825f9dccd..e9622243a 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/layerDepsModal.js +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/layerDepsModal.js @@ -6,7 +6,12 @@ * addToProject: Whether to add layers to project on accept * successAdd: function to run on success */ -function showLayerDepsModal(layer, dependencies, title, body, addToProject, successAdd) { +function showLayerDepsModal(layer, + dependencies, + title, + body, + addToProject, + successAdd) { if ($("#dependencies-modal").length === 0) { $.get(libtoaster.ctx.htmlUrl + "/layer_deps_modal.html", function(html){ @@ -33,17 +38,20 @@ function showLayerDepsModal(layer, dependencies, title, body, addToProject, succ var deplistHtml = ""; for (var i = 0; i < dependencies.length; i++) { - deplistHtml += "<li><label class=\"checkbox\"><input name=\"dependencies\" value=\""; + deplistHtml += "<li><div class=\"checkbox\"><label><input name=\"dependencies\" value=\""; deplistHtml += dependencies[i].id; deplistHtml +="\" type=\"checkbox\" checked=\"checked\"/>"; deplistHtml += dependencies[i].name; - deplistHtml += "</label></li>"; + deplistHtml += "</label></div></li>"; } $('#dependencies-list').html(deplistHtml); $("#dependencies-modal").data("deps", dependencies); - $('#dependencies-modal').modal('show'); + /* Clear any alert notifications before showing the modal */ + $(".alert").fadeOut(function(){ + $('#dependencies-modal').modal('show'); + }); /* Discard the old submission function */ $("#dependencies-modal-form").unbind('submit'); diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/layerdetails.js b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/layerdetails.js index d54540626..9ead393cb 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/layerdetails.js +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/layerdetails.js @@ -10,25 +10,27 @@ function layerDetailsPageInit (ctx) { var targetTab = $("#targets-tab"); var machineTab = $("#machines-tab"); var detailsTab = $("#details-tab"); + var editLayerSource = $("#edit-layer-source"); + var saveSourceChangesBtn = $("#save-changes-for-switch"); + var layerGitRefInput = $("#layer-git-ref"); + var layerSubDirInput = $('#layer-subdir'); + + targetTab.on('show.bs.tab', targetsTabShow); + detailsTab.on('show.bs.tab', detailsTabShow); + machineTab.on('show.bs.tab', machinesTabShow); /* setup the dependencies typeahead */ - libtoaster.makeTypeahead(layerDepInput, libtoaster.ctx.layersTypeAheadUrl, { include_added: "true" }, function(item){ + libtoaster.makeTypeahead(layerDepInput, + libtoaster.ctx.layersTypeAheadUrl, + { include_added: "true" }, function(item){ currentLayerDepSelection = item; - layerDepBtn.removeAttr("disabled"); }); - $(window).on('hashchange', function(e){ - switch(window.location.hash){ - case '#machines': - machineTab.tab('show'); - break; - case '#recipes': - targetTab.tab('show'); - break; - default: - detailsTab.tab('show'); - break; + /* disable the add layer button if its input field is empty */ + layerDepInput.on("keyup",function(){ + if ($(this).val().length === 0) { + layerDepBtn.attr("disabled", "disabled"); } }); @@ -76,7 +78,7 @@ function layerDetailsPageInit (ctx) { addRemoveDep(currentLayerDepSelection.id, true, function(){ /* Make a list item for the new layer dependency */ - var newLayerDep = $("<li><a></a><span class=\"icon-trash\" data-toggle=\"tooltip\" title=\"Delete\"></span></li>"); + var newLayerDep = $("<li><a></a><span class=\"glyphicon glyphicon-trash\" data-toggle=\"tooltip\" title=\"Delete\"></span></li>"); newLayerDep.data('layer-id', currentLayerDepSelection.id); newLayerDep.children("span").tooltip(); @@ -94,11 +96,11 @@ function layerDetailsPageInit (ctx) { /* Clear the current selection */ layerDepInput.val(""); currentLayerDepSelection = undefined; - layerDepBtn.attr("disabled","disabled"); + layerDepBtn.attr("disabled", "disabled"); }); }); - $(".icon-pencil").click(function (){ + $(".glyphicon-edit").click(function (){ var mParent = $(this).parent("dd"); mParent.prev().css("margin-top", "10px"); mParent.children("form").slideDown(); @@ -106,8 +108,12 @@ function layerDetailsPageInit (ctx) { currentVal.hide(); /* Set the current value to the input field */ mParent.find("textarea,input").val(currentVal.text()); + /* If the input field is empty, disable the submit button */ + if ( mParent.find("textarea,input").val().length == 0 ) { + mParent.find(".change-btn").attr("disabled", "disabled"); + } /* Hides the "Not set" text */ - mParent.children(".muted").hide(); + mParent.children(".text-muted").hide(); /* We're editing so hide the delete icon */ mParent.children(".delete-current-value").hide(); mParent.find(".cancel").show(); @@ -128,30 +134,31 @@ function layerDetailsPageInit (ctx) { mParent.children(".current-value").show(); /* Show the "Not set" text if we ended up with no value */ if (!mParent.children(".current-value").html()){ - mParent.children(".muted").fadeIn(); + mParent.children(".text-muted").fadeIn(); mParent.children(".delete-current-value").hide(); } else { mParent.children(".delete-current-value").show(); } - mParent.children(".icon-pencil").show(); - mParent.prev().css("margin-top", "0px"); + mParent.children(".glyphicon-edit").show(); + mParent.prev().css("margin-top", "0"); }); }); + function defaultAddBtnText(){ var text = " Add the "+ctx.layerVersion.name+" layer to your project"; addRmLayerBtn.text(text); - addRmLayerBtn.prepend("<span class=\"icon-plus\"></span>"); + addRmLayerBtn.prepend("<span class=\"glyphicon glyphicon-plus\"></span>"); addRmLayerBtn.removeClass("btn-danger"); } - detailsTab.on('show', function(){ + function detailsTabShow(){ if (!ctx.layerVersion.inCurrentPrj) defaultAddBtnText(); - window.location.hash = "details"; - }); + window.location.hash = "information"; + } function targetsTabShow(){ if (!ctx.layerVersion.inCurrentPrj){ @@ -159,7 +166,7 @@ function layerDetailsPageInit (ctx) { var text = " Add the "+ctx.layerVersion.name+" layer to your project "+ "to enable these recipes"; addRmLayerBtn.text(text); - addRmLayerBtn.prepend("<span class=\"icon-plus\"></span>"); + addRmLayerBtn.prepend("<span class=\"glyphicon glyphicon-plus\"></span>"); } else { defaultAddBtnText(); } @@ -177,7 +184,7 @@ function layerDetailsPageInit (ctx) { $("#no-recipes-yet").hide(); } - targetTab.removeClass("muted"); + targetTab.removeClass("text-muted"); if (window.location.hash === "#recipes"){ /* re run the machinesTabShow to update the text */ targetsTabShow(); @@ -192,20 +199,19 @@ function layerDetailsPageInit (ctx) { else $("#no-machines-yet").hide(); - machineTab.removeClass("muted"); + machineTab.removeClass("text-muted"); if (window.location.hash === "#machines"){ /* re run the machinesTabShow to update the text */ machinesTabShow(); } $(".select-machine-btn").click(function(e){ - if ($(this).attr("disabled") === "disabled") + if ($(this).hasClass("disabled")) e.preventDefault(); }); }); - targetTab.on('show', targetsTabShow); function machinesTabShow(){ if (!ctx.layerVersion.inCurrentPrj) { @@ -213,7 +219,7 @@ function layerDetailsPageInit (ctx) { var text = " Add the "+ctx.layerVersion.name+" layer to your project " + "to enable these machines"; addRmLayerBtn.text(text); - addRmLayerBtn.prepend("<span class=\"icon-plus\"></span>"); + addRmLayerBtn.prepend("<span class=\"glyphicon glyphicon-plus\"></span>"); } else { defaultAddBtnText(); } @@ -222,8 +228,6 @@ function layerDetailsPageInit (ctx) { window.location.hash = "machines"; } - machineTab.on('show', machinesTabShow); - $(".pagesize").change(function(){ var search = libtoaster.parseUrlParams(); search.limit = this.value; @@ -239,17 +243,17 @@ function layerDetailsPageInit (ctx) { if (added){ /* enable and switch all the button states */ - $(".build-recipe-btn").removeAttr("disabled"); - $(".select-machine-btn").removeAttr("disabled"); + $(".build-recipe-btn").removeClass("disabled"); + $(".select-machine-btn").removeClass("disabled"); addRmLayerBtn.addClass("btn-danger"); addRmLayerBtn.data('directive', "remove"); addRmLayerBtn.text(" Remove the "+ctx.layerVersion.name+" layer from your project"); - addRmLayerBtn.prepend("<span class=\"icon-trash\"></span>"); + addRmLayerBtn.prepend("<span class=\"glyphicon glyphicon-trash\"></span>"); } else { /* disable and switch all the button states */ - $(".build-recipe-btn").attr("disabled","disabled"); - $(".select-machine-btn").attr("disabled", "disabled"); + $(".build-recipe-btn").addClass("disabled"); + $(".select-machine-btn").addClass("disabled"); addRmLayerBtn.removeClass("btn-danger"); addRmLayerBtn.data('directive', "add"); @@ -257,7 +261,7 @@ function layerDetailsPageInit (ctx) { * on which tab is currently visible. Unfortunately we can't just call * tab('show') as if it's already visible it doesn't run the event. */ - switch ($(".nav-pills .active a").prop('id')){ + switch ($(".nav-tabs .active a").prop('id')){ case 'machines-tab': machinesTabShow(); break; @@ -286,7 +290,7 @@ function layerDetailsPageInit (ctx) { setLayerInCurrentPrj(add); - $("#alert-area").show(); + libtoaster.showChangeNotification(alertMsg); }); }); @@ -325,7 +329,7 @@ function layerDetailsPageInit (ctx) { text = entryElement.val(); /* Hide the "Not set" text if it's visible */ - inputArea.find(".muted").hide(); + inputArea.find(".text-muted").hide(); inputArea.find(".current-value").text(text); /* Same behaviour as cancel in that we hide the form/show current * value. @@ -343,9 +347,9 @@ function layerDetailsPageInit (ctx) { /* Disable the change button when we have no data in the input */ $("dl input, dl textarea").on("input",function() { if ($(this).val().length === 0) - $(this).parent().children(".change-btn").attr("disabled", "disabled"); + $(this).parent().next(".change-btn").attr("disabled", "disabled"); else - $(this).parent().children(".change-btn").removeAttr("disabled"); + $(this).parent().next(".change-btn").removeAttr("disabled"); }); /* This checks to see if the dt's dd has data in it or if the change data @@ -355,11 +359,11 @@ function layerDetailsPageInit (ctx) { if ($(this).is("dt")) { var dd = $(this).next("dd"); if (!dd.children("form:visible")|| !dd.find(".current-value").html()){ - if (ctx.layerVersion.sourceId == 3){ + if (ctx.layerVersion.layer_source == ctx.layerSourceTypes.TYPE_IMPORTED){ /* There's no current value and the layer is editable * so show the "Not set" and hide the delete icon */ - dd.find(".muted").show(); + dd.find(".text-muted").show(); dd.find(".delete-current-value").hide(); } else { /* We're not viewing an editable layer so hide the empty dd/dl pair */ @@ -386,10 +390,132 @@ function layerDetailsPageInit (ctx) { $(this).parents("form").submit(); }); + $("#layer-delete-confirmed").click(function(){ + + $("#delete-layer-modal button[data-dismiss='modal']").hide(); + + var message = $('<span>You have deleted <strong>1</strong> layer from your project: <strong id="deleted-layer-name"></strong>'); + message.find("#deleted-layer-name").text(ctx.layerVersion.name); - layerDepsList.find(".icon-trash").click(layerDepRemoveClick); + $.ajax({ + type: "DELETE", + url: ctx.xhrUpdateLayerUrl, + headers: { 'X-CSRFToken' : $.cookie('csrftoken')}, + success: function(data) { + if (data.error != "ok") { + console.warn(data.error); + } else { + libtoaster.setNotification("layer-deleted", message.html()); + window.location.replace(data.gotoUrl); + } + }, + error: function(data) { + console.warn("Call failed"); + console.warn(data); + } + }); + }); + + layerDepsList.find(".glyphicon-trash").click(layerDepRemoveClick); layerDepsList.find("a").tooltip(); - $(".icon-trash").tooltip(); + $(".glyphicon-trash").tooltip(); $(".commit").tooltip(); + editLayerSource.click(function() { + /* Kindly bring the git layers imported from layerindex to normal page + * and not this new page :( + */ + $(this).hide(); + saveSourceChangesBtn.attr("disabled", "disabled"); + + $("#git-repo-info, #directory-info").hide(); + $("#edit-layer-source-form").fadeIn(); + if ($("#layer-dir-path-in-details").val() == "") { + //Local dir path is empty... + $("#repo").prop("checked", true); + $("#layer-git").fadeIn(); + $("#layer-dir").hide(); + } else { + $("#layer-git").hide(); + $("#layer-dir").fadeIn(); + } + }); + + $('input:radio[name="source-location"]').change(function() { + if ($('input[name=source-location]:checked').val() == "repo") { + $("#layer-git").fadeIn(); + $("#layer-dir").hide(); + if ($("#layer-git-repo-url").val().length === 0 && layerGitRefInput.val().length === 0) { + saveSourceChangesBtn.attr("disabled", "disabled"); + } + } else { + $("#layer-dir").fadeIn(); + $("#layer-git").hide(); + } + }); + + $("#layer-dir-path-in-details").keyup(function() { + saveSourceChangesBtn.removeAttr("disabled"); + }); + + $("#layer-git-repo-url").keyup(function() { + if ($("#layer-git-repo-url").val().length > 0 && layerGitRefInput.val().length > 0) { + saveSourceChangesBtn.removeAttr("disabled"); + } + }); + + layerGitRefInput.keyup(function() { + if ($("#layer-git-repo-url").val().length > 0 && layerGitRefInput.val().length > 0) { + saveSourceChangesBtn.removeAttr("disabled"); + } + }); + + + layerSubDirInput.keyup(function(){ + if ($(this).val().length > 0){ + saveSourceChangesBtn.removeAttr("disabled"); + } + }); + + $('#cancel-changes-for-switch').click(function() { + $("#edit-layer-source-form").hide(); + $("#directory-info, #git-repo-info").fadeIn(); + editLayerSource.show(); + }); + + saveSourceChangesBtn.click(function() { + + var layerData = { + vcs_url: $('#layer-git-repo-url').val(), + commit: layerGitRefInput.val(), + dirpath: layerSubDirInput.val(), + local_source_dir: $('#layer-dir-path-in-details').val(), + }; + + if ($('input[name=source-location]:checked').val() == "repo") { + layerData.local_source_dir = ""; + } else { + layerData.vcs_url = ""; + layerData.git_ref = ""; + } + + $.ajax({ + type: "POST", + url: ctx.xhrUpdateLayerUrl, + data: layerData, + headers: { 'X-CSRFToken' : $.cookie('csrftoken')}, + success: function (data) { + if (data.error != "ok") { + console.warn(data.error); + } else { + /* success layer property changed */ + window.location.reload(); + } + }, + error: function (data) { + console.warn("Call failed"); + console.warn(data); + } + }); + }); } diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/libtoaster.js b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/libtoaster.js index 43930a2c3..86662b7a6 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/libtoaster.js +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/libtoaster.js @@ -3,96 +3,82 @@ * This object really just helps readability since we can then have * a traceable namespace. */ -var libtoaster = (function (){ - - /* makeTypeahead parameters - * elementSelector: JQuery elementSelector string - * xhrUrl: the url to get the JSON from expects JSON in the form: - * { "list": [ { "name": "test", "detail" : "a test thing" }, .... ] } +var libtoaster = (function () { + // prevent conflicts with Bootstrap 2's typeahead (required during + // transition from v2 to v3) + var typeahead = jQuery.fn.typeahead.noConflict(); + jQuery.fn._typeahead = typeahead; + + /* Make a typeahead from an input element + * + * _makeTypeahead parameters + * jQElement: input element as selected by $('selector') + * xhrUrl: the url to get the JSON from; this URL should return JSON in the + * format: + * { "results": [ { "name": "test", "detail" : "a test thing" }, ... ] } * xhrParams: the data/parameters to pass to the getJSON url e.g. - * { 'type' : 'projects' } the text typed will be passed as 'search'. - * selectedCB: function to call once an item has been selected one - * arg of the item. + * { 'type' : 'projects' }; the text typed will be passed as 'search'. + * selectedCB: function to call once an item has been selected; has + * signature selectedCB(item), where item is an item in the format shown + * in the JSON list above, i.e. + * { "name": "name", "detail": "detail" }. */ - function _makeTypeahead (jQElement, xhrUrl, xhrParams, selectedCB) { - if (!xhrUrl || xhrUrl.length === 0) - throw("No url to typeahead supplied"); + function _makeTypeahead(jQElement, xhrUrl, xhrParams, selectedCB) { + if (!xhrUrl || xhrUrl.length === 0) { + throw("No url supplied for typeahead"); + } var xhrReq; - jQElement.typeahead({ - // each time the typeahead's choices change, a - // "typeahead-choices-change" event is fired with an object - // containing the available choices in a "choices" property - source: function(query, process){ + jQElement._typeahead( + { + highlight: true, + classNames: { + open: "dropdown-menu", + cursor: "active" + } + }, + { + source: function (query, syncResults, asyncResults) { xhrParams.search = query; - /* If we have a request in progress don't fire off another one*/ - if (xhrReq) + // if we have a request in progress, cancel it and start another + if (xhrReq) { xhrReq.abort(); + } - xhrReq = $.getJSON(xhrUrl, this.options.xhrParams, function(data){ + xhrReq = $.getJSON(xhrUrl, xhrParams, function (data) { if (data.error !== "ok") { - console.log("Error getting data from server "+data.error); + console.error("Error getting data from server: " + data.error); return; } xhrReq = null; - jQElement.trigger("typeahead-choices-change", {choices: data.results}); - - return process(data.results); + asyncResults(data.results); }); }, - updater: function(item) { - var itemObj = this.$menu.find('.active').data('itemObject'); - selectedCB(itemObj); - return item; - }, - matcher: function(item) { - if (!item.hasOwnProperty('name')) { - console.log("Name property missing in data"); - return 0; - } - - if (this.$element.val().length === 0) - return 0; - return 1; - }, - highlighter: function (item) { - /* Use jquery to escape the item name and detail */ - var current = $("<span></span>").text(item.name + ' '+item.detail); - current = current.html(); - - var query = this.query.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&') - return current.replace(new RegExp('(' + query + ')', 'ig'), function ($1, match) { - return '<strong>' + match + '</strong>' - }) + // how the selected item is shown in the input + display: function (item) { + return item.name; }, - sorter: function (items) { return items; }, - xhrUrl: xhrUrl, - xhrParams: xhrParams, - xhrReq: xhrReq, - }); - - - /* Copy of bootstrap's render func but sets selectedObject value */ - function customRenderFunc (items) { - var that = this; - items = $(items).map(function (i, item) { - i = $(that.options.item).attr('data-value', item.name).data('itemObject', item); - i.find('a').html(that.highlighter(item)); - return i[0]; - }); - - items.first().addClass('active'); - this.$menu.html(items); - return this; - } + templates: { + // how the item is displayed in the dropdown + suggestion: function (item) { + var elt = document.createElement("div"); + elt.innerHTML = item.name + " " + item.detail; + return elt; + } + } + } + ); - jQElement.data('typeahead').render = customRenderFunc; + // when an item is selected using the typeahead, invoke the callback + jQElement.on("typeahead:select", function (event, item) { + selectedCB(item); + }); } /* startABuild: @@ -162,11 +148,25 @@ var libtoaster = (function (){ }); } + function _getMostRecentBuilds(url, onsuccess, onfail) { + $.ajax({ + url: url, + type: 'GET', + data : {format: 'json'}, + headers: {'X-CSRFToken': $.cookie('csrftoken')}, + success: function (data) { + onsuccess ? onsuccess(data) : console.log(data); + }, + error: function (data) { + onfail ? onfail(data) : console.error(data); + } + }); + } + /* Get a project's configuration info */ function _getProjectInfo(url, onsuccess, onfail){ $.ajax({ type: "GET", - data : { format: "json" }, url: url, headers: { 'X-CSRFToken' : $.cookie('csrftoken')}, success: function (_data) { @@ -193,7 +193,7 @@ var libtoaster = (function (){ function _editCurrentProject(data, onSuccess, onFail){ $.ajax({ type: "POST", - url: libtoaster.ctx.projectPageUrl + "?format=json", + url: libtoaster.ctx.xhrProjectUrl, data: data, headers: { 'X-CSRFToken' : $.cookie('csrftoken')}, success: function (data) { @@ -314,11 +314,11 @@ var libtoaster = (function (){ var alertMsg; if (layerDepsList.length > 0 && add === true) { - alertMsg = $("<span>You have added <strong>"+(layerDepsList.length+1)+"</strong> layers to your project: <a id=\"layer-affected-name\"></a> and its dependencies </span>"); + alertMsg = $("<span>You have added <strong>"+(layerDepsList.length+1)+"</strong> layers to your project: <a class=\"alert-link\" id=\"layer-affected-name\"></a> and its dependencies </span>"); /* Build the layer deps list */ layerDepsList.map(function(layer, i){ - var link = $("<a></a>"); + var link = $("<a class=\"alert-link\"></a>"); link.attr("href", layer.layerdetailurl); link.text(layer.name); @@ -330,9 +330,9 @@ var libtoaster = (function (){ alertMsg.append(link); }); } else if (layerDepsList.length === 0 && add === true) { - alertMsg = $("<span>You have added <strong>1</strong> layer to your project: <a id=\"layer-affected-name\"></a></span></span>"); + alertMsg = $("<span>You have added <strong>1</strong> layer to your project: <a class=\"alert-link\" id=\"layer-affected-name\"></a></span></span>"); } else if (add === false) { - alertMsg = $("<span>You have removed <strong>1</strong> layer from your project: <a id=\"layer-affected-name\"></a></span>"); + alertMsg = $("<span>You have removed <strong>1</strong> layer from your project: <a class=\"alert-link\" id=\"layer-affected-name\"></a></span>"); } alertMsg.children("#layer-affected-name").text(layer.name); @@ -342,10 +342,12 @@ var libtoaster = (function (){ } function _showChangeNotification(message){ - var alertMsg = $("#change-notification-msg"); + $(".alert-dismissible").fadeOut().promise().done(function(){ + var alertMsg = $("#change-notification-msg"); - alertMsg.html(message); - $("#change-notification, #change-notification *").fadeIn(); + alertMsg.html(message); + $("#change-notification, #change-notification *").fadeIn(); + }); } function _createCustomRecipe(name, baseRecipeId, doneCb){ @@ -374,11 +376,98 @@ var libtoaster = (function (){ }); } + /* Validate project names. Use unique project names + + All arguments accepted by this function are JQeury objects. + + For example if the HTML element has "hint-error-project-name", then + it is passed to this function as $("#hint-error-project-name"). + + Arg1 - projectName : This is a string object. In the HTML, project name will be entered here. + Arg2 - hintEerror : This is a jquery object which will accept span which throws error for + duplicate project + Arg3 - ctrlGrpValidateProjectName : This object holds the div with class "control-group" + Arg4 - enableOrDisableBtn : This object will help the API to enable or disable the form. + For example in the new project the create project button will be hidden if the + duplicate project exist. Similarly in the projecttopbar the save button will be + disabled if the project name already exist. + + Return - This function doesn't return anything. It sets/unsets the behavior of the elements. + */ + + function _makeProjectNameValidation(projectName, hintError, + ctrlGrpValidateProjectName, enableOrDisableBtn ) { + + function checkProjectName(projectName){ + $.ajax({ + type: "GET", + url: libtoaster.ctx.projectsTypeAheadUrl, + data: { 'search' : projectName }, + headers: { 'X-CSRFToken' : $.cookie('csrftoken')}, + success: function(data){ + if (data.results.length > 0 && + data.results[0].name === projectName) { + // This project name exists hence show the error and disable + // the save button + ctrlGrpValidateProjectName.addClass('has-error'); + hintError.show(); + enableOrDisableBtn.attr('disabled', 'disabled'); + } else { + ctrlGrpValidateProjectName.removeClass('has-error'); + hintError.hide(); + enableOrDisableBtn.removeAttr('disabled'); + } + }, + error: function (data) { + console.log(data); + }, + }); + } + + /* The moment user types project name remove the error */ + projectName.on("input", function() { + var projectName = $(this).val(); + checkProjectName(projectName) + }); + + /* Validate new project name */ + projectName.on("blur", function(){ + var projectName = $(this).val(); + checkProjectName(projectName) + }); + } + + // if true, the loading spinner for Ajax requests will be displayed + // if requests take more than 1200ms + var ajaxLoadingTimerEnabled = true; + + // turn on the page-level loading spinner for Ajax requests + function _enableAjaxLoadingTimer() { + ajaxLoadingTimerEnabled = true; + } + + // turn off the page-level loading spinner for Ajax requests + function _disableAjaxLoadingTimer() { + ajaxLoadingTimerEnabled = false; + } + + /* Utility function to set a notification for the next page load */ + function _setNotification(name, message){ + var data = { + name: name, + message: message + }; + + $.cookie('toaster-notification', JSON.stringify(data), { path: '/'}); + } return { + enableAjaxLoadingTimer: _enableAjaxLoadingTimer, + disableAjaxLoadingTimer: _disableAjaxLoadingTimer, reload_params : reload_params, startABuild : _startABuild, cancelABuild : _cancelABuild, + getMostRecentBuilds: _getMostRecentBuilds, makeTypeahead : _makeTypeahead, getProjectInfo: _getProjectInfo, getLayerDepsForProject : _getLayerDepsForProject, @@ -390,6 +479,8 @@ var libtoaster = (function (){ makeLayerAddRmAlertMsg : _makeLayerAddRmAlertMsg, showChangeNotification : _showChangeNotification, createCustomRecipe: _createCustomRecipe, + makeProjectNameValidation: _makeProjectNameValidation, + setNotification: _setNotification, }; })(); @@ -421,10 +512,24 @@ function reload_params(params) { window.location.href = url+"?"+callparams.join('&'); } - /* Things that happen for all pages */ $(document).ready(function() { + (function showNotificationRequest(){ + var cookie = $.cookie('toaster-notification'); + + if (!cookie) + return; + + var notificationData = JSON.parse(cookie); + + libtoaster.showChangeNotification(notificationData.message); + + $.removeCookie('toaster-notification', { path: "/"}); + })(); + + + var ajaxLoadingTimer; /* If we don't have a console object which might be the case in some @@ -493,9 +598,7 @@ $(document).ready(function() { delay: { show : 300 } }); - // show help bubble only on hover inside tables - $(".hover-help").css("visibility","hidden"); - + // show help bubble on hover inside tables $("table").on("mouseover", "th, td", function () { $(this).find(".hover-help").css("visibility","visible"); }); @@ -509,14 +612,14 @@ $(document).ready(function() { // show task type and outcome in task details pages $(".task-info").tooltip({ container: 'body', html: true, delay: {show: 200}, placement: 'right' }); - // initialise the tooltips for the icon-pencil icons - $(".icon-pencil").tooltip({ container: 'body', html: true, delay: {show: 400}, title: "Change" }); + // initialise the tooltips for the edit icons + $(".glyphicon-edit").tooltip({ container: 'body', html: true, delay: {show: 400}, title: "Change" }); // initialise the tooltips for the download icons $(".icon-download-alt").tooltip({ container: 'body', html: true, delay: { show: 200 } }); // initialise popover for debug information - $(".icon-info-sign").popover( { placement: 'bottom', html: true, container: 'body' }); + $(".glyphicon-info-sign").popover( { placement: 'bottom', html: true, container: 'body' }); // linking directly to tabs $(function(){ @@ -582,7 +685,9 @@ $(document).ready(function() { window.clearTimeout(ajaxLoadingTimer); ajaxLoadingTimer = window.setTimeout(function() { - $("#loading-notification").fadeIn(); + if (libtoaster.ajaxLoadingTimerEnabled) { + $("#loading-notification").fadeIn(); + } }, 1200); }); @@ -613,6 +718,11 @@ $(document).ready(function() { }); } + /* Make sure we don't have a notification overlay a modal */ + $(".modal").on('show.bs.modal', function(){ + $(".alert-dismissible").fadeOut(); + }); + if (libtoaster.debug) { check_for_duplicate_ids(); } else { diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/mrbsection.js b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/mrbsection.js index 09117e1da..73d0935fa 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/mrbsection.js +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/mrbsection.js @@ -1,33 +1,19 @@ function mrbSectionInit(ctx){ - - var projectBuilds; - - if (ctx.mrbType === 'project') - projectBuilds = true; - - $(".cancel-build-btn").click(function(e){ + $('#latest-builds').on('click', '.cancel-build-btn', function(e){ + e.stopImmediatePropagation(); e.preventDefault(); var url = $(this).data('request-url'); var buildReqIds = $(this).data('buildrequest-id'); - var banner = $(this).parents(".alert"); - - banner.find(".progress-info").fadeOut().promise().done(function(){ - $("#cancelling-msg-" + buildReqIds).show(); - console.log("cancel build"); - libtoaster.cancelABuild(url, buildReqIds, function(){ - if (projectBuilds == false){ - /* the all builds page is not 'self updating' like thei - * project Builds - */ - window.location.reload(); - } - }, null); - }); + + libtoaster.cancelABuild(url, buildReqIds, function () { + window.location.reload(); + }, null); }); - $(".run-again-btn").click(function(e){ + $('#latest-builds').on('click', '.rebuild-btn', function(e){ + e.stopImmediatePropagation(); e.preventDefault(); var url = $(this).data('request-url'); @@ -38,58 +24,112 @@ function mrbSectionInit(ctx){ }, null); }); + // cached version of buildData, so we can determine whether a build has + // changed since it was last fetched, and update the DOM appropriately + var buildData = {}; - var progressTimer; - - if (projectBuilds === true){ - progressTimer = window.setInterval(function() { - libtoaster.getProjectInfo(libtoaster.ctx.projectPageUrl, - function(prjInfo){ - /* These two are needed because a build can be 100% and still - * in progress due to the fact that the % done is updated at the - * start of a task so it can be doing the last task at 100% - */ - var inProgress = 0; - var allPercentDone = 0; - if (prjInfo.builds.length === 0) - return - - for (var i in prjInfo.builds){ - var build = prjInfo.builds[i]; - - if (build.outcome === "In Progress" || - $(".progress .bar").length > 0){ - /* Update the build progress */ - var percentDone; - - if (build.outcome !== "In Progress"){ - /* We have to ignore the value when it's Succeeded because it - * goes back to 0 - */ - percentDone = 100; - } else { - percentDone = build.percentDone; - inProgress++; - } - - $("#build-pc-done-" + build.id).text(percentDone); - $("#build-pc-done-title-" + build.id).attr("title", percentDone); - $("#build-pc-done-bar-" + build.id).css("width", - String(percentDone) + "%"); - - allPercentDone += percentDone; - } - } + // returns the cached version of this build, or {} is there isn't a cached one + function getCached(build) { + return buildData[build.id] || {}; + } + + // returns true if a build's state changed to "Succeeded", "Failed" + // or "Cancelled" from some other value + function buildFinished(build) { + var cached = getCached(build); + return cached.state && + cached.state !== build.state && + (build.state == 'Succeeded' || build.state == 'Failed' || + build.state == 'Cancelled'); + } + + // returns true if the state changed + function stateChanged(build) { + var cached = getCached(build); + return (cached.state !== build.state); + } + + // returns true if the tasks_complete_percentage changed + function tasksProgressChanged(build) { + var cached = getCached(build); + return (cached.tasks_complete_percentage !== build.tasks_complete_percentage); + } - if (allPercentDone === (100 * prjInfo.builds.length) && !inProgress) + // returns true if the number of recipes parsed/to parse changed + function recipeProgressChanged(build) { + var cached = getCached(build); + return (cached.recipes_parsed_percentage !== build.recipes_parsed_percentage); + } + + function refreshMostRecentBuilds(){ + libtoaster.getMostRecentBuilds( + libtoaster.ctx.mostRecentBuildsUrl, + + // success callback + function (data) { + var build; + var tmpl; + var container; + var selector; + var colourClass; + var elements; + + for (var i = 0; i < data.length; i++) { + build = data[i]; + + if (buildFinished(build)) { + // a build finished: reload the whole page so that the build + // shows up in the builds table window.location.reload(); + } + else if (stateChanged(build)) { + // update the whole template + build.warnings_pluralise = (build.warnings !== 1 ? 's' : ''); + build.errors_pluralise = (build.errors !== 1 ? 's' : ''); + + tmpl = $.templates("#build-template"); + + html = $(tmpl.render(build)); + + selector = '[data-latest-build-result="' + build.id + '"] ' + + '[data-role="build-status-container"]'; + container = $(selector); + + // initialize bootstrap tooltips in the new HTML + html.find('span.glyphicon-question-sign').tooltip(); - /* Our progress bar is not still showing so shutdown the polling. */ - if ($(".progress .bar").length === 0) - window.clearInterval(progressTimer); + container.html(html); + } + else if (tasksProgressChanged(build)) { + // update the task progress text + selector = '#build-pc-done-' + build.id; + $(selector).html(build.tasks_complete_percentage); + + // update the task progress bar + selector = '#build-pc-done-bar-' + build.id; + $(selector).width(build.tasks_complete_percentage + '%'); + } + else if (recipeProgressChanged(build)) { + // update the recipe progress text + selector = '#recipes-parsed-percentage-' + build.id; + $(selector).html(build.recipes_parsed_percentage); + + // update the recipe progress bar + selector = '#recipes-parsed-percentage-bar-' + build.id; + $(selector).width(build.recipes_parsed_percentage + '%'); + } - }); - }, 1500); + buildData[build.id] = build; + } + }, + + // fail callback + function (data) { + console.error(data); + } + ); } -} + window.setInterval(refreshMostRecentBuilds, 1500); + refreshMostRecentBuilds(); +} diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/newcustomimage_modal.js b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/newcustomimage_modal.js index cb9ed4da0..dace8e325 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/newcustomimage_modal.js +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/newcustomimage_modal.js @@ -22,26 +22,40 @@ function newCustomImageModalInit(){ var nameInput = imgCustomModal.find('input'); var invalidNameMsg = "Image names cannot contain spaces or capital letters. The only allowed special character is dash (-)."; - var duplicateNameMsg = "A recipe with this name already exists. Image names must be unique."; + var duplicateNameMsg = "An image with this name already exists. Image names must be unique."; var duplicateImageInProjectMsg = "An image with this name already exists in this project." var invalidBaseRecipeIdMsg = "Please select an image to customise."; - // capture clicks on radio buttons inside the modal; when one is selected, - // set the recipe on the modal - imgCustomModal.on("click", "[name='select-image']", function (e) { + // set button to "submit" state and enable text entry so user can + // enter the custom recipe name + showSubmitState(); + + /* capture clicks on radio buttons inside the modal; when one is selected, + * set the recipe on the modal + */ + imgCustomModal.on("click", "[name='select-image']", function(e) { clearRecipeError(); + $(".radio").each(function(){ + $(this).removeClass("has-error"); + }); var recipeId = $(e.target).attr('data-recipe'); imgCustomModal.data('recipe', recipeId); }); newCustomImgBtn.click(function(e){ + // disable the button and text entry + showLoadingState(); + e.preventDefault(); var baseRecipeId = imgCustomModal.data('recipe'); if (!baseRecipeId) { showRecipeError(invalidBaseRecipeIdMsg); + $(".radio").each(function(){ + $(this).addClass("has-error"); + }); return; } @@ -62,16 +76,37 @@ function newCustomImageModalInit(){ } } else { imgCustomModal.modal('hide'); + imgCustomModal.one('hidden.bs.modal', showSubmitState); window.location.replace(ret.url + '?notify=new'); } }); } }); + // enable text entry, show "Create image" button text + function showSubmitState() { + libtoaster.enableAjaxLoadingTimer(); + newCustomImgBtn.find('[data-role="loading-state"]').hide(); + newCustomImgBtn.find('[data-role="submit-state"]').show(); + newCustomImgBtn.removeAttr('disabled'); + nameInput.removeAttr('disabled'); + } + + // disable text entry, show "Creating image..." button text; + // we also disabled the page-level ajax loading spinner while this spinner + // is active + function showLoadingState() { + libtoaster.disableAjaxLoadingTimer(); + newCustomImgBtn.find('[data-role="submit-state"]').hide(); + newCustomImgBtn.find('[data-role="loading-state"]').show(); + newCustomImgBtn.attr('disabled', 'disabled'); + nameInput.attr('disabled', 'disabled'); + } + function showNameError(text){ invalidNameHelp.text(text); invalidNameHelp.show(); - nameInput.parent().addClass('error'); + nameInput.parent().addClass('has-error'); } function showRecipeError(text){ @@ -92,26 +127,26 @@ function newCustomImageModalInit(){ if (nameInput.val().search(/[^a-z|0-9|-]/) != -1){ showNameError(invalidNameMsg); newCustomImgBtn.prop("disabled", true); - nameInput.parent().addClass('error'); + nameInput.parent().addClass('has-error'); } else { invalidNameHelp.hide(); newCustomImgBtn.prop("disabled", false); - nameInput.parent().removeClass('error'); + nameInput.parent().removeClass('has-error'); } }); } -// Set the image recipes which can used as the basis for the custom -// image recipe the user is creating -// -// baseRecipes: a list of one or more recipes which can be -// used as the base for the new custom image recipe in the format: -// [{'id': <recipe ID>, 'name': <recipe name>'}, ...] -// -// if recipes is a single recipe, just show the text box to set the -// name for the new custom image; if recipes contains multiple recipe objects, -// show a set of radio buttons so the user can decide which to use as the -// basis for the new custom image +/* Set the image recipes which can used as the basis for the custom + * image recipe the user is creating + * baseRecipes: a list of one or more recipes which can be + * used as the base for the new custom image recipe in the format: + * [{'id': <recipe ID>, 'name': <recipe name>'}, ...] + * + * if recipes is a single recipe, just show the text box to set the + * name for the new custom image; if recipes contains multiple recipe objects, + * show a set of radio buttons so the user can decide which to use as the + * basis for the new custom image + */ function newCustomImageModalSetRecipes(baseRecipes) { var imgCustomModal = $("#new-custom-image-modal"); var imageSelector = $('#new-custom-image-modal [data-role="image-selector"]'); @@ -124,8 +159,9 @@ function newCustomImageModalSetRecipes(baseRecipes) { // hide the radio button container imageSelector.hide(); - // set the single recipe ID on the modal as it's the only one - // we can build from + /* set the single recipe ID on the modal as it's the only one + * we can build from. + */ imgCustomModal.data('recipe', baseRecipes[0].id); } else { @@ -134,14 +170,29 @@ function newCustomImageModalSetRecipes(baseRecipes) { for (var i = 0; i < baseRecipes.length; i++) { var recipe = baseRecipes[i]; imageSelectRadiosContainer.append( - '<label class="radio" data-role="image-radio">' + - recipe.name + - '<input type="radio" class="form-control" name="select-image" ' + + '<div class="radio"><label data-role="image-radio">' + + '<input type="radio" name="select-image" ' + 'data-recipe="' + recipe.id + '">' + - '</label>' + recipe.name + + '</label></div>' ); } + /* select the first radio button as default selection. Radio button + * groups should always display with an option checked + */ + imageSelectRadiosContainer.find("input:radio:first").attr("checked", "checked"); + + /* check which radio button is selected by default inside the modal, + * and set the recipe on the modal accordingly + */ + imageSelectRadiosContainer.find("input:radio").each(function(){ + if ( $(this).is(":checked") ) { + var recipeId = $(this).attr("data-recipe"); + imgCustomModal.data("recipe", recipeId); + } + }); + // show the radio button container imageSelector.show(); } diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/projectpage.js b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/projectpage.js index 3013416dd..453670364 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/projectpage.js +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/projectpage.js @@ -27,11 +27,10 @@ function projectPageInit(ctx) { var urlParams = libtoaster.parseUrlParams(); - libtoaster.getProjectInfo(libtoaster.ctx.projectPageUrl, function(prjInfo){ + libtoaster.getProjectInfo(libtoaster.ctx.xhrProjectUrl, function(prjInfo){ updateProjectLayers(prjInfo.layers); updateFreqBuildRecipes(prjInfo.freqtargets); updateProjectRelease(prjInfo.release); - updateProjectReleases(prjInfo.releases, prjInfo.release); /* If we're receiving a machine set from the url and it's different from * our current machine then activate set machine sequence. @@ -46,54 +45,13 @@ function projectPageInit(ctx) { /* Now we're really ready show the page */ $("#project-page").show(); - }); - - (function notificationRequest(){ - - if (urlParams.hasOwnProperty('notify')){ - switch (urlParams.notify){ - case 'new-project': - $("#project-created-notification").show(); - break; - case 'layer-imported': - layerImportedNotification(); - break; - default: - break; - } - } - })(); - - /* Layer imported notification */ - function layerImportedNotification(){ - var imported = $.cookie("layer-imported-alert"); - var message = "Layer imported"; - - if (!imported) - return; - else - imported = JSON.parse(imported); - - if (imported.deps_added.length === 0) { - message = "You have imported <strong><a href=\""+imported.imported_layer.layerdetailurl+"\">"+imported.imported_layer.name+"</a></strong> and added it to your project."; - } else { - var links = "<a href=\""+imported.imported_layer.layerdetailurl+"\">"+imported.imported_layer.name+"</a>, "; - - imported.deps_added.map (function(item, index){ - links +='<a href="'+item.layerdetailurl+'">'+item.name+'</a>'; - /*If we're at the last element we don't want the trailing comma */ - if (imported.deps_added[index+1] !== undefined) - links += ', '; - }); - - /* Length + 1 here to do deps + the imported layer */ - message = 'You have imported <strong><a href="'+imported.imported_layer.layerdetailurl+'">'+imported.imported_layer.name+'</a></strong> and added <strong>'+(imported.deps_added.length+1)+'</strong> layers to your project: <strong>'+links+'</strong>'; - } - - libtoaster.showChangeNotification(message); + /* Set the project name in the delete modal */ + $("#delete-project-modal .project-name").text(prjInfo.name); + }); - $.removeCookie("layer-imported-alert", { path: "/"}); + if (urlParams.hasOwnProperty('notify') && urlParams.notify === 'new-project'){ + $("#project-created-notification").show(); } /* Add/Rm layer functionality */ @@ -145,7 +103,7 @@ function projectPageInit(ctx) { for (var i in layers){ var layerObj = layers[i]; - var projectLayer = $("<li><a></a><span class=\"icon-trash\" data-toggle=\"tooltip\" title=\"Remove\"></span></li>"); + var projectLayer = $("<li><a></a><span class=\"glyphicon glyphicon-trash\" data-toggle=\"tooltip\" title=\"Remove\"></span></li>"); projectLayer.data('layer', layerObj); projectLayer.children("span").tooltip(); @@ -154,7 +112,12 @@ function projectPageInit(ctx) { link.attr("href", layerObj.layerdetailurl); link.text(layerObj.name); - link.tooltip({title: layerObj.vcs_url + " | "+ layerObj.vcs_reference, placement: "right"}); + + if (layerObj.local_source_dir) { + link.tooltip({title: layerObj.local_source_dir, placement: "right"}); + } else { + link.tooltip({title: layerObj.vcs_url + " | "+ layerObj.vcs_reference, placement: "right"}); + } var trashItem = projectLayer.children("span"); trashItem.click(function (e) { @@ -208,7 +171,7 @@ function projectPageInit(ctx) { } for (var i in recipes){ - var freqTargetCheck = $('<li><label class="checkbox"><input type="checkbox" /><span class="freq-target-name"></span></label></li>'); + var freqTargetCheck = $('<li><div class="checkbox"><label><input type="checkbox" /><span class="freq-target-name"></span></label></li>'); freqTargetCheck.find(".freq-target-name").text(recipes[i]); freqTargetCheck.find("input").val(recipes[i]); freqTargetCheck.click(function(){ @@ -264,7 +227,9 @@ function projectPageInit(ctx) { machineNameTitle.text(machineName); } - libtoaster.makeTypeahead(machineChangeInput, libtoaster.ctx.machinesTypeAheadUrl, { }, function(item){ + libtoaster.makeTypeahead(machineChangeInput, + libtoaster.ctx.machinesTypeAheadUrl, + { }, function(item){ currentMachineAddSelection = item.name; machineChangeBtn.removeAttr("disabled"); }); @@ -285,7 +250,7 @@ function projectPageInit(ctx) { machineChangeCancel.click(); /* Show the alert message */ - var message = $('<span class="lead">You have changed the machine to: <strong><span id="notify-machine-name"></span></strong></span>'); + var message = $('<span>You have changed the machine to: <strong><span id="notify-machine-name"></span></strong></span>'); message.find("#notify-machine-name").text(currentMachineAddSelection); libtoaster.showChangeNotification(message); }, @@ -301,146 +266,35 @@ function projectPageInit(ctx) { releaseTitle.text(release.description); } - function updateProjectReleases(releases, current){ - for (var i in releases){ - var releaseOption = $("<option></option>"); - releaseOption.val(releases[i].id); - releaseOption.text(releases[i].description); - releaseOption.data('release', releases[i]); - - if (releases[i].id == current.id) - releaseOption.attr("selected", "selected"); - - releaseForm.children("select").append(releaseOption); - } - } - - releaseChangeFormToggle.click(function(){ - releaseForm.slideDown(); - releaseTitle.hide(); - $(this).hide(); - }); - - cancelReleaseChange.click(function(e){ + $("#delete-project-confirmed").click(function(e){ e.preventDefault(); - releaseForm.slideUp(function(){ - releaseTitle.show(); - releaseChangeFormToggle.show(); - }); - }); - - function changeProjectRelease(release, layersToRm){ - libtoaster.editCurrentProject({ projectVersion : release.id }, - function(){ - /* Success */ - /* Update layers list with new layers */ - layersInPrjList.addClass('muted'); - libtoaster.getProjectInfo(libtoaster.ctx.projectPageUrl, - function(prjInfo){ - layersInPrjList.children().remove(); - updateProjectLayers(prjInfo.layers); - layersInPrjList.removeClass('muted'); - releaseChangedNotification(release, prjInfo.layers, layersToRm); - }); - updateProjectRelease(release); - cancelReleaseChange.click(); - }); - } - - /* Create a notification to show the changes to the layer configuration - * caused by changing a release. - */ - - function releaseChangedNotification(release, layers, layersToRm){ - - var message; - - if (layers.length === 0 && layersToRm.length === 0){ - message = $('<span><span class="lead">You have changed the project release to: <strong><span id="notify-release-name"></span></strong>.'); - message.find("#notify-release-name").text(release.description); - libtoaster.showChangeNotification(message); - return; - } - - /* Create the whitespace separated list of layers removed */ - var layersDelList = ""; - - layersToRm.map(function(layer, i){ - layersDelList += layer.name; - if (layersToRm[i+1] !== undefined) - layersDelList += ', '; - }); - - message = $('<span><span class="lead">You have changed the project release to: <strong><span id="notify-release-name"></span></strong>. This has caused the following changes in your project layers:</span><ul id="notify-layers-changed-list"></ul></span>'); - - var changedList = message.find("#notify-layers-changed-list"); - - message.find("#notify-release-name").text(release.description); - - /* Manually construct the list item for changed layers */ - var li = '<li><strong>'+layers.length+'</strong> layers changed to the <strong>'+release.name+'</strong> release: '; - for (var i in layers){ - li += '<a href='+layers[i].layerdetailurl+'>'+layers[i].name+'</a>'; - if (i !== 0) - li += ', '; - } - - changedList.append($(li)); - - /* Layers removed */ - if (layersToRm && layersToRm.length > 0){ - if (layersToRm.length == 1) - li = '<li><strong>1</strong> layer removed: '+layersToRm[0].name+'</li>'; - else - li = '<li><strong>'+layersToRm.length+'</strong> layers deleted: '+layersDelList+'</li>'; - - changedList.append($(li)); - } - - libtoaster.showChangeNotification(message); - } - - /* Show the modal dialog which gives the option to remove layers which - * aren't compatible with the proposed release - */ - function showReleaseLayerChangeModal(release, layers){ - var layersToRmList = releaseModal.find("#layers-to-remove-list"); - layersToRmList.text(""); - - releaseModal.find(".proposed-release-change-name").text(release.description); - releaseModal.data("layers", layers); - releaseModal.data("release", release); - - for (var i in layers){ - layersToRmList.append($("<li></li>").text(layers[i].name)); - } - releaseModal.modal('show'); - } - - $("#change-release-btn").click(function(e){ - e.preventDefault(); - - var newRelease = releaseForm.find("option:selected").data('release'); - - $.getJSON(ctx.testReleaseChangeUrl, - { new_release_id: newRelease.id }, - function(layers) { - if (layers.rows.length === 0){ - /* No layers to change for this release */ - changeProjectRelease(newRelease, []); - } else { - showReleaseLayerChangeModal(newRelease, layers.rows); + libtoaster.disableAjaxLoadingTimer(); + $(this).find('[data-role="submit-state"]').hide(); + $(this).find('[data-role="loading-state"]').show(); + $(this).attr("disabled", "disabled"); + $('#delete-project-modal [data-dismiss="modal"]').hide(); + + $.ajax({ + type: 'DELETE', + url: libtoaster.ctx.xhrProjectUrl, + headers: { 'X-CSRFToken' : $.cookie('csrftoken')}, + success: function (data) { + if (data.error !== "ok") { + console.warn(data.error); + } else { + var msg = $('<span>You have deleted <strong>1</strong> project: <strong id="project-deleted"></strong></span>'); + + msg.find("#project-deleted").text(libtoaster.ctx.projectName); + libtoaster.setNotification("project-deleted", msg.html()); + + window.location.replace(data.gotoUrl); + } + }, + error: function (data) { + console.warn(data); } }); }); - /* Release change modal accept */ - $("#change-release-and-rm-layers").click(function(){ - var layers = releaseModal.data("layers"); - var release = releaseModal.data("release"); - - changeProjectRelease(release, layers); - }); - } diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/projecttopbar.js b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/projecttopbar.js index b09f974e4..92ab2d67f 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/projecttopbar.js +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/projecttopbar.js @@ -4,7 +4,7 @@ function projectTopBarInit(ctx) { var projectNameForm = $("#project-name-change-form"); var projectNameContainer = $("#project-name-container"); - var projectName = $("#project-name"); + var projectName = $(".project-name"); var projectNameFormToggle = $("#project-change-form-toggle"); var projectNameChangeCancel = $("#project-name-change-cancel"); @@ -25,13 +25,14 @@ function projectTopBarInit(ctx) { e.preventDefault(); projectNameForm.hide(); projectNameContainer.fadeIn(); + $("#project-name-change-input").val(projectName.first().text()); }); $("#project-name-change-btn").click(function(){ var newProjectName = $("#project-name-change-input").val(); libtoaster.editCurrentProject({ projectName: newProjectName }, function (){ - projectName.html(newProjectName); + projectName.text(newProjectName); libtoaster.ctx.projectName = newProjectName; projectNameChangeCancel.click(); }); @@ -87,4 +88,10 @@ function projectTopBarInit(ctx) { window.location.replace(libtoaster.ctx.projectBuildsUrl); }, null); }); + + /* Call makeProjectNameValidation function */ + libtoaster.makeProjectNameValidation($("#project-name-change-input"), + $("#hint-error-project-name"), $("#validate-project-name"), + $("#project-name-change-btn")); + } diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/table.js b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/table.js index f738144ae..176ce579f 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/table.js +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/table.js @@ -15,6 +15,7 @@ function tableInit(ctx){ orderby : null, filter : null, search : null, + default_orderby: null, }; var defaultHiddenCols = []; @@ -75,14 +76,21 @@ function tableInit(ctx){ if (tableData.total === 0){ tableContainer.hide(); - if ($("#no-results-special-"+ctx.tableName).length > 0) { - /* use this page's special no-results form instead of the default */ - $("#no-results-search-input-"+ctx.tableName).val(tableParams.search); - $("#no-results-special-"+ctx.tableName).show(); - $("#results-found-"+ctx.tableName).hide(); - } else { - $("#new-search-input-"+ctx.tableName).val(tableParams.search); - $("#no-results-"+ctx.tableName).show(); + /* No results caused by a search returning nothing */ + if (tableParams.search) { + if ($("#no-results-special-"+ctx.tableName).length > 0) { + /* use this page's special no-results form instead of the default */ + $("#no-results-search-input-"+ctx.tableName).val(tableParams.search); + $("#no-results-special-"+ctx.tableName).show(); + $("#results-found-"+ctx.tableName).hide(); + } else { + $("#new-search-input-"+ctx.tableName).val(tableParams.search); + $("#no-results-"+ctx.tableName).show(); + } + } + else { + /* No results caused by there being no data */ + $("#empty-state-"+ctx.tableName).show(); } table.trigger("table-done", [tableData.total, tableParams]); @@ -90,6 +98,7 @@ function tableInit(ctx){ } else { tableContainer.show(); $("#no-results-"+ctx.tableName).hide(); + $("#empty-state-"+ctx.tableName).hide(); } setupTableChrome(tableData); @@ -101,27 +110,8 @@ function tableInit(ctx){ var row = $("<tr></tr>"); column_index = -1; for (var key_j in tableData.rows[i]){ - - /* if we have a static: version of a key, prefer the static: version for rendering */ - var orig_key_j = key_j; - - if (key_j.indexOf("static:") === 0) { - if (key_j.substr("static:".length) in tableData.rows[i]) { - continue; - } - orig_key_j = key_j.substr("static:".length) - } else if (("static:" + key_j) in tableData.rows[i]) { - key_j = "static:" + key_j; - } - - /* we skip over un-displayable column entries */ - column_index += 1; - if (! tableData.columns[column_index].displayable) { - continue; - } - var td = $("<td></td>"); - td.prop("class", orig_key_j); + td.prop("class", key_j); if (tableData.rows[i][key_j]){ td.html(tableData.rows[i][key_j]); } @@ -181,6 +171,15 @@ function tableInit(ctx){ table.css("padding-bottom", 0); tableContainer.css("visibility", "visible"); + /* If we have a hash in the url try and highlight that item in the table */ + if (window.location.hash){ + var highlight = $("table a[name="+window.location.hash.replace('#','')); + if (highlight.length > 0){ + highlight.parents("tr").addClass('highlight'); + window.scroll(0, highlight.position().top - 50); + } + } + table.trigger("table-done", [tableData.total, tableParams]); } @@ -188,12 +187,14 @@ function tableInit(ctx){ if (tableChromeDone === true) return; - var tableHeadRow = table.find("thead"); + var tableHeadRow = table.find("thead > tr"); var editColMenu = $("#table-chrome-"+ctx.tableName).find(".editcol"); tableHeadRow.html(""); editColMenu.html(""); + tableParams.default_orderby = tableData.default_orderby; + if (!tableParams.orderby && tableData.default_orderby){ tableParams.orderby = tableData.default_orderby; } @@ -209,7 +210,7 @@ function tableInit(ctx){ /* Setup the help text */ if (col.help_text.length > 0) { - var help_text = $('<i class="icon-question-sign get-help"> </i>'); + var help_text = $('<span class="glyphicon glyphicon-question-sign get-help"> </span>'); help_text.tooltip({title: col.help_text}); header.append(help_text); } @@ -219,6 +220,7 @@ function tableInit(ctx){ var title = $('<a href=\"#\" ></a>'); title.data('field-name', col.field_name); + title.attr('data-sort-field', col.field_name); title.text(col.title); title.click(sortColumnClicked); @@ -246,12 +248,12 @@ function tableInit(ctx){ } else { /* Not orderable */ header.css("font-weight", "normal"); - header.append('<span class="muted">' + col.title + '</span> '); + header.append('<span class="text-muted">' + col.title + '</span> '); } /* Setup the filter button */ if (col.filter_name){ - var filterBtn = $('<a href="#" role="button" data-filter-on="' + col.filter_name + '" class="pull-right btn btn-mini" data-toggle="modal"><i class="icon-filter filtered"></i></a>'); + var filterBtn = $('<a href="#" role="button" data-filter-on="' + col.filter_name + '" class="pull-right btn btn-link btn-xs" data-toggle="modal"><i class="glyphicon glyphicon-filter filtered"></i></a>'); filterBtn.data('filter-name', col.filter_name); filterBtn.prop('id', col.filter_name); @@ -270,7 +272,7 @@ function tableInit(ctx){ tableHeadRow.append(header); /* Now setup the checkbox state and click handler */ - var toggler = $('<li><label class="checkbox">'+col.title+'<input type="checkbox" id="checkbox-'+ col.field_name +'" class="col-toggle" value="'+col.field_name+'" /></label></li>'); + var toggler = $('<li><div class="checkbox"><label><input type="checkbox" id="checkbox-'+ col.field_name +'" class="col-toggle" value="'+col.field_name+'" />'+col.title+'</label></div></li>'); var togglerInput = toggler.find("input"); @@ -280,7 +282,8 @@ function tableInit(ctx){ if (col.hideable){ togglerInput.click(colToggleClicked); } else { - toggler.find("label").addClass("muted"); + toggler.find("label").addClass("text-muted"); + toggler.find("label").parent().addClass("disabled"); togglerInput.attr("disabled", "disabled"); } @@ -297,11 +300,12 @@ function tableInit(ctx){ /* Toggles the active state of the filter button */ function filterBtnActive(filterBtn, active){ if (active) { + filterBtn.removeClass("btn-link"); filterBtn.addClass("btn-primary"); filterBtn.tooltip({ html: true, - title: '<button class="btn btn-small btn-primary" onClick=\'$("#clear-filter-btn-'+ ctx.tableName +'").click();\'>Clear filter</button>', + title: '<button class="btn btn-sm btn-primary" onClick=\'$("#clear-filter-btn-'+ ctx.tableName +'").click();\'>Clear filter</button>', placement: 'bottom', delay: { hide: 1500, @@ -310,6 +314,7 @@ function tableInit(ctx){ }); } else { filterBtn.removeClass("btn-primary"); + filterBtn.addClass("btn-link"); filterBtn.tooltip('destroy'); } } @@ -343,29 +348,65 @@ function tableInit(ctx){ } } - function sortColumnClicked(e){ - e.preventDefault(); + /* Apply an ordering to the current table. + * + * 1. Find the column heading matching the sortSpecifier + * 2. Set its up/down arrow and add .sorted + * + * orderby: e.g. "-started_on", "completed_on" + * colHeading: column heading element to activate (by showing the caret + * up/down, depending on sort order); if not set, the correct column + * heading is selected from the DOM using orderby as a key + */ + function applyOrderby(orderby, colHeading) { + if (!orderby) { + return; + } + + // We only have one sort at a time so remove existing sort indicators + $("#" + ctx.tableName + " th .icon-caret-down").hide(); + $("#" + ctx.tableName + " th .icon-caret-up").hide(); + $("#" + ctx.tableName + " th a").removeClass("sorted"); - /* We only have one sort at a time so remove any existing sort indicators */ - $("#"+ctx.tableName+" th .icon-caret-down").hide(); - $("#"+ctx.tableName+" th .icon-caret-up").hide(); - $("#"+ctx.tableName+" th a").removeClass("sorted"); + // normalise the orderby so we can use it to find the link we want + // to style + var fieldName = orderby; + if (fieldName.indexOf('-') === 0) { + fieldName = fieldName.slice(1); + } + + // find the table header element which corresponds to the sort field + // (if we don't already have it) + if (!colHeading) { + colHeading = $('[data-sort-field="' + fieldName + '"]'); + } + + colHeading.addClass("sorted"); + + var parent = colHeading.parent(); + + if (orderby.indexOf('-') === 0) { + parent.children('.icon-caret-up').show(); + } + else { + parent.children('.icon-caret-down').show(); + } + + tableParams.orderby = orderby; + loadData(tableParams); + } - var fieldName = $(this).data('field-name'); + function sortColumnClicked(e){ + e.preventDefault(); /* if we're already sorted sort the other way */ - if (tableParams.orderby === fieldName && + var orderby = $(this).data('field-name'); + if (tableParams.orderby === orderby && tableParams.orderby.indexOf('-') === -1) { - tableParams.orderby = '-' + $(this).data('field-name'); - $(this).parent().children('.icon-caret-up').show(); - } else { - tableParams.orderby = $(this).data('field-name'); - $(this).parent().children('.icon-caret-down').show(); + orderby = '-' + orderby; } - $(this).addClass("sorted"); - - loadData(tableParams); + applyOrderby(orderby, $(this)); } function pageButtonClicked(e) { @@ -384,11 +425,13 @@ function tableInit(ctx){ table.find("."+col).show(); } else { table.find("."+col).hide(); - /* If we're ordered by the column we're hiding remove the order by */ + // If we're ordered by the column we're hiding remove the order by + // and apply the default one instead if (col === tableParams.orderby || '-' + col === tableParams.orderby){ tableParams.orderby = null; - $("#"+ctx.tableName +" .default-orderby").click(); + + applyOrderby(tableParams.default_orderby); } } @@ -415,23 +458,23 @@ function tableInit(ctx){ var hasNoRecords = (Number(filterActionData.count) == 0); var actionStr = '<div class="radio">' + - '<input type="radio" name="filter"' + - ' value="' + filterName + '"'; + '<label class="filter-title' + + (hasNoRecords ? ' text-muted' : '') + '"' + + ' for="' + filterName + '">' + + '<input type="radio" name="filter"' + + ' value="' + filterName + '"'; if (hasNoRecords) { actionStr += ' disabled="disabled"'; } actionStr += ' id="' + filterName + '">' + - '<input type="hidden" name="filter_value" value="on"' + - ' data-value-for="' + filterName + '">' + - '<label class="filter-title' + - (hasNoRecords ? ' muted' : '') + '"' + - ' for="' + filterName + '">' + - filterActionData.title + - ' (' + filterActionData.count + ')' + - '</label>' + - '</div>'; + '<input type="hidden" name="filter_value" value="on"' + + ' data-value-for="' + filterName + '">' + + filterActionData.title + + ' (' + filterActionData.count + ')' + + '</label>' + + '</div>'; var action = $(actionStr); @@ -465,22 +508,23 @@ function tableInit(ctx){ */ function createActionDateRange(filterName, filterValue, filterActionData) { var action = $('<div class="radio">' + + '<label class="filter-title"' + + ' for="' + filterName + '">' + '<input type="radio" name="filter"' + ' value="' + filterName + '" ' + ' id="' + filterName + '">' + '<input type="hidden" name="filter_value" value=""' + ' data-value-for="' + filterName + '">' + - '<label class="filter-title"' + - ' for="' + filterName + '">' + filterActionData.title + '</label>' + - '<input type="text" maxlength="10" class="input-small"' + + '<div class="form-inline form-group date-filter-controls">' + + '<input type="text" maxlength="10" class="form-control"' + ' data-date-from-for="' + filterName + '">' + - '<span class="help-inline">to</span>' + - '<input type="text" maxlength="10" class="input-small"' + + '<span>to</span>' + + '<input type="text" maxlength="10" class="form-control"' + ' data-date-to-for="' + filterName + '">' + '<span class="help-inline get-help">(yyyy-mm-dd)</span>' + - '</div>'); + '</div></div>'); var radio = action.find('[type="radio"]'); var value = action.find('[data-value-for]'); @@ -621,7 +665,7 @@ function tableInit(ctx){ queryset on the table */ var filterActionRadios = $('#filter-actions-' + ctx.tableName); - var filterApplyBtn = $('[data-role="filter-apply"]'); + var filterApplyBtn = $('[data-cat="filter-apply"]'); var setApplyButtonState = function (e, filterActionValue) { if (filterActionValue !== undefined) { @@ -662,7 +706,7 @@ function tableInit(ctx){ if (action) { // Setup the current selected filter; default to 'all' if // no current filter selected - var radioInput = action.children('input[name="filter"]'); + var radioInput = action.find('input[name="filter"]'); if ((tableParams.filter && tableParams.filter === radioInput.val()) || filterActionData.action_name == 'all') { @@ -788,6 +832,7 @@ function tableInit(ctx){ loadData(tableParams); - $(this).parent().modal('hide'); + + $('#filter-modal-'+ctx.tableName).modal('hide'); }); } diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/tests/test.js b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/tests/test.js index aac0ba60a..d7953de44 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/tests/test.js +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/tests/test.js @@ -2,7 +2,6 @@ /* Unit tests for Toaster's JS */ /* libtoaster tests */ - QUnit.test("Layer alert notification", function(assert) { var layer = { "layerdetailurl":"/toastergui/project/1/layer/22", @@ -43,9 +42,8 @@ QUnit.test("Layer alert notification", function(assert) { QUnit.test("Project info", function(assert){ var done = assert.async(); - libtoaster.getProjectInfo(libtoaster.ctx.projectPageUrl, function(prjInfo){ + libtoaster.getProjectInfo(libtoaster.ctx.xhrProjectUrl, function(prjInfo){ assert.ok(prjInfo.machine.name); - assert.ok(prjInfo.releases.length > 0); assert.ok(prjInfo.layers.length > 0); assert.ok(prjInfo.freqtargets); assert.ok(prjInfo.release); @@ -83,11 +81,11 @@ QUnit.test("Add layer", function(assert){ }, 200); /* Compare the number of layers before and after the add in the project */ - libtoaster.getProjectInfo(libtoaster.ctx.projectPageUrl, function(prjInfo){ + libtoaster.getProjectInfo(libtoaster.ctx.xhrProjectUrl, function(prjInfo){ var origNumLayers = prjInfo.layers.length; libtoaster.addRmLayer(layer, true, function(deps){ - libtoaster.getProjectInfo(libtoaster.ctx.projectPageUrl, + libtoaster.getProjectInfo(libtoaster.ctx.xhrProjectUrl, function(prjInfo){ assert.ok(prjInfo.layers.length > origNumLayers, "Layer not added to project"); @@ -135,10 +133,10 @@ QUnit.test("Make typeaheads", function(assert){ libtoaster.makeTypeahead(recipesT, libtoaster.ctx.recipesTypeAheadUrl, {}, function(){}); - assert.ok(recipesT.data('typeahead')); - assert.ok(layersT.data('typeahead')); - assert.ok(projectsT.data('typeahead')); - assert.ok(recipesT.data('typeahead')); + assert.ok(recipesT.data('ttTypeahead')); + assert.ok(layersT.data('ttTypeahead')); + assert.ok(projectsT.data('ttTypeahead')); + assert.ok(recipesT.data('ttTypeahead')); }); diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/typeahead.jquery.js b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/typeahead.jquery.js new file mode 100644 index 000000000..f3efd80cb --- /dev/null +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/typeahead.jquery.js @@ -0,0 +1,1551 @@ +/*! + * typeahead.js 0.11.1 + * https://github.com/twitter/typeahead.js + * Copyright 2013-2015 Twitter, Inc. and other contributors; Licensed MIT + */ + +(function(root, factory) { + if (typeof define === "function" && define.amd) { + define("typeahead.js", [ "jquery" ], function(a0) { + return factory(a0); + }); + } else if (typeof exports === "object") { + module.exports = factory(require("jquery")); + } else { + factory(jQuery); + } +})(this, function($) { + var _ = function() { + "use strict"; + return { + isMsie: function() { + return /(msie|trident)/i.test(navigator.userAgent) ? navigator.userAgent.match(/(msie |rv:)(\d+(.\d+)?)/i)[2] : false; + }, + isBlankString: function(str) { + return !str || /^\s*$/.test(str); + }, + escapeRegExChars: function(str) { + return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); + }, + isString: function(obj) { + return typeof obj === "string"; + }, + isNumber: function(obj) { + return typeof obj === "number"; + }, + isArray: $.isArray, + isFunction: $.isFunction, + isObject: $.isPlainObject, + isUndefined: function(obj) { + return typeof obj === "undefined"; + }, + isElement: function(obj) { + return !!(obj && obj.nodeType === 1); + }, + isJQuery: function(obj) { + return obj instanceof $; + }, + toStr: function toStr(s) { + return _.isUndefined(s) || s === null ? "" : s + ""; + }, + bind: $.proxy, + each: function(collection, cb) { + $.each(collection, reverseArgs); + function reverseArgs(index, value) { + return cb(value, index); + } + }, + map: $.map, + filter: $.grep, + every: function(obj, test) { + var result = true; + if (!obj) { + return result; + } + $.each(obj, function(key, val) { + if (!(result = test.call(null, val, key, obj))) { + return false; + } + }); + return !!result; + }, + some: function(obj, test) { + var result = false; + if (!obj) { + return result; + } + $.each(obj, function(key, val) { + if (result = test.call(null, val, key, obj)) { + return false; + } + }); + return !!result; + }, + mixin: $.extend, + identity: function(x) { + return x; + }, + clone: function(obj) { + return $.extend(true, {}, obj); + }, + getIdGenerator: function() { + var counter = 0; + return function() { + return counter++; + }; + }, + templatify: function templatify(obj) { + return $.isFunction(obj) ? obj : template; + function template() { + return String(obj); + } + }, + defer: function(fn) { + setTimeout(fn, 0); + }, + debounce: function(func, wait, immediate) { + var timeout, result; + return function() { + var context = this, args = arguments, later, callNow; + later = function() { + timeout = null; + if (!immediate) { + result = func.apply(context, args); + } + }; + callNow = immediate && !timeout; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + if (callNow) { + result = func.apply(context, args); + } + return result; + }; + }, + throttle: function(func, wait) { + var context, args, timeout, result, previous, later; + previous = 0; + later = function() { + previous = new Date(); + timeout = null; + result = func.apply(context, args); + }; + return function() { + var now = new Date(), remaining = wait - (now - previous); + context = this; + args = arguments; + if (remaining <= 0) { + clearTimeout(timeout); + timeout = null; + previous = now; + result = func.apply(context, args); + } else if (!timeout) { + timeout = setTimeout(later, remaining); + } + return result; + }; + }, + stringify: function(val) { + return _.isString(val) ? val : JSON.stringify(val); + }, + noop: function() {} + }; + }(); + var WWW = function() { + "use strict"; + var defaultClassNames = { + wrapper: "twitter-typeahead", + input: "tt-input", + hint: "tt-hint", + menu: "tt-menu", + dataset: "tt-dataset", + suggestion: "tt-suggestion", + selectable: "tt-selectable", + empty: "tt-empty", + open: "tt-open", + cursor: "tt-cursor", + highlight: "tt-highlight" + }; + return build; + function build(o) { + var www, classes; + classes = _.mixin({}, defaultClassNames, o); + www = { + css: buildCss(), + classes: classes, + html: buildHtml(classes), + selectors: buildSelectors(classes) + }; + return { + css: www.css, + html: www.html, + classes: www.classes, + selectors: www.selectors, + mixin: function(o) { + _.mixin(o, www); + } + }; + } + function buildHtml(c) { + return { + wrapper: '<span class="' + c.wrapper + '"></span>', + menu: '<div class="' + c.menu + '"></div>' + }; + } + function buildSelectors(classes) { + var selectors = {}; + _.each(classes, function(v, k) { + selectors[k] = "." + v; + }); + return selectors; + } + function buildCss() { + var css = { + wrapper: { + position: "relative", + display: "inline-block" + }, + hint: { + position: "absolute", + top: "0", + left: "0", + borderColor: "transparent", + boxShadow: "none", + opacity: "1" + }, + input: { + position: "relative", + verticalAlign: "top", + backgroundColor: "transparent" + }, + inputWithNoHint: { + position: "relative", + verticalAlign: "top" + }, + menu: { + position: "absolute", + top: "100%", + left: "0", + zIndex: "100", + display: "none" + }, + ltr: { + left: "0", + right: "auto" + }, + rtl: { + left: "auto", + right: " 0" + } + }; + if (_.isMsie()) { + _.mixin(css.input, { + backgroundImage: "url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)" + }); + } + return css; + } + }(); + var EventBus = function() { + "use strict"; + var namespace, deprecationMap; + namespace = "typeahead:"; + deprecationMap = { + render: "rendered", + cursorchange: "cursorchanged", + select: "selected", + autocomplete: "autocompleted" + }; + function EventBus(o) { + if (!o || !o.el) { + $.error("EventBus initialized without el"); + } + this.$el = $(o.el); + } + _.mixin(EventBus.prototype, { + _trigger: function(type, args) { + var $e; + $e = $.Event(namespace + type); + (args = args || []).unshift($e); + this.$el.trigger.apply(this.$el, args); + return $e; + }, + before: function(type) { + var args, $e; + args = [].slice.call(arguments, 1); + $e = this._trigger("before" + type, args); + return $e.isDefaultPrevented(); + }, + trigger: function(type) { + var deprecatedType; + this._trigger(type, [].slice.call(arguments, 1)); + if (deprecatedType = deprecationMap[type]) { + this._trigger(deprecatedType, [].slice.call(arguments, 1)); + } + } + }); + return EventBus; + }(); + var EventEmitter = function() { + "use strict"; + var splitter = /\s+/, nextTick = getNextTick(); + return { + onSync: onSync, + onAsync: onAsync, + off: off, + trigger: trigger + }; + function on(method, types, cb, context) { + var type; + if (!cb) { + return this; + } + types = types.split(splitter); + cb = context ? bindContext(cb, context) : cb; + this._callbacks = this._callbacks || {}; + while (type = types.shift()) { + this._callbacks[type] = this._callbacks[type] || { + sync: [], + async: [] + }; + this._callbacks[type][method].push(cb); + } + return this; + } + function onAsync(types, cb, context) { + return on.call(this, "async", types, cb, context); + } + function onSync(types, cb, context) { + return on.call(this, "sync", types, cb, context); + } + function off(types) { + var type; + if (!this._callbacks) { + return this; + } + types = types.split(splitter); + while (type = types.shift()) { + delete this._callbacks[type]; + } + return this; + } + function trigger(types) { + var type, callbacks, args, syncFlush, asyncFlush; + if (!this._callbacks) { + return this; + } + types = types.split(splitter); + args = [].slice.call(arguments, 1); + while ((type = types.shift()) && (callbacks = this._callbacks[type])) { + syncFlush = getFlush(callbacks.sync, this, [ type ].concat(args)); + asyncFlush = getFlush(callbacks.async, this, [ type ].concat(args)); + syncFlush() && nextTick(asyncFlush); + } + return this; + } + function getFlush(callbacks, context, args) { + return flush; + function flush() { + var cancelled; + for (var i = 0, len = callbacks.length; !cancelled && i < len; i += 1) { + cancelled = callbacks[i].apply(context, args) === false; + } + return !cancelled; + } + } + function getNextTick() { + var nextTickFn; + if (window.setImmediate) { + nextTickFn = function nextTickSetImmediate(fn) { + setImmediate(function() { + fn(); + }); + }; + } else { + nextTickFn = function nextTickSetTimeout(fn) { + setTimeout(function() { + fn(); + }, 0); + }; + } + return nextTickFn; + } + function bindContext(fn, context) { + return fn.bind ? fn.bind(context) : function() { + fn.apply(context, [].slice.call(arguments, 0)); + }; + } + }(); + var highlight = function(doc) { + "use strict"; + var defaults = { + node: null, + pattern: null, + tagName: "strong", + className: null, + wordsOnly: false, + caseSensitive: false + }; + return function hightlight(o) { + var regex; + o = _.mixin({}, defaults, o); + if (!o.node || !o.pattern) { + return; + } + o.pattern = _.isArray(o.pattern) ? o.pattern : [ o.pattern ]; + regex = getRegex(o.pattern, o.caseSensitive, o.wordsOnly); + traverse(o.node, hightlightTextNode); + function hightlightTextNode(textNode) { + var match, patternNode, wrapperNode; + if (match = regex.exec(textNode.data)) { + wrapperNode = doc.createElement(o.tagName); + o.className && (wrapperNode.className = o.className); + patternNode = textNode.splitText(match.index); + patternNode.splitText(match[0].length); + wrapperNode.appendChild(patternNode.cloneNode(true)); + textNode.parentNode.replaceChild(wrapperNode, patternNode); + } + return !!match; + } + function traverse(el, hightlightTextNode) { + var childNode, TEXT_NODE_TYPE = 3; + for (var i = 0; i < el.childNodes.length; i++) { + childNode = el.childNodes[i]; + if (childNode.nodeType === TEXT_NODE_TYPE) { + i += hightlightTextNode(childNode) ? 1 : 0; + } else { + traverse(childNode, hightlightTextNode); + } + } + } + }; + function getRegex(patterns, caseSensitive, wordsOnly) { + var escapedPatterns = [], regexStr; + for (var i = 0, len = patterns.length; i < len; i++) { + escapedPatterns.push(_.escapeRegExChars(patterns[i])); + } + regexStr = wordsOnly ? "\\b(" + escapedPatterns.join("|") + ")\\b" : "(" + escapedPatterns.join("|") + ")"; + return caseSensitive ? new RegExp(regexStr) : new RegExp(regexStr, "i"); + } + }(window.document); + var Input = function() { + "use strict"; + var specialKeyCodeMap; + specialKeyCodeMap = { + 9: "tab", + 27: "esc", + 37: "left", + 39: "right", + 13: "enter", + 38: "up", + 40: "down" + }; + function Input(o, www) { + o = o || {}; + if (!o.input) { + $.error("input is missing"); + } + www.mixin(this); + this.$hint = $(o.hint); + this.$input = $(o.input); + this.query = this.$input.val(); + this.queryWhenFocused = this.hasFocus() ? this.query : null; + this.$overflowHelper = buildOverflowHelper(this.$input); + this._checkLanguageDirection(); + if (this.$hint.length === 0) { + this.setHint = this.getHint = this.clearHint = this.clearHintIfInvalid = _.noop; + } + } + Input.normalizeQuery = function(str) { + return _.toStr(str).replace(/^\s*/g, "").replace(/\s{2,}/g, " "); + }; + _.mixin(Input.prototype, EventEmitter, { + _onBlur: function onBlur() { + this.resetInputValue(); + this.trigger("blurred"); + }, + _onFocus: function onFocus() { + this.queryWhenFocused = this.query; + this.trigger("focused"); + }, + _onKeydown: function onKeydown($e) { + var keyName = specialKeyCodeMap[$e.which || $e.keyCode]; + this._managePreventDefault(keyName, $e); + if (keyName && this._shouldTrigger(keyName, $e)) { + this.trigger(keyName + "Keyed", $e); + } + }, + _onInput: function onInput() { + this._setQuery(this.getInputValue()); + this.clearHintIfInvalid(); + this._checkLanguageDirection(); + }, + _managePreventDefault: function managePreventDefault(keyName, $e) { + var preventDefault; + switch (keyName) { + case "up": + case "down": + preventDefault = !withModifier($e); + break; + + default: + preventDefault = false; + } + preventDefault && $e.preventDefault(); + }, + _shouldTrigger: function shouldTrigger(keyName, $e) { + var trigger; + switch (keyName) { + case "tab": + trigger = !withModifier($e); + break; + + default: + trigger = true; + } + return trigger; + }, + _checkLanguageDirection: function checkLanguageDirection() { + var dir = (this.$input.css("direction") || "ltr").toLowerCase(); + if (this.dir !== dir) { + this.dir = dir; + this.$hint.attr("dir", dir); + this.trigger("langDirChanged", dir); + } + }, + _setQuery: function setQuery(val, silent) { + var areEquivalent, hasDifferentWhitespace; + areEquivalent = areQueriesEquivalent(val, this.query); + hasDifferentWhitespace = areEquivalent ? this.query.length !== val.length : false; + this.query = val; + if (!silent && !areEquivalent) { + this.trigger("queryChanged", this.query); + } else if (!silent && hasDifferentWhitespace) { + this.trigger("whitespaceChanged", this.query); + } + }, + bind: function() { + var that = this, onBlur, onFocus, onKeydown, onInput; + onBlur = _.bind(this._onBlur, this); + onFocus = _.bind(this._onFocus, this); + onKeydown = _.bind(this._onKeydown, this); + onInput = _.bind(this._onInput, this); + this.$input.on("blur.tt", onBlur).on("focus.tt", onFocus).on("keydown.tt", onKeydown); + if (!_.isMsie() || _.isMsie() > 9) { + this.$input.on("input.tt", onInput); + } else { + this.$input.on("keydown.tt keypress.tt cut.tt paste.tt", function($e) { + if (specialKeyCodeMap[$e.which || $e.keyCode]) { + return; + } + _.defer(_.bind(that._onInput, that, $e)); + }); + } + return this; + }, + focus: function focus() { + this.$input.focus(); + }, + blur: function blur() { + this.$input.blur(); + }, + getLangDir: function getLangDir() { + return this.dir; + }, + getQuery: function getQuery() { + return this.query || ""; + }, + setQuery: function setQuery(val, silent) { + this.setInputValue(val); + this._setQuery(val, silent); + }, + hasQueryChangedSinceLastFocus: function hasQueryChangedSinceLastFocus() { + return this.query !== this.queryWhenFocused; + }, + getInputValue: function getInputValue() { + return this.$input.val(); + }, + setInputValue: function setInputValue(value) { + this.$input.val(value); + this.clearHintIfInvalid(); + this._checkLanguageDirection(); + }, + resetInputValue: function resetInputValue() { + this.setInputValue(this.query); + }, + getHint: function getHint() { + return this.$hint.val(); + }, + setHint: function setHint(value) { + this.$hint.val(value); + }, + clearHint: function clearHint() { + this.setHint(""); + }, + clearHintIfInvalid: function clearHintIfInvalid() { + var val, hint, valIsPrefixOfHint, isValid; + val = this.getInputValue(); + hint = this.getHint(); + valIsPrefixOfHint = val !== hint && hint.indexOf(val) === 0; + isValid = val !== "" && valIsPrefixOfHint && !this.hasOverflow(); + !isValid && this.clearHint(); + }, + hasFocus: function hasFocus() { + return this.$input.is(":focus"); + }, + hasOverflow: function hasOverflow() { + var constraint = this.$input.width() - 2; + this.$overflowHelper.text(this.getInputValue()); + return this.$overflowHelper.width() >= constraint; + }, + isCursorAtEnd: function() { + var valueLength, selectionStart, range; + valueLength = this.$input.val().length; + selectionStart = this.$input[0].selectionStart; + if (_.isNumber(selectionStart)) { + return selectionStart === valueLength; + } else if (document.selection) { + range = document.selection.createRange(); + range.moveStart("character", -valueLength); + return valueLength === range.text.length; + } + return true; + }, + destroy: function destroy() { + this.$hint.off(".tt"); + this.$input.off(".tt"); + this.$overflowHelper.remove(); + this.$hint = this.$input = this.$overflowHelper = $("<div>"); + } + }); + return Input; + function buildOverflowHelper($input) { + return $('<pre aria-hidden="true"></pre>').css({ + position: "absolute", + visibility: "hidden", + whiteSpace: "pre", + fontFamily: $input.css("font-family"), + fontSize: $input.css("font-size"), + fontStyle: $input.css("font-style"), + fontVariant: $input.css("font-variant"), + fontWeight: $input.css("font-weight"), + wordSpacing: $input.css("word-spacing"), + letterSpacing: $input.css("letter-spacing"), + textIndent: $input.css("text-indent"), + textRendering: $input.css("text-rendering"), + textTransform: $input.css("text-transform") + }).insertAfter($input); + } + function areQueriesEquivalent(a, b) { + return Input.normalizeQuery(a) === Input.normalizeQuery(b); + } + function withModifier($e) { + return $e.altKey || $e.ctrlKey || $e.metaKey || $e.shiftKey; + } + }(); + var Dataset = function() { + "use strict"; + var keys, nameGenerator; + keys = { + val: "tt-selectable-display", + obj: "tt-selectable-object" + }; + nameGenerator = _.getIdGenerator(); + function Dataset(o, www) { + o = o || {}; + o.templates = o.templates || {}; + o.templates.notFound = o.templates.notFound || o.templates.empty; + if (!o.source) { + $.error("missing source"); + } + if (!o.node) { + $.error("missing node"); + } + if (o.name && !isValidName(o.name)) { + $.error("invalid dataset name: " + o.name); + } + www.mixin(this); + this.highlight = !!o.highlight; + this.name = o.name || nameGenerator(); + this.limit = o.limit || 5; + this.displayFn = getDisplayFn(o.display || o.displayKey); + this.templates = getTemplates(o.templates, this.displayFn); + this.source = o.source.__ttAdapter ? o.source.__ttAdapter() : o.source; + this.async = _.isUndefined(o.async) ? this.source.length > 2 : !!o.async; + this._resetLastSuggestion(); + this.$el = $(o.node).addClass(this.classes.dataset).addClass(this.classes.dataset + "-" + this.name); + } + Dataset.extractData = function extractData(el) { + var $el = $(el); + if ($el.data(keys.obj)) { + return { + val: $el.data(keys.val) || "", + obj: $el.data(keys.obj) || null + }; + } + return null; + }; + _.mixin(Dataset.prototype, EventEmitter, { + _overwrite: function overwrite(query, suggestions) { + suggestions = suggestions || []; + if (suggestions.length) { + this._renderSuggestions(query, suggestions); + } else if (this.async && this.templates.pending) { + this._renderPending(query); + } else if (!this.async && this.templates.notFound) { + this._renderNotFound(query); + } else { + this._empty(); + } + this.trigger("rendered", this.name, suggestions, false); + }, + _append: function append(query, suggestions) { + suggestions = suggestions || []; + if (suggestions.length && this.$lastSuggestion.length) { + this._appendSuggestions(query, suggestions); + } else if (suggestions.length) { + this._renderSuggestions(query, suggestions); + } else if (!this.$lastSuggestion.length && this.templates.notFound) { + this._renderNotFound(query); + } + this.trigger("rendered", this.name, suggestions, true); + }, + _renderSuggestions: function renderSuggestions(query, suggestions) { + var $fragment; + $fragment = this._getSuggestionsFragment(query, suggestions); + this.$lastSuggestion = $fragment.children().last(); + this.$el.html($fragment).prepend(this._getHeader(query, suggestions)).append(this._getFooter(query, suggestions)); + }, + _appendSuggestions: function appendSuggestions(query, suggestions) { + var $fragment, $lastSuggestion; + $fragment = this._getSuggestionsFragment(query, suggestions); + $lastSuggestion = $fragment.children().last(); + this.$lastSuggestion.after($fragment); + this.$lastSuggestion = $lastSuggestion; + }, + _renderPending: function renderPending(query) { + var template = this.templates.pending; + this._resetLastSuggestion(); + template && this.$el.html(template({ + query: query, + dataset: this.name + })); + }, + _renderNotFound: function renderNotFound(query) { + var template = this.templates.notFound; + this._resetLastSuggestion(); + template && this.$el.html(template({ + query: query, + dataset: this.name + })); + }, + _empty: function empty() { + this.$el.empty(); + this._resetLastSuggestion(); + }, + _getSuggestionsFragment: function getSuggestionsFragment(query, suggestions) { + var that = this, fragment; + fragment = document.createDocumentFragment(); + _.each(suggestions, function getSuggestionNode(suggestion) { + var $el, context; + context = that._injectQuery(query, suggestion); + $el = $(that.templates.suggestion(context)).data(keys.obj, suggestion).data(keys.val, that.displayFn(suggestion)).addClass(that.classes.suggestion + " " + that.classes.selectable); + fragment.appendChild($el[0]); + }); + this.highlight && highlight({ + className: this.classes.highlight, + node: fragment, + pattern: query + }); + return $(fragment); + }, + _getFooter: function getFooter(query, suggestions) { + return this.templates.footer ? this.templates.footer({ + query: query, + suggestions: suggestions, + dataset: this.name + }) : null; + }, + _getHeader: function getHeader(query, suggestions) { + return this.templates.header ? this.templates.header({ + query: query, + suggestions: suggestions, + dataset: this.name + }) : null; + }, + _resetLastSuggestion: function resetLastSuggestion() { + this.$lastSuggestion = $(); + }, + _injectQuery: function injectQuery(query, obj) { + return _.isObject(obj) ? _.mixin({ + _query: query + }, obj) : obj; + }, + update: function update(query) { + var that = this, canceled = false, syncCalled = false, rendered = 0; + this.cancel(); + this.cancel = function cancel() { + canceled = true; + that.cancel = $.noop; + that.async && that.trigger("asyncCanceled", query); + }; + this.source(query, sync, async); + !syncCalled && sync([]); + function sync(suggestions) { + if (syncCalled) { + return; + } + syncCalled = true; + suggestions = (suggestions || []).slice(0, that.limit); + rendered = suggestions.length; + that._overwrite(query, suggestions); + if (rendered < that.limit && that.async) { + that.trigger("asyncRequested", query); + } + } + function async(suggestions) { + suggestions = suggestions || []; + if (!canceled && rendered < that.limit) { + that.cancel = $.noop; + rendered += suggestions.length; + + // HACK: because we don't have a synchronous way of + // retrieving results, we use the async function every + // time we update the drop-down; however, the typeahead + // does some internal book-keeping which means that we + // only get the additional items in the drop-down when + // the next set of results is fetched, instead of all + // of them (it appears to implicitly track which + // results have already been shown in the drop-down); by + // forcing an overwrite, we see all of the new results + // every time we fetch a set of suggestions + //that._append(query, suggestions.slice(0, that.limit - rendered)); + that._overwrite(query, suggestions); + + that.async && that.trigger("asyncReceived", query); + } + } + }, + cancel: $.noop, + clear: function clear() { + this._empty(); + this.cancel(); + this.trigger("cleared"); + }, + isEmpty: function isEmpty() { + return this.$el.is(":empty"); + }, + destroy: function destroy() { + this.$el = $("<div>"); + } + }); + return Dataset; + function getDisplayFn(display) { + display = display || _.stringify; + return _.isFunction(display) ? display : displayFn; + function displayFn(obj) { + return obj[display]; + } + } + function getTemplates(templates, displayFn) { + return { + notFound: templates.notFound && _.templatify(templates.notFound), + pending: templates.pending && _.templatify(templates.pending), + header: templates.header && _.templatify(templates.header), + footer: templates.footer && _.templatify(templates.footer), + suggestion: templates.suggestion || suggestionTemplate + }; + function suggestionTemplate(context) { + return $("<div>").text(displayFn(context)); + } + } + function isValidName(str) { + return /^[_a-zA-Z0-9-]+$/.test(str); + } + }(); + var Menu = function() { + "use strict"; + function Menu(o, www) { + var that = this; + o = o || {}; + if (!o.node) { + $.error("node is required"); + } + www.mixin(this); + this.$node = $(o.node); + this.query = null; + this.datasets = _.map(o.datasets, initializeDataset); + function initializeDataset(oDataset) { + var node = that.$node.find(oDataset.node).first(); + oDataset.node = node.length ? node : $("<div>").appendTo(that.$node); + return new Dataset(oDataset, www); + } + } + _.mixin(Menu.prototype, EventEmitter, { + _onSelectableClick: function onSelectableClick($e) { + this.trigger("selectableClicked", $($e.currentTarget)); + }, + _onRendered: function onRendered(type, dataset, suggestions, async) { + this.$node.toggleClass(this.classes.empty, this._allDatasetsEmpty()); + this.trigger("datasetRendered", dataset, suggestions, async); + }, + _onCleared: function onCleared() { + this.$node.toggleClass(this.classes.empty, this._allDatasetsEmpty()); + this.trigger("datasetCleared"); + }, + _propagate: function propagate() { + this.trigger.apply(this, arguments); + }, + _allDatasetsEmpty: function allDatasetsEmpty() { + return _.every(this.datasets, isDatasetEmpty); + function isDatasetEmpty(dataset) { + return dataset.isEmpty(); + } + }, + _getSelectables: function getSelectables() { + return this.$node.find(this.selectors.selectable); + }, + _removeCursor: function _removeCursor() { + var $selectable = this.getActiveSelectable(); + $selectable && $selectable.removeClass(this.classes.cursor); + }, + _ensureVisible: function ensureVisible($el) { + var elTop, elBottom, nodeScrollTop, nodeHeight; + elTop = $el.position().top; + elBottom = elTop + $el.outerHeight(true); + nodeScrollTop = this.$node.scrollTop(); + nodeHeight = this.$node.height() + parseInt(this.$node.css("paddingTop"), 10) + parseInt(this.$node.css("paddingBottom"), 10); + if (elTop < 0) { + this.$node.scrollTop(nodeScrollTop + elTop); + } else if (nodeHeight < elBottom) { + this.$node.scrollTop(nodeScrollTop + (elBottom - nodeHeight)); + } + }, + bind: function() { + var that = this, onSelectableClick; + onSelectableClick = _.bind(this._onSelectableClick, this); + this.$node.on("click.tt", this.selectors.selectable, onSelectableClick); + _.each(this.datasets, function(dataset) { + dataset.onSync("asyncRequested", that._propagate, that).onSync("asyncCanceled", that._propagate, that).onSync("asyncReceived", that._propagate, that).onSync("rendered", that._onRendered, that).onSync("cleared", that._onCleared, that); + }); + return this; + }, + isOpen: function isOpen() { + return this.$node.hasClass(this.classes.open); + }, + open: function open() { + this.$node.addClass(this.classes.open); + }, + close: function close() { + this.$node.removeClass(this.classes.open); + this._removeCursor(); + }, + setLanguageDirection: function setLanguageDirection(dir) { + this.$node.attr("dir", dir); + }, + selectableRelativeToCursor: function selectableRelativeToCursor(delta) { + var $selectables, $oldCursor, oldIndex, newIndex; + $oldCursor = this.getActiveSelectable(); + $selectables = this._getSelectables(); + oldIndex = $oldCursor ? $selectables.index($oldCursor) : -1; + newIndex = oldIndex + delta; + newIndex = (newIndex + 1) % ($selectables.length + 1) - 1; + newIndex = newIndex < -1 ? $selectables.length - 1 : newIndex; + return newIndex === -1 ? null : $selectables.eq(newIndex); + }, + setCursor: function setCursor($selectable) { + this._removeCursor(); + if ($selectable = $selectable && $selectable.first()) { + $selectable.addClass(this.classes.cursor); + this._ensureVisible($selectable); + } + }, + getSelectableData: function getSelectableData($el) { + return $el && $el.length ? Dataset.extractData($el) : null; + }, + getActiveSelectable: function getActiveSelectable() { + var $selectable = this._getSelectables().filter(this.selectors.cursor).first(); + return $selectable.length ? $selectable : null; + }, + getTopSelectable: function getTopSelectable() { + var $selectable = this._getSelectables().first(); + return $selectable.length ? $selectable : null; + }, + update: function update(query) { + var isValidUpdate = query !== this.query; + if (isValidUpdate) { + this.query = query; + _.each(this.datasets, updateDataset); + } + return isValidUpdate; + function updateDataset(dataset) { + dataset.update(query); + } + }, + empty: function empty() { + _.each(this.datasets, clearDataset); + this.query = null; + this.$node.addClass(this.classes.empty); + function clearDataset(dataset) { + dataset.clear(); + } + }, + destroy: function destroy() { + this.$node.off(".tt"); + this.$node = $("<div>"); + _.each(this.datasets, destroyDataset); + function destroyDataset(dataset) { + dataset.destroy(); + } + } + }); + return Menu; + }(); + var DefaultMenu = function() { + "use strict"; + var s = Menu.prototype; + function DefaultMenu() { + Menu.apply(this, [].slice.call(arguments, 0)); + } + _.mixin(DefaultMenu.prototype, Menu.prototype, { + open: function open() { + !this._allDatasetsEmpty() && this._show(); + return s.open.apply(this, [].slice.call(arguments, 0)); + }, + close: function close() { + this._hide(); + return s.close.apply(this, [].slice.call(arguments, 0)); + }, + _onRendered: function onRendered() { + if (this._allDatasetsEmpty()) { + this._hide(); + } else { + this.isOpen() && this._show(); + } + return s._onRendered.apply(this, [].slice.call(arguments, 0)); + }, + _onCleared: function onCleared() { + if (this._allDatasetsEmpty()) { + this._hide(); + } else { + this.isOpen() && this._show(); + } + return s._onCleared.apply(this, [].slice.call(arguments, 0)); + }, + setLanguageDirection: function setLanguageDirection(dir) { + this.$node.css(dir === "ltr" ? this.css.ltr : this.css.rtl); + return s.setLanguageDirection.apply(this, [].slice.call(arguments, 0)); + }, + _hide: function hide() { + this.$node.hide(); + }, + _show: function show() { + this.$node.css("display", "block"); + } + }); + return DefaultMenu; + }(); + var Typeahead = function() { + "use strict"; + function Typeahead(o, www) { + var onFocused, onBlurred, onEnterKeyed, onTabKeyed, onEscKeyed, onUpKeyed, onDownKeyed, onLeftKeyed, onRightKeyed, onQueryChanged, onWhitespaceChanged; + o = o || {}; + if (!o.input) { + $.error("missing input"); + } + if (!o.menu) { + $.error("missing menu"); + } + if (!o.eventBus) { + $.error("missing event bus"); + } + www.mixin(this); + this.eventBus = o.eventBus; + this.minLength = _.isNumber(o.minLength) ? o.minLength : 1; + this.input = o.input; + this.menu = o.menu; + this.enabled = true; + this.active = false; + this.input.hasFocus() && this.activate(); + this.dir = this.input.getLangDir(); + this._hacks(); + this.menu.bind().onSync("selectableClicked", this._onSelectableClicked, this).onSync("asyncRequested", this._onAsyncRequested, this).onSync("asyncCanceled", this._onAsyncCanceled, this).onSync("asyncReceived", this._onAsyncReceived, this).onSync("datasetRendered", this._onDatasetRendered, this).onSync("datasetCleared", this._onDatasetCleared, this); + onFocused = c(this, "activate", "open", "_onFocused"); + onBlurred = c(this, "deactivate", "_onBlurred"); + onEnterKeyed = c(this, "isActive", "isOpen", "_onEnterKeyed"); + onTabKeyed = c(this, "isActive", "isOpen", "_onTabKeyed"); + onEscKeyed = c(this, "isActive", "_onEscKeyed"); + onUpKeyed = c(this, "isActive", "open", "_onUpKeyed"); + onDownKeyed = c(this, "isActive", "open", "_onDownKeyed"); + onLeftKeyed = c(this, "isActive", "isOpen", "_onLeftKeyed"); + onRightKeyed = c(this, "isActive", "isOpen", "_onRightKeyed"); + onQueryChanged = c(this, "_openIfActive", "_onQueryChanged"); + onWhitespaceChanged = c(this, "_openIfActive", "_onWhitespaceChanged"); + this.input.bind().onSync("focused", onFocused, this).onSync("blurred", onBlurred, this).onSync("enterKeyed", onEnterKeyed, this).onSync("tabKeyed", onTabKeyed, this).onSync("escKeyed", onEscKeyed, this).onSync("upKeyed", onUpKeyed, this).onSync("downKeyed", onDownKeyed, this).onSync("leftKeyed", onLeftKeyed, this).onSync("rightKeyed", onRightKeyed, this).onSync("queryChanged", onQueryChanged, this).onSync("whitespaceChanged", onWhitespaceChanged, this).onSync("langDirChanged", this._onLangDirChanged, this); + } + _.mixin(Typeahead.prototype, { + _hacks: function hacks() { + var $input, $menu; + $input = this.input.$input || $("<div>"); + $menu = this.menu.$node || $("<div>"); + $input.on("blur.tt", function($e) { + var active, isActive, hasActive; + active = document.activeElement; + isActive = $menu.is(active); + hasActive = $menu.has(active).length > 0; + if (_.isMsie() && (isActive || hasActive)) { + $e.preventDefault(); + $e.stopImmediatePropagation(); + _.defer(function() { + $input.focus(); + }); + } + }); + $menu.on("mousedown.tt", function($e) { + $e.preventDefault(); + }); + }, + _onSelectableClicked: function onSelectableClicked(type, $el) { + this.select($el); + }, + _onDatasetCleared: function onDatasetCleared() { + this._updateHint(); + }, + _onDatasetRendered: function onDatasetRendered(type, dataset, suggestions, async) { + this._updateHint(); + this.eventBus.trigger("render", suggestions, async, dataset); + }, + _onAsyncRequested: function onAsyncRequested(type, dataset, query) { + this.eventBus.trigger("asyncrequest", query, dataset); + }, + _onAsyncCanceled: function onAsyncCanceled(type, dataset, query) { + this.eventBus.trigger("asynccancel", query, dataset); + }, + _onAsyncReceived: function onAsyncReceived(type, dataset, query) { + this.eventBus.trigger("asyncreceive", query, dataset); + }, + _onFocused: function onFocused() { + this._minLengthMet() && this.menu.update(this.input.getQuery()); + }, + _onBlurred: function onBlurred() { + if (this.input.hasQueryChangedSinceLastFocus()) { + this.eventBus.trigger("change", this.input.getQuery()); + } + }, + _onEnterKeyed: function onEnterKeyed(type, $e) { + var $selectable; + if ($selectable = this.menu.getActiveSelectable()) { + this.select($selectable) && $e.preventDefault(); + } + }, + _onTabKeyed: function onTabKeyed(type, $e) { + var $selectable; + if ($selectable = this.menu.getActiveSelectable()) { + this.select($selectable) && $e.preventDefault(); + } else if ($selectable = this.menu.getTopSelectable()) { + this.autocomplete($selectable) && $e.preventDefault(); + } + }, + _onEscKeyed: function onEscKeyed() { + this.close(); + }, + _onUpKeyed: function onUpKeyed() { + this.moveCursor(-1); + }, + _onDownKeyed: function onDownKeyed() { + this.moveCursor(+1); + }, + _onLeftKeyed: function onLeftKeyed() { + if (this.dir === "rtl" && this.input.isCursorAtEnd()) { + this.autocomplete(this.menu.getTopSelectable()); + } + }, + _onRightKeyed: function onRightKeyed() { + if (this.dir === "ltr" && this.input.isCursorAtEnd()) { + this.autocomplete(this.menu.getTopSelectable()); + } + }, + _onQueryChanged: function onQueryChanged(e, query) { + this._minLengthMet(query) ? this.menu.update(query) : this.menu.empty(); + }, + _onWhitespaceChanged: function onWhitespaceChanged() { + this._updateHint(); + }, + _onLangDirChanged: function onLangDirChanged(e, dir) { + if (this.dir !== dir) { + this.dir = dir; + this.menu.setLanguageDirection(dir); + } + }, + _openIfActive: function openIfActive() { + this.isActive() && this.open(); + }, + _minLengthMet: function minLengthMet(query) { + query = _.isString(query) ? query : this.input.getQuery() || ""; + return query.length >= this.minLength; + }, + _updateHint: function updateHint() { + var $selectable, data, val, query, escapedQuery, frontMatchRegEx, match; + $selectable = this.menu.getTopSelectable(); + data = this.menu.getSelectableData($selectable); + val = this.input.getInputValue(); + if (data && !_.isBlankString(val) && !this.input.hasOverflow()) { + query = Input.normalizeQuery(val); + escapedQuery = _.escapeRegExChars(query); + frontMatchRegEx = new RegExp("^(?:" + escapedQuery + ")(.+$)", "i"); + match = frontMatchRegEx.exec(data.val); + match && this.input.setHint(val + match[1]); + } else { + this.input.clearHint(); + } + }, + isEnabled: function isEnabled() { + return this.enabled; + }, + enable: function enable() { + this.enabled = true; + }, + disable: function disable() { + this.enabled = false; + }, + isActive: function isActive() { + return this.active; + }, + activate: function activate() { + if (this.isActive()) { + return true; + } else if (!this.isEnabled() || this.eventBus.before("active")) { + return false; + } else { + this.active = true; + this.eventBus.trigger("active"); + return true; + } + }, + deactivate: function deactivate() { + if (!this.isActive()) { + return true; + } else if (this.eventBus.before("idle")) { + return false; + } else { + this.active = false; + this.close(); + this.eventBus.trigger("idle"); + return true; + } + }, + isOpen: function isOpen() { + return this.menu.isOpen(); + }, + open: function open() { + if (!this.isOpen() && !this.eventBus.before("open")) { + this.menu.open(); + this._updateHint(); + this.eventBus.trigger("open"); + } + return this.isOpen(); + }, + close: function close() { + if (this.isOpen() && !this.eventBus.before("close")) { + this.menu.close(); + this.input.clearHint(); + this.input.resetInputValue(); + this.eventBus.trigger("close"); + } + return !this.isOpen(); + }, + setVal: function setVal(val) { + this.input.setQuery(_.toStr(val)); + }, + getVal: function getVal() { + return this.input.getQuery(); + }, + select: function select($selectable) { + var data = this.menu.getSelectableData($selectable); + if (data && !this.eventBus.before("select", data.obj)) { + this.input.setQuery(data.val, true); + this.eventBus.trigger("select", data.obj); + this.close(); + return true; + } + return false; + }, + autocomplete: function autocomplete($selectable) { + var query, data, isValid; + query = this.input.getQuery(); + data = this.menu.getSelectableData($selectable); + isValid = data && query !== data.val; + if (isValid && !this.eventBus.before("autocomplete", data.obj)) { + this.input.setQuery(data.val); + this.eventBus.trigger("autocomplete", data.obj); + return true; + } + return false; + }, + moveCursor: function moveCursor(delta) { + var query, $candidate, data, payload, cancelMove; + query = this.input.getQuery(); + $candidate = this.menu.selectableRelativeToCursor(delta); + data = this.menu.getSelectableData($candidate); + payload = data ? data.obj : null; + cancelMove = this._minLengthMet() && this.menu.update(query); + if (!cancelMove && !this.eventBus.before("cursorchange", payload)) { + this.menu.setCursor($candidate); + if (data) { + this.input.setInputValue(data.val); + } else { + this.input.resetInputValue(); + this._updateHint(); + } + this.eventBus.trigger("cursorchange", payload); + return true; + } + return false; + }, + destroy: function destroy() { + this.input.destroy(); + this.menu.destroy(); + } + }); + return Typeahead; + function c(ctx) { + var methods = [].slice.call(arguments, 1); + return function() { + var args = [].slice.call(arguments); + _.each(methods, function(method) { + return ctx[method].apply(ctx, args); + }); + }; + } + }(); + (function() { + "use strict"; + var old, keys, methods; + old = $.fn.typeahead; + keys = { + www: "tt-www", + attrs: "tt-attrs", + typeahead: "tt-typeahead" + }; + methods = { + initialize: function initialize(o, datasets) { + var www; + datasets = _.isArray(datasets) ? datasets : [].slice.call(arguments, 1); + o = o || {}; + www = WWW(o.classNames); + return this.each(attach); + function attach() { + var $input, $wrapper, $hint, $menu, defaultHint, defaultMenu, eventBus, input, menu, typeahead, MenuConstructor; + _.each(datasets, function(d) { + d.highlight = !!o.highlight; + }); + $input = $(this); + $wrapper = $(www.html.wrapper); + $hint = $elOrNull(o.hint); + $menu = $elOrNull(o.menu); + defaultHint = o.hint !== false && !$hint; + defaultMenu = o.menu !== false && !$menu; + defaultHint && ($hint = buildHintFromInput($input, www)); + defaultMenu && ($menu = $(www.html.menu).css(www.css.menu)); + $hint && $hint.val(""); + $input = prepInput($input, www); + if (defaultHint || defaultMenu) { + $wrapper.css(www.css.wrapper); + $input.css(defaultHint ? www.css.input : www.css.inputWithNoHint); + $input.wrap($wrapper).parent().prepend(defaultHint ? $hint : null).append(defaultMenu ? $menu : null); + } + MenuConstructor = defaultMenu ? DefaultMenu : Menu; + eventBus = new EventBus({ + el: $input + }); + input = new Input({ + hint: $hint, + input: $input + }, www); + menu = new MenuConstructor({ + node: $menu, + datasets: datasets + }, www); + typeahead = new Typeahead({ + input: input, + menu: menu, + eventBus: eventBus, + minLength: o.minLength + }, www); + $input.data(keys.www, www); + $input.data(keys.typeahead, typeahead); + } + }, + isEnabled: function isEnabled() { + var enabled; + ttEach(this.first(), function(t) { + enabled = t.isEnabled(); + }); + return enabled; + }, + enable: function enable() { + ttEach(this, function(t) { + t.enable(); + }); + return this; + }, + disable: function disable() { + ttEach(this, function(t) { + t.disable(); + }); + return this; + }, + isActive: function isActive() { + var active; + ttEach(this.first(), function(t) { + active = t.isActive(); + }); + return active; + }, + activate: function activate() { + ttEach(this, function(t) { + t.activate(); + }); + return this; + }, + deactivate: function deactivate() { + ttEach(this, function(t) { + t.deactivate(); + }); + return this; + }, + isOpen: function isOpen() { + var open; + ttEach(this.first(), function(t) { + open = t.isOpen(); + }); + return open; + }, + open: function open() { + ttEach(this, function(t) { + t.open(); + }); + return this; + }, + close: function close() { + ttEach(this, function(t) { + t.close(); + }); + return this; + }, + select: function select(el) { + var success = false, $el = $(el); + ttEach(this.first(), function(t) { + success = t.select($el); + }); + return success; + }, + autocomplete: function autocomplete(el) { + var success = false, $el = $(el); + ttEach(this.first(), function(t) { + success = t.autocomplete($el); + }); + return success; + }, + moveCursor: function moveCursoe(delta) { + var success = false; + ttEach(this.first(), function(t) { + success = t.moveCursor(delta); + }); + return success; + }, + val: function val(newVal) { + var query; + if (!arguments.length) { + ttEach(this.first(), function(t) { + query = t.getVal(); + }); + return query; + } else { + ttEach(this, function(t) { + t.setVal(newVal); + }); + return this; + } + }, + destroy: function destroy() { + ttEach(this, function(typeahead, $input) { + revert($input); + typeahead.destroy(); + }); + return this; + } + }; + $.fn.typeahead = function(method) { + if (methods[method]) { + return methods[method].apply(this, [].slice.call(arguments, 1)); + } else { + return methods.initialize.apply(this, arguments); + } + }; + $.fn.typeahead.noConflict = function noConflict() { + $.fn.typeahead = old; + return this; + }; + function ttEach($els, fn) { + $els.each(function() { + var $input = $(this), typeahead; + (typeahead = $input.data(keys.typeahead)) && fn(typeahead, $input); + }); + } + function buildHintFromInput($input, www) { + return $input.clone().addClass(www.classes.hint).removeData().css(www.css.hint).css(getBackgroundStyles($input)).prop("readonly", true).removeAttr("id name placeholder required").attr({ + autocomplete: "off", + spellcheck: "false", + tabindex: -1 + }); + } + function prepInput($input, www) { + $input.data(keys.attrs, { + dir: $input.attr("dir"), + autocomplete: $input.attr("autocomplete"), + spellcheck: $input.attr("spellcheck"), + style: $input.attr("style") + }); + $input.addClass(www.classes.input).attr({ + autocomplete: "off", + spellcheck: false + }); + try { + !$input.attr("dir") && $input.attr("dir", "auto"); + } catch (e) {} + return $input; + } + function getBackgroundStyles($el) { + return { + backgroundAttachment: $el.css("background-attachment"), + backgroundClip: $el.css("background-clip"), + backgroundColor: $el.css("background-color"), + backgroundImage: $el.css("background-image"), + backgroundOrigin: $el.css("background-origin"), + backgroundPosition: $el.css("background-position"), + backgroundRepeat: $el.css("background-repeat"), + backgroundSize: $el.css("background-size") + }; + } + function revert($input) { + var www, $wrapper; + www = $input.data(keys.www); + $wrapper = $input.parent().filter(www.selectors.wrapper); + _.each($input.data(keys.attrs), function(val, key) { + _.isUndefined(val) ? $input.removeAttr(key) : $input.attr(key, val); + }); + $input.removeData(keys.typeahead).removeData(keys.www).removeData(keys.attr).removeClass(www.classes.input); + if ($wrapper.length) { + $input.detach().insertAfter($wrapper); + $wrapper.remove(); + } + } + function $elOrNull(obj) { + var isValid, $el; + isValid = _.isJQuery(obj) || _.isElement(obj); + $el = isValid ? $(obj).first() : []; + return $el.length ? $el : null; + } + })(); +});
\ No newline at end of file diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/tablefilter.py b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/tablefilter.py index 9d15bcff0..65454e140 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/tablefilter.py +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/tablefilter.py @@ -286,7 +286,7 @@ class TableFilterMap(object): def to_json(self, queryset): data = {} - for filter_name, table_filter in self.__filters.iteritems(): + for filter_name, table_filter in self.__filters.items(): data[filter_name] = table_filter.to_json() return data diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/tables.py b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/tables.py index 2cc2f4eb7..e2d23c1e8 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/tables.py +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/tables.py @@ -22,7 +22,7 @@ from toastergui.widgets import ToasterTable from orm.models import Recipe, ProjectLayer, Layer_Version, Machine, Project from orm.models import CustomImageRecipe, Package, Target, Build, LogMessage, Task -from orm.models import CustomImagePackage +from orm.models import CustomImagePackage, Package_DependencyManager from django.db.models import Q, Max, Sum, Count, When, Case, Value, IntegerField from django.conf.urls import url from django.core.urlresolvers import reverse, resolve @@ -114,28 +114,37 @@ class LayersTable(ToasterTable): git_url_template = ''' <a href="{% url 'layerdetails' extra.pid data.id %}"> + {% if data.layer.local_source_dir %} + <code>{{data.layer.local_source_dir}}</code> + {% else %} <code>{{data.layer.vcs_url}}</code> </a> + {% endif %} {% if data.get_vcs_link_url %} <a target="_blank" href="{{ data.get_vcs_link_url }}"> - <i class="icon-share get-info"></i> + <span class="glyphicon glyphicon-new-window"></span> </a> {% endif %} ''' - self.add_column(title="Git repository URL", - help_text="The Git repository for the layer source code", + self.add_column(title="Layer source code location", + help_text="A Git repository or an absolute path to a directory", hidden=True, static_data_name="layer__vcs_url", static_data_template=git_url_template) git_dir_template = ''' + {% if data.layer.local_source_dir %} + <span class="text-muted">Not applicable</span> + <span class="glyphicon glyphicon-question-sign get-help" data-original-title="" title="The source code of {{data.layer.name}} is not in a Git repository, so there is no subdirectory associated with it"> </span> + {% else %} <a href="{% url 'layerdetails' extra.pid data.id %}"> <code>{{data.dirpath}}</code> </a> + {% endif %} {% if data.dirpath and data.get_vcs_dirpath_link_url %} <a target="_blank" href="{{ data.get_vcs_dirpath_link_url }}"> - <i class="icon-share get-info"></i> + <span class="glyphicon glyphicon-new-window"></span> </a> {% endif %}''' @@ -146,16 +155,14 @@ class LayersTable(ToasterTable): static_data_template=git_dir_template) revision_template = ''' - {% load projecttags %} - {% with vcs_ref=data.get_vcs_reference %} - {% if vcs_ref|is_shaid %} - <a class="btn" data-content="<ul class='unstyled'> <li>{{vcs_ref}}</li> </ul>"> - {{vcs_ref|truncatechars:10}} - </a> + {% if data.layer.local_source_dir %} + <span class="text-muted">Not applicable</span> + <span class="glyphicon glyphicon-question-sign get-help" data-original-title="" title="The source code of {{data.layer.name}} is not in a Git repository, so there is no revision associated with it"> </span> {% else %} - {{vcs_ref}} - {% endif %} + {% with vcs_ref=data.get_vcs_reference %} + {% include 'snippets/gitrev_popover.html' %} {% endwith %} + {% endif %} ''' self.add_column(title="Git revision", @@ -166,8 +173,8 @@ class LayersTable(ToasterTable): deps_template = ''' {% with ods=data.dependencies.all%} {% if ods.count %} - <a class="btn" title="<a href='{% url "layerdetails" extra.pid data.id %}'>{{data.layer.name}}</a> dependencies" - data-content="<ul class='unstyled'> + <a class="btn btn-default" title="<a href='{% url "layerdetails" extra.pid data.id %}'>{{data.layer.name}}</a> dependencies" + data-content="<ul class='list-unstyled'> {% for i in ods%} <li><a href='{% url "layerdetails" extra.pid i.depends_on.pk %}'>{{i.depends_on.layer.name}}</a></li> {% endfor %} @@ -190,24 +197,13 @@ class LayersTable(ToasterTable): static_data_name="add-del-layers", static_data_template='{% include "layer_btn.html" %}') - project = Project.objects.get(pk=kwargs['pid']) - self.add_column(title="LayerDetailsUrl", - displayable = False, - field_name="layerdetailurl", - computation = lambda x: reverse('layerdetails', args=(project.id, x.id))) - - self.add_column(title="name", - displayable = False, - field_name="name", - computation = lambda x: x.layer.name) - class MachinesTable(ToasterTable): """Table of Machines in Toaster""" def __init__(self, *args, **kwargs): super(MachinesTable, self).__init__(*args, **kwargs) - self.empty_state = "No machines maybe you need to do a build?" + self.empty_state = "Toaster has no machine information for this project. Sadly, machine information cannot be obtained from builds, so this page will remain empty." self.title = "Compatible machines" self.default_orderby = "name" @@ -275,7 +271,7 @@ class MachinesTable(ToasterTable): field_name="layer_version__get_vcs_reference") machine_file_template = '''<code>conf/machine/{{data.name}}.conf</code> - <a href="{{data.get_vcs_machine_file_link_url}}" target="_blank"><i class="icon-share get-info"></i></a>''' + <a href="{{data.get_vcs_machine_file_link_url}}" target="_blank"><span class="glyphicon glyphicon-new-window"></i></a>''' self.add_column(title="Machine file", hidden=True, @@ -318,7 +314,11 @@ class LayerMachinesTable(MachinesTable): self.add_column(title="Description", field_name="description") - select_btn_template = '<a href="{% url "project" extra.pid %}?setMachine={{data.name}}" class="btn btn-block select-machine-btn" {% if extra.in_prj == 0%}disabled="disabled"{%endif%}>Select machine</a>' + select_btn_template = ''' + <a href="{% url "project" extra.pid %}?setMachine={{data.name}}" + class="btn btn-default btn-block select-machine-btn + {% if extra.in_prj == 0%}disabled{%endif%}">Select machine</a> + ''' self.add_column(title="Select machine", static_data_name="add-del-layers", @@ -330,10 +330,10 @@ class RecipesTable(ToasterTable): def __init__(self, *args, **kwargs): super(RecipesTable, self).__init__(*args, **kwargs) - self.empty_state = "Toaster has no recipe information. To generate recipe information you can configure a layer source then run a build." + self.empty_state = "Toaster has no recipe information. To generate recipe information you need to run a build." build_col = { 'title' : "Build", - 'help_text' : "Add or delete recipes to and from your project", + 'help_text' : "Before building a recipe, you might need to add the corresponding layer to your project", 'hideable' : False, 'filter_name' : "in_current_project", 'static_data_name' : "add-del-layers", @@ -344,8 +344,7 @@ class RecipesTable(ToasterTable): context = super(RecipesTable, self).get_context_data(**kwargs) context['project'] = project - - context['projectlayers'] = map(lambda prjlayer: prjlayer.layercommit.id, ProjectLayer.objects.filter(project=context['project'])) + context['projectlayers'] = [player.layercommit.id for player in ProjectLayer.objects.filter(project=context['project'])] return context @@ -395,7 +394,7 @@ class RecipesTable(ToasterTable): recipe_file_template = ''' <code>{{data.file_path}}</code> <a href="{{data.get_vcs_recipe_file_link_url}}" target="_blank"> - <i class="icon-share get-info"></i> + <span class="glyphicon glyphicon-new-window"></i> </a> ''' @@ -428,9 +427,19 @@ class RecipesTable(ToasterTable): orderable=True, field_name="license") + revision_link_template = ''' + {% if data.layer_version.layer.local_source_dir %} + <span class="text-muted">Not applicable</span> + <span class="glyphicon glyphicon-question-sign get-help" data-original-title="" title="The source code of {{data.layer_version.layer.name}} is not in a Git repository, so there is no revision associated with it"> </span> + {% else %} + {{data.layer_version.get_vcs_reference}} + {% endif %} + ''' + self.add_column(title="Git revision", hidden=True, - field_name="layer_version__get_vcs_reference") + static_data_name="layer_version__get_vcs_reference", + static_data_template=revision_link_template) class LayerRecipesTable(RecipesTable): @@ -466,7 +475,11 @@ class LayerRecipesTable(RecipesTable): self.add_column(title="Description", field_name="get_description_or_summary") - build_recipe_template ='<button class="btn btn-block build-recipe-btn" data-recipe-name="{{data.name}}" {%if extra.in_prj == 0 %}disabled="disabled"{%endif%}>Build recipe</button>' + build_recipe_template = ''' + <a class="btn btn-default btn-block build-recipe-btn + {% if extra.in_prj == 0 %}disabled{% endif %}" + data-recipe-name="{{data.name}}">Build recipe</a> + ''' self.add_column(title="Build recipe", static_data_name="add-del-layers", @@ -481,7 +494,16 @@ class CustomImagesTable(ToasterTable): def get_context_data(self, **kwargs): context = super(CustomImagesTable, self).get_context_data(**kwargs) + + empty_state_template = ''' + You have not created any custom images yet. + <a href="{% url 'newcustomimage' data.pid %}"> + Create your first custom image</a> + ''' + context['empty_state'] = self.render_static_data(empty_state_template, + kwargs) project = Project.objects.get(pk=kwargs['pid']) + # TODO put project into the ToasterTable base class context['project'] = project return context @@ -507,29 +529,31 @@ class CustomImagesTable(ToasterTable): static_data_template=name_link_template) recipe_file_template = ''' + {% if data.get_base_recipe_file %} <code>{{data.name}}_{{data.version}}.bb</code> - <a href="{% url 'customrecipedownload' extra.pid data.pk %}"> - <i class="icon-download-alt" data-original-title="Download recipe - file"></i> - </a>''' + <a href="{% url 'customrecipedownload' extra.pid data.pk %}" + class="glyphicon glyphicon-download-alt get-help" title="Download recipe file"></a> + {% endif %}''' self.add_column(title="Recipe file", static_data_name='recipe_file_download', static_data_template=recipe_file_template) approx_packages_template = ''' + {% if data.get_all_packages.count > 0 %} <a href="{% url 'customrecipe' extra.pid data.id %}"> {{data.get_all_packages.count}} - </a>''' + </a> + {% endif %}''' - self.add_column(title="Approx packages", + self.add_column(title="Packages", static_data_name='approx_packages', static_data_template=approx_packages_template) build_btn_template = ''' <button data-recipe-name="{{data.name}}" - class="btn btn-block build-recipe-btn" style="margin-top: 5px;" > + class="btn btn-default btn-block build-recipe-btn"> Build </button>''' @@ -695,6 +719,7 @@ class PackagesTable(ToasterTable): def setup_queryset(self, *args, **kwargs): recipe = Recipe.objects.get(pk=kwargs['recipe_id']) + self.static_context_extra['target_name'] = recipe.name self.queryset = self.create_package_list(recipe, kwargs['pid']) self.queryset = self.queryset.order_by('name') @@ -711,13 +736,15 @@ class PackagesTable(ToasterTable): self.add_column(title="Approx Size", orderable=True, + field_name="size", static_data_name="size", static_data_template="{% load projecttags %} \ {{data.size|filtered_filesizeformat}}") self.add_column(title="License", field_name="license", - orderable=True) + orderable=True, + hidden=True) self.add_column(title="Dependencies", @@ -764,7 +791,19 @@ class SelectPackagesTable(PackagesTable): self.queryset = self.queryset.order_by('name') + # This target is the target used to work out which group of dependences + # to display, if we've built the custom image we use it otherwise we + # can use the based recipe instead + if prj.build_set.filter(target__target=self.cust_recipe.name).count()\ + > 0: + self.static_context_extra['target_name'] = self.cust_recipe.name + else: + self.static_context_extra['target_name'] =\ + Package_DependencyManager.TARGET_LATEST + self.static_context_extra['recipe_id'] = kwargs['custrecipeid'] + + self.static_context_extra['current_packages'] = \ current_packages.values_list('pk', flat=True) @@ -860,26 +899,22 @@ class ProjectsTable(ToasterTable): last_activity_on_template = ''' {% load project_url_tag %} <span data-project-field="updated"> - <a href="{% project_url data %}"> {{data.updated | date:"d/m/y H:i"}} - </a> </span> ''' release_template = ''' <span data-project-field="release"> {% if data.release %} - <a href="{% url 'project' data.id %}#project-details"> - {{data.release.name}} - </a> + {{data.release.name}} {% elif data.is_default %} - <span class="muted">Not applicable</span> - <i class="icon-question-sign get-help hover-help" - data-original-title="This project does not have a release set. + <span class="text-muted">Not applicable</span> + <span class="glyphicon glyphicon-question-sign get-help hover-help" + title="This project does not have a release set. It simply collects information about the builds you start from the command line while Toaster is running" style="visibility: hidden;"> - </i> + </span> {% else %} No release available {% endif %} @@ -889,16 +924,14 @@ class ProjectsTable(ToasterTable): machine_template = ''' <span data-project-field="machine"> {% if data.is_default %} - <span class="muted">Not applicable</span> - <i class="icon-question-sign get-help hover-help" - data-original-title="This project does not have a machine + <span class="text-muted">Not applicable</span> + <span class="glyphicon glyphicon-question-sign get-help hover-help" + title="This project does not have a machine set. It simply collects information about the builds you start from the command line while Toaster is running" - style="visibility: hidden;"></i> + style="visibility: hidden;"></span> {% else %} - <a href="{% url 'project' data.id %}#machine-distro"> - {{data.get_current_machine_name}} - </a> + {{data.get_current_machine_name}} {% endif %} </span> ''' @@ -908,20 +941,16 @@ class ProjectsTable(ToasterTable): <a href="{% url 'projectbuilds' data.id %}"> {{data.get_number_of_builds}} </a> - {% else %} - <span class="muted">0</span> {% endif %} ''' last_build_outcome_template = ''' {% if data.get_number_of_builds > 0 %} - <a href="{% url 'builddashboard' data.get_last_build_id %}"> - {% if data.get_last_outcome == extra.Build.SUCCEEDED %} - <i class="icon-ok-sign success"></i> - {% elif data.get_last_outcome == extra.Build.FAILED %} - <i class="icon-minus-sign error"></i> - {% endif %} - </a> + {% if data.get_last_outcome == extra.Build.SUCCEEDED %} + <span class="glyphicon glyphicon-ok-circle"></span> + {% elif data.get_last_outcome == extra.Build.FAILED %} + <span class="glyphicon glyphicon-minus-sign"></span> + {% endif %} {% endif %} ''' @@ -935,7 +964,7 @@ class ProjectsTable(ToasterTable): errors_template = ''' {% if data.get_number_of_builds > 0 and data.get_last_errors > 0 %} - <a class="errors.count error" + <a class="errors.count text-danger" href="{% url "builddashboard" data.get_last_build_id %}#errors"> {{data.get_last_errors}} error{{data.get_last_errors | pluralize}} </a> @@ -944,7 +973,7 @@ class ProjectsTable(ToasterTable): warnings_template = ''' {% if data.get_number_of_builds > 0 and data.get_last_warnings > 0 %} - <a class="warnings.count warning" + <a class="warnings.count text-warning" href="{% url "builddashboard" data.get_last_build_id %}#warnings"> {{data.get_last_warnings}} warning{{data.get_last_warnings | pluralize}} </a> @@ -953,9 +982,7 @@ class ProjectsTable(ToasterTable): image_files_template = ''' {% if data.get_number_of_builds > 0 and data.get_last_outcome == extra.Build.SUCCEEDED %} - <a href="{% url "builddashboard" data.get_last_build_id %}#images"> - {{data.get_last_build_extensions}} - </a> + {{data.get_last_build_extensions}} {% endif %} ''' @@ -991,7 +1018,7 @@ class ProjectsTable(ToasterTable): static_data_name='machine', static_data_template=machine_template) - self.add_column(title='Number of builds', + self.add_column(title='Builds', help_text='The number of builds which have been run \ for the project', hideable=False, @@ -1121,19 +1148,17 @@ class BuildsTable(ToasterTable): def setup_columns(self, *args, **kwargs): outcome_template = ''' - <a href="{% url "builddashboard" data.id %}"> - {% if data.outcome == data.SUCCEEDED %} - <i class="icon-ok-sign success"></i> - {% elif data.outcome == data.FAILED %} - <i class="icon-minus-sign error"></i> - {% endif %} - </a> + {% if data.outcome == data.SUCCEEDED %} + <span class="glyphicon glyphicon-ok-circle"></span> + {% elif data.outcome == data.FAILED %} + <span class="glyphicon glyphicon-minus-sign"></span> + {% endif %} {% if data.cooker_log_path %} <a href="{% url "build_artifact" data.id "cookerlog" data.id %}"> - <i class="icon-download-alt get-help" - data-original-title="Download build log"></i> + <span class="glyphicon glyphicon-download-alt get-help" + data-original-title="Download build log"></span> </a> {% endif %} ''' @@ -1148,45 +1173,39 @@ class BuildsTable(ToasterTable): ''' machine_template = ''' - <a href="{% url "builddashboard" data.id %}"> - {{data.machine}} - </a> + {{data.machine}} ''' started_on_template = ''' - <a href="{% url "builddashboard" data.id %}"> - {{data.started_on | date:"d/m/y H:i"}} - </a> + {{data.started_on | date:"d/m/y H:i"}} ''' completed_on_template = ''' - <a href="{% url "builddashboard" data.id %}"> - {{data.completed_on | date:"d/m/y H:i"}} - </a> + {{data.completed_on | date:"d/m/y H:i"}} ''' failed_tasks_template = ''' {% if data.failed_tasks.count == 1 %} - <a href="{% url "task" data.id data.failed_tasks.0.id %}"> - <span class="error"> - {{data.failed_tasks.0.recipe.name}}.{{data.failed_tasks.0.task_name}} + <a class="text-danger" href="{% url "task" data.id data.failed_tasks.0.id %}"> + <span> + {{data.failed_tasks.0.recipe.name}} {{data.failed_tasks.0.task_name}} </span> </a> <a href="{% url "build_artifact" data.id "tasklogfile" data.failed_tasks.0.id %}"> - <i class="icon-download-alt" - data-original-title="Download task log file"> - </i> + <span class="glyphicon glyphicon-download-alt get-help" + title="Download task log file"> + </span> </a> {% elif data.failed_tasks.count > 1 %} <a href="{% url "tasks" data.id %}?filter=outcome%3A{{extra.Task.OUTCOME_FAILED}}"> - <span class="error">{{data.failed_tasks.count}} tasks</span> + <span class="text-danger">{{data.failed_tasks.count}} tasks</span> </a> {% endif %} ''' errors_template = ''' {% if data.errors_no %} - <a class="errors.count error" href="{% url "builddashboard" data.id %}#errors"> + <a class="errors.count text-danger" href="{% url "builddashboard" data.id %}#errors"> {{data.errors_no}} error{{data.errors_no|pluralize}} </a> {% endif %} @@ -1194,7 +1213,7 @@ class BuildsTable(ToasterTable): warnings_template = ''' {% if data.warnings_no %} - <a class="warnings.count warning" href="{% url "builddashboard" data.id %}#warnings"> + <a class="warnings.count text-warning" href="{% url "builddashboard" data.id %}#warnings"> {{data.warnings_no}} warning{{data.warnings_no|pluralize}} </a> {% endif %} @@ -1202,16 +1221,18 @@ class BuildsTable(ToasterTable): time_template = ''' {% load projecttags %} - <a href="{% url "buildtime" data.id %}"> + {% if data.outcome == extra.Build.SUCCEEDED %} + <a href="{% url "buildtime" data.id %}"> + {{data.timespent_seconds | sectohms}} + </a> + {% else %} {{data.timespent_seconds | sectohms}} - </a> + {% endif %} ''' image_files_template = ''' {% if data.outcome == extra.Build.SUCCEEDED %} - <a href="{% url "builddashboard" data.id %}#images"> {{data.get_image_file_extensions}} - </a> {% endif %} ''' @@ -1429,10 +1450,10 @@ class AllBuildsTable(BuildsTable): {{data.project.name}} </a> {% if data.project.is_default %} - <i class="icon-question-sign get-help hover-help" title="" + <span class="glyphicon glyphicon-question-sign get-help hover-help" title="" data-original-title="This project shows information about the builds you start from the command line while Toaster is - running" style="visibility: hidden;"></i> + running" style="visibility: hidden;"></span> {% endif %} ''' @@ -1482,7 +1503,6 @@ class ProjectBuildsTable(BuildsTable): """ self.project_id = kwargs['pid'] super(ProjectBuildsTable, self).setup_queryset(*args, **kwargs) - project = Project.objects.get(pk=self.project_id) self.queryset = self.queryset.filter(project=project) @@ -1496,8 +1516,23 @@ class ProjectBuildsTable(BuildsTable): self.project_id = kwargs['pid'] context = super(ProjectBuildsTable, self).get_context_data(**kwargs) + empty_state_template = ''' + This project has no builds. + <a href="{% url 'projectimagerecipes' data.pid %}"> + Choose a recipe to build</a> + ''' + context['empty_state'] = self.render_static_data(empty_state_template, + kwargs) + project = Project.objects.get(pk=self.project_id) context['mru'] = Build.get_recent(project) context['project'] = project + self.setup_queryset(**kwargs) + if self.queryset.count() == 0 and \ + project.build_set.filter(outcome=Build.IN_PROGRESS).count() > 0: + context['build_in_progress_none_completed'] = True + else: + context['build_in_progress_none_completed'] = False + return context diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/base.html b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/base.html index 210cf3360..496dd6eab 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/base.html +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/base.html @@ -8,9 +8,8 @@ {% block title %} Toaster {% endblock %} </title> <link rel="stylesheet" href="{% static 'css/bootstrap.min.css' %}" type="text/css"/> - <link rel="stylesheet" href="{% static 'css/bootstrap-responsive.min.css' %}" type='text/css'/> + <!--link rel="stylesheet" href="{% static 'css/bootstrap-theme.css' %}" type="text/css"/--> <link rel="stylesheet" href="{% static 'css/font-awesome.min.css' %}" type='text/css'/> - <link rel="stylesheet" href="{% static 'css/prettify.css' %}" type='text/css'/> <link rel="stylesheet" href="{% static 'css/default.css' %}" type='text/css'/> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> @@ -21,6 +20,10 @@ </script> <script src="{% static 'js/bootstrap.min.js' %}"> </script> + <script src="{% static 'js/typeahead.jquery.js' %}"> + </script> + <script src="{% static 'js/jsrender.min.js' %}"> + </script> <script src="{% static 'js/prettify.js' %}"> </script> <script src="{% static 'js/libtoaster.js' %}"> @@ -31,6 +34,8 @@ </script> {% endif %} <script> + $.views.settings.delimiters("<%", "%>"); + libtoaster.ctx = { jsUrl : "{% static 'js/' %}", htmlUrl : "{% static 'html/' %}", @@ -39,6 +44,7 @@ {% if project.id %} projectId : {{project.id}}, projectPageUrl : {% url 'project' project.id as purl %}{{purl|json}}, + xhrProjectUrl : {% url 'xhr_project' project.id as pxurl %}{{pxurl|json}}, projectName : {{project.name|json}}, recipesTypeAheadUrl: {% url 'xhr_recipestypeahead' project.id as paturl%}{{paturl|json}}, layersTypeAheadUrl: {% url 'xhr_layerstypeahead' project.id as paturl%}{{paturl|json}}, @@ -47,7 +53,9 @@ xhrCustomRecipeUrl : "{% url 'xhr_customrecipe' %}", projectId : {{project.id}}, xhrBuildRequestUrl: "{% url 'xhr_buildrequest' project.id %}", + mostRecentBuildsUrl: "{% url 'most_recent_builds' %}?project_id={{project.id}}", {% else %} + mostRecentBuildsUrl: "{% url 'most_recent_builds' %}", projectId : undefined, projectPageUrl : undefined, projectName : undefined, @@ -58,69 +66,72 @@ {% endblock %} </head> - <body style="height: 100%"> + <body> {% csrf_token %} - <div id="loading-notification" class="alert lead text-center" style="display:none"> + <div id="loading-notification" class="alert alert-warning lead text-center" style="display:none"> Loading <i class="fa-pulse icon-spinner"></i> </div> - <div id="change-notification" class="alert lead alert-info" style="display:none"> - <button type="button" class="close" id="hide-alert">×</button> + <div id="change-notification" class="alert alert-info alert-dismissible change-notification" style="display:none"> + <button type="button" class="close" id="hide-alert" data-toggle="alert">×</button> <span id="change-notification-msg"></span> </div> - <div class="navbar navbar-fixed-top"> - <div class="navbar-inner"> - <div class="container-fluid"> - <a class="brand logo" href="#"><img src="{% static 'img/logo.png' %}" class="" alt="Yocto logo project"/></a> - <span class="brand"> - <a href="/">Toaster</a> + <nav class="navbar navbar-default navbar-fixed-top"> + <div class="container-fluid"> + <div class="navbar-header"> + <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#global-nav" aria-expanded="false"> + <span class="sr-only">Toggle navigation</span> + <span class="icon-bar"></span> + <span class="icon-bar"></span> + <span class="icon-bar"></span> + </button> + <div class="toaster-navbar-brand"> + <a href="/"> + <img class="logo" src="{% static 'img/logo.png' %}" class="" alt="Yocto logo project"/> + </a> + <a class="brand" href="/">Toaster</a> {% if DEBUG %} - <i class="icon-info-sign" title="<strong>Toaster version information</strong>" data-content="<dl><dt>Git branch</dt><dd>{{TOASTER_BRANCH}}</dd><dt>Git revision</dt><dd>{{TOASTER_REVISION}}</dd></dl>"></i> + <span class="glyphicon glyphicon-info-sign" title="<strong>Toaster version information</strong>" data-content="<dl><dt>Git branch</dt><dd>{{TOASTER_BRANCH}}</dd><dt>Git revision</dt><dd>{{TOASTER_REVISION}}</dd></dl>"></i> {% endif %} - </span> - {% if request.resolver_match.url_name != 'landing' and request.resolver_match.url_name != 'newproject' %} - <ul class="nav"> - <li {% if request.resolver_match.url_name == 'all-builds' %} - class="active" - {% endif %}> + </div> + </div> + <div class="collapse navbar-collapse" id="global-nav"> + <ul class="nav navbar-nav"> + {% if request.resolver_match.url_name != 'landing' and request.resolver_match.url_name != 'newproject' %} + <li {% if request.resolver_match.url_name == 'all-builds' %} + class="active" + {% endif %}> <a href="{% url 'all-builds' %}"> - <i class="icon-tasks"></i> + <i class="glyphicon glyphicon-tasks"></i> All builds </a> - </li> - <li {% if request.resolver_match.url_name == 'all-projects' %} - class="active" - {% endif %}> + </li> + <li {% if request.resolver_match.url_name == 'all-projects' %} + class="active" + {% endif %}> <a href="{% url 'all-projects' %}"> <i class="icon-folder-open"></i> All projects </a> - </li> - </ul> - {% endif %} - <ul class="nav pull-right"> - <li> + </li> + {% endif %} + <li> <a target="_blank" href="http://www.yoctoproject.org/docs/latest/toaster-manual/toaster-manual.html"> - <i class="icon-book"></i> - Manual + <i class="glyphicon glyphicon-book"></i> + Documentation </a> - </li> - </ul> - <span class="pull-right divider-vertical"></span> - <div class="btn-group pull-right"> - <a class="btn" id="new-project-button" href="{% url 'newproject' %}">New project</a> + </li> + </ul> + <a class="btn btn-default navbar-btn navbar-right" id="new-project-button" href="{% url 'newproject' %}">New project</a> </div> - </div> </div> - </div> + </nav> - <div class="container-fluid top-padded"> - <div class="row-fluid"> - {% block pagecontent %} - {% endblock %} - </div> + <div class="container-fluid"> + {% block pagecontent %} + {% endblock %} </div> </body> </html> diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/basebuilddetailpage.html b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/basebuilddetailpage.html index a62e0b1cb..4d5066732 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/basebuilddetailpage.html +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/basebuilddetailpage.html @@ -3,30 +3,29 @@ {% load humanize %} {% block pagecontent %} -<div class="row-fluid"> -<!-- Breadcrumbs --> - <div class="section"> - <ul class="breadcrumb" id="breadcrumb"> - <li><a href="{% project_url build.project %}">{{build.project.name}}</a></li> - {% if not build.project.is_default %} - <li><a href="{% url 'projectbuilds' build.project.id %}">Builds</a></li> - {% endif %} - <li><a href="{%url 'builddashboard' build.pk%}">{{build.get_sorted_target_list.0.target}} {%if build.target_set.all.count > 1%}(+{{build.target_set.all.count|add:"-1"}}){%endif%} {{build.machine}} ({{build.completed_on|date:"d/m/y H:i"}})</a></li> - {% block localbreadcrumb %}{% endblock %} - </ul> - <script> - $( function () { - $('#breadcrumb > li').append('<span class="divider">→</span>'); - $('#breadcrumb > li:last').addClass("active"); - $('#breadcrumb > li:last > span').remove(); - }); - </script> - </div> <!--section--> - - <!-- Begin container --> - {% block pagedetailinfomain %}{% endblock %} - <!-- End container --> - +<div class="row"> + <!-- Breadcrumbs --> + <div class="col-md-12"> + <ul class="breadcrumb" id="breadcrumb"> + <li><a href="{% project_url build.project %}">{{build.project.name}}</a></li> + {% if not build.project.is_default %} + <li><a href="{% url 'projectbuilds' build.project.id %}">Builds</a></li> + {% endif %} + <li><a href="{%url 'builddashboard' build.pk%}">{{build.get_sorted_target_list.0.target}} {%if build.target_set.all.count > 1%}(+{{build.target_set.all.count|add:"-1"}}){%endif%} {{build.machine}} ({{build.completed_on|date:"d/m/y H:i"}})</a></li> + {% block localbreadcrumb %}{% endblock %} + </ul> + <script> +$( function () { + $('#breadcrumb > li').append('<span class="divider">→</span>'); + $('#breadcrumb > li:last').addClass("active"); + $('#breadcrumb > li:last > span').remove(); + }); + </script> + </div> </div> +<!-- Begin container --> +{% block pagedetailinfomain %}{% endblock %} +<!-- End container --> + {% endblock %} diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/basebuildpage.html b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/basebuildpage.html index 0d8c8820d..f5eee9651 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/basebuildpage.html +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/basebuildpage.html @@ -3,9 +3,95 @@ {% load project_url_tag %} {% load objects_to_dictionaries_filter %} {% load humanize %} +{% load field_values_filter %} {% block pagecontent %} + + <script> + var configVarUrl = "{% url 'configvars' build.id %}"; + + $(document).ready(function(){ + + $("#delete-build-confirm").click(function(){ + libtoaster.disableAjaxLoadingTimer(); + $(this).find('[data-role="submit-state"]').hide(); + $(this).find('[data-role="loading-state"]').show(); + $(this).attr("disabled", "disabled"); + + /* Make the modal non cancelable while delete is in progress */ + $('#delete-build-modal button[data-dismiss="modal"]').hide(); + + $.ajax({ + type: 'DELETE', + url: "{% url 'xhr_build' build.id %}", + headers: { 'X-CSRFToken' : $.cookie('csrftoken')}, + success: function (data) { + if (data.error !== "ok") { + console.warn(data.error); + } else { + libtoaster.setNotification("build-deleted", + $("#deleted-build-message").html()); + window.location.replace(data.gotoUrl); + } + }, + error: function (data) { + console.warn(data); + } + }); + }); + + + $('#breadcrumb > li').append('<span class="divider">→</span>'); + $('#breadcrumb > li:last').addClass("active"); + $('#breadcrumb > li:last > span').remove(); + + $("#build-menu li a").each(function(){ + /* Set the page active state in the Build menu */ + var currentUrl = window.location.href.split('?')[0]; + if (currentUrl === $(this).prop("href")){ + $(this).parent().addClass("active"); + } else { + /* Special case the configvar as this is part of configuration + * page but is a separate url + */ + if (window.location.pathname === configVarUrl){ + $("#menu-configuration").addClass("active"); + } else { + $(this).parent().removeClass("active"); + } + } + }); + }); + </script> + +<span style="display:none" id="deleted-build-message"> + You have deleted 1 build: <strong>{{build.get_sorted_target_list|field_values:"target"|join:", "}} {{build.machine}}</strong> completed on <strong>{{build.completed_on|date:"d/m/y H:i"}}</strong> +</span> + +<div class="modal fade" tabindex="-1" role="dialog" id="delete-build-modal" style="display: none;" data-backdrop="static" data-keyboard="false"> + <div class="modal-dialog"> + <div class="modal-content"> + <div class="modal-body"> + <p>Are you sure you want to delete the build <strong>{{build.get_sorted_target_list|field_values:"target"|join:", "}} {{build.machine}}</strong> completed on <strong>{{build.completed_on|date:"d/m/y H:i"}}</strong>?</p> + </div> + <div class="modal-footer"> + <button id="delete-build-confirm" class="btn btn-primary btn-large"> + <span data-role="submit-state">Delete build</span> + <span data-role="loading-state" style="display:none"> + <span class="fa-pulse"> + <i class="icon-spinner"></i> + </span> + Deleting build... + </span> + </button> + <button type="button" class="btn btn-link" data-dismiss="modal">Cancel</button> + </div> + </div><!-- /.modal-content --> + </div><!-- /.modal-dialog --> +</div> <!-- / modal --> + +<div class="row"> <!-- breadcrumbs --> - <div class="section"> + <div class="col-md-12"> <ul class="breadcrumb" id="breadcrumb"> <li><a href="{% project_url build.project %}">{{build.project.name}}</a></li> {% if not build.project.is_default %} @@ -20,73 +106,56 @@ </li> {% block localbreadcrumb %}{% endblock %} </ul> - <script> - $( function () { - $('#breadcrumb > li').append('<span class="divider">→</span>'); - $('#breadcrumb > li:last').addClass("active"); - $('#breadcrumb > li:last > span').remove(); - }); - </script> </div> +</div> - <div class="row-fluid"> - <!-- begin left sidebar container --> - <div id="nav" class="span2"> - <ul class="nav nav-list well"> - <li - {% if request.resolver_match.url_name == 'builddashboard' %} - class="active" - {% endif %} > - <a class="nav-parent" href="{% url 'builddashboard' build.pk %}">Build summary</a> - </li> - {% if build.target_set.all.0.is_image and build.outcome == 0 %} - <li class="nav-header">Images</li> - {% block nav-target %} - {% for t in build.get_sorted_target_list %} - <li><a href="{% url 'target' build.pk t.pk %}">{{t.target}}</a><li> - {% endfor %} - {% endblock %} - {% endif %} - <li class="nav-header">Build</li> - {% block nav-configuration %} - <li><a href="{% url 'configuration' build.pk %}">Configuration</a></li> - {% endblock %} - {% block nav-tasks %} - <li><a href="{% url 'tasks' build.pk %}">Tasks</a></li> - {% endblock %} - {% block nav-recipes %} - <li><a href="{% url 'recipes' build.pk %}">Recipes</a></li> - {% endblock %} - {% block nav-packages %} - <li><a href="{% url 'packages' build.pk %}">Packages</a></li> - {% endblock %} - <li class="nav-header">Performance</li> - {% block nav-buildtime %} - <li><a href="{% url 'buildtime' build.pk %}">Time</a></li> - {% endblock %} - {% block nav-cputime %} - <li><a href="{% url 'cputime' build.pk %}">CPU usage</a></li> - {% endblock %} - {% block nav-diskio %} - <li><a href="{% url 'diskio' build.pk %}">Disk I/O</a></li> - {% endblock %} - - <li class="divider"></li> + <!-- begin left sidebar container for builds which started properly --> + {% if build.started %} + <div class="row"> + <div id="nav" class="col-md-2"> + <ul class="nav nav-pills nav-stacked" id="build-menu"> + <li id="menu-dashboard" + {% if request.resolver_match.url_name == 'builddashboard' %} + class="active" + {% endif %} > + <a href="{% url 'builddashboard' build.pk %}">Build summary</a> + </li> + {% if build.has_images and build.outcome == build.SUCCEEDED %} + <li class="nav-header" data-menu-heading="images">Images</li> + {% block nav-target %} + {% for t in build.get_sorted_target_list %} + {% if t.has_images %} + <li id="menu-{{t.target}}"><a href="{% url 'target' build.pk t.pk %}">{{t.target}}</a><li> + {% endif %} + {% endfor %} + {% endblock %} + {% endif %} + <li class="nav-header">Build</li> + <li id="menu-configuration"><a href="{% url 'configuration' build.pk %}">Configuration</a></li> + <li><a href="{% url 'tasks' build.pk %}">Tasks</a></li> + <li><a href="{% url 'recipes' build.pk %}">Recipes</a></li> + <li><a href="{% url 'packages' build.pk %}">Packages</a></li> + <li class="nav-header">Performance</li> + <li><a href="{% url 'buildtime' build.pk %}">Time</a></li> + <li><a href="{% url 'cputime' build.pk %}">CPU usage</a></li> + <li><a href="{% url 'diskio' build.pk %}">Disk I/O</a></li> - <li> - <p class="navbar-btn"> - <a class="btn btn-block" href="{% url 'build_artifact' build.id 'cookerlog' build.id %}"> - Download build log + <li class="nav-header">Actions</li> + <li> + <a href="{% url 'build_artifact' build.id 'cookerlog' build.id %}"> + <span class="glyphicon glyphicon-download-alt"></span> + Download build log </a> - </p> - </li> + </li> - {% with build.get_custom_image_recipes as custom_image_recipes %} - {% if custom_image_recipes.count > 0 %} - <!-- edit custom image built during this build --> - <li> - <p class="navbar-btn" data-role="edit-custom-image-trigger"> - <button class="btn btn-block">Edit custom image</button> + {% with build.get_custom_image_recipes as custom_image_recipes %} + {% if custom_image_recipes.count > 0 %} + <!-- edit custom image built during this build --> + <li> + <a href="#" data-role="edit-custom-image-trigger"> + <span class="glyphicon glyphicon-edit"></span> + Edit custom image + </a> {% include 'editcustomimage_modal.html' %} <script> var editableCustomImageRecipes = {{ custom_image_recipes | objects_to_dictionaries:"id,name" | json }}; @@ -111,47 +180,55 @@ }); }); </script> - </p> - </li> - {% endif %} - {% endwith %} - - <li> - <!-- new custom image from image recipe in this build --> - <p class="navbar-btn" data-role="new-custom-image-trigger"> - <button class="btn btn-block">New custom image</button> - </p> - {% include 'newcustomimage_modal.html' %} - <script> - // imageRecipes includes both custom image recipes and built-in - // image recipes, any of which can be used as the basis for a - // new custom image - var imageRecipes = {{ build.get_image_recipes | objects_to_dictionaries:"id,name" | json }}; - - $(document).ready(function () { - var newCustomImageModal = $('#new-custom-image-modal'); - var newCustomImageTrigger = $('[data-role="new-custom-image-trigger"]'); - - // show create new custom image modal to select an image built - // during this build as the basis for the custom recipe - newCustomImageTrigger.click(function () { - if (!imageRecipes.length) { - return; - } - - newCustomImageModalSetRecipes(imageRecipes); - newCustomImageModal.modal('show'); - }); - }); - </script> - </li> - </ul> + </li> + {% endif %} + {% endwith %} - </div> - <!-- end left sidebar container --> + <!-- new custom image from image recipe in this build --> + {% if build.has_image_recipes %} + <li> + <a href="#" data-role="new-custom-image-trigger"> + <span class="glyphicon glyphicon-plus"></span> + New custom image + </a> + {% include 'newcustomimage_modal.html' %} + <script> + // imageRecipes includes both custom image recipes and built-in + // image recipes, any of which can be used as the basis for a + // new custom image + var imageRecipes = {{ build.get_image_recipes | objects_to_dictionaries:"id,name" | json }}; - <!-- begin right container --> - {% block buildinfomain %}{% endblock %} - <!-- end right container --> + $(document).ready(function () { + var newCustomImageModal = $('#new-custom-image-modal'); + var newCustomImageTrigger = $('[data-role="new-custom-image-trigger"]'); + + // show create new custom image modal to select an image built + // during this build as the basis for the custom recipe + newCustomImageTrigger.click(function () { + if (!imageRecipes.length) { + return; + } + + newCustomImageModalSetRecipes(imageRecipes); + newCustomImageModal.modal('show'); + }); + }); + </script> + {% endif %} + + <li> + <a href="#delete-build-modal" id="delete-build" data-toggle="modal" data-target="#delete-build-modal" class="text-danger"> + <span class="glyphicon glyphicon-trash"></span> + Delete build + </a> + </ul> + </div> + <!-- end left sidebar container --> + {% endif %} + + <!-- right container; need class="row" for builds without left-hand menu --> + <div{% if not build.started %} class="row"{% endif %}> + {% block buildinfomain %}{% endblock %} + </div> </div> {% endblock %} diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/baseprojectpage.html b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/baseprojectpage.html index 8778305f3..8427d2521 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/baseprojectpage.html +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/baseprojectpage.html @@ -7,24 +7,24 @@ {% block pagecontent %} -{% include "projecttopbar.html" %} -<script type="text/javascript"> - $(document).ready(function(){ - $("#config-nav .nav li a").each(function(){ - if (window.location.pathname === $(this).attr('href')) - $(this).parent().addClass('active'); - else - $(this).parent().removeClass('active'); - }); - - $("#topbar-configuration-tab").addClass("active") - }); -</script> - -<div class="row-fluid"> +<div class="row"> + {% include "projecttopbar.html" %} + <script type="text/javascript"> +$(document).ready(function(){ + $("#config-nav .nav li a").each(function(){ + if (window.location.pathname === $(this).attr('href')) + $(this).parent().addClass('active'); + else + $(this).parent().removeClass('active'); + }); + + $("#topbar-configuration-tab").addClass("active") + }); + </script> + <!-- only on config pages --> - <div id="config-nav" class="span2"> - <ul class="nav nav-list well"> + <div id="config-nav" class="col-md-2"> + <ul class="nav nav-pills nav-stacked"> <li><a class="nav-parent" href="{% url 'project' project.id %}">Configuration</a></li> <li class="nav-header">Compatible metadata</li> <li><a href="{% url 'projectcustomimages' project.id %}">Custom images</a></li> @@ -34,12 +34,18 @@ <li><a href="{% url 'projectlayers' project.id %}">Layers</a></li> <li class="nav-header">Extra configuration</li> <li><a href="{% url 'projectconf' project.id %}">BitBake variables</a></li> + + <li class="nav-header">Actions</li> + <li> + <a href="#delete-project-modal" role="button" class="text-danger" data-toggle="modal" data-target="#delete-project-modal"> + <i class="icon-trash text-danger"></i> Delete project</a> + </li> </ul> </div> - <div class="span10"> + <div class="col-md-10"> {% block projectinfomain %}{% endblock %} </div> -</div> +</div> {% endblock %} diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/basetable_bottom.html b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/basetable_bottom.html index ce023f51a..a2011fa90 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/basetable_bottom.html +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/basetable_bottom.html @@ -2,12 +2,10 @@ </table> <!-- Show pagination controls --> -<div class="pagination pagination-centered"> - <div class="pull-left"> - Showing {{objects.start_index}} to {{objects.end_index}} out of {{objects.paginator.count}} entries. - </div> +<div id="pagination-basetable_bottom"> + <!--span class="help-inline">Showing {{objects.start_index}} to {{objects.end_index}} out of {{objects.paginator.count}} entries.</span--> - <ul class="pagination" style="display: block-inline"> + <ul class="pagination"> {%if objects.has_previous %} <li><a href="javascript:reload_params({'page':{{objects.previous_page_number}}})">«</a></li> {%else%} @@ -22,16 +20,18 @@ <li class="disabled"><a href="#">»</a></li> {%endif%} </ul> - <div class="pull-right"> - <span class="help-inline" style="padding-top:5px;">Show rows:</span> - <select style="margin-top:5px;margin-bottom:0px;" class="pagesize"> - {% with "10 25 50 100 150" as list%} + <form class="navbar-form navbar-right"> + <div class="form-group"> + <label>Show rows:</label> + <select class="form-control pagesize"> + {% with "10 25 50 100 150" as list%} {% for i in list.split %} - <option value="{{i}}">{{i}}</option> + <option value="{{i}}">{{i}}</option> {% endfor %} - {% endwith %} - </select> - </div> + {% endwith %} + </select> + </div> + </form> </div> <!-- Update page display settings --> diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/basetable_top.html b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/basetable_top.html index 0ddd74904..5a9076d2a 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/basetable_top.html +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/basetable_top.html @@ -159,66 +159,83 @@ </script> <!-- control header --> -<div class="navbar"> - <div class="navbar-inner"> - <form class="navbar-search input-append pull-left span6" id="searchform"> - <input id="search" name="search" type="text" placeholder="Search {%if object_search_display %}{{object_search_display}}{%else%}{{objectname}}{%endif%}" value="{%if request.GET.search %}{{request.GET.search}}{% endif %}"/>{% if request.GET.search %}<a href="javascript:$('#search').val('');searchform.submit()" class="add-on btn" tabindex="-1"><i class="icon-remove"></i></a>{%endif%} - <input type="hidden" name="orderby" value="{{request.GET.orderby}}"> - <input type="hidden" name="page" value="1"> - <button class="btn" id="search-button" type="submit" value="Search">Search</button> - </form> - <div class="pull-right"> -{% if tablecols %} - <div class="btn-group"> - <button id="edit-columns-button" class="btn dropdown-toggle" data-toggle="dropdown">Edit columns - <span class="caret"></span> - </button> +<div class="navbar navbar-default"> + <div class="container-fluid"> + <div class="navbar-header"> + <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#table-chrome-collapse-variablehistory" aria-expanded="false"> + <span class="sr-only">Toggle table options</span> + <span class="icon-bar"></span> + <span class="icon-bar"></span> + <span class="icon-bar"></span> + </button> + </div> + <div class="collapse navbar-collapse" id="table-chrome-collapse-variablehistory"> + <form class="navbar-form navbar-left" id="searchform"> + <div class="form-group"> + <div class="btn-group"> + <input class="form-control" id="search" name="search" type="text" placeholder="Search {%if object_search_display %}{{object_search_display}}{%else%}{{objectname}}{%endif%}" value="{%if request.GET.search %}{{request.GET.search}}{% endif %}"/> + {% if request.GET.search %}<a href="javascript:$('#search').val('');searchform.submit()" tabindex="-1"><span class="remove-search-btn-variables glyphicon glyphicon-remove-circle"></span></a>{%endif%} + </div> + </div> + <input type="hidden" name="orderby" value="{{request.GET.orderby}}"> + <input type="hidden" name="page" value="1"> + <button class="btn btn-default" id="search-button" type="submit" value="Search">Search</button> + </form> + <form class="navbar-form navbar-right"> + <div class="form-group"> + <label>Show rows:</label> + <select class="pagesize form-control"> + {% with "10 25 50 100 150" as list%} + {% for i in list.split %} + <option value="{{i}}">{{i}}</option> + {% endfor %} + {% endwith %} + </select> + </div> + </form> + + <div class="btn-group navbar-right"> + {% if tablecols %} + <button id="edit-columns-button" class="btn btn-default navbar-btn dropdown-toggle" data-toggle="dropdown">Edit columns + <span class="caret"></span> + </button> <!-- {{tablecols|sortcols}} --> - <ul id='editcol' class="dropdown-menu"> - {% for i in tablecols|sortcols %} - <li> - <label {% if not i.clclass %} class="checkbox muted" {%else%} class="checkbox" {%endif%}> - <input type="checkbox" class="chbxtoggle" - {% if i.clclass %} + <ul id="editcol" class="dropdown-menu editcol"> + {% for i in tablecols|sortcols %} + <li> + <div class="checkbox"> + <label {% if not i.clclass %} class="muted" {%endif%}> + <input type="checkbox" class="chbxtoggle" + {% if i.clclass %} id="{{i.clclass}}" value="ct{{i.name}}" {% if not i.hidden %} - checked="checked" + checked="checked" {%endif%} onclick="showhideTableColumn( - $(this).attr('id'), - $(this).is(':checked'), - {% if i.ordericon %} - '{{i.orderkey}}' - {% else %} - undefined - {% endif %} + $(this).attr('id'), + $(this).is(':checked'), + {% if i.ordericon %} + '{{i.orderkey}}' + {% else %} + undefined + {% endif %} )" - {%else%} + {%else%} checked disabled - {% endif %}/> {{i.name}} - </label> - </li> - {% endfor %} - </ul> + {% endif %}/>{{i.name}} + </label> </div> -{% endif %} - <div style="display:inline"> - <span class="divider-vertical"></span> - <span class="help-inline" style="padding-top:5px;">Show rows:</span> - <select style="margin-top:5px;margin-bottom:0px;" class="pagesize"> - {% with "10 25 50 100 150" as list%} - {% for i in list.split %} - <option value="{{i}}">{{i}}</option> - {% endfor %} - {% endwith %} - </select> - </div> - </div> - </div> <!-- navbar-inner --> -</div> + </li> + {% endfor %} + </ul> + {% endif %} + </div> + </div> <!-- navbar-collapse --> + </div> <!-- container-fluid --> +</div> <!-- navbar-default --> <!-- the actual rows of the table --> <table class="table table-bordered table-hover tablesorter" id="otable"> @@ -226,11 +243,11 @@ <!-- Table header row; generated from "tablecols" entry in the context dict --> <tr> {% for tc in tablecols %}<th class="{%if tc.dclass%}{{tc.dclass}}{%endif%} {% if tc.clclass %}{{tc.clclass}}{% endif %}"> - {%if tc.qhelp%}<i class="icon-question-sign get-help" title="{{tc.qhelp}}"></i>{%endif%} - {%if tc.orderfield%}<a {%if tc.ordericon%} class="sorted" {%endif%}href="javascript:reload_params({'page': 1, 'orderby' : '{{tc.orderfield}}' })">{{tc.name}}</a>{%else%}<span class="muted">{{tc.name}}</span>{%endif%} + {%if tc.qhelp%}<span class="glyphicon glyphicon-question-sign get-help" title="{{tc.qhelp}}"></span>{%endif%} + {%if tc.orderfield%}<a {%if tc.ordericon%} class="sorted" {%endif%}href="javascript:reload_params({'page': 1, 'orderby' : '{{tc.orderfield}}' })">{{tc.name}}</a>{%else%}<span class="text-muted">{{tc.name}}</span>{%endif%} {%if tc.ordericon%} <i class="icon-caret-{{tc.ordericon}}"></i>{%endif%} {%if tc.filter%}<div class="btn-group pull-right"> - <a href="#filter_{{tc.filter.class}}" role="button" class="btn btn-mini {%if request.GET.filter%}{{tc.filter.options|filtered_icon:request.GET.filter}} {%endif%}" {%if request.GET.filter and tc.filter.options|filtered_tooltip:request.GET.filter %} title="<p>{{tc.filter.options|filtered_tooltip:request.GET.filter}}</p><p><a class='btn btn-small btn-primary' href=javascript:reload_params({'filter':''})>Show all {% if filter_search_display %}{{filter_search_display}}{% else %}{{objectname}}{% endif %}</a></p>" {%endif%} data-toggle="modal"> <i class="icon-filter filtered"></i> </a> + <a href="#filter_{{tc.filter.class}}" role="button" class="btn btn-xs {%if request.GET.filter%}{{tc.filter.options|filtered_icon:request.GET.filter}} {%endif%}" {%if request.GET.filter and tc.filter.options|filtered_tooltip:request.GET.filter %} title="<p>{{tc.filter.options|filtered_tooltip:request.GET.filter}}</p><p><a class='btn btn-sm btn-primary' href=javascript:reload_params({'filter':''})>Show all {% if filter_search_display %}{{filter_search_display}}{% else %}{{objectname}}{% endif %}</a></p>" {%endif%} data-toggle="modal"> <span class="glyphicon glyphicon-filter filtered"></span> </a> </div>{%endif%} </th>{% endfor %} </tr> diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/bpackage.html b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/bpackage.html deleted file mode 100644 index 81973cbc6..000000000 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/bpackage.html +++ /dev/null @@ -1,108 +0,0 @@ -{% extends "basebuildpage.html" %} - -{% load projecttags %} - -{% block title %} Packages built - {{build.target_set.all|dictsort:"target"|join:", "}} {{build.machine}} - {{build.project.name}} - Toaster {% endblock %} -{% block localbreadcrumb %} -<li>Packages</li> -{% endblock %} - -{% block nav-packages %} - <li class="active"><a href="{% url 'packages' build.pk %}">Packages</a></li> -{% endblock %} - -{% block buildinfomain %} -<div class="span10"> - -{% if not request.GET.filter and not request.GET.search and not objects.paginator.count %} - -<!-- Empty - no data in database --> -<div class="page-header"> - <h1> - Packages - </h1> -</div> -<div class="alert alert-info lead"> - <strong>No packages were built.</strong> How did this happen? Well, BitBake reuses as much stuff as possible. - If all of the packages needed were already built and available in your build infrastructure, BitBake - will not rebuild any of them. This might be slightly confusing, but it does make everything faster. -</div> - -{% else %} - -<div class="page-header"> - <h1> - {% if request.GET.search and objects.paginator.count > 0 %} - {{objects.paginator.count}} package{{objects.paginator.count|pluralize}} found - {%elif request.GET.search and objects.paginator.count == 0%} - No packages found - {%else%} - Packages - {%endif%} - </h1> -</div> - - {% if objects.paginator.count == 0 %} - <div class="row-fluid"> - <div class="alert"> - <form class="no-results input-append" id="searchform"> - <input id="search" name="search" class="input-xxlarge" type="text" value="{{request.GET.search}}"/>{% if request.GET.search %}<a href="javascript:$('#search').val('');searchform.submit()" class="add-on btn" tabindex="-1"><i class="icon-remove"></i></a>{% endif %} - <button class="btn" type="submit" value="Search">Search</button> - <button class="btn btn-link" onclick="javascript:$('#search').val('');searchform.submit()">Show all packages</button> - </form> - </div> - </div> - - {% else %} - {% include "basetable_top.html" %} - - {% for package in objects %} - - <tr class="data"> - - <!-- Package --> - <td class="package_name"><a href="{% url "package_built_detail" build.pk package.pk %}">{{package.name}}</a></td> - <!-- Package Version --> - <td class="package_version">{%if package.version%}<a href="{% url "package_built_detail" build.pk package.pk %}">{{package.version}}-{{package.revision}}</a>{%endif%}</td> - <!-- Package Size --> - <td class="size sizecol">{{package.size|filtered_filesizeformat}}</td> - <!-- License --> - <td class="license">{{package.license}}</td> - - {%if package.recipe%} - <!-- Recipe --> - <td class="recipe__name"><a href="{% url "recipe" build.pk package.recipe.pk %}">{{package.recipe.name}}</a></td> - <!-- Recipe Version --> - <td class="recipe__version"><a href="{% url "recipe" build.pk package.recipe.pk %}">{{package.recipe.version}}</a></td> - - <!-- Layer --> - <td class="recipe__layer_version__layer__name">{{package.recipe.layer_version.layer.name}}</td> - <!-- Layer branch --> - <td class="recipe__layer_version__branch">{{package.recipe.layer_version.branch}}</td> - <!-- Layer commit --> - <td class="recipe__layer_version__layer__commit"> - <a class="btn" - data-content="<ul class='unstyled'> - <li>{{package.recipe.layer_version.commit}}</li> - </ul>"> - {{package.recipe.layer_version.commit|truncatechars:13}} - </a> - </td> - <!-- Layer directory --> - {%else%} - <td class="recipe__name"></td> - <td class="recipe__version"></td> - <td class="recipe__layer_version__layer__name"></td> - <td class="recipe__layer_version__branch"></td> - <td class="recipe__layer_version__layer__commit"></td> - <td class="recipe__layer_version__local_path"></td> - {%endif%} - - </tr> - {% endfor %} - - {% include "basetable_bottom.html" %} - {% endif %} {# objects.paginator.count #} -{% endif %} {# Empty #} -</div> -{% endblock %} diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/builddashboard.html b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/builddashboard.html index a0da71ea4..02a29816a 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/builddashboard.html +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/builddashboard.html @@ -1,8 +1,9 @@ {% extends "basebuildpage.html" %} {% load humanize %} {% load projecttags %} +{% load field_values_filter %} -{% block title %} {{build.target_set.all|dictsort:"target"|join:", "}} {{build.machine}} - {{build.project.name}} - Toaster {% endblock %} +{% block title %} {{build.get_sorted_target_list|field_values:"target"|join:", "}} {{build.machine}} - {{build.project.name}} - Toaster {% endblock %} {% block parentbreadcrumb %} {% if build.get_sorted_target_list.count > 0 %} {{build.get_sorted_target_list.0.target}} @@ -13,61 +14,57 @@ {% block buildinfomain %} <!-- page title --> -<div class="row-fluid span10"> - <div class="page-header"> - <h1>{{build.target_set.all|dictsort:"target"|join:", "}} {{build.machine}}</h1> +<div class="{% if build.started %}col-md-10{% else %}col-md-12{% endif %}"> + <div class="page-header build-data"> + <h1>{{build.get_sorted_target_list|field_values:"target"|join:", "}} {{build.machine}}</h1> </div> -</div> <!-- build result bar --> -<div class="row-fluid span10 pull-right"> - <div class="alert {%if build.outcome == build.SUCCEEDED%}alert-success{%elif build.outcome == build.FAILED%}alert-error{%else%}alert-info{%endif%}"> - <div class="row-fluid lead"> - <span class="pull-left"><strong> - {%if build.outcome == build.SUCCEEDED%}Completed{%elif build.outcome == build.FAILED%}Failed{%else%}{%endif%} - </strong> on - {{build.completed_on|date:"d/m/y H:i"}} -</span> -{% if build.warnings.count or build.errors.count %} - with -{% endif %} -{%if build.outcome == build.SUCCEEDED or build.outcome == build.FAILED %} -{% if build.errors.count %} - <span > <i class="icon-minus-sign red"></i><strong><a href="#errors" class="error show-errors"> {{build.errors.count}} error{{build.errors.count|pluralize}}</a></strong></span> -{% endif %} -{% if build.warnings.count %} -{% if build.errors.count %} - and -{% endif %} - <span > <i class="icon-warning-sign yellow"></i><strong><a href="#warnings" class="warning show-warnings"> {{build.warnings.count}} warning{{build.warnings.count|pluralize}}</a></strong></span> -{% endif %} - <span class="pull-right">Build time: <a href="{% url 'buildtime' build.pk %}">{{ build.timespent_seconds|sectohms }}</a> - {% if build.cooker_log_path %} - <a class="btn {%if build.outcome == build.SUCCEEDED%}btn-success{%else%}btn-danger{%endif%} pull-right log" href="{% url 'build_artifact' build.id "cookerlog" build.id %}">Download build log</a> - {% endif %} - </span> + <div class="alert {%if build.outcome == build.SUCCEEDED%}alert-success{%elif build.outcome == build.FAILED%}alert-danger{%else%}alert-info{%endif%}"> + <span><strong>{%if build.outcome == build.SUCCEEDED%}Completed{%elif build.outcome == build.FAILED%}Failed{%else%}{%endif%}</strong> on {{build.completed_on|date:"d/m/y H:i"}}</span> + {% if build.warnings.count or build.errors.count %} + <span>with</span> + {% endif %} + {%if build.outcome == build.SUCCEEDED or build.outcome == build.FAILED %} + {% if build.errors.count %} + <a href="#errors" class="alert-link show-errors"> {{build.errors.count}} error{{build.errors.count|pluralize}}</a> + {% endif %} + {% if build.warnings.count %} + {% if build.errors.count %}and{% endif %} + <a href="#warnings" class="show-warnings"> {{build.warnings.count}} warning{{build.warnings.count|pluralize}}</a> + {% endif %} + {% if build.cooker_log_path %} + <a class="pull-right log" href="{% url 'build_artifact' build.id "cookerlog" build.id %}">Download build log</a> + {% endif %} + <span class="pull-right"> + Build time: + <span data-build-field="buildtime"> + {% if build.outcome == build.SUCCEEDED %} + <a href="{% url 'buildtime' build.pk %}">{{ build.timespent_seconds|sectohms }}</a> + {% else %} + {{ build.timespent_seconds|sectohms }} + {% endif %} + </span> + </span> -{%endif%} - </div> - </div> + {%endif%} </div> {% if build.errors.count %} -<div class="accordion span10 pull-right" id="errors"> - <div class="accordion-group"> - <div class="accordion-heading"> - <a class="accordion-toggle error toggle-errors"> - <h2 id="error-toggle"> - <i class="icon-minus-sign"></i> + <div class="panel panel-default" id="errors"> + <div class="panel-heading"> + <h2 class="panel-title"> + <span class="glyphicon glyphicon-minus-sign"></span> + <a data-toggle="collapse" href="#error-info" id="error-toggle"> {{build.errors.count}} error{{build.errors.count|pluralize}} - </h2> - </a> + </a> + </h2> </div> - <div class="accordion-body collapse in" id="collapse-errors"> - <div class="accordion-inner"> - <div class="span10"> + <div class="panel-collapse collapse in" id="error-info"> + <div class="panel-body"> + <div class="{% if build.started %}col-md-10{% else %}col-md-12{% endif %}"> {% for error in build.errors %} - <div class="alert alert-error" data-error="{{ error.id }}"> + <div class="alert alert-danger" data-log-message-id="{{error.pk}}"> <pre>{{error.message}}</pre> </div> {% endfor %} @@ -75,57 +72,62 @@ </div> </div> </div> -</div> {% endif %} {%if build.outcome == build.SUCCEEDED%} <!-- built images --> -{% if hasImages %} -<div class="row-fluid span10 pull-right"> - <h2>Images</h2> + {% if hasArtifacts %} + <h2 data-heading="build-artifacts">Build artifacts</h2> {% for target in targets %} {% if target.target.is_image %} - <div class="well dashboard-section"> - <h3><a href="{% url 'target' build.pk target.target.pk %}">{{target.target}}</a> - </h3> - <dl class="dl-horizontal"> - <dt>Packages included</dt> - <dd><a href="{% url 'target' build.pk target.target.pk %}">{{target.npkg}}</a></dd> - <dt>Total package size</dt> - <dd>{{target.pkgsz|filtered_filesizeformat}}</dd> - {% if target.targetHasNoImages %} - </dl> - <div class="row-fluid"> - <div class="alert alert-info span7"> - <p> - <b>This build did not create any image files</b> - </p> - <p> - This is probably because valid image and license manifest - files from a previous build already exist in your - <code>.../poky/build/tmp/deploy</code> - directory. You can - also <a href="{% url 'targetpkg' build.pk target.target.pk %}">view the - license manifest information</a> in Toaster. - </p> - </div> - </div> + <div class="well well-transparent dashboard-section" data-artifacts-for-target="{{target.target.pk}}"> + {% if target.npkg > 0 %} + <h3> + <a href="{% url 'target' build.pk target.target.pk %}" data-link="target-packages"> + {{target.target.target}} + </a> + </h3> + <dl class="dl-horizontal"> + <dt>Packages included</dt> + <dd> + <a href="{% url 'target' build.pk target.target.pk %}"> + <span data-value="target-package-count">{{target.npkg}}</span> + </a> + </dd> + <dt>Total package size</dt> + <dd> + <span data-value="target-package-size">{{target.pkgsz|filtered_filesizeformat}}</span> + </dd> + </dl> {% else %} - <dt> - <i class="icon-question-sign get-help" title="The location in disk of the license manifest, a document listing all packages installed in your image and their licenses"></i> + <h3>{{target.target.target}}</h3> + {% endif %} - License manifest + {% if target.targetHasImages %} + <dl class="dl-horizontal"> + <dt> + Manifests </dt> + <dd> - <a href="{% url 'targetpkg' build.pk target.target.pk %}">View in Toaster</a> | - <a href="{% url 'build_artifact' build.pk 'licensemanifest' target.target.pk %}">Download</a></dd> + <a data-link="license-manifest" href="{% url 'build_artifact' build.pk 'licensemanifest' target.target.pk %}">License manifest</a> + </dd> + + {% if target.target.package_manifest_path %} + <dd> + <a data-link="package-manifest" href="{% url 'build_artifact' build.pk 'packagemanifest' target.target.pk %}">Package manifest</a> + </dd> + {% endif %} + </dl> + + <dl class="dl-horizontal"> <dt> - <i class="icon-question-sign get-help" title="Image files are stored in <code>/build/tmp/deploy/images/</code>"></i> + <span class="glyphicon glyphicon-question-sign get-help" title="Image files are stored in <code>build/tmp/deploy/images/</code>"></span> Image files </dt> <dd> - <ul> - {% for i in target.imageFiles %} + <ul class="list-unstyled" data-links="image-artifacts"> + {% for i in target.imageFiles|dictsort:"suffix" %} <li> <a href="{% url 'build_artifact' build.pk 'imagefile' i.id %}"> {{i.suffix}} @@ -135,13 +137,42 @@ {% endfor %} </ul> </dd> - </dl> + <dt> + Kernel artifacts + </dt> + <dd> + <ul class="list-unstyled" data-links="kernel-artifacts"> + {% for artifact in target.target_kernel_artifacts|dictsort:"basename" %} + <li> + <a href="{% url 'build_artifact' build.id 'targetkernelartifact' artifact.id %}">{{artifact.basename}}</a> + ({{artifact.file_size|filtered_filesizeformat}}) + </li> + {% endfor %} + </ul> + </dd> + </dl> + {% endif %} + {% if target.target_sdk_artifacts_count > 0 %} + <dl class="dl-horizontal"> + <dt> + SDK artifacts + </dt> + <dd> + <ul class="list-unstyled" data-links="sdk-artifacts"> + {% for artifact in target.target_sdk_artifacts|dictsort:"basename" %} + <li> + <a href="{% url 'build_artifact' build.id 'targetsdkartifact' artifact.id %}">{{artifact.basename}}</a> + ({{artifact.file_size|filtered_filesizeformat}}) + </li> + {% endfor %} + </ul> + </dd> + </dl> {% endif %} </div> {% endif %} {% endfor %} -</div> -{% endif %} + {% endif %} {%else%} <!-- error dump --> @@ -149,13 +180,12 @@ <!-- other artifacts --> {% if build.buildartifact_set.all.count > 0 %} -<div class="row-fluid span10 pull-right"> <h2>Other artifacts</h2> - <div class="well dashboard-section"> + <div class="well well-transparent dashboard-section"> <dl class="dl-horizontal"> <dt> - <i class="icon-question-sign get-help" title="Build artifacts discovered in <i>tmp/deploy/images</i>. Usually kernel images and kernel modules."></i> + <span class="glyphicon glyphicon-question-sign get-help" title="Build artifacts discovered in <i>tmp/deploy/images</i>. Usually kernel images and kernel modules."></span> Other artifacts</dt> <dd><div> {% for ba in build.buildartifact_set.all|dictsort:"file_name" %} @@ -170,92 +200,97 @@ </dl> </div> -</div> {% endif %} -<!-- build summary --> -<div class="row-fluid span10 pull-right"> -<h2>Build summary</h2> - <div class="well span4 dashboard-section" style="margin-left:0px;"> - <h4><a href="{%url 'configuration' build.pk%}">Configuration</a></h4> - <dl> - <dt>Machine</dt><dd>{{build.machine}}</dd> - <dt>Distro</dt><dd>{{build.distro}}</dd> - <dt>Layers</dt>{% for i in build.layer_version_build.all|dictsort:"layer.name" %}<dd>{{i.layer.name}}</dd>{%endfor%} - </dl> - </div> - <div class="well span4 dashboard-section"> - <h4><a href="{%url 'tasks' build.pk%}">Tasks</a></h4> - <dl> - {% query build.task_build outcome=4 order__gt=0 as exectask%} - {% if exectask.count > 0 %} - <dt>Failed tasks</dt> - <dd> - {% if exectask.count == 1 %} - <a class="error" href="{% url "task" build.id exectask.0.id %}"> - {{exectask.0.recipe.name}} - <span class="task-name">{{exectask.0.task_name}}</span> - </a> - <a href="{% url 'build_artifact' build.id "tasklogfile" exectask.0.id %}"> - <i class="icon-download-alt" title="" data-original-title="Download task log file"></i> - </a> +{% if build.started %} + <!-- build summary --> + <h2 data-role="build-summary-heading">Build summary</h2> + <div class="row"> + <div class="col-md-4 dashboard-section"> + <div class="well well-transparent"> + <h3><a href="{%url 'configuration' build.pk%}">Configuration</a></h3> + <dl> + <dt>Machine</dt><dd>{{build.machine}}</dd> + <dt>Distro</dt><dd>{{build.distro}}</dd> + <dt>Layers</dt><dd><ul class="list-unstyled">{% for i in build.layer_version_build.all|dictsort:"layer.name" %}<li>{{i.layer.name}}</li>{%endfor%}</ul></dd> + </dl> + </div> + </div> + <div class="col-md-4 dashboard-section"> + <div class="well well-transparent"> + <h3><a href="{%url 'tasks' build.pk%}">Tasks</a></h3> + <dl> + {% query build.task_build outcome=4 order__gt=0 as exectask%} + {% if exectask.count > 0 %} + <dt>Failed tasks</dt> + <dd> + {% if exectask.count == 1 %} + <a class="text-danger" href="{% url "task" build.id exectask.0.id %}"> + {{exectask.0.recipe.name}} + <span class="task-name">{{exectask.0.task_name}}</span> + </a> - {% elif exectask.count > 1%} - <a class="error" href="{% url "tasks" build.id %}?filter=outcome%3A4">{{exectask.count}}</a> - {% endif %} - </dd> - {% endif %} - <dt>Total number of tasks</dt><dd><a href="{% url 'tasks' build.pk %}">{% query build.task_build order__gt=0 as alltasks %}{{alltasks.count}}</a></dd> - <dt> - Tasks executed - <i class="icon-question-sign get-help" title="'Executed' tasks are those that need to be run in order to generate the task output"></i> - </dt> - <dd><a href="{% url 'tasks' build.pk %}?filter=task_executed%3A1&count=25&search=&page=1&orderby=order%3A%2B">{% query build.task_build task_executed=1 order__gt=0 as exectask%}{{exectask.count}}</a></dd> - <dt> - Tasks not executed - <i class="icon-question-sign get-help" title="'Not executed' tasks don't need to run because their outcome is provided by another task"></i> - </dt> - <dd><a href="{% url 'tasks' build.pk %}?filter=task_executed%3A0&count=25&search=&page=1&orderby=order%3A%2B">{% query build.task_build task_executed=0 order__gt=0 as noexectask%}{{noexectask.count}}</a></dd> - <dt> - Reuse - <i class="icon-question-sign get-help" title="The percentage of 'not executed' tasks over the total number of tasks, which is a measure of the efficiency of your build"></i> - </dt> - <dd> -{% query build.task_build order__gt=0 as texec %} -{% if noexectask.count|multiply:100|divide:texec.count < 0 %} -0 -{% else %} -{{noexectask.count|multiply:100|divide:texec.count}} -{% endif %} -% - </dd> - </dl> - </div> - <div class="well span4 dashboard-section"> - <h4><a href="{% url 'recipes' build.pk %}">Recipes</a> & <a href="{% url 'packages' build.pk %}">Packages</a></h4> - <dl> - <dt>Recipes built</dt><dd><a href="{% url 'recipes' build.pk %}">{{recipecount}}</a></dd> - <dt>Packages built</dt><dd><a href="{% url 'packages' build.pk %}">{{packagecount}}</a></dd> - </dl> + <a href="{% url 'build_artifact' build.id "tasklogfile" exectask.0.id %}"> + <span class="glyphicon glyphicon-download-alt get-help" title="Download task log file"></i> + </a> + + {% elif exectask.count > 1%} + <a class="text-danger" href="{% url "tasks" build.id %}?limit=25&page=1&orderby=order&filter=task_outcome:failed&default_orderby=order&filter_value=on&">{{exectask.count}}</a> + {% endif %} + </dd> + {% endif %} + <dt>Total number of tasks</dt><dd><a href="{% url 'tasks' build.pk %}">{% query build.task_build order__gt=0 as alltasks %}{{alltasks.count}}</a></dd> + <dt> + Tasks executed + <span class="glyphicon glyphicon-question-sign get-help" title="'Executed' tasks are those that need to be run in order to generate the task output"></span> + </dt> + <dd><a href="{% url 'tasks' build.pk %}?limit=25&page=1&orderby=order&filter=execution_outcome:executed&default_orderby=order&filter_value=on&">{% query build.task_build task_executed=1 order__gt=0 as exectask%}{{exectask.count}}</a></dd> + <dt> + Tasks not executed + <span class="glyphicon glyphicon-question-sign get-help" title="'Not executed' tasks don't need to run because their outcome is provided by another task"></span> + </dt> + <dd><a href="{% url 'tasks' build.pk %}?limit=25&page=1&orderby=order&filter=execution_outcome:not_executed&default_orderby=order&filter_value=on&">{% query build.task_build task_executed=0 order__gt=0 as noexectask%}{{noexectask.count}}</a></dd> + <dt> + Reuse + <span class="glyphicon glyphicon-question-sign get-help" title="The percentage of 'not executed' tasks over the total number of tasks, which is a measure of the efficiency of your build"></span> + </dt> + <dd> + {% query build.task_build order__gt=0 as texec %} + {% if noexectask.count|multiply:100|divide:texec.count < 0 %} + 0 + {% else %} + {{noexectask.count|multiply:100|divide:texec.count}} + {% endif %} + % + </dd> + </dl> + </div> + </div> + <div class="col-md-4 dashboard-section"> + <div class="well well-transparent"> + <h3><a href="{% url 'recipes' build.pk %}">Recipes</a> & <a href="{% url 'packages' build.pk %}">Packages</a></h3> + <dl> + <dt>Recipes built</dt><dd><a href="{% url 'recipes' build.pk %}">{{recipecount}}</a></dd> + <dt>Packages built</dt><dd><a href="{% url 'packages' build.pk %}">{{packagecount}}</a></dd> + </dl> + </div> </div> -</div> + </div> +{% endif %} <!-- end build summary --> {% if build.warnings.count %} -<div class="accordion span10 pull-right" id="warnings"> - <div class="accordion-group"> - <div class="accordion-heading"> - <a class="accordion-toggle warning toggle-warnings"> - <h2 id="warning-toggle"> - <i class="icon-warning-sign"></i> - {{build.warnings.count}} warning{{build.warnings.count|pluralize}} - </h2> - </a> + <div class="panel panel-default" id="warnings"> + <div class="panel-heading"> + <h2 class="panel-title"> + <span class="glyphicon glyphicon-warning-sign"></span> + <a id="warning-toggle" href="#warning-info" data-toggle="collapse">{{build.warnings.count}} warning{{build.warnings.count|pluralize}}</a> + </h2> </div> - <div class="accordion-body collapse" id="collapse-warnings"> - <div class="accordion-inner"> - <div class="span10"> + <div class="panel-collapse collapse" id="warning-info"> + <div class="panel-body"> + <div class="{% if build.started %}col-md-10{% else %}col-md-12{% endif %}"> {% for warning in logmessages %}{% if warning.level == 1 %} - <div class="alert alert-warning"> + <div class="alert alert-warning" data-log-message-id="{{warning.pk}}"> <pre>{{warning.message}}</pre> </div> {% endif %}{% endfor %} @@ -263,15 +298,21 @@ </div> </div> </div> -</div> {% endif %} +</div> <!-- end 10 column row --> + <script type="text/javascript"> $(document).ready(function() { //show warnings section when requested from the previous page if (location.href.search('#warnings') > -1) { - $('#collapse-warnings').addClass('in'); + $('#warning-info').addClass('in'); } + + //show warnings section when requested from the build outcome + $(".show-warnings").click(function() { + $('#warning-info').addClass('in'); + }); }); </script> diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/buildinfo-toastertable.html b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/buildinfo-toastertable.html new file mode 100644 index 000000000..eb4c65690 --- /dev/null +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/buildinfo-toastertable.html @@ -0,0 +1,25 @@ +{% extends "basebuildpage.html" %} + +{% load projecttags %} + +{% block title %} {{title}} - {{build.target_set.all|dictsort:"target"|join:", "}} {{build.machine}} - {{build.project.name}} - Toaster {% endblock %} +{% block localbreadcrumb %} +<li>{{title}}</li> +{% endblock %} + +{% block nav-packages %} +{% endblock %} + +{% block buildinfomain %} +<div class="col-md-10"> +{# xhr_table_url is just the current url so leave it blank #} +{% with xhr_table_url='' %} + <div class="page-header build-data"> + <h1> + {{title}} (<span class="table-count-{{table_name}}">0</span>) </h2> + </h1> + </div> + {% include "toastertable.html" %} +{% endwith %} +</div> +{% endblock %} diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/buildrequestdetails.html b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/buildrequestdetails.html deleted file mode 100644 index 70fa1fb9a..000000000 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/buildrequestdetails.html +++ /dev/null @@ -1,64 +0,0 @@ -{% extends "baseprojectpage.html" %} - -{% load static %} -{% load projecttags %} -{% load humanize %} - - -{% block projectinfomain %} - <!-- begin content --> - - <div class="row-fluid"> - - <!-- end left sidebar container --> - <!-- Begin right container --> - <div class="span10"> - <div class="page-header"> - <h1> - <span data-toggle="tooltip" {%if buildrequest.brtarget_set.all.count > 1%}title="Targets: {%for target in buildrequest.brtarget_set.all%}{{target.target}} {%endfor%}"{%endif%}>{{buildrequest.brtarget_set.all.0.target}} {%if buildrequest.brtarget_set.all.count > 1%}(+ {{buildrequest.brtarget_set.all.count|add:"-1"}}){%endif%} {{buildrequest.get_machine}} </span> - - </h1> - </div> - <div class="alert alert-error"> - <p class="lead"> - <strong>Failed</strong> - on {{ buildrequest.updated|date:'d/m/y H:i' }} - with - - <i class="icon-minus-sign error" style="margin-left:6px;"></i> - <strong><a class="error accordion-toggle toggle-errors" href="#errors"> - {{buildrequest.brerror_set.all.count}} error{{buildrequest.brerror_set.all.count|pluralize}} - </a></strong> - <span class="pull-right">Build time: {{buildrequest.get_duration|sectohms}}</span> - </p> - </div> - - <div class="accordion" id="errors"> - <div class="accordion-group"> - <div class="accordion-heading"> - <a class="accordion-toggle error toggle-errors"> - <h2> - <i class="icon-minus-sign"></i> - {{buildrequest.brerror_set.all.count}} error{{buildrequest.brerror_set.all.count|pluralize}} - </h2> - </a> - </div> - <div class="accordion-body collapse in" id="collapse-errors"> - <div class="accordion-inner"> - <div class="span10"> - {% for error in buildrequest.brerror_set.all %} - <div class="alert alert-error"> - ERROR: <div class="air well"><pre>{{error.errmsg}}</pre></div> - </div> - {% endfor %} - </div> - </div> - </div> - - </div> - </div> - </div> - </div> <!-- end of row-fluid --> - - -{%endblock%} diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/builds-toastertable.html b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/builds-toastertable.html index bf13a66bd..0afe0a311 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/builds-toastertable.html +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/builds-toastertable.html @@ -13,16 +13,20 @@ {% block pagecontent %} - <div class="row-fluid"> +<div class="row"> + <div class="col-md-12"> {% with mru=mru mrb_type=mrb_type %} {% include 'mrb_section.html' %} {% endwith %} - <h1 class="page-header top-air" data-role="page-title"></h1> + <div class="page-header"> + <h1 class="top-air" data-role="page-title"></h1> + </div> {% url 'builds' as xhr_table_url %} {% include 'toastertable.html' %} </div> +</div> <script> $(document).ready(function () { diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/buildtime.html b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/buildtime.html deleted file mode 100644 index ea84ae797..000000000 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/buildtime.html +++ /dev/null @@ -1,4 +0,0 @@ -{% extends "basebuildpage.html" %} -{% block localbreadcrumb %} -<li>Build Time</li> -{% endblock %} diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/configuration.html b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/configuration.html index 85d6a622a..58989f865 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/configuration.html +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/configuration.html @@ -6,21 +6,17 @@ <li>Configuration</li> {% endblock %} -{% block nav-configuration %} - <li class="active"><a href="{% url 'configuration' build.pk %}">Configuration</a></li> -{% endblock %} - {% block buildinfomain %} <!-- page title --> -<div class="row-fluid span10"> - <div class="page-header"> +<div class="col-md-10"> + + <div class="page-header build-data"> <h1>Configuration</h1> </div> -</div> <!-- configuration table --> -<div class="row-fluid pull-right span10" id="navTab"> -<ul class="nav nav-pills"> +<div id="navTab"> +<ul class="nav nav-tabs"> <li class="active"><a href="#">Summary</a></li> <li class=""><a href="{% url 'configvars' build.id %}">BitBake variables</a></li> </ul> @@ -39,34 +35,51 @@ {%if TUNE_FEATURES %}<dt>Tune features</dt><dd>{{TUNE_FEATURES}}</dd> {% endif %} {%if TARGET_FPU %}<dt>Target FPU</dt><dd>{{TARGET_FPU}}</dd> {% endif %} {%if targets.all %}<dt>Target(s)</dt> - <dd> <ul> {% for target in targets.all %} + <dd> <ul class="list-unstyled"> {% for target in targets.all %} <li>{{target.target}}{%if forloop.counter > 1 %}<br>{% endif %}</li> {% endfor %} </ul> </dd> {% endif %} </dl> <h3>Layers</h3> - <div class="span9" style="margin-left:0px;"> - <table class="table table-bordered table-hover"> - <thead> - <tr> - <th>Layer</th> - <th>Layer branch</th> - <th>Layer commit</th> - </tr> - </thead> - <tbody>{% for lv in build.layer_version_build.all|dictsort:"layer.name" %} - <tr> + <div class="row"> + <div class="col-md-9 table-responsive"> + <table class="table table-bordered table-hover"> + <thead> + <tr> + <th>Layer</th> + <th>Layer branch</th> + <th>Layer commit</th> + </tr> + </thead> + <tbody>{% for lv in build.layer_version_build.all|dictsort:"layer.name" %} + <tr> <td>{{lv.layer.name}}</td> + {% if lv.layer.local_source_dir %} + <td> + <span class="text-muted">Not applicable</span> + <span class="glyphicon glyphicon-question-sign get-help" data-original-title="" title="The source code of {{lv.layer.name}} is not in a Git repository, so there is no branch associated with it"> </span> + </td> + {% else %} <td>{{lv.branch}}</td> - <td> <a class="btn" data-content="<ul class='unstyled'> - <li>{{lv.commit}}</li> </ul>"> - {{lv.commit|truncatechars:13}} - </a></td> - </tr>{% endfor %} - </tbody> - </table> + {% endif %} + {% if lv.layer.local_source_dir %} + <td> + <span class="text-muted">Not applicable</span> + <span class="glyphicon glyphicon-question-sign get-help" data-original-title="" title="The source code of {{lv.layer.name}} is not in a Git repository, so there is no commit associated with it"> </span> + </td> + {% else %} + <td> <a class="btn btn-default" data-content="<ul class='list-unstyled'> + <li>{{lv.commit}}</li> </ul>"> + {{lv.commit|truncatechars:13}} + </a></td> + {% endif %} + </tr>{% endfor %} + </tbody> + </table> + </div> </div> </div> - </div> + +</div> <!-- end of 10-column section --> {% endblock %} diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/configvars.html b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/configvars.html index e40c225a3..ca2e1eab3 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/configvars.html +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/configvars.html @@ -6,14 +6,10 @@ <li>Configuration</li> {% endblock %} -{% block nav-configuration %} - <li class="active"><a href="{% url 'configuration' build.pk %}">Configuration</a></li> -{% endblock %} - {% block buildinfomain %} <!-- page title --> -<div class="row-fluid span10"> - <div class="page-header"> +<div class="col-md-10"> + <div class="page-header build-data"> <h1> {% if request.GET.filter and objects.paginator.count > 0 or request.GET.search and objects.paginator.count > 0 %} {{objects.paginator.count}} variable{{objects.paginator.count|pluralize}} found @@ -24,11 +20,10 @@ {%endif%} </h1> </div> -</div> <!-- configuration table --> -<div class="row-fluid pull-right span10" id="navTab"> - <ul class="nav nav-pills"> +<div id="navTab"> + <ul class="nav nav-tabs"> <li class=""><a href="{% url 'configuration' build.id %}">Summary</a></li> <li class="active"><a href="#" >BitBake variables</a></li> </ul> @@ -37,14 +32,21 @@ <div id="variables" class="tab-pane"> {% if objects.paginator.count == 0 %} - <div class="row-fluid"> - <div class="alert"> - <form class="no-results input-append" id="searchform"> - <input id="search" name="search" class="input-xxlarge" type="text" value="{% if request.GET.search %}{{request.GET.search}}{% endif %}"/>{% if request.GET.search %}<a href="javascript:$('#search').val('');searchform.submit()" class="add-on btn" tabindex="-1"><i class="icon-remove"></i></a>{% endif %} - <button class="btn" type="submit" value="Search">Search</button> - <button class="btn btn-link" onclick="javascript:$('#search').val('');searchform.submit()">Show all variables</button> - </form> + <div class="alert alert-warning"> + <form class="no-results form-inline" id="searchform"> + <div class="form-group"> + <div class="btn-group"> + <input class="form-control" id="search" name="search" type="text" value="{% if request.GET.search %}{{request.GET.search}}{% endif %}"/> + {% if request.GET.search %} + <a href="javascript:$('#search').val('');searchform.submit()" tabindex="-1"> + <span class="remove-search-btn-variables glyphicon glyphicon-remove-circle"></span> + </a> + {% endif %} + </div> </div> + <button class="btn btn-default" type="submit" value="Search">Search</button> + <button class="btn btn-link" onclick="javascript:$('#search').val('');searchform.submit()">Show all variables</button> + </form> </div> {% else %} @@ -53,81 +55,91 @@ {% for variable in objects %} <tr class="data"> <td class="variable_name"><a data-toggle="modal" href="#variable-{{variable.pk}}">{{variable.variable_name}}</a></td> - <td class="variable_value"><a data-toggle="modal" href="#variable-{{variable.pk}}">{{variable.variable_value|truncatechars:153}}</a></td> - <td class="file"><a data-toggle="modal" href="#variable-{{variable.pk}}"> + <td class="variable_value">{{variable.variable_value|truncatechars:153}}</td> + <td class="file"> {% if variable.vhistory.all %} {% for path in variable.vhistory.all|filter_setin_files:file_filter %} {{path}}<br/> {% endfor %} {% endif %} - </a></td> + </td> <td class="description"> {% if variable.description %} {{variable.description}} <a href="http://www.yoctoproject.org/docs/current/ref-manual/ref-manual.html#var-{{variable.variable_name|variable_parent_name}}" target="_blank"> - <i class="icon-share get-info"></i></a> + <span class="glyphicon glyphicon-new-window get-info"></span></a> {% endif %} </td> </tr> {% endfor %} - +</tbody> +</table> +</div> <!-- table-responsive --> {% include "basetable_bottom.html" %} {% endif %} </div> <!-- endvariables --> <!-- file list popups --> {% for variable in objects %} - {% if variable.vhistory.count %} - <div id="variable-{{variable.pk}}" class="modal hide fade" tabindex="-1" role="dialog"> - <div class="modal-header"> - <button type="button" class="close" data-dismiss="modal" aria-hidden="true">x</button> - <h3>History of {{variable.variable_name}}</h3> - </div> - <div class="modal-body"> - {% if variable.variable_value %} - {% if variable.variable_value|length < 570 %} - <h4>{{variable.variable_name}} value is:</h4> - <p> - {{variable.variable_value}} - </p> - {% else %} - <h4>{{variable.variable_name}} value is:</h4> - <p> - <span>{{variable.variable_value|string_slice:':570'}} - <span class="full"> {{variable.variable_value|string_slice:'570:'}} - </span> - <a class="btn btn-mini full-show">...</a> - </span> - </p> - <a class="btn btn-mini full-hide">Collapse variable value <i class="icon-caret-up"></i> - </a> - {% endif %} - {% else %} - <div class="alert alert-info">The value of <strong>{{variable.variable_name}}</strong> is an empty string</div> - {% endif %} - <h4>The value was set in the following configuration files:</h4> - <table class="table table-bordered table-hover"> - <thead> - <tr> - <th>Order</th> - <th>Configuration file</th> - <th>Operation</th> - <th>Line number</th> - </tr> - </thead> - <tbody> - {% for vh in variable.vhistory.all %} - <tr> - <td>{{forloop.counter}}</td><td>{{vh.file_name}}</td><td>{{vh.operation}}</td><td>{{vh.line_number}}</td> - </tr> - {%endfor%} - </tbody> - </table> +{% if variable.vhistory.count %} +<div id="variable-{{variable.pk}}" class="modal fade" tabindex="-1" role="dialog"> + <div class="modal-dialog"> + <div class="modal-content"> + <div class="modal-header"> + <button type="button" class="close" data-dismiss="modal" aria-hidden="true">x</button> + <h3>History of {{variable.variable_name}}</h3> + </div> + <div class="modal-body"> + {% if variable.variable_value %} + {% if variable.variable_value|length < 570 %} + <h4>{{variable.variable_name}} value is:</h4> + <p> + {{variable.variable_value}} + </p> + {% else %} + <h4>{{variable.variable_name}} value is:</h4> + <p> + <span>{{variable.variable_value|string_slice:':570'}} + <span class="full"> {{variable.variable_value|string_slice:'570:'}} + </span> + <a href="#" class="full-show">...</a> + </span> + </p> + <a href="#" class="full-hide">Collapse variable value <i class="icon-caret-up"></i> + </a> + {% endif %} + {% else %} + <div class="alert alert-info">The value of <strong>{{variable.variable_name}}</strong> is an empty string</div> + {% endif %} + <h4>The value was set in the following configuration files:</h4> + <div class="table-responsive"> + <table class="table table-bordered table-hover"> + <thead> + <tr> + <th>Order</th> + <th>Configuration file</th> + <th>Operation</th> + <th>Line</th> + </tr> + </thead> + <tbody> + {% for vh in variable.vhistory.all %} + <tr> + <td>{{forloop.counter}}</td><td class="file">{{vh.file_name}}</td><td>{{vh.operation}}</td><td>{{vh.line_number}}</td> + </tr> + {%endfor%} + </tbody> + </table> </div> - </div> - {% endif %} + </div> + </div><!-- /.modal-content --> + </div><!-- /.modal-dialog --> +</div><!-- /.modal --> +{% endif %} {% endfor %} </div> <!-- buildinfomain --> +</div> <!-- end 10-column section --> + {% endblock %} diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/customise_btn.html b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/customise_btn.html index 2e54a9d90..38c258ac3 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/customise_btn.html +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/customise_btn.html @@ -1,15 +1,15 @@ -<button class="btn btn-block layer-exists-{{data.layer_version.pk}} customise-btn" data-recipe="{{data.pk}}" +<button class="btn btn-default btn-block layer-exists-{{data.layer_version.pk}} customise-btn" data-recipe="{{data.pk}}" {% if data.layer_version.pk not in extra.current_layers %} style="display:none;" {% endif %} > Customise </button> -<button class="btn btn-block layer-add-{{data.layer_version.pk}} layerbtn" data-layer='{ "id": {{data.layer_version.pk}}, "name": "{{data.layer_version.layer.name}}", "layerdetailurl": "{%url 'layerdetails' extra.pid data.layer_version.pk%}"}' data-directive="add" +<button class="btn btn-default btn-block layer-add-{{data.layer_version.pk}} layerbtn" data-layer='{ "id": {{data.layer_version.pk}}, "name": "{{data.layer_version.layer.name}}", "layerdetailurl": "{%url 'layerdetails' extra.pid data.layer_version.pk%}"}' data-directive="add" {% if data.layer_version.pk in extra.current_layers %} style="display:none;" {% endif %} > - <i class="icon-plus"></i> + <i class="glyphicon glyphicon-plus"></i> Add layer </button> diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/customrecipe.html b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/customrecipe.html index ea3c9c732..945fc9797 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/customrecipe.html +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/customrecipe.html @@ -4,7 +4,7 @@ {% load static %} {% block pagecontent %} -<div class="section"> +{% with recipe.get_base_recipe_file as base_recipe_file %} <ul class="breadcrumb"> <li> <a href="{% url 'project' project.id %}">{{project.name}}</a> @@ -17,7 +17,6 @@ {{recipe.name}} ({{recipe.layer_version.layer.name}}) </li> </ul> -</div> <script src="{% static 'js/customrecipe.js' %}"></script> <script> @@ -29,6 +28,7 @@ includedPackagesCount: {{recipe.includes_set.count}}, baseRecipeId: {{recipe.base_recipe.pk}}, xhrPackageListUrl: "{% url 'xhr_customrecipe_packages' recipe.pk %}", + xhrCustomRecipeUrl: "{% url 'xhr_customrecipe_id' recipe.pk %}", } }; @@ -40,95 +40,137 @@ } }); </script> + +<!-- Delete recipe modal --> +<div id="delete-recipe-modal" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="false"> + <div class="modal-dialog"> + <div class="modal-content"> + <div class="modal-body"> + <p>Are you sure you want to delete the <strong>{{recipe.name}}</strong> + custom image?</p> + </div> + <div class="modal-footer"> + <button type="button" class="btn btn-primary" id="delete-custom-recipe-confirmed"> + <span data-role="submit-state">Delete custom image</span> + <span data-role="loading-state" style="display:none"> + <span class="fa-pulse"> + <i class="fa-pulse icon-spinner"></i> + </span> + Deleting custom image... + </span> + </button> + <button type="button" class="btn btn-link" data-dismiss="modal">Cancel</button> + </div> + </div><!-- /.modal-content --> + </div><!-- /.modal-dialog --> +</div><!-- /.modal --> +<!-- end delete recipe modal --> + <!-- package dependencies modal --> -<div style="display:none" id="package-deps-modal" class="modal hide fade in" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="false"> - <div class="modal-header"> - <button type="button" class="close" data-dismiss="modal" aria-hidden="true">x</button> - <h3><span class="package-to-add-name"></span> dependencies</h3> - </div> - <div class="modal-body"> - <p>Based on information from a previous build it is likely that adding <strong class="package-to-add-name"></strong> will also add the following packages to your custom image:</p> - <ul id="package-add-dep-list"> - </ul> - </div> - <div class="modal-footer"> - <p class="help-block text-left">Total package size: <strong id="package-deps-total-size"></strong></p> - <button id="add-package-deps-modal-btn" type="submit" class="btn btn-primary" data-dismiss="modal">Add package</button> - <button class="btn" data-dismiss="modal">Cancel</button> - </div> -</div> +<div id="package-deps-modal" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="false"> + <div class="modal-dialog"> + <div class="modal-content"> + <div class="modal-header"> + <button type="button" class="close" data-dismiss="modal" aria-hidden="true">x</button> + <h3><span class="package-to-add-name"></span> dependencies</h3> + </div> + <div class="modal-body"> + <p>Based on information from a previous build it is likely that adding <strong class="package-to-add-name"></strong> will also add the following packages to your custom image: + </p> + <ul id="package-add-dep-list"> + </ul> + </div> + <div class="modal-footer"> + <p class="help-block text-left">Total package size: <strong id="package-deps-total-size"></strong></p> + <button id="add-package-deps-modal-btn" type="submit" class="btn btn-primary" data-dismiss="modal">Add packages</button> + <button class="btn btn-link" data-dismiss="modal">Cancel</button> + </div> + </div><!-- /.modal-content --> + </div><!-- /.modal-dialog --> +</div><!-- /.modal --> <!-- end package dependencies modal --> <!-- package reverse dependencies modal --> -<div style="display:none" id="package-reverse-deps-modal" class="modal hide fade in" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="false"> - <div class="modal-header"> - <button type="button" class="close" data-dismiss="modal" aria-hidden="true">x</button> - <h3><span class="package-to-rm-name"></span> reverse dependencies</h3> - </div> - <div class="modal-body"> - <p> <strong class="reverse-deps-count"></strong> <span class="reverse-deps-package"></span> in your image <span class="reverse-deps-depends"></span> on <strong class="package-to-rm-name"></strong>:</p> - <ul id="package-reverse-dep-list"> - </ul> - <p>In order to remove <strong class="package-to-rm-name"></strong>, you must remove <span class="reverse-deps-this"></span> <strong class="reverse-deps-count"></strong> <span class="reverse-deps-package"></span> as well.</p> - </div> - <div class="modal-footer"> - <p class="help-block text-left">Total package size: <strong id="package-reverse-deps-total-size"></strong></p> - <button id="rm-package-reverse-deps-modal-btn" type="submit" class="btn btn-primary" data-dismiss="modal">Remove all <span class="reverse-deps-count-plus1"></button> - <button class="btn" data-dismiss="modal">Cancel</button> - </div> -</div> +<div style="display:none" id="package-reverse-deps-modal" class="modal fade in" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="false"> + <div class="modal-dialog"> + <div class="modal-content"> + <div class="modal-header"> + <button type="button" class="close" data-dismiss="modal" aria-hidden="true">x</button> + <h3><span class="package-to-rm-name"></span> reverse dependencies</h3> + </div> + <div class="modal-body"> + <p> <strong class="reverse-deps-count"></strong> <span class="reverse-deps-package"></span> in your image <span class="reverse-deps-depends"></span> on <strong class="package-to-rm-name"></strong>:</p> + <ul id="package-reverse-dep-list"> + </ul> + <p>In order to remove <strong class="package-to-rm-name"></strong>, you must remove <span class="reverse-deps-this"></span> <strong class="reverse-deps-count"></strong> <span class="reverse-deps-package"></span> as well.</p> + </div> + <div class="modal-footer"> + <p class="help-block text-left">Total package size: <strong id="package-reverse-deps-total-size"></strong></p> + <button id="rm-package-reverse-deps-modal-btn" type="submit" class="btn btn-primary" data-dismiss="modal">Remove all <span class="reverse-deps-count-plus1"></button> + <button class="btn btn-link" data-dismiss="modal">Cancel</button> + </div> + </div><!-- /.modal-content --> + </div><!-- /.modal-dialog --> +</div><!-- /.modal --> <!-- end package dependencies modal --> - -<div class="row-fluid span11"> - <div class="alert alert-success lead" id="image-created-notification" style="margin-top: 15px; display: none"> - <button type="button" data-dismiss="alert" class="close">x</button> - Your custom image <strong>{{recipe.name}}</strong> has been created. You can now add or remove packages as needed. - </div> - <div class="page-header air"> - <h1> - {{recipe.name}} - <small>({{recipe.layer_version.layer.name}})</small> - </h1> - </div> +<div class="alert alert-success alert-dismissible change-notification" id="image-created-notification" style="display: none"> + <button type="button" data-dismiss="alert" class="close">×</button> + <p>Your custom image <strong>{{recipe.name}}</strong> has been created. You can now add or remove packages as needed.</p> +</div> +<div class="page-header"> + <h1> + {{recipe.name}} + <small>({{recipe.layer_version.layer.name}})</small> + </h1> </div> -<div class="row-fluid span11"> - <div class="span8"> +<div class="row"> + <div class="col-md-8"> <div class="button-place btn-group" style="width: 100%"> - <a class="btn btn-large span6 build-custom-image" href="#" style="width: 50%"> + <a class="btn btn-default btn-lg build-custom-image" href="#" style="width: 50%"> Build {{recipe.name}} </a> - <a href="{% url 'customrecipedownload' project.id recipe.id %}" class="btn btn-large span6" style="width: 50%"> + <a href="{% url 'customrecipedownload' project.id recipe.id %}" class="btn btn-default btn-lg" style="width: 50%" + {% if not base_recipe_file %} + disabled="disabled" + {% endif %}> Download recipe file + {% if not base_recipe_file %} + <i class="icon-question-sign get-help" + data-original-title="The recipe file doesn't exist yet, so you cannot download it. You need to build your custom image first"></i> + {% endif %} </a> </div> - <div id="no-results-special-{{table_name}}" class="air" style="display:none;"> - <div class="alert"> + <div id="no-results-special-{{table_name}}" class="top-air" style="display:none;"> + <div class="alert alert-warning"> <h3>No packages found</h3> - <p>You might consider <a href={% url 'projectsoftwarerecipes' project.id %}>searching the list of recipes</a> instead. If you find a recipe that matches the name of the package you want:</p> + <p>You might consider <a href={% url 'projectsoftwarerecipes' project.id %}>searching the list of recipes</a> instead.</p> + <p>If you find a recipe that matches the name of the package you want:</p> <ol> <li>Add the layer providing the recipe to your project</li> <li>Build the recipe</li> <li>Once the build completes, come back to this page and search for the package</li> </ol> - <form class="input-append no-results"> - <input type="text" class="input-xlarge no-results-search-input" id="no-results-search-input-{{table_name}}" name="search" placeholder="Search {{title|lower}}" /> - <a href="#" class="add-on btn" id="no-results-remove-search-btn" tabindex="-1"> - <i class="icon-remove"></i> - </a> - <button class="btn search-submit-{{table_name}}">Search</button> + <form class="form-inline no-results"> + <div class="form-group"> + <div class="btn-group"> + <input type="text" class="form-control no-results-search-input" id="no-results-search-input-{{table_name}}" name="search" placeholder="Search {{title|lower}}" /> + <span class="remove-search-btn-{{table_name}} glyphicon glyphicon-remove-circle" id="no-results-remove-search-btn" tabindex="-1"></span> + </div> + </div> + <button class="btn btn-default search-submit-{{table_name}}">Search</button> <button class="btn btn-link" id="no-results-show-all-packages">Show all packages</button> </form> </div> - </div> - <div id="results-found-{{table_name}}"> + </div> + <div id="results-found-{{table_name}}"> <div id="packages-table"> {% if recipe.get_all_packages.count == 0 and last_build == None %} <h2> Add | Remove packages </h2> - <div class="alert alert-info air"> + <div class="alert alert-info"> <p class="lead">Toaster has no package information for {{recipe.name}}. To generate package information, build {{recipe.name}}</p> - <button class="btn btn-info btn-large build-custom-image" style="margin:20px 0 10px 0;">Build {{recipe.name}}</button> + <button class="btn btn-info btn-lg build-custom-image" style="margin:20px 0 10px 0;">Build {{recipe.name}}</button> </div> {% else %} {# ToasterTable for Adding remove packages #} @@ -137,87 +179,90 @@ {% include "toastertable.html" %} {% endif %} </div> - </div> </div> - <div class="span4 well"> - <h2 style="margin-bottom:20px;">About {{recipe.name}}</h2> + </div> + <div class="col-md-4"> + <div class="well"> + <h2>About {{recipe.name}}</h2> - <dl> + <dl class="item-info"> <dt> - Approx. packages included - <i class="icon-question-sign get-help" title="" data-original-title="The number of packages included is based on information from previous builds and from parsing layers, so we can never be sure it is 100% accurate"></i> + Approx. packages included + <span class="glyphicon glyphicon-question-sign get-help" title="" data-original-title="The number of packages included is based on information from previous builds and from parsing layers, so we can never be sure it is 100% accurate"></span> </dt> <dd id="total-num-packages">{{recipe.get_all_packages.count}}</dd> <dt> - Approx. package size - <i class="icon-question-sign get-help" title="" data-original-title="Package size is based on information from previous builds, so we can never be sure it is 100% accurate"></i> + Approx. package size + <span class="glyphicon glyphicon-question-sign get-help" title="Package size is based on information from previous builds, so we can never be sure it is 100% accurate"></span> </dt> <dd id="total-size-packages">{{approx_pkg_size.size__sum|filtered_filesizeformat}}</dd> {% if last_build %} <dt>Last build</dt> <dd> - <i class="icon-ok-sign success"></i> - <a href="{% url 'projectbuilds' project.id%}">{{last_build.completed_on|date:"d/m/y H:i"}}</a> + <span class="glyphicon glyphicon-ok-circle"></span> + <a href="{% url 'projectbuilds' project.id%}">{{last_build.completed_on|date:"d/m/y H:i"}}</a> </dd> {% endif %} - <dt>Layer</dt> - <dd><a href="{% url 'layerdetails' project.id recipe.layer_version.pk %}">{{recipe.layer_version.layer.name}}</a></dd> <dt>Based on</dt> <dd><a href="{% url 'recipedetails' project.id recipe.base_recipe.pk %}">{{recipe.base_recipe.name}}</a></dd> {% if recipe.get_last_successful_built_target %} {% with recipe.get_last_successful_built_target as last_build_target %} <dt>Last build</dt> <dd> - <i class="icon-ok-sign success"></i> - <a href="{% url 'builddashboard' last_build_target.build.pk %}"> - {{last_build_target.build.completed_on|date:"d/m/y H:i"}}</a> + <span class="glyphicon glyphicon-ok-circle"></span> + <a href="{% url 'builddashboard' last_build_target.build.pk %}"> + {{last_build_target.build.completed_on|date:"d/m/y H:i"}}</a> </dd> {% endwith %} {% endif %} + {% if base_recipe_file %} <dt>Recipe file</dt> <dd> - <code>{{recipe.name}}_{{recipe.version}}.bb</code> - <a href="{% url 'customrecipedownload' project.pk recipe.pk %}"><i class="icon-share" title="" data-original-title="View recipe file"></i></a> + <code>{{recipe.name}}_{{recipe.version}}.bb</code> + <a href="{% url 'customrecipedownload' project.pk recipe.pk %}"><span class="glyphicon glyphicon-download-alt" data-toggle="tooltip" title="Download recipe file"></span></a> </dd> + {% endif %} <dt>Layer</dt> <dd><a href="{% url 'layerdetails' project.id recipe.layer_version.pk %}">{{recipe.layer_version.layer.name}}</a></dd> {% if recipe.summary %} <dt> - Summary + Summary </dt> <dd> - {{recipe.summary}} + {{recipe.summary}} </dd> {% endif %} {% if recipe.description %} <dt> - Description + Description </dt> <dd> - {{recipe.description}} + {{recipe.description}} </dd> {% endif %} <dt>Version</dt> <dd> - {{recipe.version}} + {{recipe.version}} </dd> {% if recipe.section %} <dt>Section</dt> <dd> - {{recipe.section}} + {{recipe.section}} </dd> {% endif %} <dt>License</dt> <dd> - {{recipe.license}} - <i class="icon-question-sign get-help" title="" data-original-title="All custom images have their license set to MIT. This is because the license applies only to the recipe (.bb) file, and not to the image itself. To see which licenses apply to the image you must check the license manifest generated with each build"></i> - </dd> + {{recipe.license}} + <span class="glyphicon glyphicon-question-sign get-help" title="All custom images have their license set to MIT. This is because the license applies only to the recipe (.bb) file, and not to the image itself. To see which licenses apply to the image you must check the license manifest generated with each build"></i> + </dd> </dl> - <!-- - <i class="icon-trash no-tooltip"></i> - <a href="#" class="error" id="delete">Delete custom image</a> - --> + <i class="icon-trash text-danger"></i> + <a href="#delete-recipe-modal" data-target="#delete-recipe-modal" data-toggle="modal" class="text-danger" id="delete-recipe"> + Delete custom image + </a> </div> </div> +</div> - {% endblock %} +{% endwith %}{# end base_recipe_file #} +{% endblock %} diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/detail_pagination_bottom.html b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/detail_pagination_bottom.html index f40c21d99..15adfbc91 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/detail_pagination_bottom.html +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/detail_pagination_bottom.html @@ -6,8 +6,8 @@ {# only paginate if 10 or more rows unfiltered, all pages #} {% if object_count >= 10 %} -<div class="pagination"> - <ul> +<div id="pagination-detail"> + <ul class="pagination"> {%if objects.has_previous %} <li><a href="javascript:reload_params({'page':{{objects.previous_page_number}}})">«</a></li> {%else%} @@ -23,16 +23,18 @@ {%endif%} </ul> - <div class="pull-right"> - <span class="help-inline" style="padding-bottom:10px;">Show rows:</span> - <select class="pagesize"> - {% with "10 25 50 100 150" as list%} + <form class="navbar-form navbar-right"> + <div class=form-group"> + <label>Show rows:</label> + <select class="pagesize form-control"> + {% with "10 25 50 100 150" as list%} {% for i in list.split %} - <option value="{{i}}">{{i}}</option> + <option value="{{i}}">{{i}}</option> {% endfor %} - {% endwith %} - </select> - </div> + {% endwith %} + </select> + </div> + </form> </div> <!-- Update page display settings --> diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/detail_search_header.html b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/detail_search_header.html index 7bea3f47f..7a9865908 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/detail_search_header.html +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/detail_search_header.html @@ -18,51 +18,55 @@ $(document).ready(function() { }); }); </script> -<div class="row-fluid"> + {% if objects.paginator.count > 10 or request.GET.search %} {% if objects.paginator.count == 0 %} - <div class="alert"> - <h3>No {{search_what}} found</h3> - <form id="searchform" class="input-append"> - {% else %} - <form id="searchform" class="navbar-search input-append pull-left"> - {% endif %} + <div class="alert alert-warning"> + <h4>No {{search_what}} found</h4> + <form id="searchform" class="form-inline"> + {% else %} + <form id="searchform" class="navbar-form navbar-left detail-page-controls"> + {% endif %} - <input id="search" class="input-xlarge" type="text" placeholder="Search {{search_what}}" name="search" value="{% if request.GET.search %}{{request.GET.search}}{% endif %}"> - <input type="hidden" value="name:+" name="orderby"> - <input type="hidden" value="l" name="page"> - {% if request.GET.search %} - <a class="add-on btn search-clear"> - <i class="icon-remove"></i> - </a> - {% endif %} - <button type="submit" class="btn">Search</button> - {% if objects.paginator.count == 0 %} - <button type="submit" class="btn btn-link search-clear"> - Show all {{search_what}} - </button> - {% endif %} - </form> -{% endif %} + <div class="form-group"> + <div class="btn-group"> + <input id="search" class="form-control" type="text" placeholder="Search {{search_what}}" name="search" value="{% if request.GET.search %}{{request.GET.search}}{% endif %}"> + <input type="hidden" value="name:+" name="orderby"> + <input type="hidden" value="l" name="page"> + {% if request.GET.search %} + <span class="remove-search-btn-detail-search search-clear glyphicon glyphicon-remove-circle"></span> + {% endif %} + </div> + </div> + <button type="submit" class="btn btn-default">Search</button> + {% if objects.paginator.count == 0 %} + <button type="submit" class="btn btn-link search-clear"> + Show all {{search_what}} + </button> + {% endif %} + </form> + {% endif %} -{% if objects.paginator.count == 0 %} + {% if objects.paginator.count == 0 %} </div> {# end alert #} -{% else %} + {% else %} {% if object_count > 10 %} - <div class="pull-right"> - <span class="help-inline" style="padding-top:5px;">Show rows:</span> - <select style="margin-top:5px;margin-bottom:0px;" class="pagesize"> - {% with "10 25 50 100 150" as list%} - {% for i in list.split %} + <form class="navbar-form navbar-right"> + <div class="form-group"> + <label>Show rows:</label> + <select class="pagesize form-control"> + {% with "10 25 50 100 150" as list%} + {% for i in list.split %} {% if request.session.limit == i %} - <option value="{{i}}" selected>{{i}}</option> + <option value="{{i}}" selected>{{i}}</option> {% else %} - <option value="{{i}}">{{i}}</option> + <option value="{{i}}">{{i}}</option> {% endif %} - {% endfor %} - {% endwith %} - </select> - </div> + {% endfor %} + {% endwith %} + </select> + </div> + </form> {% endif %} {% endif %} -</div> {# row-fluid #} + diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/detail_sorted_header.html b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/detail_sorted_header.html index 6ce292e5a..4434df439 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/detail_sorted_header.html +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/detail_sorted_header.html @@ -10,14 +10,14 @@ <!-- Table header row; generated from "tablecols" entry in the context dict --> <tr> {% for tc in tablecols %}<th class="{%if tc.dclass%}{{tc.dclass}}{% endif %} {%if tc.class %}{{tc.clclass}}{% endif %}"> - {%if tc.qhelp%}<i class="icon-question-sign get-help" title="{{tc.qhelp}}"></i>{%endif%} - {%if tc.orderfield%}<a {%if tc.ordericon%} class="sorted" {%endif%}href="javascript:reload_params({'page': 1, 'orderby' : '{{tc.orderfield}}' })">{{tc.name}}</a>{%else%}<span class="muted">{{tc.name}}</span>{%endif%} - {%if tc.ordericon%} <i class="icon-caret-{{tc.ordericon}}"></i>{%endif%} + {%if tc.qhelp%}<span class="glyphicon glyphicon-question-sign get-help" title="{{tc.qhelp}}"></span>{%endif%} + {%if tc.orderfield%}<a {%if tc.ordericon%} class="sorted" {%endif%}href="javascript:reload_params({'page': 1, 'orderby' : '{{tc.orderfield}}' })">{{tc.name}}</a>{%else%}<span class="text-muted">{{tc.name}}</span>{%endif%} + {%if tc.ordericon%} <span class="icon-caret-{{tc.ordericon}}"></span>{%endif%} {% if request.GET.search and forloop.first %} <span class="badge badge-info">{{objects.paginator.count}}</span> {% endif %} {%if tc.filter%}<div class="btn-group pull-right"> - <a href="#filter_{{tc.filter.class}}" role="button" class="btn btn-mini {%if request.GET.filter%}{{tc.filter.options|filtered_icon:request.GET.filter}} {%endif%}" {%if request.GET.filter and tc.filter.options|filtered_tooltip:request.GET.filter %} title="<p>{{tc.filter.options|filtered_tooltip:request.GET.filter}}</p><p><a class='btn btn-small btn-primary' href=javascript:reload_params({'filter':''})>Show all {% if filter_search_display %}{{filter_search_display}}{% else %}{{objectname}}{% endif %}</a></p>" {%endif%} data-toggle="modal"> <i class="icon-filter filtered"></i> </a> + <a href="#filter_{{tc.filter.class}}" role="button" class="btn btn-mini {%if request.GET.filter%}{{tc.filter.options|filtered_icon:request.GET.filter}} {%endif%}" {%if request.GET.filter and tc.filter.options|filtered_tooltip:request.GET.filter %} title="<p>{{tc.filter.options|filtered_tooltip:request.GET.filter}}</p><p><a class='btn btn-sm btn-primary' href=javascript:reload_params({'filter':''})>Show all {% if filter_search_display %}{{filter_search_display}}{% else %}{{objectname}}{% endif %}</a></p>" {%endif%} data-toggle="modal"> <i class="glyphicon glyphicon-filter filtered"></i> </a> </div>{%endif%} </th>{% endfor %} </tr> diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/dirinfo.html b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/dirinfo.html index ecb46bf7a..ab89e279f 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/dirinfo.html +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/dirinfo.html @@ -18,6 +18,13 @@ {% load projecttags %} <script type='text/javascript'> + var ctx = {}; + ctx.target = "{{target.target}}"; + + $(document).ready(function(){ + $("#menu-"+ctx.target).addClass("active"); + }); + function setupTreetable() { $("#dirtable").treetable({ expandable: true, @@ -59,9 +66,6 @@ function formatRow(o) { /* setup tr-wide formatting */ var tr = '<tr class="'; - if (o.link_to != null) { - tr += 'muted '; - } if (o.isdir && o.childcount) { tr += 'branch" data-tt-branch="true" '; } @@ -75,7 +79,7 @@ tr += '>'; /* setup td specific formatting */ - var link_to = td(o.link_to); + var link_to = '<td class="text-muted">' + o.link_to + '</td>'; var size = '<td class = "sizecol">' + o.size + '</td>' var permission = td(o.permission); var owner = td(o.owner); @@ -96,7 +100,7 @@ if (o.childcount) { name += '<a href="">'; } - name += '<i class="icon-folder-close"></i>'; + name += '<span class="glyphicon glyphicon-folder-close"></span>'; name += ' ' + o.name; if (o.childcount) { name += '</a>'; @@ -110,10 +114,10 @@ namespan = 3; } var colspan = 'colspan="' + namespan + '"'; - name = '<td ' + colspan + '><i class="icon-file"></i>'; + name = '<td ' + colspan + '><span class="glyphicon glyphicon-file"></span>'; } else { - name = '<td><i class="icon-hand-right"></i>'; + name = '<td class="text-muted"><span class="glyphicon glyphicon-hand-right"></span>'; } name += ' ' + o.name; name += '</td>'; @@ -134,10 +138,10 @@ package += '</a>'; if (o.installed_package != o.package) { /* make class muted and add hover help */ - package += '<span class="muted"> as ' + o.installed_package + ' </span>'; - package += '<i class="icon-question-sign get-help hover-help" '; + package += '<span class="text-muted"> as ' + o.installed_package + ' </span>'; + package += '<span class="glyphicon glyphicon-question-sign get-help hover-help" '; package += 'title="' + o.package + ' was renamed at packaging time and was installed in your image as ' + o.installed_package + '">'; - package += '</i>'; + package += '</span>'; } } package = td(package); @@ -184,22 +188,22 @@ } </script> -<div class="span10"> +<div class="col-md-10"> - <div class="page-header"> + <div class="page-header build-data"> <h1> {{target.target}} </h1> </div> - <ul class="nav nav-pills"> - <li class=""> + <ul class="nav nav-tabs"> + <li> <a href="{% url 'target' build.id target.id %}"> - <i class="icon-question-sign get-help" title="Of all the packages built, the subset installed in the root file system of this image"></i> + <span class="glyphicon glyphicon-question-sign get-help" title="Of all the packages built, the subset installed in the root file system of this image"></span> Packages included ({{target.package_count}} - {{packages_sum|filtered_filesizeformat}}) </a> </li> <li class="active"> <a href="{% url 'dirinfo' build.id target.id %}"> - <i class="icon-question-sign get-help" title="The directories and files in the root file system of this image"></i> + <span class="glyphicon glyphicon-question-sign get-help" title="The directories and files in the root file system of this image"></span> Directory structure </a> </li> @@ -235,7 +239,7 @@ </tbody> </table> </div> <!-- directory-structure --> -</div> <!-- span10 --> +</div> <!-- col-md-10 --> {% endblock buildinfomain %} diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/editcustomimage_modal.html b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/editcustomimage_modal.html index 8046c08fb..baa36c0e1 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/editcustomimage_modal.html +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/editcustomimage_modal.html @@ -6,32 +6,36 @@ choose which one to edit required context: build - a Build object --> -<div class="modal hide fade in" aria-hidden="false" id="edit-custom-image-modal"> - <div class="modal-header"> - <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button> - <h3>Which image do you want to edit?</h3> - </div> +<div class="modal fade" aria-hidden="false" id="edit-custom-image-modal"> + <div class="modal-dialog"> + <div class="modal-content"> + <div class="modal-header"> + <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button> + <h3>Which image do you want to edit?</h3> + </div> - <div class="modal-body"> - <div class="row-fluid"> - {% for recipe in build.get_custom_image_recipes %} - <label class="radio"> - {{recipe.name}} - <input type="radio" class="form-control" name="select-custom-image" - data-url="{% url 'customrecipe' build.project.id recipe.id %}"> - </label> - {% endfor %} - </div> - <span class="help-block error" id="invalid-custom-image-help" style="display:none"> - Please select a custom image to edit. - </span> - </div> + <div class="modal-body"> + {% for recipe in build.get_custom_image_recipes %} + <div class="radio"> + <label> + <input type="radio" name="select-custom-image" + data-url="{% url 'customrecipe' build.project.id recipe.id %}"> + {{recipe.name}} + </label> + </div> + {% endfor %} + <span class="help-block text-danger" id="invalid-custom-image-help" style="display:none"> + Please select a custom image to edit. + </span> + </div> - <div class="modal-footer"> - <button class="btn btn-primary btn-large" data-url="#" - data-action="edit-custom-image" disabled> - Edit custom image - </button> + <div class="modal-footer"> + <button class="btn btn-primary btn-lg" data-url="#" + data-action="edit-custom-image" disabled> + Edit custom image + </button> + </div> + </div> </div> </div> @@ -46,7 +50,7 @@ $(document).ready(function () { return $('[name="select-custom-image"]:checked'); }; - radios.change(function () { + function enableSubmit() { if (getSelectedRadios().length === 1) { editCustomImageButton.removeAttr('disabled'); error.hide(); @@ -55,6 +59,14 @@ $(document).ready(function () { editCustomImageButton.attr('disabled', 'disabled'); error.show(); } + }; + + $("#edit-custom-image-modal").on("shown.bs.modal", function() { + enableSubmit(); + }); + + radios.change(function () { + enableSubmit(); }); editCustomImageButton.click(function () { @@ -67,5 +79,10 @@ $(document).ready(function () { error.show(); } }); + + // Select the first custom image listed. Radio button groups + // should always have an option selected by default + $("input:radio:first").attr("checked", "checked"); + }); </script> diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/filtersnippet.html b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/filtersnippet.html index 1101aa810..1286ca315 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/filtersnippet.html +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/filtersnippet.html @@ -1,57 +1,72 @@ {% load projecttags %} <!-- '{{f.class}}' filter --> {% with f.class as key %} -<form id="filter_{{f.class}}" class="modal hide fade" tabindex="-1" role="dialog" aria-hidden="true"> + +<div id="filter_{{f.class}}" class="modal fade" tabindex="-1" role="dialog" aria-hidden="true"> + <div class="modal-dialog"> + <div class="modal-content"> + <form> <input type="hidden" name="search" value="{%if request.GET.search %}{{request.GET.search}}{%endif%}"/> <div class="modal-header"> - <button type="button" class="close" data-dismiss="modal" aria-hidden="true">x</button> - {% if search_term %} - <h3>Filter {{total_count}} {%if filter_search_display%}{{filter_search_display|title}}{%else%}{{objectname|title}}{%endif%} matching '{{search_term}}' by '{{tc.name}}'</h3> - {% else %} - <h3>Filter {%if filter_search_display%}{{filter_search_display|title}}{%else%}{{objectname|title}}{%endif%} by '{{tc.name}}'</h3> - {% endif %} + <button type="button" class="close" data-dismiss="modal" aria-hidden="true">x</button> + {% if search_term %} + <h3>Filter {{total_count}} {%if filter_search_display%}{{filter_search_display|title}}{%else%}{{objectname|title}}{%endif%} matching '{{search_term}}' by '{{tc.name}}'</h3> + {% else %} + <h3>Filter {%if filter_search_display%}{{filter_search_display|title}}{%else%}{{objectname|title}}{%endif%} by '{{tc.name}}'</h3> + {% endif %} </div> <div class="modal-body"> - <p>{{f.label}}</p> - <label class="radio"> - <input type="radio" name="filter" {%if request.GET.filter%}{{f.options|check_filter_status:request.GET.filter}} {%else%} checked {%endif%} value="" data-key="{{key}}"> All {%if filter_search_display%}{{filter_search_display|title}}{%else%}{{objectname|title}}{%endif%} + <p>{{f.label}}</p> + <div class="radio"> + <label> + <input type="radio" name="filter" {%if request.GET.filter%}{{f.options|check_filter_status:request.GET.filter}} {%else%} checked {%endif%} value="" data-key="{{key}}"> All {%if filter_search_display%}{{filter_search_display|title}}{%else%}{{objectname|title}}{%endif%} </label> - {% for option in f.options %} - {% if option.1 == 'daterange' %} - <div class="form-inline"> - <label class="radio"> - <input type="radio" name="filter" id="filter_value_{{key}}" {%if key == daterange_selected %}checked{%endif%} value="{{option.1}}" data-key="{{key}}"> {{option.0}} - {% else %} - {% if 1 %} - <label class="radio"> - <input type="radio" name="filter" {%if request.GET.filter == option.1 %}checked{%endif%} value="{{option.1}}" data-key="{{key}}"> {{option.0}} - {% comment "do not disable radio selections by count for now" %}{% else %} - <label class="radio muted"> - <input type="radio" name="filter" disabled {%if request.GET.filter == option.1 %}checked{%endif%} value="{{option.1}}" data-key="{{key}}"> {{option.0}} + </div> + {% for option in f.options %} + <div class="radio"> + {% if option.1 == 'daterange' %} + <label> + <input type="radio" name="filter" id="filter_value_{{key}}" {%if key == daterange_selected %}checked{%endif%} value="{{option.1}}" data-key="{{key}}"> {{option.0}} + {% else %} + {% if 1 %} + <label> + <input type="radio" name="filter" {%if request.GET.filter == option.1 %}checked{%endif%} value="{{option.1}}" data-key="{{key}}"> {{option.0}} + {% comment "do not disable radio selections by count for now" %}{% else %} + <label class="text-muted"> + <input type="radio" name="filter" disabled {%if request.GET.filter == option.1 %}checked{%endif%} value="{{option.1}}" data-key="{{key}}"> {{option.0}} {% endcomment %}{% endif %} - {% endif %} - {% if option.3 %}<i class="icon-question-sign get-help" data-placement="right" title="{{option.3}}"></i>{% endif %} - </label> + {% endif %} + {% if option.3 %}<i class="icon-question-sign get-help" data-placement="right" title="{{option.3}}"></i>{% endif %} + </label></div> {% if option.1 == 'daterange' %} - <input type="text" id="date_from_{{key}}" name="date_from_{{key}}" disabled class="input-small" /><label class="help-inline">to</label> - <input type="text" id="date_to_{{key}}" name="date_to_{{key}}" disabled class="input-small" /> - <label class="help-inline get-help" >(dd/mm/yyyy)</label> - </div> - {% endif %} - {% endfor %} + <input type="text" id="date_from_{{key}}" name="date_from_{{key}}" disabled class="input-sm" /><label class="help-inline">to</label> + <input type="text" id="date_to_{{key}}" name="date_to_{{key}}" disabled class="input-sm" /> + <label class="help-inline get-help" >(dd/mm/yyyy)</label> + </div> + {% endif %} + {% endfor %} <!-- daterange persistence --> {% if last_date_from and last_date_to %} <input type="hidden" id="last_date_from_{{key}}" name="last_date_from" value="{{last_date_from}}"/> <input type="hidden" id="last_date_to_{{key}}" name="last_date_to" value="{{last_date_to}}"/> {% endif %} + </div> + <div class="modal-footer"> + <div class="row"> + <div class="col-md-6"> + <button type="submit" class="btn btn-primary" data-key="{{key}}">Apply</button> </div> - <div class="modal-footer"> - <button type="submit" class="btn btn-primary" data-key="{{key}}">Apply</button> - {% if request.GET.filter %} - {% if request.GET.filter|string_remove_regex:':.*' != f.options.0.1|string_remove_regex:':.*' %} - <span class="help-inline pull-left">You can only apply one filter to the table. This filter will override the current filter.</span> - {% endif %} - {% endif %} + <div class="col-md-6"> + {% if request.GET.filter %} + {% if request.GET.filter|string_remove_regex:':.*' != f.options.0.1|string_remove_regex:':.*' %} + <p class="text-right text-muted">You can only apply one filter to the table. This filter will override the current filter.</p> + {% endif %} + {% endif %} </div> -</form> + </div> + </div> + </form> + </div><!-- /.modal-content --> +</div><!-- /.modal-dialog --> +</div> <!--/.modal --> {% endwith %} diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/generic-toastertable-page.html b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/generic-toastertable-page.html index 33aa8ce0d..b3eabe1a2 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/generic-toastertable-page.html +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/generic-toastertable-page.html @@ -7,7 +7,7 @@ <h2>{{title}} (<span class="table-count-{{table_name}}"></span>) {% if project.release %} - <i class="icon-question-sign get-help heading-help" title="This page lists {{title}} compatible with the release selected for this project, which is {{project.release.description}}"></i> + <i class="glyphicon glyphicon-question-sign get-help" title="This page lists {{title|lower}} compatible with the release selected for this project, which is {{project.release.description}}"></i> {% endif %} </h2> diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/importlayer.html b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/importlayer.html index 1848f410e..1f426969a 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/importlayer.html +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/importlayer.html @@ -5,135 +5,163 @@ {% block title %} Import layer - {{project.name}} - Toaster {% endblock %} {% block pagecontent %} -{% include "projecttopbar.html" %} - - - {% if project and project.release %} - <script src="{% static 'js/layerDepsModal.js' %}"></script> - <script src="{% static 'js/importlayer.js' %}"></script> - <script> - $(document).ready(function (){ - var ctx = { - xhrImportLayerUrl : "{% url 'xhr_importlayer' %}", - }; - - try { - importLayerPageInit(ctx); - } catch (e) { - document.write("Sorry, An error has occurred loading this page"); - console.warn(e); - } - }); - </script> - - <form class="span11"> - <fieldset> - <legend>Layer repository information</legend> - <span class="help-block">The layer you are importing must be compatible with <strong>{{project.release.description}}</strong>, which is the release you are using in this project.</span> - <div class="alert alert-error" id="import-error" style="display:none"> - <button type="button" class="close" data-dismiss="alert">×</button> - <h3> </h3> - <p></p> - <ul></ul> - </div> - - <div class="control-group" id="layer-name-ctrl"> - <label class="control-label air" for="import-layer-name"> - Layer name - <span class="icon-question-sign get-help" title="Something like 'meta-mylayer'. Your layer name must be unique and can only include letters, numbers and dashes"></span> - </label> - <div class="controls"> - <input id="import-layer-name" type="text" required autofocus data-autocomplete="off" data-provide="typeahead"> - <span class="help-inline" style="display: none;" id="invalid-layer-name-hint">A valid layer name can only include letters, numbers and dashes</span> - <span class="help-inline" style="display: none;" id="duplicated-layer-name-hint"></span> - </div> - - </div> - <div id="duplicate-layer-info" style="display:none"> - <div class="alert warning"> - <h3>A layer called <a href="" class="dup-layer-link"><span class="dup-layer-name"></span></a> already exists</h3> - <p>Layer names must be unqiue. Please use a different layer name.</p> - </div> - <dl> - <dt> - The <span class="dup-layer-name"></span> repository url is - </dt> - <dd> - <span id="dup-layer-vcs-url"></span> - </dd> - - <dt> - The <span class="dup-layer-name"></span> revision is - </dt> - <dd> - <span id="dup-layer-revision"></span> - </dd> - </dl> - - <p><a href="" class="dup-layer-link">View the <span class="dup-layer-name"></span> layer information</a></p> - - </div> - - <div class="fields-apart-from-layer-name"> - <label for="layer-git-repo-url" class="project-form"> - Git repository URL - <span class="icon-question-sign get-help" title="Fetch/clone URL of the repository. Currently, Toaster only supports Git repositories." ></span> - </label> - - <input type="text" id="layer-git-repo-url" class="input-xxlarge" required> - <label class="project-form" for="layer-subdir"> - Repository subdirectory - <span class="muted">(optional)</span> - <span class="icon-question-sign get-help" title="Subdirectory within the repository where the layer is located, if not in the root (usually only used if the repository contains more than one layer)"></span> - </label> - <input type="text" id="layer-subdir"> - - <div class="control-group" id="layer-revision-ctrl"> - <label class="control-label project-form" for="layer-git-ref">Git revision - <span class="icon-question-sign get-help" title="You can provide a Git branch, a tag or a commit SHA as the revision"></span> - </label> - <div class="controls"> - <input type="text" class="span3" id="layer-git-ref" required> - <span class="help-inline" style="diaply:none;" id="invalid-layer-revision-hint"></span> - </div> - </div> - </div> - - </fieldset> - - <div class="fields-apart-from-layer-name"> - <fieldset class="air"> - <legend> - Layer dependencies - <span class="muted">(optional)</span> - <span class="icon-question-sign get-help heading-help" title="Other layers this layer depends upon"></span> - </legend> - <ul class="unstyled configuration-list" id="layer-deps-list"> - </ul> - <div class="input-append"> - <input type="text" autocomplete="off" data-minLength="1" data-autocomplete="off" data-provide="typeahead" placeholder="Type a layer name" id="layer-dependency" class="input-xlarge"> - <a class="btn" id="add-layer-dependency-btn"> - Add layer - </a> - </div> - <span class="help-inline">You can only add layers Toaster knows about</span> - </fieldset> - <div class="air" id="form-actions"> - <button class="btn btn-primary btn-large" data-toggle="modal" id="import-and-add-btn" data-target="#dependencies-message" disabled>Import and add to project</button> - <span class="help-inline" id="import-and-add-hint" style="vertical-align: middle;">To import a layer you need to enter a layer name, a Git repository URL and a revision (branch, tag or commit)</span> - </div> - </div> - </form> - - {% else %} {#project and project release#} - <div class="page-header"> - <h1>Import layer</h1> - </div> - <div class="alert alert-info" id="import-error" > - <h3>Unsupported project type</h3> - <p>This project does not support importing layers.</p> - <ul></ul> - </div> - - {% endif %} +<div class="row"> + {% include "projecttopbar.html" %} + {% if project and project.release %} + <script src="{% static 'js/layerDepsModal.js' %}"></script> + <script src="{% static 'js/importlayer.js' %}"></script> + <script> + $(document).ready(function (){ + var ctx = { + xhrImportLayerUrl : "{% url 'xhr_importlayer' %}", + }; + + try { + importLayerPageInit(ctx); + } catch (e) { + document.write("Sorry, An error has occurred loading this page"); + console.warn(e); + } + }); + </script> + + <form class="col-md-11"> + <span class="help-block">The layer you are importing must be compatible with <strong>{{project.release.description}}</strong>, which is the release you are using in this project.</span> + <div class="alert alert-error" id="import-error" style="display:none"> + <button type="button" class="close" data-dismiss="alert">×</button> + <h3> </h3> + <p></p> + <ul></ul> + </div> + <fieldset> + <div class="form-group" id="layer-name-ctrl"> + <label class="control-label" for="import-layer-name"> + Layer name + <span class="glyphicon glyphicon-question-sign get-help" title="Something like 'meta-mylayer'. Your layer name must be unique and can only include letters, numbers and dashes"></span> + </label> + <input class="form-control" id="import-layer-name" type="text" required autofocus data-autocomplete="off" data-provide="typeahead"> + <span class="help-block" style="display: none;" id="invalid-layer-name-hint">A valid layer name can only include letters, numbers and dashes</span> + <span class="help-inline" style="display: none;" id="duplicated-layer-name-hint"></span> + </div> + </fieldset> + <div id="duplicate-layer-info" style="display:none"> + <div class="alert alert-warning"> + <h3>A layer called <a href="" class="dup-layer-link"><span class="dup-layer-name"></span></a> already exists</h3> + <p>Layer names must be unqiue. Please use a different layer name.</p> + <dl id="git-layer-dup" style="display:none;"> + <dt> + The <span class="dup-layer-name"></span> repository url is + </dt> + <dd> + <span id="dup-layer-vcs-url"></span> + </dd> + <dt> + The <span class="dup-layer-name"></span> revision is + </dt> + <dd> + <span id="dup-layer-revision"></span> + </dd> + </dl> + <dl id="local-layer-dup" style="display:none;"> + <dt> + The <span class="dup-layer-name"></span> directory is + </dt> + <dd> + <span id="dup-local-source-dir-name"></span> + </dd> + </dl> + <p><a href="" class="dup-layer-link">View the <span class="dup-layer-name"></span> layer information</a></p> + </div> + </div> + <fieldset class="fields-apart-from-layer-name" id="repo-select"> + <legend class="radioLegend">Where is the layer source code?</legend> + <div class="radio"> + <label> + <input type="radio" id="git-repo-radio" name="repo" value="git" checked="checked"> + In a <strong>Git repository</strong> + </label> + <p class="help-block radio-help">To build the layer Toaster must be able to access the Git repository, otherwise builds will fail. Toaster will fetch and checkout your chosen Git revision every time you start a build.</p> + </div> + <div class="radio"> + <label> + <input type="radio" id="local-dir-radio" name="repo" value="local"> + In a <strong>directory</strong> + </label> + <p class="help-block radio-help">Use this option for quick layer development, by simply providing the path to the layer source code.</p> + </div> + </fieldset> + <fieldset class="fields-apart-from-layer-name" id="git-repo"> + <legend>Git repository information</legend> + <div class="form-group"> + <label for="layer-git-repo-url"> + Git repository URL + <span class="glyphicon glyphicon-question-sign get-help" title="Fetch/clone URL of the repository. Currently, Toaster only supports Git repositories." ></span> + </label> + <input type="text" id="layer-git-repo-url" class="form-control" required> + </div> + <div class="form-group"> + <label for="layer-subdir"> + Repository subdirectory + <span class="text-muted">(optional)</span> + <span class="glyphicon glyphicon-question-sign get-help" title="Subdirectory within the repository where the layer is located, if not in the root (usually only used if the repository contains more than one layer)"></span> + </label> + <input type="text" class="form-control" id="layer-subdir"> + </div> + <div class="form-group" id="layer-revision-ctrl"> + <label for="layer-git-ref"> + Git revision + <span class="glyphicon glyphicon-question-sign get-help" title="You can provide a Git branch, a tag or a commit SHA as the revision"></span> + </label> + <input type="text" class="form-control" id="layer-git-ref" required> + <span class="help-inline" style="display:none;" id="invalid-layer-revision-hint"></span> + </div> + </fieldset> + + <fieldset class="fields-apart-from-layer-name" id="local-dir" style="display:none;"> + <legend>Layer directory information</legend> + <div class="form-group"> + <label for="local-dir-path" class="control-label">Enter the absolute path to the layer directory</label> + <input type="text" class="form-control" id="local-dir-path" required/> + <p class="help-block" id="hintError-dir-path-starts-with-slash" style="display:none;">The absolute path must start with "/".</p> + <p class="help-block" id="hintError-dir-path" style="display:none;">The directory path cannot include spaces or any of these characters: . \ ? % * : | " " < ></p> + </div> + </fieldset> + + <fieldset class="fields-apart-from-layer-name"> + <legend> + Layer dependencies + <small class="text-muted">(optional)</small> + <span class="glyphicon glyphicon-question-sign get-help heading-help" title="Other layers this layer depends upon"></span> + </legend> + <ul class="list-unstyled lead" id="layer-deps-list"> + </ul> + <div class="form-inline"> + <div class="form-group"> + <input type="text" autocomplete="off" data-minLength="1" data-autocomplete="off" data-provide="typeahead" placeholder="Type a layer name" id="layer-dependency" class="form-control"> + </div> + <button class="btn btn-default" id="add-layer-dependency-btn"> + Add layer + </button> + <span class="help-inline">You can only add layers Toaster knows about</span> + </fieldset> + <div class="top-air fields-apart-from-layer-name" id="form-actions"> + <button class="btn btn-primary btn-lg" data-toggle="modal" id="import-and-add-btn" data-target="#dependencies-message" disabled>Import and add to project</button> + <span class="help-inline" id="import-git-layer-and-add-hint" style="vertical-align: middle;">To import a layer you need to enter a layer name, a Git repository URL and a Git revision (branch, tag or commit)</span> + <span class="help-inline" id="import-local-dir-and-add-hint" style="vertical-align: middle;display:none;">To import a layer you need to enter a layer name and the absolute path to the layer directory</span> + </div> + </div> +</form> + + {% else %} {#project and project release#} + <div class="page-header"> + <h1>Import layer</h1> + </div> + <div class="alert alert-info" id="import-error" > + <h3>Unsupported project type</h3> + <p>This project does not support importing layers.</p> + <ul></ul> + </div> + + {% endif %} +</div> {% endblock %} diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/landing.html b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/landing.html index a1b5cdce7..498663294 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/landing.html +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/landing.html @@ -6,24 +6,22 @@ {% block title %} Welcome to Toaster {% endblock %} {% block pagecontent %} - <div class="container-fluid"> - <div class="row-fluid"> - <div class="hero-unit span12 well-transparent"> - <div class="row-fluid"> + <div class="row"> + <div class="jumbotron well-transparent"> - <div class="span6"> + <div class="col-md-6"> <h1>This is Toaster</h1> <p>A web interface to <a href="http://www.openembedded.org">OpenEmbedded</a> and <a href="http://www.yoctoproject.org/tools-resources/projects/bitbake">BitBake</a>, the <a href="http://www.yoctoproject.org">Yocto Project</a> build system.</p> {% if lvs_nos %} - <p class="hero-actions"> - <a class="btn btn-primary btn-large" href="{% url 'newproject' %}"> + <p class="top-air"> + <a class="btn btn-primary btn-lg" href="{% url 'newproject' %}"> To start building, create your first Toaster project </a> </p> {% else %} - <div class="alert alert-info lead air"> + <div class="alert alert-info lead top-air"> Toaster has no layer information. Without layer information, you cannot run builds. To generate layer information you can: <ul> <li> @@ -36,7 +34,7 @@ </div> {% endif %} - <ul class="unstyled"> + <ul class="list-unstyled lead"> <li> <a href="http://www.yoctoproject.org/docs/latest/toaster-manual/toaster-manual.html"> Read the Toaster manual @@ -51,12 +49,11 @@ </ul> </div> - <div class="span6"> - <img alt="Yocto Project" class="thumbnail" src="{% static 'img/toaster_bw.png' %}"/> + <div class="col-md-6"> + <img alt="Yocto Project Toaster" class="img-thumbnail" src="{% static 'img/toaster_bw.png' %}"/> </div> - </div> </div> </div> - </div> + {% endblock %} diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/landing_not_managed.html b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/landing_not_managed.html index 9b37f5530..baa4b72c1 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/landing_not_managed.html +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/landing_not_managed.html @@ -8,8 +8,8 @@ {% block pagecontent %} - <div class="container-fluid"> - <div class="row-fluid"> + <div class="container"> + <div class="row"> <!-- Empty - no build module --> <div class="page-header top-air"> <h1> diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/layer_btn.html b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/layer_btn.html index 10de37d4b..b2f73eba7 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/layer_btn.html +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/layer_btn.html @@ -1,17 +1,17 @@ -<button class="btn btn-danger btn-block layer-exists-{{data.pk}} layerbtn" data-layer='{ "id": {{data.pk}}, "name": "{{data.layer.name}}", "layerdetailurl": "{%url 'layerdetails' extra.pid data.pk%}"}' data-directive="remove" +<a class="btn btn-danger btn-block layer-exists-{{data.pk}} layerbtn" data-layer='{ "id": {{data.pk}}, "name": "{{data.layer.name}}", "layerdetailurl": "{%url 'layerdetails' extra.pid data.pk%}"}' data-directive="remove" {% if data.pk not in extra.current_layers %} style="display:none;" {% endif %} > - <i class="icon-trash"></i> + <span class="glyphicon glyphicon-trash"></span> Remove layer -</button> -<button class="btn btn-block layer-add-{{data.pk}} layerbtn" data-layer='{ "id": {{data.pk}}, "name": "{{data.layer.name}}", "layerdetailurl": "{%url 'layerdetails' extra.pid data.pk%}"}' data-directive="add" +</a> +<a class="btn btn-default btn-block layer-add-{{data.pk}} layerbtn" data-layer='{ "id": {{data.pk}}, "name": "{{data.layer.name}}", "layerdetailurl": "{%url 'layerdetails' extra.pid data.pk%}"}' data-directive="add" {% if data.pk in extra.current_layers %} style="display:none;" {% endif %} > - <i class="icon-plus"></i> + <span class="glyphicon glyphicon-plus"></span> Add layer -</button> +</a> diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/layerdetails.html b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/layerdetails.html index 82be3703b..f1569bd63 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/layerdetails.html +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/layerdetails.html @@ -6,270 +6,367 @@ {% block title %} {{layerversion.layer.name}} - {{project.name}} - Toaster {% endblock %} {% block pagecontent %} -<div class="section"> - <ul class="breadcrumb"> - <li> - <a href="{% url 'project' project.id %}">{{project.name}}</a> - <span class="divider">→</span> - </li> - <li><a href="{% url 'projectlayers' project.id %}">Compatible layers</a> - <span class="divider">→</span> - </li> - <li class="active"> - {{layerversion.layer.name}} ({{layerversion.get_vcs_reference|truncatechars:13}}) - </li> - </ul> +<div id="delete-layer-modal" class="modal fade" tabindex="-1" role="dialog" + data-keyboard="false" data-backdrop="static"> + <div class="modal-dialog" role="document"> + <div class="modal-content"> + <div class="modal-body"> + Are you sure you want to delete the <strong>{{layerversion.layer.name}}</strong> layer? + </div> + <div class="modal-footer"> + <button type="button" id="layer-delete-confirmed" class="btn + btn-primary">Delete layer</button> + <button type="button" class="btn btn-default btn-link" data-dismiss="modal">Cancel</button> + </div> + </div> + </div> </div> -{# If this is not an imported layer then hide the edit ui #} -{% if not layerversion.layer_source_id or layerversion.layer_source.sourcetype != layerversion.layer_source.TYPE_IMPORTED %} -<style scoped> - .icon-pencil { - display:none; - } -.delete-current-value{ - display: none; -} - li .icon-trash { - display:none; - } - .add-deps { - display:none; - } -</style> -{% endif %} - +<div class="row"> + <div class="col-md-12"> + <ul class="breadcrumb"> + <li> + <a href="{% url 'project' project.id %}">{{project.name}}</a> + <span class="divider">→</span> + </li> + <li><a href="{% url 'projectlayers' project.id %}">Compatible layers</a> + <span class="divider">→</span> + </li> + <li class="active"> + {{layerversion.layer.name}} ({{layerversion.get_vcs_reference|truncatechars:13}}) + </li> + </ul> -<script src="{% static 'js/layerdetails.js' %}"></script> -<script> + {# If this is not an imported layer then hide the edit ui #} + {% if layerversion.layer_source != layer_source.TYPE_IMPORTED %} + <style scoped> + .glyphicon-edit { + display:none; + } + .delete-current-value{ + display: none; + } + li .glyphicon-trash { + display:none; + } + .add-deps { + display:none; + } + </style> + {% endif %} - $(document).ready(function (){ - var ctx = { - xhrUpdateLayerUrl : "{% url 'xhr_updatelayer' %}", - layerVersion : { - name : "{{layerversion.layer.name}}", - id : {{layerversion.id}}, - commit: "{{layerversion.get_vcs_reference}}", - {%if layerversion.id in projectlayers %} - inCurrentPrj : true, - {% else %} - inCurrentPrj : false, - {% endif %} - layerdetailurl : "{% url 'layerdetails' project.id layerversion.id %}", - sourceId: {{layerversion.layer_source_id|json}}, - } - }; + <script src="{% static 'js/layerdetails.js' %}"></script> + <script> - try { - layerDetailsPageInit(ctx); - } catch (e) { - document.write("Sorry, An error has occurred loading this page"); - console.warn(e); - } - }); -</script> + $(document).ready(function(){ + var ctx = { + xhrUpdateLayerUrl : "{% url 'xhr_layer' project.id layerversion.pk %}", + layerVersion : { + name : "{{layerversion.layer.name}}", + id : {{layerversion.id}}, + commit: "{{layerversion.get_vcs_reference}}", + {%if layerversion.id in projectlayers %} + inCurrentPrj : true, + {% else %} + inCurrentPrj : false, + {% endif %} + layerdetailurl : "{% url 'layerdetails' project.id layerversion.id %}", + layer_source: {{layerversion.layer_source|json}}, + }, + layerSourceTypes: {{layer_source|json}}, + }; -<div class="row-fluid span11"> - <div class="page-header"> - <h1>{{layerversion.layer.name}} <small class="commit" - {% if layerversion.get_vcs_reference|length > 13 %} - data-toggle="tooltip" title="{{layerversion.get_vcs_reference}}" - {% endif %}> - ({{layerversion.get_vcs_reference|truncatechars:13}})</small></h1> - </div> -</div> + try { + layerDetailsPageInit(ctx); + } catch (e) { + document.write("Sorry, An error has occurred loading this page"); + console.warn(e); + } + }); + </script> -<!-- container for tabs --> -<div class="row-fluid span7 tabbable"> - <div class="alert alert-info lead" id="alert-area" style="display:none"> - <button type="button" class="close" id="dismiss-alert">×</button> - <span id="alert-msg"></span> - </div> - <ul class="nav nav-pills"> - <li class="active"> - <a data-toggle="tab" href="#information" id="details-tab">Layer details</a> - </li> - <li> - <a data-toggle="tab" href="#recipes" class="muted" id="targets-tab">Recipes (<span class="table-count-recipestable"></span>)</a> - </li> - <li> - <a data-toggle="tab" href="#machines" class="muted" id="machines-tab">Machines (<span class="table-count-machinestable"></span>)</a> - </li> - </ul> - <div class="tab-content"> - <span class="button-place"> - {% if layerversion.id not in projectlayers %} - <button id="add-remove-layer-btn" data-directive="add" class="btn btn-large btn-block"> - <span class="icon-plus"></span> - Add the {{layerversion.layer.name}} layer to your project - </button> + <div class="page-header"> + {% if layerversion.layer.local_source_dir %} + <h1>{{layerversion.layer.name}} <small class="commit" style="display:none;"></small> + </h1> {% else %} - <button id="add-remove-layer-btn" data-directive="remove" class="btn btn-block btn-large btn-danger"> - <span class="icon-trash"></span> - Remove the {{layerversion.layer.name}} layer from your project - </button> + <h1>{{layerversion.layer.name}} <small class="commit" + {% if layerversion.get_vcs_reference|length > 13 %} + data-toggle="tooltip" title="{{layerversion.get_vcs_reference}}" + {% endif %}>({{layerversion.get_vcs_reference|truncatechars:13}})</small> + </h1> {% endif %} - </span> + </div> + <div class="row"> + <!-- container for tabs --> + <div class="col-md-8 tabbable"> + <div class="alert alert-info lead" id="alert-area" style="display:none"> + <button type="button" class="close" id="dismiss-alert">×</button> + <span id="alert-msg"></span> + </div> - <!-- layer details pane --> - <div id="information" class="tab-pane active"> - <dl class="dl-horizontal"> - <dt class=""> - <i class="icon-question-sign get-help" title="Fetch/clone URL of the repository"></i> - Repository URL - </dt> - <dd> - <span class="current-value">{{layerversion.layer.vcs_url}}</span> - {% if layerversion.get_vcs_link_url %} - <a href="{{layerversion.get_vcs_link_url}}/" class="icon-share get-info" target="_blank"></a> - {% endif %} - <form id="change-repo-form" class="control-group" style="display:none"> - <div class="input-append"> - <input type="text" class="input-xlarge" value="{{layerversion.layer.vcs_url}}"> - <button data-layer-prop="vcs_url" class="btn change-btn" type="button">Save</button> - <a href="#" style="display:none" class="btn btn-link cancel">Cancel</a> - </div> - </form> - <i class="icon-pencil" ></i> - </dd> - <dt> - <i class="icon-question-sign get-help" title="Subdirectory within the repository where the layer is located, if not in the root (usually only used if the repository contains more than one layer)"></i> - Repository subdirectory - </dt> - <dd> - <span class="muted" style="display:none">Not set</span> - <span class="current-value">{{layerversion.dirpath}}</span> - {% if layerversion.get_vcs_dirpath_link_url %} - <a href="{{layerversion.get_vcs_dirpath_link_url}}" class="icon-share get-info" target="_blank"></a> - {% endif %} - <form id="change-subdir-form" style="display:none;"> - <div class="input-append"> - <input type="text" value="{{layerversion.dirpath}}"> - <button data-layer-prop="dirpath" class="btn change-btn" type="button">Save</button> - <a href="#" style="display:none" class="btn btn-link cancel">Cancel</a> + {% if layerversion.id not in projectlayers %} + <button id="add-remove-layer-btn" data-directive="add" class="btn btn-default btn-lg btn-block"> + <span class="glyphicon glyphicon-plus"></span> + Add the {{layerversion.layer.name}} layer to your project + </button> + {% else %} + <button id="add-remove-layer-btn" data-directive="remove" class="btn btn-default btn-block btn-lg btn-danger"> + <span class="glyphicon glyphicon-trash"></span> + Remove the {{layerversion.layer.name}} layer from your project + </button> + {% endif %} + + <ul class="nav nav-tabs"> + <li class="active"> + <a data-toggle="tab" href="#information" id="details-tab">Layer details</a> + </li> + <li> + <a data-toggle="tab" href="#recipes" class="text-muted" id="targets-tab">Recipes (<span class="table-count-recipestable"></span>)</a> + </li> + <li> + <a data-toggle="tab" href="#machines" class="text-muted" id="machines-tab">Machines (<span class="table-count-machinestable"></span>)</a> + </li> + </ul> + <div class="tab-content"> + + <!-- layer details pane --> + <div id="information" class="tab-pane active"> + <h3>Layer source code location</h3> + {% if layerversion.layer.local_source_dir %} + <dl class="dl-horizontal" id="directory-info"> + <dt> + Path to the layer directory + </dt> + <dd> + <code>{{layerversion.layer.local_source_dir}}</code> + </dd> + </dl> + {% else %} + <dl class="dl-horizontal" id="git-repo-info"> + <dt class=""> + <span class="glyphicon glyphicon-question-sign get-help" title="Fetch/clone URL of the repository"></span> + Repository URL + </dt> + <dd> + <span class="current-value">{{layerversion.layer.vcs_url}}</span> + {% if layerversion.get_vcs_link_url %} + <a href="{{layerversion.get_vcs_link_url}}/" class="glyphicon glyphicon-new-window" target="_blank"></a> + {% endif %} + <form id="change-repo-form" class="form-inline" style="display:none"> + <div class="form-group"> + <input type="text" class="form-control" value="{{layerversion.layer.vcs_url}}"> </div> </form> - <i id="change-subdir" class="icon-pencil"></i> - <span class="icon-trash delete-current-value" data-toggle="tooltip" title="Delete"></span> - </dd> - <dt> - <i class="icon-question-sign get-help" title="The Git branch, tag or commit"></i> - Git revision - </dt> - <dd> - <span class="current-value">{{layerversion.get_vcs_reference}}</span> - <form style="display:none;"> - <div class="input-append"> - <input type="text" value="{{layerversion.get_vcs_reference}}"> - <button data-layer-prop="commit" class="btn change-btn" type="button">Save</button> - <a href="#" style="display:none" class="btn btn-link cancel">Cancel</a> - </div> - </form> - <i class="icon-pencil"></i> </dd> + {% if layerversion.dirpath %} <dt> - <i class="icon-question-sign get-help" title="Other layers this layer depends upon"></i> - Layer dependencies + <span class="glyphicon glyphicon-question-sign get-help" title="Subdirectory within the repository where the layer is located, if not in the root (usually only used if the repository contains more than one layer)"></span> + Repository subdirectory </dt> <dd> - <ul class="unstyled current-value" id="layer-deps-list"> - {% for ld in layerversion.dependencies.all %} - <li data-layer-id="{{ld.depends_on.id}}"> - <a data-toggle="tooltip" title="{{ld.depends_on.layer.vcs_url}} | {{ld.depends_on.get_vcs_reference}}" href="{% url 'layerdetails' project.id ld.depends_on.id %}">{{ld.depends_on.layer.name}}</a> - <span class="icon-trash " data-toggle="tooltip" title="Delete"></span> - </li> - {% endfor %} - </ul> - <div class="input-append add-deps"> - <input type="text" autocomplete="off" data-minLength="1" data-autocomplete="off" placeholder="Type a layer name" id="layer-dep-input"> - <a class="btn" id="add-layer-dependency-btn" > - Add layer - </a> - </div> - <span class="help-block add-deps">You can only add layers Toaster knows about</span> - </dd> - </dl> - </div> - <!-- end layerdetails tab --> - <!-- targets tab --> - <div id="recipes" class="tab-pane"> - <!-- Recipe table --> - <div id="no-recipes-yet" class="alert alert-info" style="display:none"> - <p>Toaster does not have recipe information for the <strong> {{layerversion.layer.name}} </strong> layer.</p> - <p>Toaster learns about layers when you build them. If this layer provides any recipes, they will be listed here after you build the <strong> {{layerversion.layer.name}} </strong> layer.</p> - </div> + <span class="text-muted" style="display:none">Not set</span> + <span class="current-value">{{layerversion.dirpath}}</span> + {% if layerversion.get_vcs_dirpath_link_url %} + <a href="{{layerversion.get_vcs_dirpath_link_url}}" class="glyphicon glyphicon-new-window" target="_blank"></a> + {% endif %} + <form id="change-subdir-form" class="form-inline" style="display:none;"> + <div class="form-group"> + <input type="text" class="form-control" value="{{layerversion.dirpath}}"> + </div> + </form> + </dd> + {% endif %} + <dt> + <span class="glyphicon glyphicon-question-sign get-help" title="The Git branch, tag or commit"></span> + Git revision + </dt> + <dd> + <span class="current-value">{{layerversion.get_vcs_reference}}</span> + <form style="display:none;" class="form-inline"> + <div class="form-group"> + <input type="text" class="form-control" value="{{layerversion.get_vcs_reference}}"> + </div> + </form> + </dd> + </dl> + {% endif %} + {% if layerversion.layer_source == layer_source.TYPE_IMPORTED %} + <button class="btn btn-default btn-lg" id="edit-layer-source" style="margin-left:220px;">Edit layer source code location</button> + {% endif %} + <form id="edit-layer-source-form" style="display:none;"> + <fieldset> + <legend class="radioLegend">Where is the layer source code?</legend> + <div class="radio"> + <label> + <input type="radio" name="source-location" id="repo" value="repo"> + In a <strong>Git repository</strong> + </label> + <p class="help-block" style="margin-left:20px;width:70%;">To build the layer Toaster must be able to access the Git repository, otherwise builds will fail. Toaster will fetch and checkout your chosen Git revision every time you start a build.</p> + </div> + <div class="radio" style="margin-top:15px;"> + <label> + <input type="radio" name="source-location" id="dir" value="dir" checked> + In a <strong>directory</strong> + </label> + <p class="help-block" style="margin-left:20px;width:70%;">Use this option for quick layer development, by simply providing the path to the layer source code.</p> + </div> + </fieldset> + <fieldset id="layer-git"> + <legend>Git repository information</legend> + <div class="form-group"> + <label for="layer-git-repo-url"> + Git repository URL + <span class="glyphicon glyphicon-question-sign get-help" title="Fetch/clone URL of the repository. Currently, Toaster only supports Git repositories." ></span> + </label> + <input type="text" id="layer-git-repo-url" class="form-control" value="{{layerversion.layer.vcs_url|default_if_none:''}}"> + </div> + <div class="form-group"> + <label for="layer-subdir"> + Repository subdirectory + <span class="text-muted">(optional)</span> + <span class="glyphicon glyphicon-question-sign get-help" title="Subdirectory within the repository where the layer is located, if not in the root (usually only used if the repository contains more than one layer)"></span> + </label> + <input type="text" class="form-control" id="layer-subdir" value="{{layerversion.dirpath|default_if_none:''}}"> + </div> + <div class="form-group" id="layer-revision-ctrl"> + <label for="layer-git-ref">Git revision + <span class="glyphicon glyphicon-question-sign get-help" title="You can provide a Git branch, a tag or a commit SHA as the revision"></span> + </label> + <input type="text" class="form-control" id="layer-git-ref" value="{{layerversion.get_vcs_reference|default_if_none:''}}"> + <span class="help-inline" style="display:none;" id="invalid-layer-revision-hint"></span> + </div> + </fieldset> + <fieldset id="layer-dir"> + <legend>Layer directory information</legend> + <div class="form-group"> + <label for="layer-dir-path"> + Enter the absolute path to the layer directory + </label> + <input type="text" id="layer-dir-path-in-details" class="form-control" value="{{layerversion.layer.local_source_dir}}" required> + </div> + </fieldset> - {% url 'layerrecipestable' project.id layerversion.id as xhr_table_url %} - {% with "recipestable" as table_name %} - {% with "Recipes" as title %} - {% include 'toastertable-simple.html' %} - {% endwith %} - {% endwith %} - </div> + <div style="margin-top:25px;"> + <a href="#" class="btn btn-primary btn-lg" id="save-changes-for-switch">Save changes</a> + <a href="#" class="btn btn-link btn-lg" id="cancel-changes-for-switch">Cancel</a> + </div> + </form> - <div id="machines" class="tab-pane"> + <h3 class="top-air">Layer dependencies + <span class="glyphicon glyphicon-question-sign get-help" title="Other layers this layer depends upon"></span> + </h3> - <div id="no-machines-yet" class="alert alert-info" style="display:none"> - <p>Toaster does not have machine information for the <strong> {{layerversion.layer.name}} </strong> layer.</p> - <p>Toaster learns about layers when you build them. If this layer provides any machines, they will be listed here after you build the <strong> {{layerversion.layer.name}} </strong> layer.</p> - </div> + <ul class="list-unstyled current-value lead" id="layer-deps-list"> + {% for ld in layerversion.dependencies.all %} + <li data-layer-id="{{ld.depends_on.id}}"> + <a data-toggle="tooltip" title="{{ld.depends_on.layer.vcs_url}} | {{ld.depends_on.get_vcs_reference}}" href="{% url 'layerdetails' project.id ld.depends_on.id %}">{{ld.depends_on.layer.name}}</a> + <span class="glyphicon glyphicon-trash " data-toggle="tooltip" title="Delete"></span> + </li> + {% endfor %} + </ul> + <form class="form-inline add-deps"> + <div class="form-group"> + <input class="form-control" type="text" autocomplete="off" data-minLength="1" data-autocomplete="off" placeholder="Type a layer name" id="layer-dep-input"> + </div> + <a class="btn btn-default" id="add-layer-dependency-btn" disabled="disabled"> + Add layer + </a> + <span class="help-block add-deps">You can only add layers Toaster knows about</span> + </form> + </div> + <!-- end layerdetails tab --> + <!-- targets tab --> + <div id="recipes" class="tab-pane"> + <!-- Recipe table --> + <div id="no-recipes-yet" class="alert alert-info" style="display:none"> + <p>Toaster does not have recipe information for the <strong> {{layerversion.layer.name}} </strong> layer.</p> + <p>Toaster learns about layers when you build them. If this layer provides any recipes, they will be listed here after you build the <strong> {{layerversion.layer.name}} </strong> layer.</p> + </div> + {% url 'layerrecipestable' project.id layerversion.id as xhr_table_url %} + {% with "recipestable" as table_name %} + {% with "Recipes" as title %} + {% include 'toastertable-simple.html' %} + {% endwith %} + {% endwith %} + </div> + <div id="machines" class="tab-pane"> - <!-- Machines table --> - {% url 'layermachinestable' project.id layerversion.id as xhr_table_url %} - {% with "machinestable" as table_name %} - {% with "Machines" as title %} - {% include 'toastertable-simple.html' %} - {% endwith %} - {% endwith %} + <div id="no-machines-yet" class="alert alert-info" style="display:none"> + <p>Toaster does not have machine information for the <strong> {{layerversion.layer.name}} </strong> layer.</p> + <p>Sadly, machine information cannot be obtained from builds, so this page will remain empty.</p> </div> - </div> <!-- end tab content --> - </div> <!-- end tabable --> - <div class="row-fluid span4 well"> <!-- info side panel --> + + <!-- Machines table --> + {% url 'layermachinestable' project.id layerversion.id as xhr_table_url %} + {% with "machinestable" as table_name %} + {% with "Machines" as title %} + {% include 'toastertable-simple.html' %} + {% endwith %} + {% endwith %} + </div> + </div> <!-- end tab content --> + </div> <!-- end tabable --> + + <div class="col-md-4"> <!-- info side panel --> + <div class="well"> <h2>About {{layerversion.layer.name}}</h2> <dl class="item-info"> <dt> - Summary - <i class="icon-question-sign get-help" title="One-line description of the layer"></i> + Summary + <span class="glyphicon glyphicon-question-sign get-help" title="One-line description of the layer"></span> </dt> <dd> - <span class="muted" style="display:none">Not set</span> - <span class="current-value">{{layerversion.layer.summary|default_if_none:''}}</span> - <form style="display:none; margin-bottom:20px"> - <textarea class="span12" rows="2">{% if layerversion.layer.summary %}{{layerversion.layer.summary}}{% endif %}</textarea> - <button class="btn change-btn" data-layer-prop="summary" type="button">Save</button> - <a href="#" class="btn btn-link cancel">Cancel</a> - </form> - <i class="icon-pencil"></i> - <span class="icon-trash delete-current-value" data-toggle="tooltip" title="Delete"></span> + <span class="text-muted" style="display:none">Not set</span> + <span class="current-value">{{layerversion.layer.summary|default_if_none:''}}</span> + <form style="display:none; margin-bottom:20px; margin-top:5px;"> + <div class="form-group"> + <textarea class="form-control" rows="2">{% if layerversion.layer.summary %}{{layerversion.layer.summary}}{% endif %}</textarea> + </div> + <button class="btn btn-default change-btn" data-layer-prop="summary" type="button">Save</button> + <a href="#" class="btn btn-link cancel">Cancel</a> + </form> + <span class="glyphicon glyphicon-edit"></span> + <span class="glyphicon glyphicon-trash delete-current-value" data-toggle="tooltip" title="Delete"></span> </dd> <dt> - Description + Description </dt> <dd> - <span class="muted" style="display:none">Not set</span> - <span class="current-value">{{layerversion.layer.description|default_if_none:''}}</span> - <form style="display:none; margin-bottom:20px"> - <textarea class="span12" rows="6">{% if layerversion.layer.description %}{{layerversion.layer.description}}{% endif %}</textarea> - <button class="btn change-btn" data-layer-prop="description" type="button" >Save</button> - <a href="#" class="btn btn-link cancel">Cancel</a> - </form> - <i class="icon-pencil"></i> - <span class="icon-trash delete-current-value" data-toggle="tooltip" title="Delete"></span> + <span class="text-muted" style="display:none">Not set</span> + <span class="current-value">{{layerversion.layer.description|default_if_none:''}}</span> + <form style="display:none; margin-bottom:20px; margin-top:5px;"> + <div class="form-group"> + <textarea class="form-control" rows="6">{% if layerversion.layer.description %}{{layerversion.layer.description}}{% endif %}</textarea> + </div> + <button class="btn btn-default change-btn" data-layer-prop="description" type="button" >Save</button> + <a href="#" class="btn btn-link cancel">Cancel</a> + </form> + <span class="glyphicon glyphicon-edit"></span> + <span class="glyphicon glyphicon-trash delete-current-value" data-toggle="tooltip" title="Delete"></span> </dd> - {% if layerversion.layer.up_id %} + {% if layerversion.layer_source == layer_source.TYPE_LAYERINDEX %} <dt>Layer index</dt> <dd> - <a href="http://layers.openembedded.org/layerindex/branch/{{layerversion.up_branch.name}}/layer/{{layerversion.layer.name}}">layer index link</a> - - </dd> + <a href="http://layers.openembedded.org/layerindex/branch/{{layerversion.release.name}}/layer/{{layerversion.layer.name}}">Layer index {{layerversion.layer.name}}</a> + </dd> + {% endif %} + </dl> + {# Only show delete link for imported layers #} + {% if layerversion.layer_source == layer_source.TYPE_IMPORTED %} + <i class="icon-trash text-danger"></i> + <a href="#delete-layer-modal" role="button" class="text-danger" + data-toggle="modal" data-target="#delete-layer-modal">Delete layer</a> {% endif %} - - </dl> + </div> </div> + </div> + </div> <!-- close column 12 div --> +</div> <!-- close top row div --> - {% endblock %} +{% endblock %} diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/machine_btn.html b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/machine_btn.html index 7b08f6a9a..5d93d7aa9 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/machine_btn.html +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/machine_btn.html @@ -1,16 +1,16 @@ -<a href="{% url 'project' extra.pid %}?setMachine={{data.name}}" class="btn btn-block layer-exists-{{data.layer_version.id}}" +<a href="{% url 'project' extra.pid %}?setMachine={{data.name}}" class="btn btn-default btn-block layer-exists-{{data.layer_version.id}}" {% if data.layer_version.pk not in extra.current_layers %} style="display:none;" {% endif %} > Select machine</a> -<button class="btn btn-block layerbtn layer-add-{{data.layer_version.id}}" data-layer='{ "id": {{data.layer_version.id}}, "name": "{{data.layer_version.layer.name}}", "layerdetailurl": "{%url 'layerdetails' extra.pid data.layer_version.id %}"}' data-directive="add" +<a class="btn btn-default btn-block layerbtn layer-add-{{data.layer_version.id}}" data-layer='{ "id": {{data.layer_version.id}}, "name": "{{data.layer_version.layer.name}}", "layerdetailurl": "{%url 'layerdetails' extra.pid data.layer_version.id %}"}' data-directive="add" {% if data.layer_version.pk in extra.current_layers %} style="display:none;" {% endif %} > - <i class="icon-plus"></i> + <span class="glyphicon glyphicon-plus"></span> Add layer - <i title="" class="icon-question-sign get-help" data-original-title="To enable this machine, you must first add the {{data.layer_version.layer.name}} layer to your project"></i> -</button> + <span class="glyphicon glyphicon-question-sign get-help" title="To select this machine, you must first add the {{data.layer_version.layer.name}} layer to your project"></i> +</a> diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/mrb_section.html b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/mrb_section.html index b5e798d7c..b761ffe1d 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/mrb_section.html +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/mrb_section.html @@ -1,178 +1,274 @@ {% load static %} -{% load projecttags %} -{% load project_url_tag %} {% load humanize %} +{% load project_url_tag %} <script src="{% static 'js/mrbsection.js' %}"></script> -<script> - $(document).ready(function () { - var ctx = { - mrbType : "{{mrb_type}}", - } - try { - mrbSectionInit(ctx); - } catch (e) { - document.write("Sorry, An error has occurred loading this page"); - console.warn(e); - } - }); -</script> - -{%if mru and mru.count > 0%} - - {%if mrb_type == 'project' %} - <h2 class="page-header"> +{% if mru %} + {% if mrb_type == 'project' %} + <h2> Latest project builds {% if project.is_default %} - <i class="icon-question-sign get-help heading-help" title="" data-original-title="Builds in this project cannot be started from Toaster: they are started from the command line"></i> - {% endif %} - </h2> + <span class="glyphicon glyphicon-question-sign get-help heading-help" data-original-title="Builds in this project cannot be started from Toaster: they are started from the command line"></span> + {% endif %} + </h2> {% else %} - <div class="page-header top-air"> - <h1> - Latest builds - </h1> + <div class="page-header"> + <h1>Latest builds</h1> </div> {% endif %} + <div id="latest-builds"> - {% for build in mru %} - <div data-latest-build-result="{{ build.id }}" class="alert build-result {%if build.outcome == build.SUCCEEDED%}alert-success{%elif build.outcome == build.FAILED%}alert-error{%else%}alert-info{%endif%}{% if mrb_type != 'project' %} project-name{% endif %}"> - {% if mrb_type != 'project' %} - <span class="label {%if build.outcome == build.SUCCEEDED%}label-success{%elif build.outcome == build.FAILED%}label-important{%else%}label-info{%endif%}"> - <a href={% project_url build.project %}> - {{build.project.name}} - </a> - </span> - {% endif %} - <div class="row-fluid"> - <div class="span3 lead"> - {%if build.outcome == build.SUCCEEDED or build.outcome == build.FAILED %} - <a href="{%url 'builddashboard' build.pk%}" class="{%if build.outcome == build.SUCCEEDED %}success{%else%}error{%endif%}"> - {% endif %} - {% if build.target_set.all.count > 0 %} - <span data-toggle="tooltip" - {% if build.target_set.all.count > 1 %} - {{build.get_sorted_target_list.0.target}} - title="Recipes: - {% for target in build.get_sorted_target_list %} - {% if target.task %} - {{target.target}}:{{target.task}} - {% else %} - {{target.target}} - {% endif %} - {% endfor %}" - {% endif %} - > - {% if build.target_set.all.0.task %} - {{build.get_sorted_target_list.0.target}}:{{build.target_set.all.0.task}} - {% else %} - {{build.get_sorted_target_list.0.target}} - {% endif %} - {% if build.target_set.all.count > 1 %} - (+{{build.target_set.all.count|add:"-1"}}) - {% endif %} - </span> - {% endif %} - {%if build.outcome == build.SUCCEEDED or build.outcome == build.FAILED %} + {% for build in mru %} + <div data-latest-build-result="{{build.id}}" class="alert build-result {% if build.outcome == build.SUCCEEDED %}alert-success{% elif build.outcome == build.FAILED %}alert-danger{% else %}alert-info{% endif %}"> + <!-- project title --> + {% if mrb_type != 'project' %} + <div class="row project-name"> + <div class="col-md-12"> + <small> + <a class="alert-link text-uppercase" href="{% project_url build.project %}"> + {{build.project.name}} </a> - {% endif %} - </div> - {% if build.outcome == build.SUCCEEDED or build.outcome == build.FAILED %} - <div class="span2 lead"> - {% if build.completed_on|format_build_date %} - {{ build.completed_on|date:'d/m/y H:i' }} - {% else %} - {{ build.completed_on|date:'H:i' }} - {% endif %} - </div> - {% endif %} - {%if build.outcome == build.SUCCEEDED or build.outcome == build.FAILED %} - <div class="span2 lead"> - {% if build.errors.count %} - <i class="icon-minus-sign red"></i> <a href="{%url 'builddashboard' build.pk%}#errors" class="error">{{build.errors.count}} error{{build.errors.count|pluralize}}</a> - {% endif %} - </div> - <div class="span2 lead"> - {% if build.warnings.count %} - <i class="icon-warning-sign yellow"></i> <a href="{%url 'builddashboard' build.pk%}#warnings" class="warning">{{build.warnings.count}} warning{{build.warnings.count|pluralize}}</a> - {% endif %} - </div> - <div class="lead "> - <span class="lead"> - Build time: <a href="{% url 'buildtime' build.pk %}">{{ build.timespent_seconds|sectohms }}</a> - </span> - {% if build.project.is_default %} - <i class="pull-right icon-question-sign get-help - {% if build.outcome == build.SUCCEEDED %} - get-help-green - {% elif build.outcome == build.FAILED %} - get-help-red - {% else %} - get-help-blue - {% endif %} - " title="Builds in this project cannot be started from Toaster: they are started from the command line"> - </i> - {% else %} - <button class="run-again-btn btn - {% if build.outcome == build.SUCCEEDED %} - btn-success - {% elif build.outcome == build.FAILED %} - btn-danger - {% else %} - btn-info - {%endif%} - pull-right" - data-request-url="{% url 'xhr_buildrequest' build.project.pk %}" - data-target='{{build.target_set.all|get_tasks|json}}'> - - - Rebuild - </button> - {% endif %} + </small> </div> - {%endif%} - {%if build.outcome == build.IN_PROGRESS %} - <div class="span4" style="display:none" id="cancelling-msg-{{build.buildrequest.pk}}"> - <p class="lead">Cancelling the build ...</p> - </div> - <div class="span4 offset1 progress-info"> - <div class="progress" id="build-pc-done-title-{{build.pk}}" style="margin-top:5px;" data-toggle="tooltip" title="{{build.completeper}}% of tasks complete"> - <div id="build-pc-done-bar-{{build.pk}}" style="width: {{build.completeper}}%;" class="bar"></div> + </div> + {% endif %} + + <div class="row" data-role="build-status-container"> + <div class="col-md-12"> + Loading... + </div> + </div> + </div> + {% endfor %} + </div> +{% endif %} + +<!-- build main template --> +<script id="build-template" type="text/x-jsrender"> + <div class="col-md-3"> + <!-- only show link for completed builds --> + <%if state == 'Succeeded' || state == 'Failed'%> + <a class="alert-link" href="<%:dashboard_url%>"> + <span data-toggle="tooltip" data-role="targets-text" title="Recipes: <%:targets%>"> + <%:targets_abbreviated%> + </span> + </a> + <%else targets_abbreviated !== ''%> + <span data-toggle="tooltip" data-role="targets-text" title="Recipes: <%:targets%>"> + <%:targets_abbreviated%> + </span> + <%else%> + Fetching recipe names... + <%/if%> + </div> + + <div data-build-state="<%:state%>"> + <%if state == 'Parsing'%> + <%include tmpl='#parsing-recipes-build-template'/%> + <%else state == 'Queued'%> + <%include tmpl='#queued-build-template'/%> + <%else state == 'Succeeded' || state == 'Failed'%> + <%include tmpl='#succeeded-or-failed-build-template'/%> + <%else state == 'Cancelling'%> + <%include tmpl='#cancelling-build-template'/%> + <%else state == 'Starting'%> + <%include tmpl='#starting-template'/%> + <%else state == 'In Progress'%> + <%include tmpl='#in-progress-build-template'/%> + <%else state == 'Cancelled'%> + <%include tmpl='#cancelled-build-template'/%> + <%/if%> + </div> +</script> + +<!-- queued build --> +<script id="queued-build-template" type="text/x-jsrender"> + <div class="col-md-5"> + <span class="glyphicon glyphicon-question-sign get-help get-help-blue" + title="This build is waiting for the build directory to become available"> + </span> + + Build queued + </div> + + <div class="col-md-4"> + <!-- cancel button --> + <%include tmpl='#cancel-template'/%> + </div> +</script> + +<!-- parsing recipes build --> +<script id="parsing-recipes-build-template" type="text/x-jsrender"> + <!-- progress bar and parse completion percentage --> + <div data-role="build-status" class="col-md-4 col-md-offset-1 progress-info"> + <!-- progress bar --> + <div class="progress"> + <div id="recipes-parsed-percentage-bar-<%:id%>" + style="width: <%:recipes_parsed_percentage%>%;" + class="progress-bar"> </div> </div> - <div class="lead span3 progress-info"><span id="build-pc-done-{{build.pk}}">{{build.completeper}}</span>% of tasks complete</div> - {# No build cancel for command line builds project #} - {% if build.project.is_default %} - <i class="icon-question-sign get-help get-help-blue pull-right" title="" data-original-title="Builds in this project cannot be cancelled from Toaster: they can only be cancalled from the command line"></i> - {% else %} - <div class="lead pull-right progress-info"> - <button class="cancel-build-btn btn btn-info pull-right" - data-buildrequest-id={{build.buildrequest.pk}} - data-request-url="{% url 'xhr_buildrequest' build.project.pk %}" > - Cancel - </button> - </div> - {% endif %} + </div> + + <div class="col-md-4 progress-info"> + <!-- parse completion percentage --> + <span class="glyphicon glyphicon-question-sign get-help get-help-blue" + title="BitBake is parsing the layers required for your build"> + </span> + + Parsing <span id="recipes-parsed-percentage-<%:id%>"><%:recipes_parsed_percentage%></span>% complete + + <%include tmpl='#cancel-template'/%> + </div> +</script> + +<!-- in progress build; tasks still starting --> +<script id="starting-template" type="text/x-jsrender"> + <div class="col-md-5"> + <span class="glyphicon glyphicon-question-sign get-help get-help-blue" + title="This build is waiting for tasks to start"> + </span> + + Tasks starting... + </div> - {%endif%} {# end if in progress #} + <div class="col-md-4"> + <!-- cancel button --> + <%include tmpl='#cancel-template'/%> + </div> +</script> - {% if build.outcome == build.CANCELLED %} - <div class="span4"> - <p class="lead">Build cancelled</p> +<!-- in progress build; at least one task finished --> +<script id="in-progress-build-template" type="text/x-jsrender"> + <!-- progress bar and task completion percentage --> + <div data-role="build-status" class="col-md-4 col-md-offset-1 progress-info"> + <!-- progress bar --> + <div class="progress" id="build-pc-done-title-<%:id%>"> + <div id="build-pc-done-bar-<%:id%>" + style="width: <%:tasks_complete_percentage%>%;" + class="progress-bar"> + </div> </div> - <button class="btn btn-info pull-right run-again-btn" - data-request-url="{% url 'xhr_buildrequest' build.project.pk %}" - data-target='{{build.target_set.all|get_tasks|json}}'> - Rebuild + </div> + + <div class="col-md-4 progress-info"> + <!-- task completion percentage --> + <span id="build-pc-done-<%:id%>"><%:tasks_complete_percentage%></span>% of + tasks complete + + <!-- cancel button --> + <%include tmpl='#cancel-template'/%> + </div> +</script> + +<!-- cancelling build --> +<script id="cancelling-build-template" type="text/x-jsrender"> + <div class="col-md-9"> + Cancelling the build ... + </div> +</script> + +<!-- succeeded or failed build --> +<script id="succeeded-or-failed-build-template" type="text/x-jsrender"> + <!-- completed_on --> + <div class="col-md-2"> + <%:completed_on%> + </div> + + <!-- errors --> + <div class="col-md-2"> + <%if errors%> + <span class="glyphicon glyphicon-minus-sign"></span> + <a href="<%:dashboard_errors_url%>" class="alert-link"> + <%:errors%> error<%:errors_pluralise%> + </a> + <%/if%> + </div> + + <!-- warnings --> + <div class="col-md-2"> + <%if warnings%> + <span class="glyphicon glyphicon-warning-sign build-warnings"></span> + <a href="<%:dashboard_warnings_url%>" class="alert-link build-warnings"> + <%:warnings%> warning<%:warnings_pluralise%> + </a> + <%/if%> + </div> + + <!-- build time --> + <div class="col-md-3"> + Build time: - </button> - {% endif %} + <span data-role="data-recent-build-buildtime-field"> + <%if state == 'Succeeded'%> + <a class="alert-link" href="<%:buildtime_url%>"><%:buildtime%></a> + <%else%> + <%:buildtime%> + <%/if%> + </span> + + <!-- rebuild button --> + <%include tmpl='#rebuild-template'/%> + </div> +</script> + +<!-- cancelled build --> +<script id="cancelled-build-template" type="text/x-jsrender"> + <!-- build cancelled message --> + <div class="col-md-6"> + Build cancelled </div> -</div> - {% endfor %} + <!-- rebuild button --> + <div class="col-md-3"> + <%include tmpl='#rebuild-template'/%> </div> +</script> + +<!-- rebuild button or no rebuild icon --> +<script id="rebuild-template" type="text/x-jsrender"> + <%if is_default_project_build%> + <!-- no rebuild info icon --> + <span class="pull-right glyphicon glyphicon-question-sign get-help <%if state == 'Succeeded'%>get-help-green<%else state == 'Failed'%>get-help-red<%else%>get-help-blue<%/if%>" + title="Builds in this project cannot be started from Toaster: they are started from the command line"> + </span> + <%else%> + <!-- rebuild button --> + <span class="rebuild-btn alert-link <%if state == 'Success'%>success<%else state == 'Failed'%>danger<%else%>info<%/if%> pull-right" + data-request-url="<%:rebuild_url%>" data-target='<%:build_targets_json%>'> + <span class="glyphicon glyphicon-repeat"></span> + Rebuild + </span> + <%/if%> +</script> -{%endif%} +<!-- cancel button or no cancel icon --> +<script id="cancel-template" type="text/x-jsrender"> + <%if is_default_project_build%> + <!-- no cancel icon --> + <span class="glyphicon glyphicon-question-sign get-help get-help-blue pull-right" title="Builds in this project cannot be cancelled from Toaster: they can only be cancelled from the command line"></span> + <%else%> + <!-- cancel button --> + <span class="cancel-build-btn pull-right alert-link" + data-buildrequest-id="<%:buildrequest_id%>" data-request-url="<%:cancel_url%>"> + <span class="glyphicon glyphicon-remove-circle"></span> + Cancel + </span> + <%/if%> +</script> +<script> + $(document).ready(function () { + var ctx = { + mrbType : "{{mrb_type}}", + } + + try { + mrbSectionInit(ctx); + } catch (e) { + document.write("Sorry, An error has occurred loading this page"); + console.warn(e); + } + }); +</script> diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/newcustomimage.html b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/newcustomimage.html index 46aed901f..980179a40 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/newcustomimage.html +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/newcustomimage.html @@ -5,10 +5,17 @@ {% block pagecontent %} {% include "newcustomimage_modal.html" %} -{% include "projecttopbar.html" %} -{% url table_name project.id as xhr_table_url %} -<h2>{{title}} (<span class="table-count-{{table_name}}">0</span>)</h2> -{% include "toastertable.html" %} +<div class="row"> + + {% include "projecttopbar.html" %} + + <div class="col-md-12"> + {% url table_name project.id as xhr_table_url %} + <h2>{{title}} (<span class="table-count-{{table_name}}">0</span>)</h2> + {% include "toastertable.html" %} + </div> + +</div> {% endblock %} diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/newcustomimage_modal.html b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/newcustomimage_modal.html index caeb30235..d448d3afc 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/newcustomimage_modal.html +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/newcustomimage_modal.html @@ -12,37 +12,49 @@ }); </script> -<div class="modal hide fade in" id="new-custom-image-modal" aria-hidden="false"> - <div class="modal-header"> - <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button> - <h3>New custom image</h3> - </div> +<div class="modal fade" id="new-custom-image-modal" aria-hidden="false"> + <div class="modal-dialog"> + <div class="modal-content"> + <div class="modal-header"> + <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button> + <h3>New custom image</h3> + </div> - <div class="modal-body"> - <!-- - this container is visible if there are multiple image recipes which could - be used as a basis for the new custom image; radio buttons are added to it - via newCustomImageModalSetRecipes() as required - --> - <div data-role="image-selector" style="display:none;"> - <h4>Which image do you want to customise?</h4> - <div data-role="image-selector-radios"></div> - <span class="help-block error" id="invalid-recipe-help" style="display:none"></span> - <div class="air"></div> - </div> + <div class="modal-body"> + <!-- + this container is visible if there are multiple image recipes which could + be used as a basis for the new custom image; radio buttons are added to it + via newCustomImageModalSetRecipes() as required + --> + <div data-role="image-selector" style="display:none;"> + <h4>Which image do you want to customise?</h4> + <span class="help-block text-danger" id="invalid-recipe-help" style="display:none"></span> + <div data-role="image-selector-radios"></div> + <div class="air"></div> + </div> - <h4>Name your custom image</h4> + <h4>Name your custom image</h4> - <div class="row-fluid"> - <span class="help-block span8">Image names must be unique. They should not contain spaces or capital letters, and the only allowed special character is dash (-).<p></p> - </span></div> - <div class="control-group controls"> - <input type="text" class="huge" placeholder="Type the custom image name" required> - <span class="help-block error" id="invalid-name-help" style="display:none"></span> - </div> - </div> + <div class="row"> + <div class="col-md-10"> + <p class="help-block">Image names must be unique. They should not contain spaces or capital letters, and the only allowed special character is dash (-). + </p> + <div class="form-group"> + <input type="text" class="form-control input-lg" placeholder="Type the custom image name" required> + </div> + <span class="help-block text-danger" id="invalid-name-help" style="display:none"></span> + </div> + </div> + </div> - <div class="modal-footer"> - <button id="create-new-custom-image-btn" class="btn btn-primary btn-large" data-original-title="" title="" disabled>Create custom image</button> + <div class="modal-footer"> + <button id="create-new-custom-image-btn" class="btn btn-primary btn-large" disabled> + <span data-role="submit-state">Create custom image</span> + <span data-role="loading-state" style="display:none"> + <i class="fa-pulse icon-spinner"></i> Creating custom image... + </span> + </button> + </div> + </div> </div> </div> diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/newproject.html b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/newproject.html index e83b2bea6..acb614e9d 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/newproject.html +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/newproject.html @@ -5,24 +5,21 @@ {% block title %} Create a new project - Toaster {% endblock %} {% block pagecontent %} -<div class="row-fluid"> +<div class="row"> + <div class="col-md-12"> <div class="page-header"> <h1>Create a new project</h1> </div> - <div class="container-fluid"> {% if alert %} - <div class="alert alert-error row-fluid" role="alert">{{alert}}</div> + <div class="alert alert-danger" role="alert">{{alert}}</div> {% endif %} - </div> - <div class="row-fluid"> - <div class="span6"> <form method="POST">{% csrf_token %} - - <fieldset> - <label>Project name <span class="muted">(required)</span></label> - <input type="text" class="input-xlarge" required id="new-project-name" name="projectname"> - </fieldset> + <div class="form-group" id="validate-project-name"> + <label class="control-label">Project name <span class="text-muted">(required)</span></label> + <input type="text" class="form-control" required id="new-project-name" name="projectname"> + </div> + <p class="help-block text-danger" style="display: none;" id="hint-error-project-name">A project with this name exists. Project names must be unique.</p> <!-- <fieldset> <label class="project-form">Project type</label> @@ -35,13 +32,13 @@ <input type="hidden" name="ptype" value="build" /> {% if releases.count > 0 %} - <fieldset class="release"> + <div class="release form-group"> {% if releases.count > 1 %} - <label class="project-form"> + <label class="control-label"> Release - <i class="icon-question-sign get-help" title="The version of the build system you want to use"></i> + <span class="glyphicon glyphicon-question-sign get-help" title="The version of the build system you want to use"></span> </label> - <select name="projectversion" id="projectversion"> + <select name="projectversion" id="projectversion" class="form-control"> {% for release in releases %} <option value="{{release.id}}" {%if defaultbranch == release.name %} @@ -50,25 +47,28 @@ >{{release.description}}</option> {% endfor %} </select> + <div class="row"> + <div class="col-md-4"> {% for release in releases %} - <div class="row-fluid helptext" id="description-{{release.id}}" style="display: none"> - <span class="help-block span5">{{release.helptext|safe}}</span> - </div> + <div class="helptext" id="description-{{release.id}}" style="display: none"> + <span class="help-block">{{release.helptext|safe}}</span> + </div> {% endfor %} {% else %} <input type="hidden" name="projectversion" value="{{releases.0.id}}"/> {% endif %} + </div> + </div> </fieldset> {% endif %} - - <div class="form-actions"> - <input type="submit" id="create-project-button" class="btn btn-primary btn-large" value="Create project"/> + <div class="top-air"> + <input type="submit" id="create-project-button" class="btn btn-primary btn-lg" value="Create project"/> <span class="help-inline" style="vertical-align:middle;">To create a project, you need to enter a project name</span> </div> + </form> - </div> <!-- - <div class="span5 well"> + <div class="col-md-5 well"> <span class="help-block"> <h4>Toaster project types</h4> <p>With a <strong>build project</strong> you configure and run your builds from Toaster.</p> @@ -81,7 +81,7 @@ <p>If you create a <strong>build project</strong>, you will need to select a <strong>release</strong>, which is the version of the build system you want to use to run your builds.</p> </div> --> - </div> + </div> </div> <script type="text/javascript"> @@ -113,6 +113,11 @@ $('#description-' + new_release).fadeIn(); }); + libtoaster.makeProjectNameValidation($("#new-project-name"), + $("#hint-error-project-name"), $("#validate-project-name"), + $(".btn-primary")); + + /* // Hide the project release when you select an analysis project function projectType() { if ($("input[type='radio']:checked").val() == 'build') { diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/package_built_dependencies.html b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/package_built_dependencies.html index e6f20c330..a5d589357 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/package_built_dependencies.html +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/package_built_dependencies.html @@ -2,16 +2,16 @@ {% load projecttags %} {% block tabcontent %} - <ul class="nav nav-pills"> + <ul class="nav nav-tabs"> <li class=""> <a href="{% url 'package_built_detail' build.id package.id %}"> - <i class="icon-question-sign get-help" title="Shows the files produced by this package."></i> + <span class="glyphicon glyphicon-question-sign get-help" title="Shows the files produced by this package."></span> Generated files ({{package.buildfilelist_package.count}}) </a> </li> <li class="active"> <a href="{% url 'package_built_dependencies' build.id package.id %}"> - <i class="icon-question-sign get-help" title="Shows the runtime packages required by this package."></i> + <span class="glyphicon glyphicon-question-sign get-help" title="Shows the runtime packages required by this package."></span> Runtime dependencies ({{dependency_count}}) </a> </li> @@ -24,14 +24,14 @@ </div> {% else %} <div class="alert alert-info"> - <strong>{{package.fullpackagespec}}</strong> is <strong>not included</strong> in any image. This page shows you the projected runtime dependencies if you include <strong>{{package.fullpackagespec}}</strong> in future builds. + <strong>{{package.fullpackagespec}}</strong> is <strong>not included</strong> in any image. This page shows you the projected runtime dependencies if you were to include <strong>{{package.fullpackagespec}}</strong> in an image. </div> <table class="table table-bordered table-hover"> <thead> <tr> <th>Package</th> <th>Version</th> - <th class="sizecol span2">Size</th> + <th class="sizecol col-md-2">Size</th> </tr> </thead> <tbody> @@ -62,9 +62,9 @@ <tr> <th>Package</th> <th>Version</th> - <th class="sizecol span2">Size</th> + <th class="sizecol col-md-2">Size</th> <th> - <i class="icon-question-sign get-help" title="Five relationship types exist: recommends, suggests, provides, replaces and conflicts"></i> + <span class="glyphicon glyphicon-question-sign get-help" title="Five relationship types exist: recommends, suggests, provides, replaces and conflicts"></span> Relationship type </th> </tr> @@ -87,7 +87,7 @@ <td class="sizecol">{{other_dep.size|filtered_filesizeformat}}</td> <td> {{other_dep.dep_type_display}} - <i class="icon-question-sign get-help hover-help" title="{{other_dep.dep_type_help}}" ></i> + <span class="glyphicon glyphicon-question-sign get-help hover-help" title="{{other_dep.dep_type_help}}" ></span> </td> </tr> {% endfor %} diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/package_built_detail.html b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/package_built_detail.html index 9be8ccb85..0bdbc2a41 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/package_built_detail.html +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/package_built_detail.html @@ -6,13 +6,13 @@ <!-- Generated Files --> {% if package.buildtargetlist_package.count == 0 %} {# Not included case #} - <ul class="nav nav-pills"> + <ul class="nav nav-tabs"> <li class="active"> <a href="#"> - <i class="icon-question-sign get-help" title="Files added to a root file system when you include {{package.name}} in an image"></i> + <span class="glyphicon glyphicon-question-sign get-help" title="Files added to a root file system when you include {{package.name}} in an image"></span> Generated files ({{packageFileCount}}) </a></li> <li class=""><a href="{% url 'package_built_dependencies' build.id package.id %}"> - <i class="icon-question-sign get-help" title="Projected runtime dependencies when you include {{package.name}} in an image"></i> + <span class="glyphicon glyphicon-question-sign get-help" title="Projected runtime dependencies when you include {{package.name}} in an image"></span> Runtime dependencies ({{dependency_count}}) </a></li> </ul> @@ -21,7 +21,7 @@ <!-- Package file list or if empty, alert pane --> {% if packageFileCount > 0 %} <div class="alert alert-info"> - <strong>{{package.fullpackagespec}}</strong> is <strong>not included</strong> in any image. This page shows you the files added to an image root file system if you include <strong>{{package.fullpackagespec}}</strong> in future builds. + <strong>{{package.fullpackagespec}}</strong> is <strong>not included</strong> in any image. This page shows you the files that would be added to an image root file system with <strong>{{package.fullpackagespec}}</strong> included in it. </div> {% include "tablesort.html" %} <tbody> diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/package_detail_base.html b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/package_detail_base.html index 9fa28a8f8..66f8e7f06 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/package_detail_base.html +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/package_detail_base.html @@ -14,8 +14,8 @@ function fmtAliasHelp(package_name, alias, hover) { var r = null; if (alias != null && alias != '') { - r = '<span class="muted"> as ' + alias + ' '; - r += '<i class="icon-question-sign get-help'; + r = '<span class="text-muted"> as ' + alias + ' '; + r += '<span class="glyphicon glyphicon-question-sign get-help'; if (hover) { r+= ' hover-help'; } @@ -23,9 +23,9 @@ r+= ' heading-help'; } r += '"'; - title = package_name + ' was renamed at packaging time and was installed on your system as ' + alias; + title = '<code>' + package_name + '</code> was renamed at packaging time and was installed on your system as <code>' + alias + '</code>'; r += ' title="' + title + '">'; - r += '</i>'; + r += '</span>'; r += '</span>'; document.write(r); } @@ -43,28 +43,32 @@ {% endblock localbreadcrumb %} {% block pagedetailinfomain %} - <div class="row span11"> - <div class="page-header"> + <div class="row"> + <div class="col-md-12"> + <div class="page-header build-data"> {% block mainheading %} <h1>{{package.fullpackagespec}}</h1> {% endblock %} </div> <!-- page-header --> - </div> <!-- row span11 page-header --> + </div> <!-- col-md-12 page-header --> + </div> <!-- end row --> {% block twocolumns %} - <div class="row span7 tabbable"> + <div class="row"> + <div class="col-md-8 tabbable"> {% block tabcontent %} {% endblock tabcontent %} - </div> <!-- row span7 --> + </div> <!-- row col-md-8 --> - <div class="row span4 well"> + <div class="col-md-4"> + <div class="well"> <h2>Package information</h2> <!-- info presented as definition list --> <dl class="item-info"> <dt> Size - <i class="icon-question-sign get-help" title="The size of the package"></i> + <span class="glyphicon glyphicon-question-sign get-help" title="The size of the package"></span> </dt> <dd> {% comment %} @@ -81,7 +85,7 @@ <dt> License - <i class="icon-question-sign get-help" title="The license under which this package is distributed"></i> + <span class="glyphicon glyphicon-question-sign get-help" title="The license under which this package is distributed"></span> </dt> <dd>{{package.license}}</dd> @@ -97,7 +101,7 @@ <dt> Recipe - <i class="icon-question-sign get-help" title="The name of the recipe building this package"></i> + <span class="glyphicon glyphicon-question-sign get-help" title="The name of the recipe building this package"></span> </dt> <dd> {% if package.recipe_id > 0 %} @@ -109,13 +113,13 @@ <dt> Recipe version - <i class="icon-question-sign get-help" title="The version of the recipe building this package"></i> + <span class="glyphicon glyphicon-question-sign get-help" title="The version of the recipe building this package"></span> </dt> <dd>{{package.recipe.version}}</dd> <dt> Layer - <i class="icon-question-sign get-help" title="The name of the layer providing the recipe that builds this package"></i> + <span class="glyphicon glyphicon-question-sign get-help" title="The name of the layer providing the recipe that builds this package"></span> </dt> <dd> {{package.recipe.layer_version.layer.name}} @@ -124,26 +128,42 @@ # Removed per team meeting of 1/29/2014 until # decision on index search algorithm <a href="http://layers.openembedded.org" target="_blank"> - <i class="icon-share get-info"></i> + <i class="glyphicon glyphicon-share get-info"></i> </a> {% endcomment %} {% endif %} </dd> - {% if package.recipe.layer_version.branch %} <dt> Layer branch - <i class="icon-question-sign get-help" title="The Git branch of the layer providing the recipe that builds this package"></i> + <span class="glyphicon glyphicon-question-sign get-help" title="The Git branch of the layer providing the recipe that builds this package"></span> + {%if package.recipe.layer_version.layer.local_source_dir %} + <dd> + <span class="text-muted">Not applicable</span> + <span class="glyphicon glyphicon-question-sign get-help" title="The source code of {{package.recipe.layer_version.layer.name}} is not in a Git repository, so there is no branch associated with it"></span> + </dd> + {% endif %} </dt> + {% if not package.recipe.layer_version.layer.local_source_dir %} <dd>{{package.recipe.layer_version.branch}}</dd> - {% endif %} + {% endif %} <dt> Layer commit - <i class="icon-question-sign get-help" title="The Git commit of the layer providing the recipe that builds this package"></i> + <span class="glyphicon glyphicon-question-sign get-help" title="The Git commit of the layer providing the recipe that builds this package"></span> + {%if package.recipe.layer_version.layer.local_source_dir %} + <dd> + <span class="text-muted">Not applicable</span> + <span class="glyphicon glyphicon-question-sign get-help" title="The source code of {{package.recipe.layer_version.layer.name}} is not in a Git repository, so there is no commit associated with it"></span> + </dd> + {% endif %} </dt> + {% if not package.recipe.layer_version.layer.local_source_dir %} <dd class="iscommit">{{package.recipe.layer_version.commit}}</dd> + {% endif %} </dl> - </div> <!-- row4 well --> + <div> <!-- end well --> + </div> <!-- end 4-column section --> {% endblock twocolumns %} + </div> <!-- end row --> {% endblock pagedetailinfomain %} diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/package_included_dependencies.html b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/package_included_dependencies.html index 8a0508e70..95e56ded2 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/package_included_dependencies.html +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/package_included_dependencies.html @@ -20,11 +20,11 @@ <tr> <th>Package</th> <th>Version</th> - <th class='sizecol span2'>Size</th> + <th class='sizecol col-md-2'>Size</th> </tr> </thead> <tbody> - {% for runtime_dep in runtime_deps %} + {% for runtime_dep in runtime_deps %} <tr {{runtime_dep.size|format_vpackage_rowclass}} > {% if runtime_dep.size != -1 %} <td> @@ -41,7 +41,7 @@ <td>{{runtime_dep.version}} </td> <td class='sizecol'>{{runtime_dep.size|filtered_filesizeformat}} </td> </tr> - {% endfor %} + {% endfor %} </tbody> </table> {% else %} @@ -57,15 +57,15 @@ <tr> <th>Package</th> <th>Version</th> - <th class='sizecol span2'>Size</th> + <th class='sizecol col-md-2'>Size</th> <th> - <i class="icon-question-sign get-help" title="Five relationship types exist: recommends, suggests, provides, replaces and conflicts"></i> + <span class="glyphicon glyphicon-question-sign get-help" title="Five relationship types exist: recommends, suggests, provides, replaces and conflicts"></span> Relationship type </th> </tr> </thead> <tbody> - {% for other_dep in other_deps %} + {% for other_dep in other_deps %} {% if other_dep.installed %} <tr {{other_dep.size|format_vpackage_rowclass}}> {% if other_dep.size != -1 %} @@ -86,21 +86,21 @@ <td class='sizecol'>{{other_dep.size|filtered_filesizeformat}} </td> <td> {{other_dep.dep_type_display}} - <i class="icon-question-sign get-help hover-help" title="{{other_dep.dep_type_help}}" ></i> + <span class="glyphicon glyphicon-question-sign get-help hover-help" title="{{other_dep.dep_type_help}}" ></span> </td> </tr> {% else %} - <tr class="muted"> + <tr class="text-muted"> <td>{{other_dep.name}}</td> <td>{{other_dep.version}}</td> <td></td> <td> {{other_dep.dep_type_display}} - <i class="icon-question-sign get-help hover-help" title="{{other_dep.dep_type_help}}" ></i> + <span class="glyphicon glyphicon-question-sign get-help hover-help" title="{{other_dep.dep_type_help}}" ></span> </td> </tr> {% endif %} - {% endfor %} + {% endfor %} </tbody> </table> {% endifnotequal %} diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/package_included_tabs.html b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/package_included_tabs.html index 958aa8827..e89fa211c 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/package_included_tabs.html +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/package_included_tabs.html @@ -1,12 +1,12 @@ - <ul class="nav nav-pills"> + <ul class="nav nav-tabs"> {% if active_tab == "detail" %} <li class="active"> {% else %} <li class=""> {% endif %} <a href="{% url 'package_included_detail' build.id target.id package.id %}"> - <i class="icon-question-sign get-help" title="The files this package adds to the image root file system"></i> + <span class="glyphicon glyphicon-question-sign get-help" title="The files this package adds to the image root file system"></span> Files in root file system ({{packageFileCount}}) </a> </li> @@ -16,7 +16,7 @@ <li class=""> {% endif %} <a href="{% url 'package_included_dependencies' build.id target.id package.id %}"> - <i class="icon-question-sign get-help" title="Package runtime dependencies"></i> + <span class="glyphicon glyphicon-question-sign get-help" title="Package runtime dependencies"></span> Runtime dependencies ({{dependency_count}}) </a> </li> @@ -26,7 +26,7 @@ <li class=""> {% endif %} <a href="{% url 'package_included_reverse_dependencies' build.id target.id package.id %}"> - <i class="icon-question-sign get-help" title="The package runtime reverse dependencies (i.e. the packages in this image that depend on this package). Reverse dependencies reflect only the 'depends' dependency type"></i> + <span class="glyphicon glyphicon-question-sign get-help" title="The package runtime reverse dependencies (i.e. the packages in this image that depend on this package). Reverse dependencies reflect only the 'depends' dependency type"></span> Reverse runtime dependencies ({{reverse_count}}) </a> </li> diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/pkg_add_rm_btn.html b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/pkg_add_rm_btn.html index 0aefc5625..303faecbd 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/pkg_add_rm_btn.html +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/pkg_add_rm_btn.html @@ -1,12 +1,12 @@ {# TODO move to snippets dir #} {% if data.is_locale_package %} <p class="text-center"> - <span class="muted">Locale package</span> - <i class="icon-question-sign get-help hover-help" title="" - data-original-title="This package is included in your image + <span class="text-muted">Locale package</span> + <span class="glyphicon glyphicon-question-sign get-help hover-help" + title="This package is included in your image based on the locale specified in the IMAGE_LINGUAS variable" style="visibility: hidden;"> - </i> + </span> </p> {% else %} @@ -21,12 +21,12 @@ <i class="icon-trash no-tooltip"></i> Remove package </button> - <button class="btn btn-block add-rm-package-btn" data-directive="add" data-id="{{data.pk}}" data-package-url="{% url 'xhr_customrecipe_packages' extra.recipe_id data.pk %}" data-name="{{data.name}}" style=" + <button class="btn btn-default btn-block add-rm-package-btn" data-directive="add" data-id="{{data.pk}}" data-package-url="{% url 'xhr_customrecipe_packages' extra.recipe_id data.pk %}" data-name="{{data.name}}" style=" {% if data.pk in extra.current_packages %} display:none {% endif %} "> - <i class="icon-plus"></i> + <i class="glyphicon glyphicon-plus"></i> Add package </button> </div> diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/project.html b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/project.html index 125676881..5abe24130 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/project.html +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/project.html @@ -24,44 +24,55 @@ }); </script> -<!-- Comment out the ability to change the project release, until we decide what to do this functionality --> - -<!--div id="change-release-modal" class="modal hide fade in" tabindex="-1" role="dialog" aria-labelledby="change-release-modal" aria-hidden="false"> - <div class="modal-header"> - <button type="button" class="close" data-dismiss="modal" aria-hidden="true">x</button> - <h3>Changing Yocto Project release to <span class="proposed-release-change-name"></span></h3> - </div> - <div class="modal-body"> - <p>The following added layers do not exist for <span class="proposed-release-change-name"></span>: </p> - <ul id="layers-to-remove-list"> - </ul> - <p>If you change the Yocto Project release to <span class="proposed-release-change-name"></span>, the above layers will be deleted from your added layers.</p> - </div> - <div class="modal-footer"> - <button id="change-release-and-rm-layers" data-dismiss="modal" type="submit" class="btn btn-primary">Change release and delete layers</button> - <button class="btn" data-dismiss="modal" aria-hidden="true">Cancel</button> - </div> -</div--> +<div id="delete-project-modal" class="modal fade" tabindex="-1" role="dialog" data-backdrop="static" data-keyboard="false"> + <div class="modal-dialog"> + <div class="modal-content"> + <div class="modal-header"> + <h4>Are you sure you want to delete this project?</h4> + </div> + <div class="modal-body"> + <p>Deleting the <strong class="project-name"></strong> project + will:</p> + <ul> + <li>Cancel its builds currently in progress</li> + <li>Remove its configuration information</li> + <li>Remove its imported layers</li> + <li>Remove its custom images</li> + <li>Remove all its build information</li> + </ul> + </div> + <div class="modal-footer"> + <button type="button" class="btn btn-primary" id="delete-project-confirmed"> + <span data-role="submit-state">Delete project</span> + <span data-role="loading-state" style="display:none"> + <span class="fa-pulse"> + <i class="fa-pulse icon-spinner"></i> + </span> + Deleting project... + </span> + </button> + <button type="button" class="btn btn-link" data-dismiss="modal">Cancel</button> + </div> + </div><!-- /.modal-content --> + </div><!-- /.modal-dialog --> +</div> -<div class="row-fluid" id="project-page" style="display:none"> - <div class="span6"> +<div class="row" id="project-page" style="display:none"> + <div class="col-md-6"> <div class="well well-transparent" id="machine-section"> <h3>Machine</h3> - <p class="lead"><span id="project-machine-name"></span> <i title="" data-original-title="" id="change-machine-toggle" class="icon-pencil"></i></p> - - <form id="select-machine-form" style="display:none;"> - <div class="alert alert-info"> - <strong>Machine changes have a big impact on build outcome.</strong> You cannot really compare the builds for the new machine with the previous ones. - </div> + <p class="lead"><span id="project-machine-name"></span> <span class="glyphicon glyphicon-edit" id="change-machine-toggle"></span></p> - <div class="input-append"> - <input id="machine-change-input" autocomplete="off" value="" data-provide="typeahead" data-minlength="1" data-autocomplete="off" type="text"> - <button id="machine-change-btn" class="btn" type="button">Save</button> <a href="#" id="cancel-machine-change" class="btn btn-link">Cancel</a> + <form id="select-machine-form" style="display:none;" class="form-inline"> + <span class="help-block">Machine suggestions come from the list of layers added to your project. If you don't see the machine you are looking for, <a href="{% url 'projectmachines' project.id %}">check the full list of machines</a></span> + <div class="form-group"> + <input class="form-control" id="machine-change-input" autocomplete="off" value="" data-provide="typeahead" data-minlength="1" data-autocomplete="off" type="text"> </div> - - <p><a href="{% url 'projectmachines' project.id %}" class="link">View compatible machines</a></p> + <button id="machine-change-btn" class="btn btn-default" type="button">Save</button> + <a href="#" id="cancel-machine-change" class="btn btn-link">Cancel</a> + <p class="form-link"><a href="{% url 'projectmachines' project.id %}">View compatible machines</a></p> </form> </div> @@ -69,11 +80,11 @@ <h3>Most built recipes</h3> <div class="alert alert-info" style="display:none" id="no-most-built"> - <span class="lead">You haven't built any recipes yet</span> - <p style="margin-top: 10px;"><a href="{% url 'projectimagerecipes' project.id %}">Choose a recipe to build</a></p> + <h4>You haven't built any recipes yet</h4> + <p class="form-link"><a href="{% url 'projectimagerecipes' project.id %}">Choose a recipe to build</a></p> </div> - <ul class="unstyled configuration-list" id="freq-build-list"> + <ul class="list-unstyled lead" id="freq-build-list"> </ul> <button class="btn btn-primary" id="freq-build-btn" disabled="disabled">Build selected recipes</button> </div> @@ -97,44 +108,37 @@ </div> </div> - <div class="span6"> + <div class="col-md-6"> <div class="well well-transparent" id="layer-container"> - <h3>Layers <span class="muted counter">(<span id="project-layers-count"></span>)</span> - <i data-original-title="OpenEmbedded organises metadata into modules called 'layers'. Layers allow you to isolate different types of customizations from each other. <a href='http://www.yoctoproject.org/docs/current/dev-manual/dev-manual.html#understanding-and-creating-layers' target='_blank'>More on layers</a>" class="icon-question-sign get-help heading-help" title=""></i> + <h3>Layers <span class="counter">(<span id="project-layers-count"></span>)</span> + <span title="OpenEmbedded organises recipes and machines into thematic groups called <strong>layers</strong>. Click on a layer name to see the recipes and machines it includes." class="glyphicon glyphicon-question-sign get-help"></span> </h3> - <div class="alert lead" id="no-layers-in-project" style="display:none"> - You need to add some layers. For that you can: + <div class="alert alert-warning" id="no-layers-in-project" style="display:none"> + <h4>This project has no layers</h4> + In order to build this project you need to add some layers first. For that you can: <ul> - <li><a href="{% url 'projectlayers' project.id %}">View all layers compatible with this project</a></li> + <li><a href="{% url 'projectlayers' project.id %}">Choose from the layers compatible with this project</a></li> <li><a href="{% url 'importlayer' project.id %}">Import a layer</a></li> <li><a href="http://www.yoctoproject.org/docs/current/dev-manual/dev-manual.html#understanding-and-creating-layers" target="_blank">Read about layers in the documentation</a></li> + <li>Or type a layer name below</li> </ul> - <p>Or type a layer name below.</p> </div> - <form style="margin-top:20px"> - <!--div class="control-group error"--> - - <div class="input-append"> - <input id="layer-add-input" autocomplete="off" placeholder="Type a layer name" data-minlength="1" data-autocomplete="off" data-provide="typeahead" data-source="" type="text"> - <button id="add-layer-btn" class="btn" disabled>Add</button> + <form class="form-inline"> + <div class="form-group"> + <input id="layer-add-input" class="form-control" autocomplete="off" placeholder="Type a layer name" data-minlength="1" data-autocomplete="off" data-provide="typeahead" data-source="" type="text"> </div> - - <div id="import-alert" class="alert alert-info" style="display:none;"> - Toaster does not know about this layer. Please <a href="#">import it</a> - </div> - - <p> + <button id="add-layer-btn" class="btn btn-default" disabled>Add layer</button> + <p class="form-link"> <a href="{% url 'projectlayers' project.id %}" id="view-compatible-layers">View compatible layers</a> - <i data-original-title="View all the layers you can build with the release selected for this project, which is Yocto Project master" class="icon-question-sign get-help" title=""></i> - | <a href="{% url 'importlayer' project.id %}">Import layer</a> + <span class="text-muted">|</span> + <a href="{% url 'importlayer' project.id %}">Import layer</a> </p> </form> - <ul class="unstyled configuration-list" id="layers-in-project-list"> + <ul class="list-unstyled lead" id="layers-in-project-list"> </ul> - </div> </div> </div> {% endblock %} diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/projectbuilds-toastertable.html b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/projectbuilds-toastertable.html index 6d7e10bac..a5fed2dd4 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/projectbuilds-toastertable.html +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/projectbuilds-toastertable.html @@ -3,54 +3,63 @@ {% load static %} {% block extraheadcontent %} - <link rel="stylesheet" href="{% static 'css/jquery-ui.min.css' %}" type='text/css'> - <link rel="stylesheet" href="{% static 'css/jquery-ui.structure.min.css' %}" type='text/css'> - <link rel="stylesheet" href="{% static 'css/jquery-ui.theme.min.css' %}" type='text/css'> - <script src="{% static 'js/jquery-ui.min.js' %}"> - </script> +<link rel="stylesheet" href="{% static 'css/jquery-ui.min.css' %}" type='text/css'> +<link rel="stylesheet" href="{% static 'css/jquery-ui.structure.min.css' %}" type='text/css'> +<link rel="stylesheet" href="{% static 'css/jquery-ui.theme.min.css' %}" type='text/css'> +<script src="{% static 'js/jquery-ui.min.js' %}"> +</script> {% endblock %} {% block title %} {{title}} - {{project.name}} - Toaster {% endblock %} {% block pagecontent %} +<div class="row"> {% include "projecttopbar.html" %} - <div class="row-fluid"> + <div class="col-md-12"> {% with mru=mru mrb_type=mrb_type %} - {% include 'mrb_section.html' %} + {% include 'mrb_section.html' %} {% endwith %} - <h2 class="page-header top-air" data-role="page-title"></h2> + <h2 class="top-air" data-role="page-title"></h2> + {% if not build_in_progress_none_completed %} {% url 'projectbuilds' project.id as xhr_table_url %} {% include 'toastertable.html' %} + {% endif %} </div> - <script> - $(document).ready(function () { - // title - var tableElt = $("#{{table_name}}"); - var titleElt = $("[data-role='page-title']"); + <script> + $(document).ready(function () { + // title + var tableElt = $("#{{table_name}}"); + var titleElt = $("[data-role='page-title']"); - tableElt.on("table-done", function (e, total, tableParams) { - var title = "All project builds"; + tableElt.on("table-done", function (e, total, tableParams) { + var title = "All project builds"; - if (tableParams.search || tableParams.filter) { - if (total === 0) { - title = "No project builds found"; - } - else if (total > 0) { - title = total + " project build" + (total > 1 ? 's' : '') + " found"; - } - } + if (tableParams.search || tableParams.filter) { + if (total === 0) { + title = "No project builds found"; + } + else if (total > 0) { + title = total + " project build" + (total > 1 ? 's' : '') + " found"; + } + } - titleElt.text(title); - }); + if (total === 0) { + titleElt.hide(); + } else { + titleElt.show(); + titleElt.text(title); + } + }); - // highlight builds tab - $("#topbar-builds-tab").addClass("active") - }); - </script> + // highlight builds tab + $("#topbar-builds-tab").addClass("active") + }); + </script> +</div> {% endblock %} diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/projectbuilds.html b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/projectbuilds.html index 3402fc4fe..50697a159 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/projectbuilds.html +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/projectbuilds.html @@ -39,10 +39,10 @@ {% if objects.paginator.count == 0 %} {% if request.GET.filter or request.GET.search %} - <div class="row-fluid"> + <div class="row"> <div class="alert"> <form class="no-results input-append" id="searchform"> - <input id="search" name="search" class="input-xxlarge" type="text" value="{{request.GET.search}}"/>{% if request.GET.search %}<a href="javascript:$('#search').val('');searchform.submit()" class="add-on btn" tabindex="-1"><i class="icon-remove"></i></a>{% endif %} + <input id="search" name="search" class="input-xxlarge" type="text" value="{{request.GET.search}}"/>{% if request.GET.search %}<a href="javascript:$('#search').val('');searchform.submit()" class="input-append-addon btn" tabindex="-1"><i class="glyphicon glyphicon-remove"></i></a>{% endif %} <button class="btn" type="submit" value="Search">Search</button> <button class="btn btn-link" onclick="javascript:$('#search').val('');searchform.submit()">Show all builds</button> </form> diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/projectconf.html b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/projectconf.html index 27a898b65..fcf6df2bf 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/projectconf.html +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/projectconf.html @@ -6,945 +6,1034 @@ {% block projectinfomain %} <h2>Bitbake variables</h2> - - <div style="padding-left:19px;"> - - <dl class="dl-vertical"> - {% if distro_defined %} - <dt> - <span class="js-config-var-name js-config-var-managed-name">DISTRO</span> - <i class="icon-question-sign get-help" title="The short name of the distribution. If the variable is blank, meta/conf/distro/defaultsetup.conf will be used. <br /><a href='http://www.yoctoproject.org/docs/1.6.1/ref-manual/ref-manual.html#var-DISTRO' target='_blank'>Read more in the manual</a>"></i> - </dt> - <dd class="lead"> - <span id="distro">{{distro}}</span> - <i class="icon-pencil" id="change-distro-icon"></i> - <form id="change-distro-form" style="display:none;"> - <div class="input-append"> - <span id="edit-distro-name-div" class="control-group"> - <input type="text" id="new-distro" value="{{distro}}"> - <button id="apply-change-distro" class="btn" type="button">Save</button> - <button id="cancel-change-distro" type="button" class="btn btn-link">Cancel</button> - </span> - <span class="help-block error" id="distro-error-message"></span> - </div> - </form> - </dd> - {% endif %} - - {% if dl_dir_defined %} - <dt> - <span class="js-config-var-name js-config-var-managed-name">DL_DIR</span> - <i class="icon-question-sign get-help" title="Absolute path to the directory used to store downloads required for your builds. By default, Toaster projects share the same downloads directory.<br /><a href='http://www.yoctoproject.org/docs/2.1/ref-manual/ref-manual.html#var-DL_DIR' target='_blank'>Read more in the manual</a>"></i> - </dt> - <dd class="lead"> - <span id="dl_dir"{% if dl_dir %}{%else%} class="muted"{%endif%}>{% if dl_dir %}{{dl_dir}}{%else%}Not set{%endif%}</span> - <i class="icon-pencil" id="change-dl_dir-icon"></i> - <form id="change-dl_dir-form" style="display:none;"> - <div class="row-fluid"> - <span class="help-block span4">To set DL_DIR type the absolute path of the download folder.</span> - </div> - <div class="input-append" id="validate-dl_dir"> - <input type="text" class="input-xlarge" id="new-dl_dir" placeholder="Type absolute path of the DL_DIR folder"> - <button id="apply-change-dl_dir" class="btn" type="button">Save</button> - <button id="cancel-change-dl_dir" type="button" class="btn btn-link">Cancel</button> - </br><span class="help-block error" id="hintError-dl_dir">A valid directory cannot include spaces or any of these characters: . \ ? % * : | " " < ></span> - </div> - </form> - </dd> - {% endif %} - - {% if fstypes_defined %} - <dt> - <span class="js-config-var-name js-config-var-managed-name">IMAGE_FSTYPES</span> - <i class="icon-question-sign get-help" title="Formats of root file system images that you want to have created <br /><a href='http://www.yoctoproject.org/docs/1.6.1/ref-manual/ref-manual.html#var-IMAGE_FSTYPES' target='_blank'>Read more in the manual</a>"></i> - </dt> - <dd class="lead"> - <span id="image_fstypes">{{fstypes}}</span> - <i class="icon-pencil" id="change-image_fstypes-icon"></i> - <form id="change-image_fstypes-form" style="display:none;"> - <input id="filter-image_fstypes" type="text" placeholder="Search image types" class="span4"> - <div id="all-image_fstypes" class="scrolling"> - </div> - <span class="help-block" id="fstypes-error-message">You must select at least one image type</span> - <button id="apply-change-image_fstypes" type="button" class="btn">Save</button> - <button id="cancel-change-image_fstypes" type="button" class="btn btn-link">Cancel</button> - </form> - </dd> - {% endif %} - - {% if image_install_append_defined %} - <dt> - <span class="js-config-var-name js-config-var-managed-name">IMAGE_INSTALL_append</span> - <i class="icon-question-sign get-help" title="Specifies additional packages to install into an image. If your build creates more than one image, the packages will be installed in <strong>all of them</strong> <br /><a href='http://www.yoctoproject.org/docs/1.6.1/ref-manual/ref-manual.html#var-IMAGE_INSTALL' target='_blank'>Read more in the manual</a>"></i> - </dt> - <dd class="lead"> - <span id="image_install"{% if image_install_append %}{%else%} class="muted"{%endif%}>{% if image_install_append %}{{image_install_append}}{%else%}Not set{%endif%}</span> - <i class="icon-pencil" id="change-image_install-icon"></i> - <i class="icon-trash" id="delete-image_install-icon" {% if image_install_append %}{%else%}style="display:none;"{%endif%}></i> - <form id="change-image_install-form" style="display:none;"> - <div class="row-fluid"> - <span class="help-block span4">To set IMAGE_INSTALL_append to more than one package, type the package names separated by a space.</span> - </div> - <div class="input-append"> - <input type="text" class="input-xlarge" id="new-image_install" placeholder="Type one or more package names"> - <button id="apply-change-image_install" class="btn" type="button">Save</button> - <button id="cancel-change-image_install" type="button" class="btn btn-link">Cancel</button> - </div> - </form> - </dd> - {% endif %} - - {% if package_classes_defined %} - <dt> - <span class="js-config-var-name js-config-var-managed-name">PACKAGE_CLASSES</span> - <i class="icon-question-sign get-help" title="Specifies the package manager to use when packaging data <br /><a href='http://www.yoctoproject.org/docs/1.6.1/ref-manual/ref-manual.html#var-PACKAGE_CLASSES' target='_blank'>Read more in the manual</a>"></i> - </dt> - <dd class="lead"> - <span id="package_classes">{{package_classes}}</span> - <i id="change-package_classes-icon" class="icon-pencil"></i> - <form id="change-package_classes-form" style="display:none;"> - <label> - Root file system package format - <i class="icon-question-sign get-help" title="The package format used to generate the root file system. Options are <code>dev</code>, <code>ipk</code> and <code>rpm</code>"></i> - </label> - <select id="package_classes-select"> - <option>package_deb</option> - <option>package_ipk</option> - <option>package_rpm</option> - </select> - <label> - Additional package formats - <i class="icon-question-sign get-help" title="Extra package formats to build"></i> - </label> - <label class="checkbox" id="package_class_1"> - <input type="checkbox" id="package_class_1_input"> package_deb - </label> - <label class="checkbox" id="package_class_2"> - <input type="checkbox" id="package_class_2_input"> package_ipk - </label> - <div style="padding-top:10px;"> - <button id="apply-change-package_classes" type="button" class="btn">Save</button> - <button id="cancel-change-package_classes" type="button" class="btn btn-link">Cancel</button> - </div> - </form> - </dd> - {% endif %} - - {% if sstate_dir_defined %} - <dt> - <span class="js-config-var-name js-config-var-managed-name">SSTATE_DIR</span> - <i class="icon-question-sign get-help" title="Absolute path to the directory used to store shared state cache files. These files are reused across the builds, which makes the builds faster. By default, Toaster projects share the same cache directory.<br /><a href='http://www.yoctoproject.org/docs/2.1/ref-manual/ref-manual.html#var-SSTATE_DIR' target='_blank'>Read more in the manual</a>"></i> - </dt> - <dd class="lead"> - <span id="sstate_dir"{% if sstate_dir %}{%else%} class="muted"{%endif%}>{% if sstate_dir %}{{sstate_dir}}{%else%}Not set{%endif%}</span> - <i class="icon-pencil" id="change-sstate_dir-icon"></i> - <form id="change-sstate_dir-form" style="display:none;"> - <div class="row-fluid"> - <span class="help-block span4">To set SSTATE_DIR type the absolute path of the download folder.</span> - </div> - <div class="input-append" id="validate-sstate_dir"> - <input type="text" class="input-xlarge" id="new-sstate_dir" placeholder="Type absolute path of the SSTATE_DIR folder"> - <span class="error">A valid directory name required</span> - <button id="apply-change-sstate_dir" class="btn" type="button">Save</button> - <button id="cancel-change-sstate_dir" type="button" class="btn btn-link">Cancel</button> - </br><p class="help-block error" id="hintError-sstate_dir">A valid directory cannot include spaces or any of these characters: . \ ? % * : | " " < ></span> - </div> - </form> - </dd> - {% endif %} - </dl> - - <!-- <ul class="unstyled configuration-list" id="configvar-list"> --> - <dl id="configvar-list"> - <!-- the added configuration variables are inserted here --> - </dl> - - <!-- pass the fstypes list, black list, and externally managed variables here --> - {% for fstype in vars_fstypes %} - <input type="hidden" class="js-checkbox-fstypes-list" value="{{fstype}}"> - {% endfor %} - {% for b in vars_blacklist %} - <input type="hidden" class="js-config-blacklist-name" value="{{b}}"> - {% endfor %} - {% for b in vars_managed %} - <input type="hidden" class="js-config-managed-name" value="{{b}}"> - {% endfor %} - - <div class="row-fluid"> - <form id="variable-form"> - <fieldset style="padding-left:0px;"> - <legend>Add variable</legend> - <div class="span3" style="margin-left:0px;"> - <span id="add-configvar-name-div" class="control-group"> - <label> - Variable - <i title="" class="icon-question-sign get-help" - data-original-title="Variable names are case sensitive, - cannot have spaces, and can only include letters, numbers, underscores - and dashes"></i> - </label> - <input type="text" placeholder="Type variable name" id="variable"> - <span class="help-block error" id="new-variable-error-message"></span> - </span> - <label>Value</label> - <input id="value" type="text" placeholder="Type variable value"><p> - <div> - <button id="add-configvar-button" class="btn save" type="button" disabled>Add variable</button> - </div> - </div> - <div class="span5 help-block"> - <h5>Some variables are reserved from Toaster</h5> - <p>Toaster cannot set any variables that impact 1) the configuration of the build servers, - or 2) where artifacts produced by the build are stored. Such variables include: </p> - <p> - <code><a href="http://www.yoctoproject.org/docs/1.6.1/ref-manual/ref-manual.html#var-BB_DISKMON_DIRS" target="_blank">BB_DISKMON_DIRS</a></code> - <code><a href="http://www.yoctoproject.org/docs/1.6.1/ref-manual/ref-manual.html#var-BB_NUMBER_THREADS" target="_blank">BB_NUMBER_THREADS</a></code> - <code>CVS_PROXY_HOST</code> - <code>CVS_PROXY_PORT</code> - <code><a href="http://www.yoctoproject.org/docs/1.6.1/ref-manual/ref-manual.html#var-PARALLEL_MAKE" target="_blank">PARALLEL_MAKE</a></code> - <code><a href="http://www.yoctoproject.org/docs/1.6.1/ref-manual/ref-manual.html#var-SSTATE_MIRRORS" target="_blank">SSTATE_MIRRORS</a></code> - <code><a href="http://www.yoctoproject.org/docs/1.6.1/ref-manual/ref-manual.html#var-TMPDIR" target="_blank">TMPDIR</a></code></p> - <p>Plus the following standard shell environment variables:</p> - <p><code>http_proxy</code> <code>ftp_proxy</code> <code>https_proxy</code> <code>all_proxy</code></p> - </div> - </fieldset> - </form> +<div> + <dl> + {% if distro_defined %} + <dt> + <span class="js-config-var-name js-config-var-managed-name">DISTRO</span> + <span class="glyphicon glyphicon-question-sign get-help" title="The short name of the distribution. If the variable is blank, meta/conf/distro/defaultsetup.conf will be used"></span> + </dt> + <dd class="variable-list"> + <span class="lead" id="distro">{{distro}}</span> + <span class="glyphicon glyphicon-edit" id="change-distro-icon"></span> + <form id="change-distro-form" class="form-inline" style="display:none;"> + <div id="edit-distro-name-div" class="form-group"> + <input type="text" class="form-control" id="new-distro" value="{{distro}}"> + </div> + <button id="apply-change-distro" class="btn btn-default" type="button">Save</button> + <button id="cancel-change-distro" type="button" class="btn btn-link">Cancel</button> + <span class="help-block" id="distro-error-message"></span> + </form> + </dd> + {% endif %} + + {% if dl_dir_defined %} + <dt> + <span class="js-config-var-name js-config-var-managed-name">DL_DIR</span> + <span class="glyphicon glyphicon-question-sign get-help" title="Absolute path to the directory used to store downloads required for your builds. By default, Toaster projects share the same downloads directory"></span> + </dt> + <dd class="variable-list"> + <span id="dl_dir" class="lead {% if not dl_dir %} text-muted {% endif %}">{% if dl_dir %}{{dl_dir}}{%else%}Not set{%endif%}</span> + <span class="glyphicon glyphicon-edit" id="change-dl_dir-icon"></span> + <form id="change-dl_dir-form" class="form-inline" style="display:none;"> + <div class="form-group" id="validate-dl_dir"> + <input type="text" class="form-control" id="new-dl_dir" placeholder="Type an absolute path"> + </div> + <button id="apply-change-dl_dir" class="btn btn-default" type="button">Save</button> + <button id="cancel-change-dl_dir" type="button" class="btn btn-link">Cancel</button> + <p class="help-block" id="hintError-dl_dir" style="display:none;">The directory path cannot include spaces or any of these characters: . \ ? % * : | " " < ></p> + <p class="help-block" id="hintError-initialChar-dl_dir" style="display:none;">The directory path should either start with a /, e.g. /home/toaster/downloads; or with a variable, e.g. ${TOPDIR}/downloads.</p> + </form> + </dd> + {% endif %} + + {% if fstypes_defined %} + <dt> + <span class="js-config-var-name js-config-var-managed-name">IMAGE_FSTYPES</span> + <span class="glyphicon glyphicon-question-sign get-help" title="Formats of root file system images that you want to create"></span> + </dt> + <dd class="variable-list"> + <span class="lead" id="image_fstypes">{{fstypes}}</span> + <span class="glyphicon glyphicon-edit" id="change-image_fstypes-icon"></span> + <form id="change-image_fstypes-form" style="display:none;"> + <label>Type the image types you want to build:</label> + <div class="form-group form-inline" id="validate-image_fstypes"> + <input type="text" class="form-control "id="new-imagefs_types"> + <button id="apply-change-image_fstypes" type="button" class="btn btn-default">Save</button> + <button id="cancel-change-image_fstypes" type="button" class="btn btn-link">Cancel</button> + </div> + <p class="help-block text-danger" style="display:none;" id="hintError-image-fs_type">A valid image type cannot include underscores</p> + <label>Or choose from known image types:</label> + <input id="filter-image_fstypes" type="text" placeholder="Search image types" class="form-control"> + <div id="all-image_fstypes" class="scrolling"></div> + </form> + </dd> + {% endif %} + + {% if image_install_append_defined %} + <dt> + <span class="js-config-var-name js-config-var-managed-name">IMAGE_INSTALL_append</span> + <span class="glyphicon glyphicon-question-sign get-help" title="Specifies additional packages to install into an image. If your build creates more than one image, the packages will be installed in all of them"></span> + </dt> + <dd class="variable-list"> + <span id="image_install" class="lead {% if not image_install_append %} text-muted {%endif%}">{% if image_install_append %}{{image_install_append}}{%else%}Not set{%endif%}</span> + <span class="glyphicon glyphicon-edit" id="change-image_install-icon"></span> + <span class="glyphicon glyphicon-trash" id="delete-image_install-icon" {% if image_install_append %}{%else%}style="display:none;"{%endif%}></span> + <form id="change-image_install-form" class="form-inline" style="display:none;"> + <div class="row"> + <div class="col-md-4"> + <span class="help-block">To set IMAGE_INSTALL_append to more than one package, type the package names separated by a space.</span> </div> - - </div> - - <script> - - // global variables - var do_reload=false; - - // validate new variable name - function validate_new_variable() { - var variable = $("input#variable").val(); - var value = $("input#value").val(); - - // presumed innocence - $('#new-variable-error-message').text(""); - var error_msg = ""; - - var existing_configvars = document.getElementsByClassName('js-config-var-name'); - for (var i = 0, length = existing_configvars.length; i < length; i++) { - if (existing_configvars[i].innerHTML.toUpperCase() == variable.toUpperCase()) { - error_msg = "This variable is already set in this page, edit its value instead"; - } - } - - var blacklist_configvars = document.getElementsByClassName('js-config-blacklist-name'); - for (var i = 0, length = blacklist_configvars.length; i < length; i++) { - if (blacklist_configvars[i].value.toUpperCase() == variable.toUpperCase()) { - error_msg = "You cannot edit this variable in Toaster because it is set by the build servers"; - } - } - - var managed_configvars = document.getElementsByClassName('js-config-managed-name'); - for (var i = 0, length = managed_configvars.length; i < length; i++) { - if (managed_configvars[i].value.toUpperCase() == variable.toUpperCase()) { - error_msg = "You cannot set this variable here. Please set it in the <a href=\"{% url 'project' project.id %}\">project main page</a>"; - } - } - - var bad_chars = /[^a-zA-Z0-9\-_]/.test(variable); - var has_spaces = (0 <= variable.indexOf(" ")); - var only_spaces = (0 < variable.length) && (0 == variable.trim().length); - - if (only_spaces) { - error_msg = "A valid variable name cannot include spaces"; - } else if (bad_chars && has_spaces) { - error_msg = "A valid variable name can only include letters, numbers, underscores, dashes, and cannot include spaces"; - } else if (bad_chars) { - error_msg = "A valid variable name can only include letters, numbers, underscores, and dashes"; - } - - if ("" != error_msg) { - $('#new-variable-error-message').html(error_msg); - $(".save").attr("disabled","disabled"); - - // add one (and only one) error class append - var d = document.getElementById("add-configvar-name-div"); - d.className = d.className.replace(" error",""); - d.className = d.className + " error"; - - return false; - } else if (0 == variable.length) { - $(".save").attr("disabled","disabled"); - return false; - } - - var d = document.getElementById("add-configvar-name-div"); - d.className = d.className.replace(" error",""); - - // now set the "Save" enablement if 'value' also passes - if (value.trim().length > 0) { - $(".save").removeAttr("disabled"); - } else { - $(".save").attr("disabled","disabled"); - } - - return true; - } - - // validate distro name - function validate_distro_name() { - var value = $("input#new-distro").val(); - - // presumed innocence - $('#distro-error-message').text(""); - var error_msg = ""; - - var has_spaces = (0 <= value.indexOf(" ")); - - if (has_spaces) { - error_msg = "A valid distro name cannot include spaces"; - } else if (0 == value.length) { - error_msg = " "; - } - - if ("" != error_msg) { - $('#distro-error-message').text(error_msg); - $("#apply-change-distro").attr("disabled","disabled"); - - // add one (and only one) error class append - var d = document.getElementById("edit-distro-name-div"); - d.className = d.className.replace(" error",""); - d.className = d.className + " error"; - - return false; - } - - var d = document.getElementById("edit-distro-name-div"); - d.className = d.className.replace(" error",""); - $("#apply-change-distro").removeAttr("disabled"); - return true; - } - - // Test to insure at least one FS Type is checked - function enableFsTypesSave() { - var any_checked = 0; - $(".fs-checkbox-fstypes:checked").each(function(){ - any_checked = 1; - }); - if ( 0 == any_checked ) { - $("#apply-change-image_fstypes").attr("disabled","disabled"); - $('#fstypes-error-message').show(); - } - else { - $("#apply-change-image_fstypes").removeAttr("disabled"); - $('#fstypes-error-message').hide(); - } - } - - // Preset or reset the Package Class checkbox labels - function updatePackageClassCheckboxes() { - $('#package_class_1, #package_class_2').hide(); - if ($('select').val() == 'package_deb') { - $('#package_class_1').html('<input type="checkbox" id="package_class_1_input"> package_ipk'); - $('#package_class_2').html('<input type="checkbox" id="package_class_2_input"> package_rpm'); - } - if ($('select').val() == 'package_ipk') { - $('#package_class_1').html('<input type="checkbox" id="package_class_1_input"> package_deb'); - $('#package_class_2').html('<input type="checkbox" id="package_class_2_input"> package_rpm'); - } - if ($('select').val() == 'package_rpm') { - $('#package_class_1').html('<input type="checkbox" id="package_class_1_input"> package_deb'); - $('#package_class_2').html('<input type="checkbox" id="package_class_2_input"> package_ipk'); - } - $('#package_class_1, #package_class_2').fadeIn(1500); - } - - // Re-assert handlers when the page is served and/or refreshed via Ajax - function setEventHandlersForDynamicElements() { - - // change variable value - $('.js-icon-pencil-config_var').click(function (evt) { - var pk = evt.target.attributes["x-data"].value; - var current_val = $("span#config_var_value_"+pk).text(); - $('.js-icon-pencil-config_var, .js-icon-trash-config_var, #config_var_value_'+pk).hide(); - $("#change-config_var-form_"+pk).slideDown(); - $("input#new-config_var_"+pk).val(current_val); - }); - - $('.js-cancel-change-config_var').click(function (evt) { - var pk = evt.target.attributes["x-data"].value; - $("#change-config_var-form_"+pk).slideUp(function() { - $('.js-icon-pencil-config_var, .js-icon-trash-config_var, #config_var_value_'+pk).show(); - }); - }); - - $(".js-new-config_var").on('input', function(){ - if ($(this).val().length == 0) { - $(".js-apply-change-config_var").attr("disabled","disabled"); - } - else { - $(".js-apply-change-config_var").removeAttr("disabled"); - } - }); - - $('.js-apply-change-config_var').click(function (evt) { - var xdata = evt.target.attributes["x-data"].value.split(":"); - var pk = xdata[0]; - var variable = xdata[1]; - var val = $('#new-config_var_'+pk).val(); - postEditAjaxRequest({"configvarChange" : variable+':'+val}); - $('#config_var_value_'+pk).parent().removeClass('muted'); - $("#change-config_var-form_"+pk).slideUp(function() { - $('.js-icon-pencil-config_var, .js-icon-trash-config_var, #config_var_value_'+pk).show(); - }); - }); - - // delete variable - $(".js-icon-trash-config_var").click(function (evt) { - var xdata = evt.target.attributes["x-data"].value.split(":"); - var pk = xdata[0]; - - // hide the dangling trash tooltip - $('#config_var_trash_'+pk).hide(); - - // fade out the variable+value div, then refresh the variable list - $('#config_var_entry_'+pk).parent().parent().fadeOut(1000, function(){ - postEditAjaxRequest({"configvarDel": evt.target.attributes["x-data"].value}); - }); - - }); - - } - - function onEditPageUpdate(data) { - // update targets - var i; var orightml = ""; - - var configvars_sorted = data.configvars.sort(function(a, b){return a[0] > b[0]}); - - var managed_configvars = document.getElementsByClassName('js-config-var-managed-name'); - - for (i = 0; i < configvars_sorted.length; i++) { - // skip if the variable name has a special context (not user defined) - var var_context=undefined; - for (var j = 0, length = managed_configvars.length; j < length; j++) { - if ((managed_configvars[j].innerHTML == configvars_sorted[i][0]) || - (managed_configvars[j].value == configvars_sorted[i][0]) ) { - var_context='m'; - } - } - if (var_context == undefined) { - orightml += '<div> <dt><span id="config_var_entry_'+configvars_sorted[i][2]+'" class="js-config-var-name"></span><i class="icon-trash js-icon-trash-config_var" id="config_var_trash_'+configvars_sorted[i][2]+'" x-data="'+configvars_sorted[i][2]+'"></i> </dt>' - orightml += '<dd class="lead">' - orightml += ' <span id="config_var_value_'+configvars_sorted[i][2]+'"></span>' - orightml += ' <i class="icon-pencil js-icon-pencil-config_var" x-data="'+configvars_sorted[i][2]+'"></i>' - orightml += ' <form id="change-config_var-form_'+configvars_sorted[i][2]+'" style="display:none;">' - orightml += ' <div class="input-append">' - orightml += ' <input type="text" class="input-xlarge js-new-config_var" id="new-config_var_'+configvars_sorted[i][2]+'" value="">' - orightml += ' <button class="btn js-apply-change-config_var" type="button" x-data="'+configvars_sorted[i][2]+':'+configvars_sorted[i][0]+'" disabled>Save</button>' - orightml += ' <button type="button" class="btn btn-link js-cancel-change-config_var" x-data="'+configvars_sorted[i][2]+'">Cancel</button>' - orightml += ' </div>' - orightml += ' </form>' - orightml += '</dd> </div>' - } - } - - // update configvars list HTML framework - $("dl#configvar-list").html(orightml); - - // insert the name/value pairs safely as non-HTML - for (i = 0; i < configvars_sorted.length; i++) { - $('#config_var_entry_'+configvars_sorted[i][2]).text(configvars_sorted[i][0]); - $('#config_var_value_'+configvars_sorted[i][2]).text(configvars_sorted[i][1]); - } - - // Add the tooltips - $(".js-icon-trash-config_var").each( function(){ setDeleteTooltip($(this)); }); - $(".js-icon-pencil-config_var").each(function(){ setChangeTooltip($(this)); }); - - // re-assert these event handlers - setEventHandlersForDynamicElements(); - } - - function onEditAjaxSuccess(data, textstatus) { - // console.log("XHR returned:", data, "(" + textstatus + ")"); - if (data.error != "ok") { - alert("error on request:\n" + data.error); - return; - } - - // delayed page reload? - if (do_reload) { - do_reload=false; - location.reload(true); - } else { - onEditPageUpdate(data); - } - } - - function onEditAjaxError(jqXHR, textstatus, error) { - alert("XHR errored:\n" + error + "\n(" + textstatus + ")"); - // re-assert the event handlers - } - - /* ensure cookie exists {% csrf_token %} */ - function postEditAjaxRequest(reqdata) { - var ajax = $.ajax({ - type:"POST", - data: $.param(reqdata), - url:"{% url 'xhr_configvaredit' project.id%}", - headers: { 'X-CSRFToken': $.cookie("csrftoken")}, - success: onEditAjaxSuccess, - error: onEditAjaxError, - }) - } - - function setDeleteTooltip(object) { - object.tooltip({ container: 'body', html: true, delay: {show: 400}, title: "Delete" }); - } - function setChangeTooltip(object) { - object.tooltip({ container: 'body', html: true, delay: {show: 400}, title: "Change" }); + </div> + <div class="form-group"> + <input type="text" class="form-control" id="new-image_install" placeholder="Type one or more package names"> + </div> + <button id="apply-change-image_install" class="btn btn-default" type="button">Save</button> + <button id="cancel-change-image_install" type="button" class="btn btn-link">Cancel</button> + </form> + </dd> + {% endif %} + + {% if package_classes_defined %} + <dt> + <span class="js-config-var-name js-config-var-managed-name">PACKAGE_CLASSES</span> + <span class="glyphicon glyphicon-question-sign get-help" title="Specifies the package manager to use when packaging data"></span> + </dt> + <dd class="variable-list"> + <span class="lead" id="package_classes">{{package_classes}}</span> + <span id="change-package_classes-icon" class="glyphicon glyphicon-edit"></span> + <form id="change-package_classes-form" style="display:none;"> + <div class="form-group"> + <label class="control-label"> + Root file system package format + <span class="glyphicon glyphicon-question-sign get-help" title="The package format used to generate the root file system. Options are <code>deb</code>, <code>ipk</code> and <code>rpm</code>"></i> + </label> + <select id="package_classes-select" class="form-control"> + <option>package_deb</option> + <option>package_ipk</option> + <option>package_rpm</option> + </select> + </div> + <div class="form-group"> + <label class="control-label"> + Additional package formats + <span class="glyphicon glyphicon-question-sign get-help" title="Extra package formats to build"></span> + </label> + <div class="checkbox"> + <label id="package_class_1"> + <input type="checkbox" id="package_class_1_input"> package_deb + </label> + </div> + <div class="checkbox"> + <label id="package_class_2"> + <input type="checkbox" id="package_class_2_input"> package_ipk + </label> + </div> + </div> + <button id="apply-change-package_classes" type="button" class="btn btn-default">Save</button> + <button id="cancel-change-package_classes" type="button" class="btn btn-link">Cancel</button> + </form> + </dd> + {% endif %} + + {% if sstate_dir_defined %} + <dt> + <span class="js-config-var-name js-config-var-managed-name">SSTATE_DIR</span> + <span class="glyphicon glyphicon-question-sign get-help" title="Absolute path to the directory used to store shared state cache files. These files are reused across the builds, which makes the builds faster. By default, Toaster projects share the same cache directory"></span> + </dt> + <dd class="variable-list"> + <span id="sstate_dir" class="lead {% if not sstate_dir %} text-muted {% endif %}">{% if sstate_dir %}{{sstate_dir}}{%else%}Not set{%endif%}</span> + <span class="glyphicon glyphicon-edit" id="change-sstate_dir-icon"></span> + <form class="form-inline" id="change-sstate_dir-form" style="display:none;"> + <div class="form-group" id="validate-sstate_dir"> + <input type="text" class="form-control" id="new-sstate_dir" placeholder="Type an absolute path"> + </div> + <button id="apply-change-sstate_dir" class="btn btn-default" type="button">Save</button> + <button id="cancel-change-sstate_dir" type="button" class="btn btn-link">Cancel</button> + <p class="help-block" id="hintError-sstate_dir" style="display:none;">The directory path cannot include spaces or any of these characters: . \ ? % * : | " " < ></p> + <p class="help-block" id="hintError-initialChar-sstate_dir" style="display:none;">The directory path should either start with a /, e.g. /home/toaster/sstate-cache; or with a variable, e.g. ${TOPDIR}/sstate-cache.</p> + </form> + </dd> + {% endif %} + </dl> + + <!-- <ul class="list-unstyled configuration-list" id="configvar-list"> --> + <dl id="configvar-list"> + <!-- the added configuration variables are inserted here --> + </dl> + + <!-- pass the fstypes list, black list, and externally managed variables here --> + {% for fstype in vars_fstypes %} + <input type="hidden" class="js-checkbox-fstypes-list" value="{{fstype}}"> + {% endfor %} + {% for b in vars_blacklist %} + <input type="hidden" class="js-config-blacklist-name" value="{{b}}"> + {% endfor %} + {% for b in vars_managed %} + <input type="hidden" class="js-config-managed-name" value="{{b}}"> + {% endfor %} + + <form id="variable-form"> + <fieldset> + <legend>Add variable</legend> + <div class="row"> + <div class="col-md-3"> + <div id="add-configvar-name-div" class="form-group"> + <label class="control-label"> + Variable + <span class="glyphicon glyphicon-question-sign get-help" + title="Variable names are case sensitive, + cannot have spaces, and can only include letters, numbers, underscores + and dashes"></span> + </label> + <input type="text" class="form-control" placeholder="Type the variable name" id="variable"> + </div> + <p class="help-block" id="new-variable-error-message"></p> + <div class="form-group"> + <label clas="control-label">Value</label> + <input id="value" type="text" class="form-control" placeholder="Type the variable value"> + </div> + <button id="add-configvar-button" class="btn btn-default save" type="button" disabled>Add variable</button> + </div> + <div class="col-md-5 help-block"> + <h5>Some variables cannot be set from Toaster</h5> + <p>Toaster cannot set any variables that impact 1) the configuration of the build servers, + or 2) where artifacts produced by the build are stored. Such variables include: </p> + <p> + <code><a href="http://www.yoctoproject.org/docs/1.6.1/ref-manual/ref-manual.html#var-BB_DISKMON_DIRS" target="_blank">BB_DISKMON_DIRS</a></code> + <code><a href="http://www.yoctoproject.org/docs/1.6.1/ref-manual/ref-manual.html#var-BB_NUMBER_THREADS" target="_blank">BB_NUMBER_THREADS</a></code> + <code>CVS_PROXY_HOST</code> + <code>CVS_PROXY_PORT</code> + <code><a href="http://www.yoctoproject.org/docs/1.6.1/ref-manual/ref-manual.html#var-PARALLEL_MAKE" target="_blank">PARALLEL_MAKE</a></code> + <code><a href="http://www.yoctoproject.org/docs/1.6.1/ref-manual/ref-manual.html#var-TMPDIR" target="_blank">TMPDIR</a></code></p> + <p>Plus the following standard shell environment variables:</p> + <p><code>http_proxy</code> <code>ftp_proxy</code> <code>https_proxy</code> <code>all_proxy</code></p> + </div> + </div> + </fieldset> + </form> +</div> + +</div> + +<script> + +// global variables +var do_reload=false; + +// validate new variable name +function validate_new_variable() { + var variable = $("input#variable").val(); + var value = $("input#value").val(); + + // presumed innocence + $('#new-variable-error-message').text(""); + var error_msg = ""; + + var existing_configvars = document.getElementsByClassName('js-config-var-name'); + for (var i = 0, length = existing_configvars.length; i < length; i++) { + if (existing_configvars[i].innerHTML.toUpperCase() == variable.toUpperCase()) { + error_msg = "This variable is already set in this page. Edit its value instead"; + } + } + + var blacklist_configvars = document.getElementsByClassName('js-config-blacklist-name'); + for (var i = 0, length = blacklist_configvars.length; i < length; i++) { + if (blacklist_configvars[i].value.toUpperCase() == variable.toUpperCase()) { + error_msg = "You cannot edit this variable in Toaster because it is set by the build servers"; + } + } + + var managed_configvars = document.getElementsByClassName('js-config-managed-name'); + for (var i = 0, length = managed_configvars.length; i < length; i++) { + if (managed_configvars[i].value.toUpperCase() == variable.toUpperCase()) { + error_msg = "You cannot set this variable here. Please set it in the <a href=\"{% url 'project' project.id %}\">project main page</a>"; + } + } + + var bad_chars = /[^a-zA-Z0-9\-_/]/.test(variable); + var has_spaces = (0 <= variable.indexOf(" ")); + var only_spaces = (0 < variable.length) && (0 == variable.trim().length); + + if (only_spaces) { + error_msg = "A valid variable name cannot include spaces"; + } else if (bad_chars && has_spaces) { + error_msg = "A valid variable name can only include letters, numbers and the special characters <code> _ - /</code>. Variable names cannot include spaces"; + } else if (bad_chars) { + error_msg = "A valid variable name can only include letters, numbers and the special characters <code>_ - /</code>"; + } + + if ("" != error_msg) { + $('#new-variable-error-message').html(error_msg); + $(".save").attr("disabled","disabled"); + + // add one (and only one) error class append + $("#add-configvar-name-div").addClass("has-error"); + $("#new-variable-error-message").addClass("text-danger"); + + return false; + } else if (0 == variable.length) { + $(".save").attr("disabled","disabled"); + return false; + } + + $("#add-configvar-name-div").removeClass("has-error"); + + // now set the "Save" enablement if 'value' also passes + if (value.trim().length > 0) { + $(".save").removeAttr("disabled"); + } else { + $(".save").attr("disabled","disabled"); + } + + return true; +} + +// validate distro name +function validate_distro_name() { + var value = $("input#new-distro").val(); + + // presumed innocence + $('#distro-error-message').text(""); + var error_msg = ""; + + var has_spaces = (0 <= value.indexOf(" ")); + + if (has_spaces) { + error_msg = "A valid distro name cannot include spaces"; + } else if (0 == value.length) { + error_msg = " "; + } + + if ("" != error_msg) { + $('#distro-error-message').text(error_msg); + $("#apply-change-distro").attr("disabled","disabled"); + + // add one (and only one) error class append + $("#change-distro-form").addClass("has-error"); + + return false; + } + + $("#change-distro-form").removeClass("has-error"); + $("#apply-change-distro").removeAttr("disabled"); + return true; +} + +// Test to insure at least one FS Type is checked +function enableFsTypesSave() { + var any_checked = 0; + $(".fs-checkbox-fstypes:checked").each(function(){ + any_checked = 1; + }); + if ( 0 == any_checked ) { + $("#apply-change-image_fstypes").attr("disabled","disabled"); + $('.scrolling').addClass('has-error'); + $('#fstypes-error-message').show(); + } + else { + $("#apply-change-image_fstypes").removeAttr("disabled"); + $('.scrolling').removeClass('has-error'); + $('#fstypes-error-message').hide(); + } +} + +// Preset or reset the Package Class checkbox labels +function updatePackageClassCheckboxes() { + $('#package_class_1, #package_class_2').hide(); + if ($('select').val() == 'package_deb') { + $('#package_class_1').html('<input type="checkbox" id="package_class_1_input"> package_ipk'); + $('#package_class_2').html('<input type="checkbox" id="package_class_2_input"> package_rpm'); + } + if ($('select').val() == 'package_ipk') { + $('#package_class_1').html('<input type="checkbox" id="package_class_1_input"> package_deb'); + $('#package_class_2').html('<input type="checkbox" id="package_class_2_input"> package_rpm'); + } + if ($('select').val() == 'package_rpm') { + $('#package_class_1').html('<input type="checkbox" id="package_class_1_input"> package_deb'); + $('#package_class_2').html('<input type="checkbox" id="package_class_2_input"> package_ipk'); + } + $('#package_class_1, #package_class_2').fadeIn(1500); +} + +// Re-assert handlers when the page is served and/or refreshed via Ajax +function setEventHandlersForDynamicElements() { + + // change variable value + $('.js-icon-pencil-config_var').click(function (evt) { + var pk = $(this).attr("x-data"); + var current_val = $("#config_var_value_"+pk).text(); + $("#config_var_value_"+pk).hide(); + $("#config_var_trash_"+pk).hide(); + $(".js-icon-pencil-config_var[x-data="+pk+"]").hide(); + $("#change-config_var-form_"+pk).slideDown(); + $("#new-config_var_"+pk).val(current_val); + if ( $("#new-config_var_"+pk).val().length ) { + $("#apply-change-config_var_"+pk).removeAttr("disabled"); + } + else { + $("#apply-change-config_var_"+pk).attr("disabled"); + } + }); + + $('.js-cancel-change-config_var').click(function (evt) { + var pk = evt.target.attributes["x-data"].value; + $("#change-config_var-form_"+pk).slideUp(function() { + $("#config_var_trash_"+pk).show(); + $('#config_var_value_'+pk).show(); + $(".js-icon-pencil-config_var[x-data="+pk+"]").show(); + }); + }); + + $(".js-new-config_var").on('input', function(){ + if ($(this).val().length == 0) { + $(this).parent("div").next(".btn-default").attr("disabled","disabled"); + } + else { + $(this).parent("div").next(".btn-default").removeAttr("disabled"); + } + }); + + $('.js-apply-change-config_var').click(function (evt) { + var xdata = evt.target.attributes["x-data"].value.split(":"); + var pk = xdata[0]; + var variable = xdata[1]; + var val = $('#new-config_var_'+pk).val(); + postEditAjaxRequest({"configvarChange" : variable+':'+val}); + $("#change-config_var-form_"+pk).slideUp(); + $("#config_var_trash_"+pk).fadeIn(); + $('#config_var_value_'+pk).fadeIn(); + $(".js-icon-pencil-config_var[x-data="+pk+"]").fadeIn(); + }); + + // delete variable + $(".js-icon-trash-config_var").click(function (evt) { + var pk = $(this).attr("x-data"); + + // fade out the variable+value div, then refresh the variable list + $(this).fadeOut(); + $(this).tooltip("hide"); + $("config_var_entry_"+pk).fadeOut(); + $('#config_var_value_'+pk).parent("dd").fadeOut(); + postEditAjaxRequest({"configvarDel": evt.target.attributes["x-data"].value}); + }); + +} + +function onEditPageUpdate(data) { + // update targets + var i; var orightml = ""; + + var configvars_sorted = data.configvars.sort(function(a, b){return a[0] > b[0]}); + + var managed_configvars = document.getElementsByClassName('js-config-var-managed-name'); + + for (i = 0; i < configvars_sorted.length; i++) { + // skip if the variable name has a special context (not user defined) + var var_context=undefined; + for (var j = 0, length = managed_configvars.length; j < length; j++) { + if ((managed_configvars[j].innerHTML == configvars_sorted[i][0]) || + (managed_configvars[j].value == configvars_sorted[i][0]) ) { + var_context='m'; + } + } + if (var_context == undefined) { + orightml += '<dt><span id="config_var_entry_'+configvars_sorted[i][2]+'" class="js-config-var-name"></span><span class="glyphicon glyphicon-trash js-icon-trash-config_var" id="config_var_trash_'+configvars_sorted[i][2]+'" x-data="'+configvars_sorted[i][2]+'"></span> </dt>' + orightml += '<dd class="variable-list">' + orightml += ' <span class="lead" id="config_var_value_'+configvars_sorted[i][2]+'"></span>' + orightml += ' <span class="glyphicon glyphicon-edit js-icon-pencil-config_var" x-data="'+configvars_sorted[i][2]+'"></span>' + orightml += ' <form class="form-inline" id="change-config_var-form_'+configvars_sorted[i][2]+'" style="display:none;">' + orightml += ' <div class="form-group">' + orightml += ' <input type="text" class="form-control js-new-config_var" id="new-config_var_'+configvars_sorted[i][2]+'" value=""></div>' + orightml += ' <button id="apply-change-config_var_'+configvars_sorted[i][2]+'" class="btn btn-default js-apply-change-config_var" type="button" x-data="'+configvars_sorted[i][2]+':'+configvars_sorted[i][0]+'" disabled>Save</button>' + orightml += ' <button type="button" class="btn btn-link js-cancel-change-config_var" x-data="'+configvars_sorted[i][2]+'">Cancel</button>' + orightml += ' </form>' + orightml += '</dd>' + } + } + + // update configvars list HTML framework + $("dl#configvar-list").html(orightml); + + // insert the name/value pairs safely as non-HTML + for (i = 0; i < configvars_sorted.length; i++) { + $('#config_var_entry_'+configvars_sorted[i][2]).text(configvars_sorted[i][0]); + $('#config_var_value_'+configvars_sorted[i][2]).text(configvars_sorted[i][1]); + } + + // Add the tooltips + $(".js-icon-trash-config_var").each( function(){ setDeleteTooltip($(this)); }); + $(".js-icon-pencil-config_var").each(function(){ setChangeTooltip($(this)); }); + + // re-assert these event handlers + setEventHandlersForDynamicElements(); +} + +function onEditAjaxSuccess(data, textstatus) { + console.log("XHR returned:", data, "(" + textstatus + ")"); + if (data.error != "ok") { + alert("error on request:\n" + data.error); + return; + } + + // delayed page reload? + if (do_reload) { + do_reload=false; + location.reload(true); + } else { + onEditPageUpdate(data); + } +} + +function onEditAjaxError(jqXHR, textstatus, error) { + alert("XHR errored:\n" + error + "\n(" + textstatus + ")"); + // re-assert the event handlers +} + +/* ensure cookie exists {% csrf_token %} */ +function postEditAjaxRequest(reqdata) { + var ajax = $.ajax({ + type:"POST", + data: $.param(reqdata), + url:"{% url 'xhr_configvaredit' project.id%}", + headers: { 'X-CSRFToken': $.cookie("csrftoken")}, + success: onEditAjaxSuccess, + error: onEditAjaxError, + }) +} + +function setDeleteTooltip(object) { + object.tooltip({ container: 'body', html: true, delay: {show: 400}, title: "Delete" }); +} +function setChangeTooltip(object) { + object.tooltip({ container: 'body', html: true, delay: {show: 400}, title: "Change" }); +} + +$(document).ready(function() { + + // + // Register handlers for static elements + // + + {% if distro_defined %} + // change distro variable + $('#change-distro-icon').click(function() { + $('#change-distro-icon, #distro').hide(); + $("#change-distro-form").slideDown(); + $("#new-distro").val( $('#distro').text() ); + $("#apply-change-distro").removeAttr("disabled"); + }); + + $('#cancel-change-distro').click(function(){ + $("#change-distro-form").slideUp(function() { + $('#distro, #change-distro-icon').show(); + + // reset any dangling error state + $('#distro-error-message').text(""); + $("#change-distro-form").removeClass("has-error"); + }); + }); + + // validate new distro name + $("input#new-distro").on('input', function (evt) { + validate_distro_name(); + }); + + $('#apply-change-distro').click(function(){ + //$('#repo').parent().removeClass('highlight-go'); + var name = $('#new-distro').val(); + postEditAjaxRequest({"configvarChange" : 'DISTRO:'+name}); + $('#distro').text(name); + $("#change-distro-form").slideUp(function () { + $('#distro, #change-distro-icon').show(); + }); + }); + {% endif %} + + {% if dl_dir_defined %} + + // change DL_DIR variable + $('#change-dl_dir-icon').click(function() { + $('#change-dl_dir-form').removeClass('has-error'); + // preset the edit value + var current_val = $("#dl_dir").text().trim(); + if (current_val == "Not set") { + current_val=""; + $("#apply-change-dl_dir").attr("disabled","disabled"); + } + $("input#new-dl_dir").val(current_val); + // enable / disable the save button based on the input value + if ( current_val.length ) { + $("#apply-change-dl_dir").removeAttr("disabled"); + } + else { + $("#apply-change-dl_dir").attr("disabled","disabled"); + } + + $('#change-dl_dir-icon, #dl_dir').hide(); + $("#change-dl_dir-form").slideDown(); + }); + + $('#cancel-change-dl_dir').click(function(){ + $("#hintError-dl_dir").hide(); + $("#hintError-initialChar-dl_dir").hide(); + $("#change-dl_dir-form").slideUp(function() { + $('#dl_dir, #change-dl_dir-icon').show(); + }); + }); + + $("#new-dl_dir").on('input', function(){ + if ($(this).val().trim().length == 0) { + $("#apply-change-dl_dir").attr("disabled","disabled"); + $('#change-dl_dir-form').addClass('has-error'); + $('#hintError-dl_dir').hide(); + $('#hintError-initialChar-dl_dir').hide(); + } + else { + var input = $(this); + var reBeginWithSlash = /^\//; + var reCheckVariable = /^\$/; + var re = /([ <>\\|":\.%\?\*]+)/; + var invalidDir = re.test(input.val()); + var invalidSlash = reBeginWithSlash.test(input.val()); + var invalidVar = reCheckVariable.test(input.val()); + if (!invalidSlash && !invalidVar) { + $('#change-dl_dir-form').addClass('has-error'); + $("#apply-change-dl_dir").attr("disabled","disabled"); + $('#hintError-initialChar-dl_dir').show(); + } else if (invalidDir) { + $('#change-dl_dir-form').addClass('has-error'); + $("#apply-change-dl_dir").attr("disabled","disabled"); + $('#hintError-dl_dir').show(); + } else { + $('#change-dl_dir-form').removeClass('has-error'); + $("#apply-change-dl_dir").removeAttr("disabled"); + $('#hintError-dl_dir').hide(); + $('#hintError-initialChar-dl_dir').hide(); + } + } + }); + + $('#apply-change-dl_dir').click(function(){ + var value = $('#new-dl_dir').val().trim(); + postEditAjaxRequest({"configvarChange" : 'DL_DIR:'+value}); + $('#dl_dir').text(value); + $('#dl_dir').removeClass('muted'); + $("#change-dl_dir-form").slideUp(function () { + $('#dl_dir, #change-dl_dir-icon').show(); + }); + }); + + {% endif %} + + {% if fstypes_defined %} + // change IMAGE_FSTYPES variable + + // get value of fstypes and add to the textbox + $("#new-imagefs_types").val("{{fstypes}}"); + + // If value of new-imagefs_types is empty disable save button + $("#new-imagefs_types").on("input", function() { + $(this).val($(this).val().replace(/\s+/g,' ')); + if ($(this).val().length === 0) { + //$('#apply-change-image_fstypes').prop('disabled', true); + $('#apply-change-image_fstypes').attr("disabled", "disabled"); + } else { + //$('#apply-change-image_fstypes').prop('disabled', false); + $('#apply-change-image_fstypes').removeAttr("disabled"); + } + + /*If user types imagefs do the action on checkboxes. + Lets say if an imagefstype typed by user and the same + imagefs is unchecked in the checkbox, then checkbox needs + to get checked. Similarly when user deletes imagefs from + textbox the checkbox which is checked gets unchecked. + */ + $('#all-image_fstypes input').each(function(){ + var imagefs_userval = $('#new-imagefs_types').val(); + if( imagefs_userval.indexOf($(this).val()) > -1) { + $(this).prop('checked', true); + } else { + $(this).prop('checked', false); + } + }); + + // Validate underscore in image fs types + if ($(this).val().indexOf('_') > -1) { + $('#validate-image_fstypes').addClass('has-error'); + $('#hintError-image-fs_type').show(); + $("#apply-change-image_fstypes").prop("disabled", true); + } else { + $('#validate-image_fstypes').removeClass('has-error'); + $('#hintError-image-fs_type').hide(); + } + }); + + $('#change-image_fstypes-icon').click(function() { + $('#change-image_fstypes-icon, #image_fstypes').hide(); + $("#change-image_fstypes-form").slideDown(); + // avoid false substring matches by including space separators + var html = ""; + var fstypes = " " + document.getElementById("image_fstypes").innerHTML + " "; + var fstypes_list = document.getElementsByClassName('js-checkbox-fstypes-list'); + // Add the checked boxes first + if (" " != fstypes) { + for (var i = 0, length = fstypes_list.length; i < length; i++) { + if (0 <= fstypes.indexOf(" "+fstypes_list[i].value+" ")) { + html += '<div class="checkbox"><label><input type="checkbox" class="fs-checkbox-fstypes" value="'+fstypes_list[i].value+'" checked="checked">'+fstypes_list[i].value+'</label></div>'; } - - $(document).ready(function() { - - // - // Register handlers for static elements - // - - {% if distro_defined %} - // change distro variable - $('#change-distro-icon').click(function() { - $('#change-distro-icon, #distro').hide(); - $("#change-distro-form").slideDown(); - $("#new-distro").val( $('#distro').text() ); - }); - - $('#cancel-change-distro').click(function(){ - $("#change-distro-form").slideUp(function() { - $('#distro, #change-distro-icon').show(); - - // reset any dangling error state - $('#distro-error-message').text(""); - var d = document.getElementById("edit-distro-name-div"); - d.className = d.className.replace(" error",""); - }); - }); - - // validate new distro name - $("input#new-distro").on('input', function (evt) { - validate_distro_name(); - }); - - $('#apply-change-distro').click(function(){ - //$('#repo').parent().removeClass('highlight-go'); - var name = $('#new-distro').val(); - postEditAjaxRequest({"configvarChange" : 'DISTRO:'+name}); - $('#distro').text(name); - $("#change-distro-form").slideUp(function () { - $('#distro, #change-distro-icon').show(); - }); - }); - {% endif %} - - {% if dl_dir_defined %} - - // change DL_DIR variable - $('#change-dl_dir-icon').click(function() { - $('#hintError-dl_dir').hide(); - // preset the edit value - var current_val = $("span#dl_dir").text().trim(); - if (current_val == "Not set") { - current_val=""; - $("#apply-change-dl_dir").attr("disabled","disabled"); - } - $("input#new-dl_dir").val(current_val); - - $('#change-dl_dir-icon, #dl_dir').hide(); - $("#change-dl_dir-form").slideDown(); - }); - - $('#cancel-change-dl_dir').click(function(){ - $("#change-dl_dir-form").slideUp(function() { - $('#dl_dir, #change-dl_dir-icon').show(); - }); - }); - - $("#new-dl_dir").on('input', function(){ - if ($(this).val().trim().length == 0) { - $("#apply-change-dl_dir").attr("disabled","disabled"); - } - else { - var input = $(this); - var re = /^\/([^ <>\\|":\.%\?\*]+)$/; - var invalidDir = re.test(input.val()); - console.log(invalidDir); - if ( invalidDir ) { - $('#validate-dl_dir').removeClass('control-group error'); - $("#apply-change-dl_dir").removeAttr("disabled"); - $('#hintError-dl_dir').hide(); - } else { - $('#validate-dl_dir').addClass('control-group error'); - $("#apply-change-dl_dir").attr("disabled","disabled"); - $('#hintError-dl_dir').show(); - } - } - }); - - $('#apply-change-dl_dir').click(function(){ - var value = $('#new-dl_dir').val().trim(); - postEditAjaxRequest({"configvarChange" : 'DL_DIR:'+value}); - $('#dl_dir').text(value); - $('#dl_dir').removeClass('muted'); - $("#change-dl_dir-form").slideUp(function () { - $('#dl_dir, #change-dl_dir-icon').show(); - }); - }); - - {% endif %} - - {% if fstypes_defined %} - // change IMAGE_FSTYPES variable - - $('#change-image_fstypes-icon').click(function() { - $('#change-image_fstypes-icon, #image_fstypes').hide(); - $("#change-image_fstypes-form").slideDown(); - // avoid false substring matches by including space separators - var html = ""; - var fstypes = " " + document.getElementById("image_fstypes").innerHTML + " "; - var fstypes_list = document.getElementsByClassName('js-checkbox-fstypes-list'); - // Add the checked boxes first - if (" " != fstypes) { - for (var i = 0, length = fstypes_list.length; i < length; i++) { - if (0 <= fstypes.indexOf(" "+fstypes_list[i].value+" ")) { - html += '<label class="checkbox"><input type="checkbox" class="fs-checkbox-fstypes" value="'+fstypes_list[i].value+'" checked="checked">'+fstypes_list[i].value+'</label>\n'; - } - } - } - // Add the un-checked boxes second - for (var i = 0, length = fstypes_list.length; i < length; i++) { - if (0 > fstypes.indexOf(" "+fstypes_list[i].value+" ")) { - html += '<label class="checkbox"><input type="checkbox" class="fs-checkbox-fstypes" value="'+fstypes_list[i].value+'">'+fstypes_list[i].value+'</label>\n'; - } - } - // Add the 'no search matches' line last - html += '<label id="no-match-fstypes">No image types found</label>\n'; - // Display the list - document.getElementById("all-image_fstypes").innerHTML = html; - $('#no-match-fstypes').hide(); - - // Watch elements to disable Save when none are checked - $(".fs-checkbox-fstypes").each(function(){ - $(this).click(function() { - enableFsTypesSave(); - }); - }); - - // clear the previous filter values and warning messages - $("input#filter-image_fstypes").val(""); - $('#fstypes-error-message').hide(); - }); - - $('#cancel-change-image_fstypes').click(function(){ - $("#change-image_fstypes-form").slideUp(function() { - $('#image_fstypes, #change-image_fstypes-icon').show(); - }); - }); - - $('#filter-image_fstypes').on('input', function(){ - var valThis = $(this).val().toLowerCase(); - var matchCount=0; - $('#all-image_fstypes label').each(function(){ - var text = $(this).text().toLowerCase(); - var match = text.indexOf(valThis); - if (match >= 0) { - $(this).show(); - matchCount += 1; - } - else { - $(this).hide(); - } - }); - if (matchCount === 0) { - $('#no-match-fstypes').show(); - } else { - $('#no-match-fstypes').hide(); - } - }); - - $('#apply-change-image_fstypes').click(function(){ - // extract the selected fstypes and sort them - var fstypes_array = []; - var checkboxes = document.getElementsByClassName('fs-checkbox-fstypes'); - $(".fs-checkbox-fstypes:checked").each(function(){ - fstypes_array.push($(this).val()); - }); - fstypes_array.sort(); - - // now make a string of them - var fstypes = ''; - for (var i = 0, length = fstypes_array.length; i < length; i++) { - fstypes += fstypes_array[i] + ' '; - } - fstypes = fstypes.trim(); - - postEditAjaxRequest({"configvarChange" : 'IMAGE_FSTYPES:'+fstypes}); - $('#image_fstypes').text(fstypes); - $('#image_fstypes').parent().removeClass('muted'); - - $("#change-image_fstypes-form").slideUp(function() { - $('#image_fstypes, #change-image_fstypes-icon').show(); - }); - }); - {% endif %} - - - {% if image_install_append_defined %} - - // init IMAGE_INSTALL_append trash icon - setDeleteTooltip($('#delete-image_install-icon')); - - // change IMAGE_INSTALL_append variable - $('#change-image_install-icon').click(function() { - // preset the edit value - var current_val = $("span#image_install").text().trim(); - if (current_val == "Not set") { - current_val=""; - $("#apply-change-image_install").attr("disabled","disabled"); - } else { - // insure these non-empty values have single space prefix - current_val=" " + current_val; - } - $("input#new-image_install").val(current_val); - - $('#change-image_install-icon, #delete-image_install-icon, #image_install').hide(); - $("#change-image_install-form").slideDown(); - }); - - $('#cancel-change-image_install').click(function(){ - $("#change-image_install-form").slideUp(function() { - $('#image_install, #change-image_install-icon').show(); - if ($("span#image_install").text() != "Not set") { - $('#delete-image_install-icon').show(); - setDeleteTooltip($('#delete-image_install-icon')); - } - }); - }); - - $("#new-image_install").on('input', function(){ - if ($(this).val().trim().length == 0) { - $("#apply-change-image_install").attr("disabled","disabled"); - } - else { - $("#apply-change-image_install").removeAttr("disabled"); - } - }); - - $('#apply-change-image_install').click(function(){ - // insure these non-empty values have single space prefix - var value = " " + $('#new-image_install').val().trim(); - postEditAjaxRequest({"configvarChange" : 'IMAGE_INSTALL_append:'+value}); - $('#image_install').text(value); - $('#image_install').removeClass('muted'); - $("#change-image_install-form").slideUp(function () { - $('#image_install, #change-image_install-icon').show(); - if (value.length > -1) { - $('#delete-image_install-icon').show(); - setDeleteTooltip($('#delete-image_install-icon')); - } - }); - }); - - // delete IMAGE_INSTALL_append variable value - $('#delete-image_install-icon').click(function(){ - $(this).tooltip('hide'); - postEditAjaxRequest({"configvarChange" : 'IMAGE_INSTALL_append:'+''}); - $('#image_install').parent().fadeOut(1000, function(){ - $('#image_install').addClass('muted'); - $('#image_install').text('Not set'); - $('#delete-image_install-icon').hide(); - $('#image_install').parent().fadeIn(1000); - }); - }); - {% endif %} - - - {% if package_classes_defined %} - // change PACKAGE_CLASSES variable - $('#change-package_classes-icon').click(function() { - $('#change-package_classes-icon, #package_classes').hide(); - $("#change-package_classes-form").slideDown(); - - // initialize the pulldown and checkboxes - var value = $("#package_classes").text(); - if ( value.indexOf("package_deb") == 0 ) { - $("#package_classes-select").prop('selectedIndex', 0); - updatePackageClassCheckboxes(); - if ( value.indexOf("_ipk") > 0 ) { - $("#package_class_1_input").attr("checked",true); - } - if ( value.indexOf("_rpm") > 0 ) { - $("#package_class_2_input").attr("checked",true); - } - } - - if ( value.indexOf("package_ipk") == 0 ) { - $("#package_classes-select").prop('selectedIndex', 1); - updatePackageClassCheckboxes(); - if ( value.indexOf("_deb") > 0 ) { - $("#package_class_1_input").attr("checked",true); - } - if ( value.indexOf("_rpm") > 0 ) { - $("#package_class_2_input").attr("checked",true); - } - } - - if ( value.indexOf("package_rpm") == 0 ) { - $("#package_classes-select").prop('selectedIndex', 2); - updatePackageClassCheckboxes(); - if ( value.indexOf("_deb") > 0 ) { - $("#package_class_1_input").attr("checked",true); - } - if ( value.indexOf("_ipk") > 0 ) { - $("#package_class_2_input").attr("checked",true); - } - } - }); - - $('#cancel-change-package_classes').click(function(){ - $("#change-package_classes-form").slideUp(function() { - $('#package_classes, #change-package_classes-icon').show(); - }); - }); - - $('select').change(function() { - updatePackageClassCheckboxes(); - }); - - $('#apply-change-package_classes').click(function(){ - var e = document.getElementById("package_classes-select"); - var val = e.options[e.selectedIndex].text; - - pc1_checked = document.getElementById("package_class_1_input").checked; - pc2_checked = document.getElementById("package_class_2_input").checked; - if (val == "package_deb") { - if (pc1_checked) val = val + " package_ipk"; - if (pc2_checked) val = val + " package_rpm"; - } - if (val == "package_ipk") { - if (pc1_checked) val = val + " package_deb"; - if (pc2_checked) val = val + " package_rpm"; - } - if (val == "package_rpm") { - if (pc1_checked) val = val + " package_deb"; - if (pc2_checked) val = val + " package_ipk"; - } - - $('#package_classes').text(val); - //$('#package_classes').parent().removeClass('muted'); - postEditAjaxRequest({"configvarChange" : 'PACKAGE_CLASSES:'+val}); - $("#change-package_classes-form").slideUp(function() { - $('#package_classes, #change-package_classes-icon').show(); - }); - }); - {% endif %} - - {% if sstate_dir_defined %} - - // change SSTATE_DIR variable - $('#change-sstate_dir-icon').click(function() { - $('#hintError-sstate_dir').hide(); - // preset the edit value - var current_val = $("span#sstate_dir").text().trim(); - if (current_val == "Not set") { - current_val=""; - $("#apply-change-sstate_dir").attr("disabled","disabled"); - } - $("input#new-sstate_dir").val(current_val); - - $('#change-sstate_dir-icon, #sstate_dir').hide(); - $("#change-sstate_dir-form").slideDown(); - }); - - $('#cancel-change-sstate_dir').click(function(){ - $("#change-sstate_dir-form").slideUp(function() { - $('#sstate_dir, #change-sstate_dir-icon').show(); - }); - }); - - $("#new-sstate_dir").on('input', function(){ - if ($(this).val().trim().length == 0) { - $("#apply-change-sstate_dir").attr("disabled","disabled"); - } - else { - var input = $(this); - var re = /^\/([^ <>\\|":\.%\?\*]+)$/; - var invalidDir = re.test(input.val()); - console.log(invalidDir); - if ( invalidDir ) { - $('#validate-sstate_dir').removeClass('control-group error'); - $("#apply-change-sstate_dir").removeAttr("disabled"); - $('#hintError-sstate_dir').hide(); - } else { - $('#validate-sstate_dir').addClass('control-group error'); - $("#apply-change-sstate_dir").attr("disabled","disabled"); - $('#hintError-sstate_dir').show(); - } - } - }); - - $('#apply-change-sstate_dir').click(function(){ - var value = $('#new-sstate_dir').val().trim(); - postEditAjaxRequest({"configvarChange" : 'SSTATE_DIR:'+value}); - $('#sstate_dir').text(value); - $('#sstate_dir').removeClass('muted'); - $("#change-sstate_dir-form").slideUp(function () { - $('#sstate_dir, #change-sstate_dir-icon').show(); - }); - }); - - {% endif %} - - // add new variable - $("button#add-configvar-button").click( function (evt) { - var variable = $("input#variable").val(); - var value = $("input#value").val(); - - postEditAjaxRequest({"configvarAdd" : variable+':'+value}); - - // clear the previous values - $("input#variable").val(""); - $("input#value").val(""); - // Disable add button - $(".save").attr("disabled","disabled"); - - // Reload page if admin-removed core managed value is manually added back in - if (0 <= " DISTRO DL_DIR IMAGE_FSTYPES IMAGE_INSTALL_append PACKAGE_CLASSES SSTATE_DIR ".indexOf( " "+variable+" " )) { - // delayed reload to avoid race condition with postEditAjaxRequest - do_reload=true; - } - }); - - // validate new variable name and value - $("#variable, #value").on('input', function() { - validate_new_variable(); - }); - - // - // draw and register the dynamic configuration variables and handlers - // - - var data = { - configvars : [] - }; - {% for c in configvars %} - data.configvars.push([ "{{c.name}}","{{c.value}}","{{c.pk}}" ]); - {% if '' != vars_context|get_dict_value:c.name %} - data.vars_context[ "{{c.name}}" ] = "{{vars_context|get_dict_value:c.name }}"; - {% endif %} - {% endfor %} - - // draw these elements and assert their event handlers - onEditPageUpdate(data); - }); - - </script> + } + } + // Add the un-checked boxes second + for (var i = 0, length = fstypes_list.length; i < length; i++) { + if (0 > fstypes.indexOf(" "+fstypes_list[i].value+" ")) { + html += '<div class="checkbox"><label><input type="checkbox" class="fs-checkbox-fstypes" value="'+fstypes_list[i].value+'">'+fstypes_list[i].value+'</label></div>'; + } + } + // Add the 'no search matches' line last + html += '<label id="no-match-fstypes" class="text-muted">No image types found</label>\n'; + // Display the list + document.getElementById("all-image_fstypes").innerHTML = html; + $('#no-match-fstypes').hide(); + + // clear the previous filter values and warning messages + $("input#filter-image_fstypes").val(""); + }); + + // When checkbox is checked/unchecked kindly update the text + $(document).on("change", "#all-image_fstypes :checkbox", function() { + var imagefs = $(this); + var imagefs_obj = $('#new-imagefs_types'); + var imagefs_userval = imagefs_obj.val(); + if ($(this).is(':checked')) { + if (imagefs_userval.indexOf($(imagefs).val()) === -1) { + imagefs_obj.val(imagefs_userval + " " + $(imagefs).val()); + } + } else { + if (imagefs_userval.indexOf($(imagefs).val()) > -1) { + imagefs_obj.val(imagefs_userval.replace($(imagefs).val(), '').trim()); + } + } + if ($('#new-imagefs_types').val().length === 0) { + $("#apply-change-image_fstypes").prop("disabled", true); + } else { + $("#apply-change-image_fstypes").prop("disabled", false); + } + }); + + $('#cancel-change-image_fstypes').click(function(){ + $("#new-imagefs_types").val("{{fstypes}}"); + $("#change-image_fstypes-form").slideUp(function() { + $('#image_fstypes, #change-image_fstypes-icon').show(); + }); + }); + + $('#filter-image_fstypes').on('input', function(){ + var valThis = $(this).val().toLowerCase(); + var matchCount=0; + $('#all-image_fstypes label').each(function(){ + var text = $(this).text().toLowerCase(); + var match = text.indexOf(valThis); + if (match >= 0) { + $(this).show(); + matchCount += 1; + } + else { + $(this).hide(); + } + }); + if (matchCount === 0) { + $('#no-match-fstypes').show(); + } else { + $('#no-match-fstypes').hide(); + } + }); + + $('#apply-change-image_fstypes').click(function(){ + var fstypes = $('#new-imagefs_types').val(); + + postEditAjaxRequest({"configvarChange" : 'IMAGE_FSTYPES:'+fstypes}); + $('#image_fstypes').text(fstypes); + $('#image_fstypes').parent().removeClass('muted'); + + $("#change-image_fstypes-form").slideUp(function() { + $('#image_fstypes, #change-image_fstypes-icon').show(); + }); + }); + {% endif %} + + + {% if image_install_append_defined %} + + // init IMAGE_INSTALL_append trash icon + setDeleteTooltip($('#delete-image_install-icon')); + + // change IMAGE_INSTALL_append variable + $('#change-image_install-icon').click(function() { + // preset the edit value + var current_val = $("span#image_install").text().trim(); + if (current_val == "Not set") { + current_val=""; + $("#apply-change-image_install").attr("disabled","disabled"); + } else { + // insure these non-empty values have single space prefix + current_val=" " + current_val; + $("#apply-change-image_install").removeAttr("disabled"); + } + $("input#new-image_install").val(current_val); + + $('#change-image_install-icon, #delete-image_install-icon, #image_install').hide(); + $("#change-image_install-form").slideDown(); + }); + + $('#cancel-change-image_install').click(function(){ + $("#change-image_install-form").slideUp(function() { + $('#image_install, #change-image_install-icon').show(); + if ($("span#image_install").text() != "Not set") { + $('#delete-image_install-icon').show(); + setDeleteTooltip($('#delete-image_install-icon')); + } + }); + }); + + $("#new-image_install").on('input', function(){ + if ($(this).val().trim().length == 0) { + $("#apply-change-image_install").attr("disabled","disabled"); + } + else { + $("#apply-change-image_install").removeAttr("disabled"); + } + }); + + $('#apply-change-image_install').click(function(){ + // insure these non-empty values have single space prefix + var value = " " + $('#new-image_install').val().trim(); + postEditAjaxRequest({"configvarChange" : 'IMAGE_INSTALL_append:'+value}); + $('#image_install').text(value); + $('#image_install').removeClass('text-muted'); + $("#change-image_install-form").slideUp(function () { + $('#image_install, #change-image_install-icon').show(); + if (value.length > -1) { + $('#delete-image_install-icon').show(); + setDeleteTooltip($('#delete-image_install-icon')); + } + }); + }); + + // delete IMAGE_INSTALL_append variable value + $('#delete-image_install-icon').click(function(){ + $(this).tooltip('hide'); + postEditAjaxRequest({"configvarChange" : 'IMAGE_INSTALL_append:'+''}); + $('#image_install').parent().fadeOut(1000, function(){ + $('#image_install').addClass('text-muted'); + $('#image_install').text('Not set'); + $('#delete-image_install-icon').hide(); + $('#image_install').parent().fadeIn(1000); + }); + }); + {% endif %} + + + {% if package_classes_defined %} + // change PACKAGE_CLASSES variable + $('#change-package_classes-icon').click(function() { + $('#change-package_classes-icon, #package_classes').hide(); + $("#change-package_classes-form").slideDown(); + + // initialize the pulldown and checkboxes + var value = $("#package_classes").text(); + if ( value.indexOf("package_deb") == 0 ) { + $("#package_classes-select").prop('selectedIndex', 0); + updatePackageClassCheckboxes(); + if ( value.indexOf("_ipk") > 0 ) { + $("#package_class_1_input").attr("checked",true); + } + if ( value.indexOf("_rpm") > 0 ) { + $("#package_class_2_input").attr("checked",true); + } + } + + if ( value.indexOf("package_ipk") == 0 ) { + $("#package_classes-select").prop('selectedIndex', 1); + updatePackageClassCheckboxes(); + if ( value.indexOf("_deb") > 0 ) { + $("#package_class_1_input").attr("checked",true); + } + if ( value.indexOf("_rpm") > 0 ) { + $("#package_class_2_input").attr("checked",true); + } + } + + if ( value.indexOf("package_rpm") == 0 ) { + $("#package_classes-select").prop('selectedIndex', 2); + updatePackageClassCheckboxes(); + if ( value.indexOf("_deb") > 0 ) { + $("#package_class_1_input").attr("checked",true); + } + if ( value.indexOf("_ipk") > 0 ) { + $("#package_class_2_input").attr("checked",true); + } + } + }); + + $('#cancel-change-package_classes').click(function(){ + $("#change-package_classes-form").slideUp(function() { + $('#package_classes, #change-package_classes-icon').show(); + }); + }); + + $('select').change(function() { + updatePackageClassCheckboxes(); + }); + + $('#apply-change-package_classes').click(function(){ + var e = document.getElementById("package_classes-select"); + var val = e.options[e.selectedIndex].text; + + pc1_checked = document.getElementById("package_class_1_input").checked; + pc2_checked = document.getElementById("package_class_2_input").checked; + if (val == "package_deb") { + if (pc1_checked) val = val + " package_ipk"; + if (pc2_checked) val = val + " package_rpm"; + } + if (val == "package_ipk") { + if (pc1_checked) val = val + " package_deb"; + if (pc2_checked) val = val + " package_rpm"; + } + if (val == "package_rpm") { + if (pc1_checked) val = val + " package_deb"; + if (pc2_checked) val = val + " package_ipk"; + } + + $('#package_classes').text(val); + //$('#package_classes').parent().removeClass('muted'); + postEditAjaxRequest({"configvarChange" : 'PACKAGE_CLASSES:'+val}); + $("#change-package_classes-form").slideUp(function() { + $('#package_classes, #change-package_classes-icon').show(); + }); + }); + {% endif %} + + {% if sstate_dir_defined %} + + // change SSTATE_DIR variable + $('#change-sstate_dir-icon').click(function() { + $('#change-sstate_dir-form').removeClass('has-error'); + // preset the edit value + var current_val = $("span#sstate_dir").text().trim(); + if (current_val == "Not set") { + current_val=""; + $("#apply-change-sstate_dir").attr("disabled","disabled"); + } + $("input#new-sstate_dir").val(current_val); + + // enable / disable the save button based on the input value + if ( current_val.length ) { + $("#apply-change-sstate_dir").removeAttr("disabled"); + } + else { + $("#apply-change-sstate_dir").attr("disabled","disabled"); + } + + $('#change-sstate_dir-icon, #sstate_dir').hide(); + $("#change-sstate_dir-form").slideDown(); + }); + + $('#cancel-change-sstate_dir').click(function(){ + $("#hintError-sstate_dir").hide(); + $("#hintError-initialChar-sstate_dir").hide(); + $("#change-sstate_dir-form").slideUp(function() { + $('#sstate_dir, #change-sstate_dir-icon').show(); + }); + }); + + $("#new-sstate_dir").on('input', function(){ + if ($(this).val().trim().length == 0) { + $("#apply-change-sstate_dir").attr("disabled","disabled"); + $('#change-sstate_dir-form').addClass('has-error'); + $('#hintError-sstate_dir').hide(); + $('#hintError-initialChar-sstate_dir').hide(); + } + else { + var input = $(this); + var reBeginWithSlash = /^\//; + var reCheckVariable = /^\$/; + var re = /([ <>\\|":\.%\?\*]+)/; + var invalidDir = re.test(input.val()); + var invalidSlash = reBeginWithSlash.test(input.val()); + var invalidVar = reCheckVariable.test(input.val()); + if (!invalidSlash && !invalidVar) { + $('#change-sstate_dir-form').addClass('has-error'); + $("#apply-change-sstate_dir").attr("disabled","disabled"); + $('#hintError-initialChar-sstate_dir').show(); + } else if (invalidDir) { + $('#change-sstate_dir-form').addClass('has-error'); + $("#apply-change-sstate_dir").attr("disabled","disabled"); + $('#hintError-sstate_dir').show(); + } else { + $('#change-sstate_dir-form').removeClass('has-error'); + $("#apply-change-sstate_dir").removeAttr("disabled"); + $('#hintError-sstate_dir').hide(); + $('#hintError-initialChar-sstate_dir').hide(); + } + } + }); + + $('#apply-change-sstate_dir').click(function(){ + var value = $('#new-sstate_dir').val().trim(); + postEditAjaxRequest({"configvarChange" : 'SSTATE_DIR:'+value}); + $('#sstate_dir').text(value); + $('#sstate_dir').removeClass('text-muted'); + $("#change-sstate_dir-form").slideUp(function () { + $('#sstate_dir, #change-sstate_dir-icon').show(); + }); + }); + + {% endif %} + + // add new variable + $("button#add-configvar-button").click( function (evt) { + var variable = $("input#variable").val(); + var value = $("input#value").val(); + + postEditAjaxRequest({"configvarAdd" : variable+':'+value}); + + // clear the previous values + $("input#variable").val(""); + $("input#value").val(""); + // Disable add button + $(".save").attr("disabled","disabled"); + + // Reload page if admin-removed core managed value is manually added back in + if (0 <= " DISTRO DL_DIR IMAGE_FSTYPES IMAGE_INSTALL_append PACKAGE_CLASSES SSTATE_DIR ".indexOf( " "+variable+" " )) { + // delayed reload to avoid race condition with postEditAjaxRequest + do_reload=true; + } + }); + + // validate new variable name and value + $("#variable, #value").on('input', function() { + validate_new_variable(); + }); + + // + // draw and register the dynamic configuration variables and handlers + // + + var data = { + configvars : [] + }; + {% for c in configvars %} + data.configvars.push([ "{{c.name}}","{{c.value}}","{{c.pk}}" ]); + {% if '' != vars_context|get_dict_value:c.name %} + data.vars_context[ "{{c.name}}" ] = "{{vars_context|get_dict_value:c.name }}"; + {% endif %} + {% endfor %} + + // draw these elements and assert their event handlers + onEditPageUpdate(data); +}); + +</script> {% endblock %} diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/projects-toastertable.html b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/projects-toastertable.html index 5814f32d0..d8020a97f 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/projects-toastertable.html +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/projects-toastertable.html @@ -4,33 +4,39 @@ {% block pagecontent %} - <div class="page-header top-air"> - <h1 data-role="page-title"></h1> - </div> - - {% url 'projects' as xhr_table_url %} - {% include 'toastertable.html' %} - - <script> - $(document).ready(function () { - var tableElt = $("#{{table_name}}"); - var titleElt = $("[data-role='page-title']"); - - tableElt.on("table-done", function (e, total, tableParams) { - var title = "All projects"; - - if (tableParams.search || tableParams.filter) { - if (total === 0) { - title = "No projects found"; - } - else if (total > 0) { - title = total + " project" + (total > 1 ? 's' : '') + " found"; - } - } - - titleElt.text(title); +<div class="row"> + <div class="col-md-12"> + + <div class="page-header"> + <h1 data-role="page-title"></h1> + </div> + + {% url 'projects' as xhr_table_url %} + {% include 'toastertable.html' %} + + <script> +$(document).ready(function () { + var tableElt = $("#{{table_name}}"); + var titleElt = $("[data-role='page-title']"); + + tableElt.on("table-done", function (e, total, tableParams) { + var title = "All projects"; + + if (tableParams.search || tableParams.filter) { + if (total === 0) { + title = "No projects found"; + } + else if (total > 0) { + title = total + " project" + (total > 1 ? 's' : '') + " found"; + } + } + + titleElt.text(title); }); }); - </script> + </script> + + </div> +</div> {% endblock %} diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/projecttopbar.html b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/projecttopbar.html index 007de06ff..768ca9455 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/projecttopbar.html +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/projecttopbar.html @@ -16,63 +16,64 @@ }); </script> -<div class="alert alert-success lead" id="project-created-notification" style="margin-top:15px; display:none"> - <button type="button" class="close" data-dismiss="alert">×</button> - Your project <strong>{{project.name}}</strong> has been created. You can now <a href="{% url 'projectmachines' project.id %}">select your target machine</a> and <a href="{% url 'projectimagerecipes' project.id %}">choose image recipes</a> to build. -</div> - -<!-- project name --> -<div class="page-header"> - <h1 id="project-name-container"> - <span id="project-name">{{project.name}}</span> +<div class="col-md-12"> + <div class="alert alert-success alert-dismissible change-notification" id="project-created-notification" style="display:none"> + <button type="button" class="close" data-dismiss="alert">×</button> + <p>Your project <strong>{{project.name}}</strong> has been created. You can now <a class="alert-link" href="{% url 'projectmachines' project.id %}">select your target machine</a> and <a class="alert-link" href="{% url 'projectimagerecipes' project.id %}">choose image recipes</a> to build.</p> + </div> + <!-- project name --> + <div class="page-header"> + <h1 id="project-name-container"> + <span class="project-name">{{project.name}}</span> - <i class="icon-pencil" data-original-title="" id="project-change-form-toggle" title=""></i> + <span class="glyphicon glyphicon-edit" id="project-change-form-toggle"></i> - {% if project.is_default %} - <i class="icon-question-sign get-help heading-help" title="" data-original-title="This project shows information about the builds you start from the command line while Toaster is running"></i> - {% endif %} - </h1> - <form id="project-name-change-form" style="margin-bottom: 0px; display: none;"> - <div class="input-append"> - <input class="huge input-xxlarge" type="text" id="project-name-change-input" autocomplete="off" value="{{project.name}}"> - <button id="project-name-change-btn" class="btn btn-large" type="button">Save</button> - <a href="#" id="project-name-change-cancel" class="btn btn-large btn-link">Cancel</a> - </div> - </form> -</div> + {% if project.is_default %} + <span class="glyphicon glyphicon-question-sign get-help" title="This project shows information about the builds you start from the command line while Toaster is running"></span> + {% endif %} + </h1> + <form id="project-name-change-form" class="form-inline" style="display: none;"> + <div class="form-group"> + <input class="form-control input-lg" type="text" id="project-name-change-input" autocomplete="off" value="{{project.name}}"> + </div> + <button id="project-name-change-btn" class="btn btn-default btn-lg" type="button">Save</button> + <a href="#" id="project-name-change-cancel" class="btn btn-lg btn-link">Cancel</a> + </form> + </div> -{% if not project.is_default %} + {% if not project.is_default %} <div id="project-topbar"> - <ul class="nav nav-pills"> + <ul class="nav nav-tabs"> <li id="topbar-configuration-tab"> - <a href="{% url 'project' project.id %}"> - Configuration - </a> + <a href="{% url 'project' project.id %}"> + Configuration + </a> </li> <li> - <a href="{% url 'projectbuilds' project.id %}"> - Builds ({{project.get_number_of_builds}}) - </a> + <a href="{% url 'projectbuilds' project.id %}"> + Builds ({{project.get_number_of_builds}}) + </a> </li> <li> - <a href="{% url 'importlayer' project.id %}"> - Import layer - </a> + <a href="{% url 'importlayer' project.id %}"> + Import layer + </a> </li> <li> - <a href="{% url 'newcustomimage' project.id %}"> - New custom image - </a> + <a href="{% url 'newcustomimage' project.id %}"> + New custom image + </a> </li> <li class="pull-right"> - <form class="form-inline" style="margin-bottom:0px;"> - <i class="icon-question-sign get-help heading-help" data-placement="left" title="" data-original-title="Type the name of one or more recipes you want to build, separated by a space. You can also specify a task by appending a colon and a task name to the recipe name, like so: <code>busybox:clean</code>"></i> - <div class="input-append"> - <input id="build-input" type="text" class="input-xlarge input-lg build-target-input" placeholder="Type the recipe you want to build" autocomplete="off" disabled> - <button id="build-button" class="btn btn-primary btn-large build-button" data-project-id="{{project.id}}" disabled>Build</button> + <form class="form-inline"> + <div class="form-group"> + <span class="glyphicon glyphicon-question-sign get-help" data-placement="left" title="Type the name of one or more recipes you want to build, separated by a space. You can also specify a task by appending a colon and a task name to the recipe name, like so: <code>busybox:clean</code>"></span> + <input id="build-input" type="text" class="form-control input-lg" placeholder="Type the recipe you want to build" autocomplete="off" disabled> </div> + <button id="build-button" class="btn btn-primary btn-lg" data-project-id="{{project.id}}" disabled>Build</button> </form> </li> </ul> </div> -{% endif %} + {% endif %} +</div> diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/recipe.html b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/recipe.html index 1d6d64e3c..bf2cd7169 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/recipe.html +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/recipe.html @@ -12,35 +12,43 @@ <!-- Begin container --> -<div class="row span11"> - <div class="page-header"> +<div class="row"> + <div class="col-md-12"> + <div class="page-header build-data"> <h1>{{object.name}}_{{object.version}}</h1> </div> + </div> </div> -<div class="row span7 tabbable"> - <ul class="nav nav-pills"> +<div class="row"> + <div class="col-md-8 tabbable"> + <ul class="nav nav-tabs"> <li class="{{tab_states.1}}"> <a href="#information" data-toggle="tab"> - <i class="icon-question-sign get-help" title="Build-related information about the recipe"></i> + <span class="glyphicon glyphicon-question-sign get-help" title="Build-related + information about the recipe"></span> Recipe details </a> </li> <li> <a href="{% url "recipe_packages" build.pk object.id %}"> - <i class="icon-question-sign get-help" title="The packaged output resulting from building the recipe"></i> + <span class="glyphicon glyphicon-question-sign get-help" title="The packaged + output resulting from building the recipe"></span> Packages ({{package_count}}) </a> </li> <li class="{{tab_states.3}}"> <a href="#dependencies" data-toggle="tab"> - <i class="icon-question-sign get-help" title="The recipe build-time dependencies (i.e. other recipes)"></i> + <span class="glyphicon glyphicon-question-sign get-help" title="The recipe + build-time dependencies (i.e. other recipes)"></span> Build dependencies ({{object.r_dependencies_recipe.all.count}}) </a> </li> <li class="{{tab_states.4}}"> <a href="#brought-in-by" data-toggle="tab"> - <i class="icon-question-sign get-help" title="The recipe build-time reverse dependencies (i.e. the recipes that depend on this recipe)"></i> + <span class="glyphicon glyphicon-question-sign get-help" title="The recipe + build-time reverse dependencies (i.e. the recipes that + depend on this recipe)"></span> Reverse build dependencies ({{object.r_dependencies_depends.all.count}}) </a> </li> @@ -49,34 +57,55 @@ <div class="tab-pane {{tab_states.1}}" id="information"> <dl class="dl-horizontal"> <dt> - <i class="icon-question-sign get-help" title="The name of the layer providing the recipe"></i> + <span class="glyphicon glyphicon-question-sign get-help" title="The name of + the layer providing the recipe"></span> Layer </dt> <dd>{{layer.name}}</dd> <dt> - <i class="icon-question-sign get-help" title="Path to the recipe .bb file"></i> + <span class="glyphicon glyphicon-question-sign get-help" title="Path to the + recipe .bb file"></span> Recipe file </dt> <dd><code>{{object.file_path}} {% if object.pathflags %}<i>({{object.pathflags}})</i>{% endif %}</code></dd> - {% if layer_version.branch %} <dt> - <i class="icon-question-sign get-help" title="The Git branch of the layer providing the recipe"></i> + <span class="glyphicon glyphicon-question-sign get-help" + title="The Git branch of the layer providing the + recipe"></span> Layer branch </dt> + {% if layer_version.layer.local_source_dir %} + <dd> + <span class="text-muted">Not applicable</span> + <span class="glyphicon glyphicon-question-sign get-help" title="The source + code of {{layer_version.layer.name}} is not in a git repository + so there is no branch associated with it"></span> + </dd> + {% else %} <dd>{{layer_version.branch}}</dd> - {% endif %} + {% endif %} <dt> - <i class="icon-question-sign get-help" title="The Git commit of the layer providing the recipe"></i> + <span class="glyphicon glyphicon-question-sign get-help" title="The Git + commit of the layer providing the recipe"></span> Layer commit </dt> + {% if layer_version.layer.local_source_dir %} + <dd> + <span class="text-muted">Not applicable</span> + <span class="glyphicon glyphicon-question-sign get-help" title="The source + code of {{layer_version.layer.name}} is not in a git repository + so there is no commit associated with it"></span> + </dd> + {% else %} <dd class="iscommit">{{layer_version.commit}}</dd> + {% endif %} {% if object.provides_set.all %} <dt> - <i class="icon-question-sign get-help" + <span class="glyphicon glyphicon-question-sign get-help" title="A list of aliases by which a particular recipe can be known. The additional aliases are synonyms for the recipe and can be useful satisfying dependencies of other recipes during - the build"></i> + the build"></span> PROVIDES </dt> <dd><code>{% for provider in object.provides_set.all %}{{ provider.name }} {% endfor %}</code></dd> @@ -89,30 +118,45 @@ <strong>{{object.name}}_{{object.version}}</strong> does not have any tasks in this build. </div> {% else %} + <div class="table-responsive"> <table class="table table-bordered table-hover"> <thead> - <tr> + <tr> <th> - <i class="icon-question-sign get-help" title="The running sequence of each task in the build"></i> + <span class="glyphicon glyphicon-question-sign + get-help" title="The running sequence of each task + in the build"></span> Order </th> <th> - <i class="icon-question-sign get-help" title="The name of the task"></i> + <span class="glyphicon glyphicon-question-sign get-help" title="The name + of the task"></span> Task </th> <th> - <i class="icon-question-sign get-help" title="This value tells you if a task had to run (executed) in order to generate the task output, or if the output was provided by another task and therefore the task didn't need to run (not executed)"></i> + <span class="glyphicon glyphicon-question-sign get-help" title="This + value tells you if a task had to run (executed) in + order to generate the task output, or if the output was + provided by another task and therefore the task didn't need + to run (not executed)"></span> Executed </th> <th> - <i class="icon-question-sign get-help" title="This column tells you if 'executed' tasks succeeded or failed. The column also tells you why 'not executed' tasks did not need to run"></i> + <span class="glyphicon glyphicon-question-sign get-help" title="This + column tells you if 'executed' tasks succeeded or + failed. The column also tells you why 'not executed' + tasks did not need to run"></span> Outcome </th> <th> - <i class="icon-question-sign get-help" title="This column tells you if a task tried to restore output from the <code>sstate-cache</code> directory or mirrors, and reports the result: Succeeded, Failed or File not in cache"></i> + <span class="glyphicon glyphicon-question-sign get-help" title="This + column tells you if a task tried to restore output + from the <code>sstate-cache</code> directory or + mirrors, and reports the result: Succeeded, Failed or File + not in cache"></span> Cache attempt </th> - </tr> + </tr> </thead> <tbody> @@ -120,26 +164,30 @@ <tr {{ task|task_color }} > - <td><a {{ task|task_color }} href="{% url "task" build.pk task.pk %}">{{task.order}}</a></td> + <td>{{task.order}}</td> <td> - <a {{ task|task_color }} href="{% url "task" build.pk task.pk %}">{{task.task_name}}</a> - {% if task.get_description %}<i class="icon-question-sign get-help hover-help" title="" data-original-title="{{task.get_description}}"></i> {% endif %} + <a href="{% url "task" build.pk task.pk %}">{{task.task_name}}</a> + {% if task.get_description %}<span class="glyphicon + glyphicon-question-sign get-help hover-help" + title="{{task.get_description}}"></span> + {% endif %} </td> - <td><a {{ task|task_color }} href="{% url "task" build.pk task.pk %}">{{task.get_executed_display}}</a></td> + <td>{{task.get_executed_display}}</td> - <td> - <a {{ task|task_color }} href="{% url "task" build.pk task.pk %}">{{task.get_outcome_display}} </a> + <td>{{task.get_outcome_display}} {% if task.outcome = task.OUTCOME_FAILED %} <a href="{% url 'build_artifact' build.pk "tasklogfile" task.pk %}"> - <i class="icon-download-alt" title="Download task log file"></i> + <span class="glyphicon glyphicon-download-alt + get-help" title="Download task log + file"></span> </a> {% endif %} <i class="icon-question-sign get-help hover-help" title="{{task.get_outcome_help}}"></i> </td> <td> {% ifnotequal task.sstate_result task.SSTATE_NA %} - <a {{ task|task_color }} href="{% url "task" build.pk task.pk %}">{{task.get_sstate_result_display}}</a> + {{task.get_sstate_result_display}} {% endifnotequal %} </td> @@ -148,6 +196,7 @@ {% endfor %} </tbody> </table> + </div> {% endif %} </div> <div class="tab-pane {{tab_states.3}}" id="dependencies"> @@ -174,13 +223,13 @@ <tr> <td><a href="{% url "recipe" build.pk rr.depends_on.pk %}">{{rr.depends_on.name}}</a> {% if rr.via %} - <span class="muted">satisfied via {{rr.via.name}}</span> - <i class="icon-question-sign get-help hover-help" + <span class="text-muted">satisfied via <code class="text-muted">{{rr.via.name}}</code></span> + <span class="glyphicon glyphicon-question-sign get-help hover-help" title="This dependency is satisfied by the PROVIDES value - {{rr.via.name}} in the {{rr.depends_on.name}} recipe"></i> + <code>{{rr.via.name}}</code> in the <code>{{rr.depends_on.name}}</code> recipe"></span> {% endif %} </td> - <td><a href="{% url "recipe" build.pk rr.depends_on.pk %}">{{rr.depends_on.version}}</a></td> + <td>{{rr.depends_on.version}}</td> </tr> {% endfor %} @@ -213,13 +262,13 @@ <tr> <td><a href="{% url "recipe" build.pk rr.recipe.pk %}">{{rr.recipe.name}}</a> {% if rr.via %} - <span class="muted"> satisfied via {{rr.via.name}}</span> - <i class="icon-question-sign get-help hover-help" + <span class="text-muted"> satisfied via <code class="text-muted">{{rr.via.name}}</code></span> + <span class="glyphicon glyphicon-question-sign get-help hover-help" title="This dependency is satisfied by the PROVIDES value - {{rr.via.name}} in the {{rr.depends_on.name}} recipe"></i> + <code>{{rr.via.name}}</code> in the <code>{{rr.depends_on.name}}</code> recipe"></i> {% endif %} </td> - <td><a href="{% url "recipe" build.pk rr.recipe.pk %}">{{rr.recipe.version}}</a></td> + <td>{{rr.recipe.version}}</td> </tr> {% endfor %} @@ -231,7 +280,8 @@ </div> </div> -<div class="row span4 well"> +<div class="col-md-4"> + <div class="well"> <h2>About {{object.name}}</h2> <dl class="item-info"> {% if object.summary %} @@ -253,7 +303,7 @@ {% if object.section %} <dt> Section - <i class="icon-question-sign get-help" title="The section in which recipes should be categorized"></i> + <span class="glyphicon glyphicon-question-sign get-help" title="The section in which recipes should be categorized"></span> </dt> <dd>{{object.section}}</dd> {% endif %} @@ -262,6 +312,9 @@ <dd>{{object.license}}</dd> {% endif %} </dl> + </div> </div> +</div> <!-- end row --> + {% endblock %} diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/recipe_btn.html b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/recipe_btn.html index baab06eb5..e3729643a 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/recipe_btn.html +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/recipe_btn.html @@ -1,16 +1,17 @@ -<button data-recipe-name="{{data.name}}" class="btn btn-block layer-exists-{{data.layer_version.pk}} build-recipe-btn" style="margin-top: 5px; +<a data-recipe-name="{{data.name}}" class="btn btn-default btn-block layer-exists-{{data.layer_version.pk}} build-recipe-btn" style="margin-top: 5px; {% if data.layer_version.pk not in extra.current_layers %} display:none; {% endif %}" > Build recipe -</button> -<button class="btn btn-block layerbtn layer-add-{{data.layer_version.pk}}" data-layer='{ "id": {{data.layer_version.pk}}, "name": "{{data.layer_version.layer.name}}", "layerdetailurl": "{%url 'layerdetails' extra.pid data.layer_version.pk%}"}' data-directive="add" +</a> +<a class="btn btn-default btn-block layerbtn layer-add-{{data.layer_version.pk}}" data-layer='{ "id": {{data.layer_version.pk}}, "name": "{{data.layer_version.layer.name}}", "layerdetailurl": "{%url 'layerdetails' extra.pid data.layer_version.pk%}"}' data-directive="add" {% if data.layer_version.pk in extra.current_layers %} style="display:none;" {% endif %} > - <i class="icon-plus"></i> + <span class="glyphicon glyphicon-plus"></span> Add layer - <i title="" class="icon-question-sign get-help" data-original-title="To build this target, you must first add the {{data.layer_version.layer.name}} layer to your project"></i> -</button> + <span class="glyphicon glyphicon-question-sign get-help" title="To build this + recipe you must first add the {{data.layer_version.layer.name}} layer to your project"></i> +</a> diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/recipe_packages.html b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/recipe_packages.html index d25847bc0..37a586f38 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/recipe_packages.html +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/recipe_packages.html @@ -11,35 +11,43 @@ <!-- Begin container --> -<div class="row-fluid span11"> - <div class="page-header"> +<div class="row"> + <div class="col-md-12"> + <div class="page-header build-data"> <h1>{{recipe.name}}_{{recipe.version}}</h1> </div> + </div> </div> -<div class="row-fluid span7 tabbable"> - <ul class="nav nav-pills"> +<div class="row"> + <div class="col-md-8 tabbable"> + <ul class="nav nav-tabs"> <li> <a href="{% url "recipe" build.pk recipe.id "1" %}"> - <i class="icon-question-sign get-help" title="Build-related information about the recipe"></i> + <span class="glyphicon glyphicon-question-sign get-help" title="Build-related + information about the recipe"></span> Recipe details </a> </li> <li class="active"> - <a href="#packages-built" data-toggle="tab"> - <i class="icon-question-sign get-help" title="The packaged output resulting from building the recipe"></i> + <a href="#packages-built" data-toggle="tab"> + <span class="glyphicon glyphicon-question-sign get-help" title="The packaged + output resulting from building the recipe"></span> Packages ({{object_count}}) </a> </li> <li> <a href="{% url "recipe" build.pk recipe.id "3" %}"> - <i class="icon-question-sign get-help" title="The recipe build-time dependencies (i.e. other recipes)"></i> + <span class="glyphicon glyphicon-question-sign get-help" title="The recipe + build-time dependencies (i.e. other recipes)"></span> Build dependencies ({{recipe.r_dependencies_recipe.all.count}}) </a> </li> <li> <a href="{% url "recipe" build.pk recipe.id "4" %}"> - <i class="icon-question-sign get-help" title="The recipe build-time reverse dependencies (i.e. the recipes that depend on this recipe)"></i> + <span class="glyphicon glyphicon-question-sign get-help" title="The recipe + build-time reverse dependencies (i.e. the recipes that + depend on this recipe)"></span> Reverse build dependencies ({{recipe.r_dependencies_depends.all.count}}) </a> </li> @@ -72,8 +80,8 @@ <tr> <td><a href="{% url "package_built_detail" build.pk package.pk %}">{{package.name}}</a></td> - <td><a href="{% url "package_built_detail" build.pk package.pk %}">{{package.version}}_{{package.revision}}</a></td> - <td class="sizecol"><a href="{% url "package_built_detail" build.pk package.pk %}">{{package.size|filtered_filesizeformat}}</a></td> + <td>{{package.version}}_{{package.revision}}</td> + <td class="sizecol">{{package.size|filtered_filesizeformat}}</td> </tr> {% endfor %} @@ -86,9 +94,10 @@ {% endif %} </div> {# tab-pane #} </div> {# tab-content #} -</div> {# span7 #} +</div> {# col-md-8 #} -<div class="row span4 well"> +<div class="col-md-4"> + <div class="well"> <h2>About {{recipe.name}}</h2> <dl class="item-info"> {% if recipe.summary %} @@ -119,5 +128,8 @@ <dd>{{recipe.license}}</dd> {% endif %} </dl> + </div> </div> + +</div> <!-- end row --> {% endblock pagedetailinfomain %} diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/recipedetails.html b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/recipedetails.html index 23aa171ce..66c1f7bcd 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/recipedetails.html +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/recipedetails.html @@ -4,25 +4,23 @@ {% load static %} {% block pagecontent %} -<div class="section"> - <ul class="breadcrumb"> - <li> - <a href="{% url 'project' project.id %}">{{project.name}}</a> - <span class="divider">→</span> - </li> - <li> - {% if recipe.is_image %} - <a href="{% url 'projectimagerecipes' project.id %}">Image recipes</a> - {% else %} - <a href="{% url 'projectsoftwarerecipes' project.id %}">Software recipes</a> - {% endif %} - <span class="divider">→</span> - </li> - <li class="active"> - {{recipe.name}} ({{recipe.layer_version.layer.name}}) - </li> - </ul> -</div> +<ul class="breadcrumb"> + <li> + <a href="{% url 'project' project.id %}">{{project.name}}</a> + <span class="divider">→</span> + </li> + <li> + {% if recipe.is_image %} + <a href="{% url 'projectimagerecipes' project.id %}">Image recipes</a> + {% else %} + <a href="{% url 'projectsoftwarerecipes' project.id %}">Software recipes</a> + {% endif %} + <span class="divider">→</span> + </li> + <li class="active"> + {{recipe.name}} ({{recipe.layer_version.layer.name}}) + </li> +</ul> <script src="{% static 'js/recipedetails.js' %}"></script> <script> @@ -51,42 +49,40 @@ {% include 'newcustomimage_modal.html' %} -<div class="row-fluid span11"> - <div class="alert alert-success lead" id="image-created-notification" style="margin-top: 15px; display: none"> - <button type="button" data-dismiss="alert" class="close">x</button> - Your custom image <strong>{{recipe.name}}</strong> has been created. You can now add or remove packages as needed. - </div> - <div class="page-header air"> - <h1> - {{recipe.name}} - <small>({{recipe.layer_version.layer.name}})</small> - </h1> - </div> +<div class="alert alert-success lead" id="image-created-notification" style="margin-top: 15px; display: none"> + <button type="button" data-dismiss="alert" class="close">x</button> + Your custom image <strong>{{recipe.name}}</strong> has been created. You can now add or remove packages as needed. +</div> +<div class="page-header"> + <h1> + {{recipe.name}} + <small>({{recipe.layer_version.layer.name}})</small> + </h1> </div> -<div class="row-fluid span11"> - <div class="span8"> +<div class="row"> + <div class="col-md-8"> <div class="button-place btn-group" id="customise-build-btns" style="width: 100%; {% if not in_project %} display:none; {% endif %}"> - <button class="btn btn-large span6 build-recipe-btn" style="width: 50%"> + <button class="btn btn-default btn-lg build-recipe-btn" style="width: 50%"> Build {{recipe.name}} </button> {% if recipe.is_image %} - <button class="btn btn-large span6 customise-btn" data-recipe="{{recipe.pk}}" style="width: 50%"> + <button class="btn btn-default btn-lg customise-btn" data-recipe="{{recipe.pk}}" style="width: 50%"> Customise {{recipe.name}} </button> {% endif %} </div> <div class="button-place"> - <button class="btn btn-block btn-large" id="add-layer-btn" + <button class="btn btn-default btn-block btn-lg" id="add-layer-btn" style="width:100%; {% if in_project %} display:none; {% endif %}"> - <i class="icon-plus"></i> + <i class="glyphicon glyphicon-plus"></i> Add the {{recipe.layer_version.layer.name}} layer to your project to build or customise this image recipe </button> </div> @@ -106,7 +102,7 @@ style="display:none" {% endif %} > <p class="lead">Toaster has no package information for {{recipe.name}}. To generate package information, build {{recipe.name}}</p> - <button class="btn btn-info btn-large build-recipe-btn" style="margin:20px 0 10px 0;">Build {{recipe.name}}</button> + <button class="btn btn-info btn-lg build-recipe-btn" style="margin:20px 0 10px 0;">Build {{recipe.name}}</button> </div> <div class="alert alert-info air" id="packages-alert" @@ -119,58 +115,60 @@ </div> </div> </div> - <div class="span4 well"> - <h2 style="margin-bottom:20px;">About {{recipe.name}}</h2> - <dl> - <dt> + <div class="col-md-4"> + <div class="well"> + <h2>About {{recipe.name}}</h2> + <dl class="item-info"> + <dt> Approx. packages included - <i class="icon-question-sign get-help" title="" data-original-title="The number of packages included is based on information from previous builds and from parsing layers, so we can never be sure it is 100% accurate"></i> - </dt> - <dd class="no-packages">{{packages.count}}</dd> - <dt> + <span class="glyphicon glyphicon-question-sign get-help" title="The number of packages included is based on information from previous builds and from parsing layers, so we can never be sure it is 100% accurate"></span> + </dt> + <dd class="no-packages">{{packages.count}}</dd> + <dt> Approx. package size - <i class="icon-question-sign get-help" title="" data-original-title="Package size is based on information from previous builds, so we can never be sure it is 100% accurate"></i> - </dt> - <dd>{{approx_pkg_size.size__sum|filtered_filesizeformat}}</dd> - {% if last_build %} - <dt>Last build</dt> - <dd> - <i class="icon-ok-sign success"></i> + <span class="glyphicon glyphicon-question-sign get-help" title="Package size is based on information from previous builds, so we can never be sure it is 100% accurate"></span> + </dt> + <dd>{{approx_pkg_size.size__sum|filtered_filesizeformat}}</dd> + {% if last_build %} + <dt>Last build</dt> + <dd> + <span class="glyphicon glyphicon-ok-circle"></span> <a href="{% url 'projectbuilds' project.id%}">{{last_build.completed_on|date:"d/m/y H:i"}}</a> - </dd> - {% endif %} - <dt>Recipe file</dt> - <dd> + </dd> + {% endif %} + <dt>Recipe file</dt> + <dd> <code>{{recipe.file_path|cut_path_prefix:recipe.layer_version.local_path}}</code> - <a href="{{recipe.get_vcs_recipe_file_link_url}}"><i class="icon-share" title="" data-original-title="View recipe file"></i></a> - </dd> - <dt>Layer</dt> - <dd><a href="{% url 'layerdetails' project.id recipe.layer_version.pk %}">{{recipe.layer_version.layer.name}}</a></dd> - <dt> + <a href="{{recipe.get_vcs_recipe_file_link_url}}"><span class="glyphicon glyphicon-new-window" title="View recipe file" data-toggle="tooltip"></span></a> + </dd> + <dt>Layer</dt> + <dd><a href="{% url 'layerdetails' project.id recipe.layer_version.pk %}">{{recipe.layer_version.layer.name}}</a></dd> + <dt> Summary - </dt> - <dd> + </dt> + <dd> {{recipe.summary}} - </dd> - <dt> + </dd> + <dt> Description - </dt> - <dd> + </dt> + <dd> {{recipe.description}} - </dd> - <dt>Version</dt> - <dd> + </dd> + <dt>Version</dt> + <dd> {{recipe.version}} - </dd> - <dt>Section</dt> - <dd> + </dd> + <dt>Section</dt> + <dd> {{recipe.section}} - </dd> - <dt>License</dt> - <dd> + </dd> + <dt>License</dt> + <dd> {{recipe.license}} - </dd> - </dl> + </dd> + </dl> + </div> </div> </div> diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/recipes.html b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/recipes.html deleted file mode 100644 index d14489346..000000000 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/recipes.html +++ /dev/null @@ -1,113 +0,0 @@ -{% extends "basebuildpage.html" %} - -{% load projecttags %} - -{% block title %} Recipes - {{build.target_set.all|dictsort:"target"|join:", "}} {{build.machine}} - {{build.project.name}} - Toaster {% endblock %} -{% block localbreadcrumb %} -<li>Recipes</li> -{% endblock %} - -{% block nav-recipes %} - <li class="active"><a href="{% url 'recipes' build.pk %}">Recipes</a></li> -{% endblock %} - -{% block buildinfomain %} -<div class="span10"> -<div class="page-header"> -<h1> - {% if request.GET.search and objects.paginator.count > 0 %} - {{objects.paginator.count}} recipe{{objects.paginator.count|pluralize}} found - {%elif request.GET.search and objects.paginator.count == 0%} - No recipes found - {%else%} - Recipes - {%endif%} - </h1> -</div> - -{% if objects.paginator.count == 0 %} - <div class="row-fluid"> - <div class="alert"> - <form class="no-results input-append" id="searchform"> - <input id="search" name="search" class="input-xxlarge" type="text" value="{%if request.GET.search%}{{request.GET.search}}{%endif%}"/>{% if request.GET.search %}<a href="javascript:$('#search').val('');searchform.submit()" class="add-on btn" tabindex="-1"><i class="icon-remove"></i></a>{% endif %} - <button class="btn" type="submit" value="Search">Search</button> - <button class="btn btn-link" onclick="javascript:$('#search').val('');searchform.submit()">Show all recipes</button> - </form> - </div> - </div> - -{% else %} -{% include "basetable_top.html" %} - - {% for recipe in objects %} - - <tr class="data"> - <td class="recipe__name"> - <a href="{% url "recipe" build.pk recipe.pk %}">{{recipe.name}}</a> - </td> - <td class="recipe__version"> - <a href="{% url "recipe" build.pk recipe.pk %}">{{recipe.version}}</a> - </td> - <!-- Depends --> - <td class="depends_on"> - {% with deps=recipe_deps|get_dict_value:recipe.pk %} - {% with count=deps|length %} - {% if count %} - <a class="btn" - title="<a href='{% url "recipe" build.pk recipe.pk %}#dependencies'>{{recipe.name}}</a> dependencies" - data-content="<ul class='unstyled'> - {% for i in deps|dictsort:"depends_on.name"%} - <li><a href='{% url "recipe" build.pk i.depends_on.pk %}'>{{i.depends_on.name}}</a></li> - {% endfor %} - </ul>"> - {{count}} - </a> - {% endif %} - {% endwith %} - {% endwith %} - </td> - <!-- Brought in by --> - <td class="depends_by"> - {% with revs=recipe_revs|get_dict_value:recipe.pk %} - {% with count=revs|length %} - {% if count %} - <a class="btn" - title="<a href='{% url "recipe" build.pk recipe.pk %}#brought-in-by'>{{recipe.name}}</a> reverse dependencies" - data-content="<ul class='unstyled'> - {% for i in revs|dictsort:"recipe.name" %} - <li><a href='{% url "recipe" build.pk i.recipe.pk %}'>{{i.recipe.name}}</a></li> - {% endfor %} - </ul>"> - {{count}} - </a> - {% endif %} - {% endwith %} - {% endwith %} - </td> - <!-- Recipe file --> - <td class="recipe_file">{{recipe.file_path}} {% if recipe.pathflags %}<i>({{recipe.pathflags}})</i>{% endif %}</td> - <!-- Section --> - <td class="recipe_section">{{recipe.section}}</td> - <!-- License --> - <td class="recipe_license">{{recipe.license}}</td> - <!-- Layer --> - <td class="layer_version__layer__name">{{recipe.layer_version.layer.name}}</td> - <!-- Layer branch --> - <td class="layer_version__branch">{{recipe.layer_version.branch}}</td> - <!-- Layer commit --> - <td class="layer_version__layer__commit"> - <a class="btn" - data-content="<ul class='unstyled'> - <li>{{recipe.layer_version.commit}}</li> - </ul>"> - {{recipe.layer_version.commit|truncatechars:13}} - </a> - </td> - </tr> - - {% endfor %} - -{% include "basetable_bottom.html" %} -{% endif %} -</div> -{% endblock %} diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/snippets/gitrev_popover.html b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/snippets/gitrev_popover.html new file mode 100644 index 000000000..c1e3dabfb --- /dev/null +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/snippets/gitrev_popover.html @@ -0,0 +1,8 @@ +{% load projecttags %} +{% if vcs_ref|is_shaid %} +<a class="btn btn-default" data-content="{{vcs_ref}}"> + {{vcs_ref|truncatechars:10}} +</a> +{% else %} +{{vcs_ref}} +{% endif %} diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/snippets/pkg_dependencies_popover.html b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/snippets/pkg_dependencies_popover.html index a3fcdb09e..273437e38 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/snippets/pkg_dependencies_popover.html +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/snippets/pkg_dependencies_popover.html @@ -1,14 +1,38 @@ {# Popover that displays the dependences and sizes of a package 'data' used in the Packages table #} -{% with data.package_dependencies_source.all_depends.count as dep_count %} {% load projecttags %} -{% if dep_count %} - <a data-content="<ul class='unstyled'> - {% for dep in data.package_dependencies_source.all_depends %} - <li>{{dep.depends_on.name}} {% if dep.depends_on.size > 0 %}({{dep.depends_on.size|filtered_filesizeformat}}){% endif %}</li> - {% endfor %} - </ul>" title="" class="btn" data-original-title=" - <strong>{{data.name}}</strong> dependencies - <strong>{{data.package_dependencies_source.get_total_source_deps_size.depends_on__size__sum|filtered_filesizeformat}}</strong>"> - {{dep_count}} -</a> + +{% with package_deps=data.package_dependencies_source|for_target:extra.target_name %} +{% with count_package=package_deps.packages|length %} + +{% if count_package > 0 %} + <a data-content='<ul class="list-unstyled"> + {% for dep in package_deps.packages %} + <li> + {% if extra.add_links %} + <a href="{% url 'package_included_detail' extra.build.pk extra.target_id dep.depends_on.pk %}"> + {{dep.depends_on.name}}</a> + {% else %} + {{dep.depends_on.name}} + {% endif %} + {% if dep.depends_on.size > 0 %} + ({{dep.depends_on.size|filtered_filesizeformat}}) + {% endif %} + </li> + {% endfor %} + </ul>' class="btn btn-default" title=' + <strong> + {% if extra.add_links %} + <a href="{% url 'package_included_dependencies' extra.build.pk extra.target_id data.pk %}"> + {{data.name}}</a> + {% else %} + {{data.name}} + {% endif %} + </strong> + dependencies - + <strong>{{package_deps.size|filtered_filesizeformat}}</strong>'> + {{count_package}} + </a> {% endif %} + +{% endwith %} {% endwith %} diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/snippets/pkg_revdependencies_popover.html b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/snippets/pkg_revdependencies_popover.html index 453a9d013..e6ef816e7 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/snippets/pkg_revdependencies_popover.html +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/snippets/pkg_revdependencies_popover.html @@ -1,14 +1,38 @@ -{# Popover that displays the reverse dependencies and sizes of a package 'data' used in the Packages table #} -{% with data.package_dependencies_target.all_depends.count as dep_count %} +{# Popover that displays the reverse dependences and sizes of a package 'data' used in the Packages table #} {% load projecttags %} -{% if dep_count %} - <a data-content="<ul class='unstyled'> - {% for dep in data.package_dependencies_target.all_depends|dictsort:'package.name' %} - <li>{{dep.package.name}} {% if dep.package.size > 0 %}({{dep.package.size|filtered_filesizeformat}}){% endif %}</li> - {% endfor %} - </ul>" title="" class="btn" data-original-title=" - <strong>{{data.name}}</strong> reverse dependencies - <strong>{{data.package_dependencies_target.get_total_revdeps_size.package_id__size__sum|filtered_filesizeformat}}</strong>"> - {{dep_count}} -</a> + +{% with package_deps=data.package_dependencies_target|for_target:extra.target_name %} +{% with count_package=package_deps.packages|length %} + +{% if count_package > 0 %} + <a data-content='<ul class="list-unstyled"> + {% for dep in package_deps.packages|dictsort:"package.name" %} + <li> + {% if extra.add_links %} + <a href="{% url 'package_included_detail' extra.build.pk extra.target_id dep.package.pk %}"> + {{dep.package.name}}</a> + {% else %} + {{dep.package.name}} + {% endif %} + {% if dep.package.size > 0 %} + ({{dep.package.size|filtered_filesizeformat}}) + {% endif %} + </li> + {% endfor %} + </ul>' class="btn btn-default" title=' + <strong> + {% if extra.add_links %} + <a href="{% url 'package_included_reverse_dependencies' extra.build.pk extra.target_id data.pk %}"> + {{data.name}}</a> + {% else %} + {{data.name}} + {% endif %} + </strong> + dependencies - + <strong>{{package_deps.size|filtered_filesizeformat}}</strong>'> + {{count_package}} + </a> {% endif %} + +{% endwith %} {% endwith %} diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/target.html b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/target.html index 4c33eaa84..1924a0dad 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/target.html +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/target.html @@ -17,9 +17,8 @@ {% endblock %} {% block buildinfomain %} - -<div class="row-fluid span10"> - <div class="page-header"> +<div class="col-md-10"> + <div class="page-header build-data"> <h1> {% if request.GET.search and objects.paginator.count > 0 %} {{objects.paginator.count}} package{{objects.paginator.count|pluralize}} found @@ -30,134 +29,28 @@ {% endif %} </h1> </div> -</div> - -<div class="row-fluid pull-right span10" id="navTab"> - <ul class="nav nav-pills"> +<div id="navTab"> + <ul class="nav nav-tabs"> <li class="active"> <a href="#target"> - <i class="icon-question-sign get-help" title="Of all the packages built, the subset installed in the root file system of this image"></i> + <span class="glyphicon glyphicon-question-sign get-help" title="Of all the packages built, the subset installed in the root file system of this image"></span> Packages included ({{target.package_count}} - {{packages_sum|filtered_filesizeformat}}) </a> </li> <li> <a href="{% url 'dirinfo' build.id target.id %}"> - <i class="icon-question-sign get-help" title="The directories and files in the root file system of this image"></i> + <span class="glyphicon glyphicon-question-sign get-help" title="The directories and files in the root file system of this image"></span> Directory structure </a> </li> </ul> <div id="image-packages" class="tab-pane"> - - {% if objects.paginator.count == 0 %} - <div class="row-fluid"> - <div class="alert"> - <form class="no-results input-append" id="searchform"> - <input id="search" name="search" class="input-xxlarge" type="text" value="{% if request.GET.search %}{{request.GET.search}}{% endif %}"/>{% if request.GET.search %}<a href="javascript:$('#search').val('');searchform.submit()" class="add-on btn" tabindex="-1"><i class="icon-remove"></i></a>{% endif %} - <button class="btn" type="submit" value="Search">Search</button> - <button class="btn btn-link" onclick="javascript:$('#search').val('');searchform.submit()">Show all packages</button> - </form> - </div> - </div> - - - {% else %} - {% include "basetable_top.html" %} - {% for package in objects %} - <tr> - {# order of the table data must match the columns defined in template's context tablecols #} - <td class="package_name"> - <a href="{% url 'package_included_detail' build.id target.id package.id %}"> - {{package.name}} - </a> - {% if package.installed_name and package.name != package.installed_name %} - <span class="muted"> as {{package.installed_name}}</span> - <i class="icon-question-sign get-help hover-help" title='{{package.name|add:" was renamed at packaging time and was installed in your image as "|add:package.installed_name}}'></i> - {% endif %} - </td> - <td class="package_version"> - <a href="{% url 'package_included_detail' build.id target.id package.id %}"> - {{package.version|filtered_packageversion:package.revision}} - </a> - </td> - <td class="license"> - {{package.license}} - </td> - <td class="size sizecol"> - {{package.size|filtered_installedsize:package.installed_size|filtered_filesizeformat}} - </td> - - <td class="size_over_total sizecol"> - {{package|filter_sizeovertotal:packages_sum}} - </td> - <td class="depends"> - {% with deps=package.runtime_dependencies %} - {% with deps_count=deps|length %} - {% if deps_count > 0 %} - <a class="btn" - title="<a href='{% url "package_included_dependencies" build.id target.id package.id %}'>{{package.name}}</a> dependencies" - data-content="<ul class='unstyled'> - {% for i in deps|dictsort:'depends_on.name' %} - <li><a href='{% url "package_included_detail" build.pk target.id i.depends_on.pk %}'>{{i.depends_on.name}}</a></li> - {% endfor %} - </ul>"> - {{deps_count}} - </a> - {% endif %} - {% endwith %} - {% endwith %} - </td> - <td class="brought_in_by"> - {% with rdeps=package.reverse_runtime_dependencies %} - {% with rdeps_count=rdeps|length %} - {% if rdeps_count > 0 %} - <a class="btn" - title="<a href='{% url "package_included_reverse_dependencies" build.id target.id package.id %}'>{{package.name}}</a> reverse dependencies" - data-content="<ul class='unstyled'> - {% for i in rdeps|dictsort:'package.name' %} - <li><a href='{% url "package_included_detail" build.id target.id i.package.id %}'>{{i.package.name}}</a></li> - {% endfor %} - </ul>"> - {{rdeps_count}} - </a> - {% endif %} - {% endwith %} - {% endwith %} - </td> - <td class="recipe_name"> - {% if package.recipe.version %} - <a href="{% url 'recipe' build.id package.recipe_id %}"> - {{ package.recipe.name }} - </a> - {% endif %} - </td> - <td class="recipe_version"> - {% if package.recipe.version %} - <a href="{% url 'recipe' build.id package.recipe_id %}"> - {{ package.recipe.version }} - </a> - {% endif %} - </td> - <td class="layer_name"> - {{ package.recipe.layer_version.layer.name }} - </td> - <td class="layer_branch"> - {{ package.recipe.layer_version.branch}} - </td> - <td class="layer_commit"> - <a class="btn" - data-content="<ul class='unstyled'> - <li>{{package.recipe.layer_version.commit}}</li> - </ul>"> - {{package.recipe.layer_version.commit|truncatechars:13}} - </a> - </td> - </tr> - {% endfor %} - - {% include "basetable_bottom.html" %} - {% endif %} + {# xhr_table_url is just the current url so leave it blank #} + {% with xhr_table_url='' %} + {% include "toastertable.html" %} + {% endwith %} </div> <!-- tabpane --> -</div> <!--span 10--> + </div> <!--navTab --> +<!-- col-md-10 --> {% endblock buildinfomain %} diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/task.html b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/task.html index 576826243..214c77783 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/task.html +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/task.html @@ -11,60 +11,88 @@ {% block pagedetailinfomain %} -<div class="row span11"> - <div class="page-header"> +<div class="row"> + <div class="col-md-12"> + <div class="page-header build-data"> <h1><a href="{%url 'recipe' build.pk task.recipe.pk %}">{{task.recipe.name}}_{{task.recipe.version}}</a> {{task.task_name}}</h1> </div> {# Outcome section #} <h2 {{ task|task_color:True }}> {{task.get_outcome_display}} - <i class="icon-question-sign get-help heading-help" title="{{task.get_outcome_help}}"></i> + <span class="glyphicon glyphicon-question-sign get-help" title="{{task.get_outcome_help}}"></i> </h2> {%if task.task_executed %} {# executed tasks outcome #} {% if task.logfile %} - <a class="btn btn-large" href="{% url 'build_artifact' build.id "tasklogfile" task.pk %}" style="margin:15px;">Download task log</a> + <a class="btn btn-default btn-lg" + href="{% url 'build_artifact' build.id 'tasklogfile' task.pk %}"> + Download task log + </a> {% endif %} {# show stack trace for failed task #} {% if task.outcome == task.OUTCOME_FAILED and log_head %} <h3>Python stack trace</h3> <div> <pre style="min-height:160px;"> - <code>{{log_head}}</code><a id="full-trace-show" data-target="#fulltrace" data-toggle="collapse" class="btn btn-mini">...</a> + <code>{{log_head}}</code><a id="full-trace-show" data-target="#fulltrace" data-toggle="collapse" class="btn btn-xs">...</a> <div id="fulltrace" class="collapse" style="margin-top: -20px; height: 0px;"> - <code>{{log_body}}</code><br><a id="full-trace-hide" class="btn btn-mini collapsed" style="font-family:Helvetica Neue" data-target="#fulltrace" data-toggle="collapse">Collapse stack trace<i class="icon-caret-up"></i></a></div></pre> + <code>{{log_body}}</code><br><a id="full-trace-hide" class="btn btn-xs collapsed" style="font-family:Helvetica Neue" data-target="#fulltrace" data-toggle="collapse">Collapse stack trace<i class="icon-caret-up"></i></a></div></pre> </div> {% endif %} {% else %} {# not executed tasks outcome #} {% if task.outcome == task.OUTCOME_PREBUILT %} {% if not showing_matches %} - <a class="btn" href="javascript:reload_params({'show_matches' : 'true' })">Match to tasks in previous builds <i class="icon-question-sign get-help" style="margin-top:20px;" title="This shows you a list of tasks from previous builds with the same signature generated from the same inputs as used in the prebuilt task. Any of them could be the task that generated the output this prebuilt task is reusing"></i></a> + <a class="btn btn-default" + href="javascript:reload_params({'show_matches' : 'true' + })">Match to tasks in previous builds <span class="glyphicon +glyphicon-question-sign get-help" title="This shows you a list of tasks from +previous builds with the same signature generated from the same inputs as used +in the prebuilt task. Any of them could be the task that generated the output +this prebuilt task is reusing"></span></a> {% elif matching_tasks %} <h3 class="details">Prebuilt task could be based on - <i class="icon-question-sign get-help heading-help" title="This table shows a list of tasks from previous builds with the same signature generated from the same inputs as used in the prebuilt task. Any of them could be the task that generated the output this prebuilt task is reusing"></i> + <span class="glyphicon glyphicon-question-sign get-help" title="This + table shows a list of tasks from previous builds with the + same signature generated from the same inputs as used in the + prebuilt task. Any of them could be the task that generated the + output this prebuilt task is reusing"></span> </h3> + <div class="table-responsive"> <table class="table table-bordered table-hover"> <thead> <th> - <i class="icon-question-sign get-help" title="The name of the recipe to which each task applies"></i> + <span class="glyphicon glyphicon-question-sign + get-help" title="The name of the recipe to which + each task applies"></span> Recipe </th> <th> - <i class="icon-question-sign get-help" title="The name of the task"></i> + <span class="glyphicon glyphicon-question-sign get-help" title="The name + of the task"></span> Task </th> <th> - <i class="icon-question-sign get-help" title="This value tells you if a task had to run (executed) in order to generate the task output, or if the output was provided by another task and therefore the task didn't need to run (not executed)"></i> + <span class="glyphicon glyphicon-question-sign get-help" title="This + value tells you if a task had to run (executed) in + order to generate the task output, or if the output was + provided by another task and therefore the task didn't need + to run (not executed)"></span> Executed </th> <th> - <i class="icon-question-sign get-help" title="This column tells you if 'executed' tasks succeeded or failed. The column also tells you why 'not executed' tasks did not need to run"></i> + <span class="glyphicon glyphicon-question-sign + get-help" title="This column tells you if + 'executed' tasks succeeded or failed. The column also + tells you why 'not executed' tasks did not need to + run"></span> Outcome </th> <th> - <i class="icon-question-sign get-help" title="The date and time the build finished"></i> + <span class="glyphicon glyphicon-question-sign + get-help" title="The date and time the build + finished"></span> Build completed on </th> </thead> @@ -72,27 +100,34 @@ {% for match in matching_tasks %} <tr {{ match|task_color }}> <td> - <a href="{%url "task" match.build.pk match.pk%}">{{match.recipe.name}}</a> + {{match.recipe.name}} </td> <td> <a href="{%url "task" match.build.pk match.pk%}">{{match.task_name}}</a> {% if task.get_description %} - <i class="icon-question-sign get-help hover-help" title="{{task.get_description}}"></i> + <span class="glyphicon + glyphicon-question-sign get-help + hover-help" + title="{{task.get_description}}"></span> {% endif %} </td> <td> - <a href="{%url "task" match.build.pk match.pk%}">{{match.get_executed_display}}</a> + {{match.get_executed_display}} </td> <td> - <a href="{%url "task" match.build.pk match.pk%}">{{match.get_outcome_display}} </a><i class="icon-question-sign get-help hover-help" title="{{match.get_outcome_help}}"></i> + {{match.get_outcome_display}} + <span class="glyphicon glyphicon-question-sign + get-help hover-help" + title="{{match.get_outcome_help}}"></span> </td> <td> - <a href="{%url "task" match.build.pk match.pk%}">{{match.build.completed_on|date:"d/m/y H:i"}}</a> + {{match.build.completed_on|date:"d/m/y H:i"}} </td> </tr> {% endfor %} </tbody> </table> + </div> {% else %} <p class="alert"> <strong> We have found no tasks matching this prebuilt task</strong><br/> @@ -102,10 +137,11 @@ {% elif task.outcome == task.OUTCOME_COVERED %} <dl class="dl-horizontal"> <dt> - <i class="icon-question-sign get-help" title="The task(s) providing the outcome of this task"></i> Task covered by + <span class="glyphicon glyphicon-question-sign get-help" title="The task(s) + providing the outcome of this task"></span> Task covered by </dt> <dd> - <ul> + <ul class="list-unstyled"> {% for t in covered_by %} <li> <a href="{%url 'task' t.build.pk t.pk%}" @@ -122,7 +158,10 @@ {%elif task.outcome == task.OUTCOME_CACHED%} {% for t in task.get_related_setscene %} {% if forloop.last %} - <a class="btn btn-large" href="{% url 'build_artifact' build.id "tasklogfile" t.pk %}" style="margin:15px;">Download task log</a> + <a class="btn btn-default btn-lg" + href="{% url 'build_artifact' build.id "tasklogfile" t.pk %}"> + Download task log + </a> {% endif %} {% endfor %} @@ -137,38 +176,56 @@ {% if task.task_executed %} <h2> Executed - <i class="icon-question-sign get-help heading-help" title="'Executed' tasks are those that need to run in order to generate the task output"></i> + <span class="glyphicon glyphicon-question-sign get-help" + title="'Executed' tasks are those that need to run in order to + generate the task output"></span> {% else %} - <h2 class="muted"> + <h2> Not Executed - <i class="icon-question-sign get-help heading-help" title="'Not executed' tasks don't need to run because their outcome is provided by another task"></i> + <span class="glyphicon glyphicon-question-sign get-help" title="'Not + executed' tasks don't need to run because their outcome is provided + by another task"></span> {% endif %} </h2> <dl class="dl-horizontal"> <dt> - <i class="icon-question-sign get-help" title="To make builds more efficient, the build system detects changes in the 'inputs' to a given task by creating a 'task signature'. If the signature changes, the build system assumes the inputs have changed and the task needs to be rerun"></i> + <span class="glyphicon glyphicon-question-sign get-help" title="To make builds more + efficient, the build system detects changes in the 'inputs' to a + given task by creating a 'task signature'. If the signature changes, + the build system assumes the inputs have changed and the task needs to be +rerun"></span> Task inputs signature </dt> <dd> {{task.sstate_checksum}} </dd> -</dl> {% if task.sstate_result != task.SSTATE_NA %} + </dl> <div class="alert alert-info">Attempting to restore output from sstate cache - <i class="icon-question-sign get-help get-help-blue" title="The build system is searching for the task output in your <code>sstate-cache</code> directory and mirrors. If the build system finds the task output, it will reuse it instead of building it from scratch by running the real task. Reusing the task output makes the build faster"></i> + <span class="glyphicon glyphicon-question-sign get-help + get-help-blue" title="The build system is searching for the + task output in your <code>sstate-cache</code> directory and + mirrors. If the build system finds the task output, it will reuse it + instead of building it from scratch by running the real task. Reusing the + task output makes the build faster"></span> </div> <dl class="dl-horizontal"> <dt> - <i class="icon-question-sign get-help" title="The name of the file searched for in your <code>sstate-cache</code> directory and mirrors"></i> + <span class="glyphicon glyphicon-question-sign get-help" title="The name of the + file searched for in your <code>sstate-cache</code> + directory and mirrors"></span> File searched for </dt> <dd><code>{{task.path_to_sstate_obj}}</code></dd> <dt> - <i class="icon-question-sign get-help" title="The locations searched for the above file (i.e. your <code>sstate-cache</code> directory and any mirrors you have set up)"></i> + <span class="glyphicon glyphicon-question-sign get-help" title="The locations + searched for the above file (i.e. your + <code>sstate-cache</code> directory and any mirrors you have + set up)"></span> URI(s) searched </dt> - <dd><ul>{% for uri in uri_list %}<li><code>{{uri}}</code></li>{% endfor %}</ul></dd> + <dd><ul class="list-unstyled">{% for uri in uri_list %}<li><code>{{uri}}</code></li>{% endfor %}</ul></dd> </dl> {% endif %} {% if task.sstate_result == task.SSTATE_MISS %} @@ -176,7 +233,7 @@ <strong>File not in sstate cache.</strong> Running the real task instead. </div> {% elif task.sstate_result == task.SSTATE_FAILED%} - <div class="alert"> + <div class="alert alert-warning"> <strong>Failed</strong> to restore output from sstate cache. The file was found but could not be unpacked. </div> <dl class="dl-horizontal"> @@ -192,13 +249,16 @@ {% endif %} <dl class="dl-horizontal"> <dt> - <i class="icon-question-sign get-help" title="The running sequence of each task in the build"></i> + <span class="glyphicon glyphicon-question-sign get-help" title="The + running sequence of each task in the build"></span> Task order </dt> - <dd><a href="{%url "tasks_task" build.pk task.order %}#{{task.order}}">{{task.order}}</a></dd> + <dd><a href="{%url "tasks" build.pk %}?page={{task_in_tasks_table_pg}}&limit=25#task-{{task.order}}">{{task.order}}</a></dd> {% if task.task_executed %} <dt> - <i class="icon-question-sign get-help" title="Indicates if this task executes a Python or Shell function(s)"></i> + <span class="glyphicon glyphicon-question-sign get-help" + title="Indicates if this task executes a Python or Shell + function(s)"></span> Task script type </dt> <dd>{{task.get_script_type_display}}</dd> @@ -211,29 +271,31 @@ <dd><code>{{task.source_url}}</code></dd> --> <dt> - <i class="icon-question-sign get-help" title="Task dependency chain (i.e. other tasks)"></i> + <span class="glyphicon glyphicon-question-sign get-help" title="Task dependency chain + (i.e. other tasks)"></span> Dependencies </dt> <dd> - <ul> - {% for dep in deps %} - <li><a href="{%url 'task' dep.build.pk dep.pk%}" class="task-info" title="{{dep.get_executed_display}} | {{dep.get_outcome_display}}">{{dep.recipe.name}}_{{dep.recipe.version}} <span class="task-name">{{dep.task_name}}</span></a></li> - {% empty %} - <li class="muted">This task has no dependencies</li> - {% endfor %} + <ul class="list-unstyled"> + {% for dep in deps %} + <li><a href="{%url 'task' dep.build.pk dep.pk%}" class="task-info" title="{{dep.get_executed_display}} | {{dep.get_outcome_display}}">{{dep.recipe.name}}_{{dep.recipe.version}} <span class="task-name">{{dep.task_name}}</span></a></li> + {% empty %} + <li class="text-muted" style="margin-bottom: -10px;">This task has no dependencies</li> + {% endfor %} </ul> </dd> <dt> - <i class="icon-question-sign get-help" title="Tasks that depend on this task"></i> + <span class="glyphicon glyphicon-question-sign get-help" title="Tasks that depend on this + task"></span> Reverse dependencies </dt> <dd> - <ul> - {% for dep in rdeps %} - <li><a href="{%url 'task' dep.build.pk dep.pk%}" class="task-info" title="{{dep.get_executed_display}} | {{dep.get_outcome_display}}">{{dep.recipe.name}}_{{dep.recipe.version}} <span class="task-name">{{dep.task_name}}</span></a></li> - {% empty %} - <li class="muted">This task has no reverse dependencies</li> - {% endfor %} + <ul class="list-unstyled"> + {% for dep in rdeps %} + <li><a href="{%url 'task' dep.build.pk dep.pk%}" class="task-info" title="{{dep.get_executed_display}} | {{dep.get_outcome_display}}">{{dep.recipe.name}}_{{dep.recipe.version}} <span class="task-name">{{dep.task_name}}</span></a></li> + {% empty %} + <li class="text-muted">This task has no reverse dependencies</li> + {% endfor %} </ul> </dl> @@ -244,28 +306,36 @@ <dl class="dl-horizontal"> {% if task.elapsed_time %} <dt> - <i class="icon-question-sign get-help" title="How long it took the task to finish in seconds"></i> + <span class="glyphicon glyphicon-question-sign get-help" title="How + long it took the task to finish in seconds"></span> Time (secs) </dt> <dd>{{task.elapsed_time|format_none_and_zero|floatformat:2}}</dd> {% endif %} {% if task.cpu_time_user > 0 %} <dt> - <i class="icon-question-sign get-help" title="Total amount of time spent executing in user mode, in seconds. Note that this time can be greater than the task time due to parallel execution."></i> + <span class="glyphicon glyphicon-question-sign get-help" title="Total amount of time + spent executing in user mode, in seconds. Note that this time + can be greater than the task time due to parallel + execution."></span> User CPU time (secs) </dt> <dd>{{task.cpu_time_user|format_none_and_zero|floatformat:2}}</dd> {% endif %} {% if task.cpu_time_system > 0 %} <dt> - <i class="icon-question-sign get-help" title="Total amount of time spent executing in kernel mode, in seconds. Note that this time can be greater than the task time due to parallel execution."></i> + <span class="glyphicon glyphicon-question-sign get-help" title="Total amount of time + spent executing in kernel mode, in seconds. Note that this time + can be greater than the task time due to parallel + execution."></span> System CPU time (secs) </dt> <dd>{{task.cpu_time_system|format_none_and_zero|floatformat:2}}</dd> {% endif %} {% if task.disk_io > 0 %} <dt> - <i class="icon-question-sign get-help" title="Number of bytes written to and read from the disk during the task"></i> + <span class="glyphicon glyphicon-question-sign get-help" title="Number of bytes + written to and read from the disk during the task"></span> Disk I/O (bytes) </dt> <dd>{{task.disk_io|format_none_and_zero|intcomma}}</dd> @@ -273,5 +343,6 @@ </dl> </div> + {% endblock %} diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/tasks.html b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/tasks.html deleted file mode 100644 index 84bc10386..000000000 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/tasks.html +++ /dev/null @@ -1,143 +0,0 @@ -{% extends "basebuildpage.html" %} -{% load humanize %} -{% load projecttags %} - -{% block title %} {{mainheading}} - {{build.target_set.all|dictsort:"target"|join:", "}} {{build.machine}} - {{build.project.name}} - Toaster{% endblock %} -{% block localbreadcrumb %} -<li>{{mainheading}}</li> -{% endblock %} - -{% block nav-tasks %} - {% if 'Tasks' == mainheading %} - <li class="active"><a href="{% url 'tasks' build.pk %}">Tasks</a></li> - {% else %} - <li><a href="{% url 'tasks' build.pk %}">Tasks</a></li> - {% endif %} -{% endblock %} -{% block nav-buildtime %} - {% if 'Time' == mainheading %} - <li class="active"><a href="{% url 'buildtime' build.pk %}">Time</a></li> - {% else %} - <li><a href="{% url 'buildtime' build.pk %}">Time</a></li> - {% endif %} -{% endblock %} - -{% block nav-cputime %} - {% if 'CPU time' == mainheading %} - <li class="active"><a href="{% url 'cputime' build.pk %}">CPU time</a></li> - {% else %} - <li><a href="{% url 'cputime' build.pk %}">CPU time</a></li> - {% endif %} -{% endblock %} - -{% block nav-diskio %} - {% if 'Disk I/O' == mainheading %} - <li class="active"><a href="{% url 'diskio' build.pk %}">Disk I/O</a></li> - {% else %} - <li><a href="{% url 'diskio' build.pk %}">Disk I/O</a></li> - {% endif %} -{% endblock %} - -{% block buildinfomain %} -<div class="span10"> -{% if not request.GET.filter and not request.GET.search and not objects.paginator.count %} - <!-- Empty - no data in database --> - <div class="page-header"> - <h1>{{mainheading}}</h1> - </div> - <div class="alert alert-info lead"> - No data was recorded for this build. - </div> - -{% else %} - - <div class="page-header"> - <h1> - {% if request.GET.filter and objects.paginator.count > 0 or request.GET.search and objects.paginator.count > 0 %} - {{objects.paginator.count}} task{{objects.paginator.count|pluralize}} found - {%elif request.GET.filter and objects.paginator.count == 0 or request.GET.search and objects.paginator.count == 0 %} - No tasks found - {%else%} - {{mainheading}} - {%endif%} - </h1> - </div> - - {% if objects.paginator.count == 0 %} - <div class="row-fluid"> - <div class="alert"> - <form class="no-results input-append" id="searchform"> - <input id="search" name="search" class="input-xxlarge" type="text" value="{{request.GET.search}}"/>{% if request.GET.search %}<a href="javascript:$('#search').val('');searchform.submit()" class="add-on btn" tabindex="-1"><i class="icon-remove"></i></a>{% endif %} - <button class="btn" type="submit" value="Search">Search</button> - <button class="btn btn-link" onclick="javascript:$('#search').val('');searchform.submit()">Show all tasks</button> - </form> - </div> - </div> - - - {% else %} - {% include "basetable_top.html" %} - - {% for task in objects %} - <tr {{ task|task_color }} id="{{task.order}}"> - <td class="order"> - <a href="{%url "task" build.pk task.pk%}">{{task.order}}</a> - </td> - <td class="recipe_name" > - <a href="{% url "recipe" build.pk task.recipe.pk %}">{{task.recipe.name}}</a> - </td> - <td class="recipe_version"> - <a href="{% url "recipe" build.pk task.recipe.pk %}">{{task.recipe.version}}</a> - </td> - <td class="task_name"> - <a href="{%url "task" build.pk task.pk%}">{{task.task_name}}</a> {% if task.get_description %}<i class="icon-question-sign get-help hover-help" title="{{task.get_description}}"></i> {% endif %} - </td> - <td class="executed"> - <a href="{%url "task" build.pk task.pk%}">{{task.get_executed_display}}</a> - </td> - <td class="outcome"> - <a href="{%url "task" build.pk task.pk%}">{{task.get_outcome_display}} </a> - {% if task.outcome = task.OUTCOME_FAILED %} - <a href="{% url 'build_artifact' build.pk "tasklogfile" task.pk %}"> - <i class="icon-download-alt" title="Download task log file"></i> - </a> - {% endif %} - <i class="icon-question-sign get-help hover-help" title="{{task.get_outcome_help}}"></i> - </td> - <td class="cache_attempt"> - <a href="{%url "task" build.pk task.pk%}">{{task.get_sstate_result_display|format_none_and_zero}}</a> - </td> - <td class="time_taken"> - {{task.elapsed_time|format_none_and_zero|floatformat:2}} - </td> - <td class="cpu_time_system"> - {{task.cpu_time_system|format_none_and_zero|floatformat:2}} - </td> - <td class="cpu_time_user"> - {{task.cpu_time_user|format_none_and_zero|floatformat:2}} - </td> - <td class="disk_io"> - {{task.disk_io|format_none_and_zero|intcomma}} - </td> - - </tr> - {% endfor %} - - {% include "basetable_bottom.html" %} - {% endif %} {# objects.paginator.count #} -{% endif %} {# empty #} -</div> - -<script type="text/javascript"> - - $(document).ready(function() { - // highlight heading on the column for the field used for ordering - if (location.href.search('#') > -1) { - var task_order = location.href.split('#')[1]; - $("#" + task_order).addClass("highlight"); - } - }); - -</script> - -{% endblock %} diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/toastertable-filter.html b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/toastertable-filter.html index 4d28793bf..25eef52a3 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/toastertable-filter.html +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/toastertable-filter.html @@ -1,20 +1,24 @@ <!-- filter modal --> -<div id="filter-modal-{{table_name}}" class="modal hide fade" tabindex="-1" role="dialog" aria-hidden="false"> - <form id="filter-modal-form-{{table_name}}" style="margin-bottom: 0px"> - <div class="modal-header"> - <button type="button" class="close" data-dismiss="modal" aria-hidden="true">x</button> - <h3 id="filter-modal-title-{{table_name}}"> </h3> - </div> - <div class="modal-body"> - <p>Show:</p> - <span id="filter-actions-{{table_name}}"></span> - </div> - <div class="modal-footer"> - <button class="btn btn-primary" type="submit" data-role="filter-apply"> +<div id="filter-modal-{{table_name}}" class="modal fade" tabindex="-1" role="dialog" aria-hidden="false"> + <div class="modal-dialog"> + <div class="modal-content"> + <div class="modal-header"> + <button type="button" class="close" data-dismiss="modal" aria-hidden="true">x</button> + <h3 id="filter-modal-title-{{table_name}}"> </h3> + </div> + <div class="modal-body"> + <form id="filter-modal-form-{{table_name}}" style="margin-bottom: 0px"> + <p>Show:</p> + <span id="filter-actions-{{table_name}}"></span> + </div> + <div class="modal-footer"> + <button class="btn btn-primary" type="submit" data-role="filter-apply"> Apply - </button> + </button> + </div> + </form> </div> - </form> + </div> </div> <button id="clear-filter-btn-{{table_name}}" style="display:none"></button> <!-- end filter modal --> diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/toastertable-simple.html b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/toastertable-simple.html index 212318bc5..56cd2ce37 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/toastertable-simple.html +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/toastertable-simple.html @@ -26,37 +26,38 @@ {% include 'toastertable-filter.html' %} -<div class="row-fluid" id="no-results-{{table_name}}" style="display:none"> - <div class="alert"> - <form class="no-results input-append"> - <input class="input-xlarge" id="new-search-input-{{table_name}}" name="search" type="text" placeholder="Search {{title|lower}}" value="{% if request.GET.search %}{{request.GET.search}}{% endif %}"/> - <a href="#" class="add-on btn remove-search-btn-{{table_name}}" tabindex="-1"> - <i class="icon-remove"></i> - </a> - <button class="btn search-submit-{{table_name}}" >Search</button> - <button class="btn btn-link remove-search-btn-{{table_name}}">Show {{title|lower}} - </button> +<div id="no-results-{{table_name}}" style="display:none"> + <div class="alert alert-warning"> + <form class="no-results form-inline"> + <div class="form-group"> + <div class="btn-group"> + <input class="form-control" id="new-search-input-{{table_name}}" name="search" type="text" placeholder="Search {{title|lower}}" value="{% if request.GET.search %}{{request.GET.search}}{% endif %}"/> + <span class="remove-search-btn-{{table_name}} glyphicon glyphicon-remove-circle" tabindex="-1"></span> + </div> + </div> + <button class="btn btn-default search-submit-{{table_name}}">Search</button> + <button class="btn btn-link remove-search-btn-{{table_name}}">Show all {{title|lower}}</button> </form> </div> </div> <div id="table-container-{{table_name}}" style="visibility: hidden"> <!-- control header --> - <div class="row-fluid" id="table-chrome-{{table_name}}"> - <div class="navbar-search input-append pull-left"> - - <input class="input-xlarge" id="search-input-{{table_name}}" name="search" type="text" placeholder="Search {{title|lower}}" value="{% if request.GET.search %}{{request.GET.search}}{% endif %}"/> - <a href="#" style="display:none" class="add-on btn remove-search-btn-{{table_name}}" tabindex="-1"> - <i class="icon-remove"></i> - </a> - <button class="btn" id="search-submit-{{table_name}}" >Search</button> - </div> - - <div class="pull-right"> + <div id="table-chrome-{{table_name}}"> + <div class="container-fluid detail-page-contols"> + <form class="navbar-form navbar-left"> + <div class="form-group"> + <div class="btn-group"> + <input class="form-control" id="search-input-{{table_name}}" name="search" type="text" placeholder="Search {{title|lower}}" value="{% if request.GET.search %}{{request.GET.search}}{% endif %}"/> + <span href="#" style="display:none" class="remove-search-btn-{{table_name}} glyphicon glyphicon-remove-circle" tabindex="-1"></span> + </div> + </div> + <button class="btn btn-default" id="search-submit-{{table_name}}" >Search</button> + </form> - <div style="display:inline"> - <span class="divider-vertical"></span> - <span class="help-inline" style="padding-top:5px;">Show rows:</span> - <select style="margin-top:5px;margin-bottom:0px;" class="pagesize-{{table_name}}"> + <form class="navbar-form navbar-right"> + <div class="form-group"> + <label>Show rows:</label> + <select class="form-control pagesize-{{table_name}}"> {% with "10 25 50 100 150" as list%} {% for i in list.split %} <option value="{{i}}">{{i}}</option> @@ -64,31 +65,36 @@ {% endwith %} </select> </div> - </div> + </form> + </div> </div> <!-- The actual table --> - <table class="table table-bordered table-hover tablesorter" id="{{table_name}}"> - <thead> - <tr><th></th></tr> - </thead> - <tbody></tbody> - </table> + <div class="table-responsive"> + <table class="table table-bordered table-hover" id="{{table_name}}"> + <thead> + <tr><th></th></tr> + </thead> + <tbody></tbody> + </table> + </div> <!-- Pagination controls --> - <div class="pagination pagination-centered" id="pagination-{{table_name}}"> - <ul class="pagination" style="display: block-inline"> + <div id="pagination-{{table_name}}"> + <ul class="pagination"> </ul> - <div class="pull-right"> - <span class="help-inline" style="padding-top:5px;">Show rows:</span> - <select style="margin-top:5px;margin-bottom:0px;" class="pagesize-{{table_name}}"> - {% with "10 25 50 100 150" as list%} - {% for i in list.split %} - <option value="{{i}}">{{i}}</option> - {% endfor %} - {% endwith %} - </select> - </div> + <form class="navbar-form navbar-right"> + <div class="form-group"> + <label>Show rows:</label> + <select class="form-control pagesize-{{table_name}}"> + {% with "10 25 50 100 150" as list%} + {% for i in list.split %} + <option value="{{i}}">{{i}}</option> + {% endfor %} + {% endwith %} + </select> + </div> + </form> </div> </div> diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/toastertable.html b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/toastertable.html index 21c3d36c7..aa148955e 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/toastertable.html +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/toastertable.html @@ -25,14 +25,20 @@ {% include 'toastertable-filter.html' %} -<div class="row-fluid" id="no-results-{{table_name}}" style="display:none"> - <div class="alert"> - <form class="no-results input-append"> - <input class="input-xxlarge" id="new-search-input-{{table_name}}" name="search" type="text" placeholder="Search {{title|lower}}" value="{%if request.GET.search %}{{request.GET.search}}{%endif%}"/> - <a href="#" class="add-on btn remove-search-btn-{{table_name}}" tabindex="-1"> - <i class="icon-remove"></i> - </a> - <button class="btn search-submit-{{table_name}}"> +<div class="row-fluid" id="empty-state-{{table_name}}" style="display:none"> + <div class="alert alert-info">{{empty_state|safe}}</div> +</div> + +<div id="no-results-{{table_name}}" style="display:none"> + <div class="alert alert-warning"> + <form class="form-inline"> + <div class="form-group"> + <div class="btn-group"> + <input class="form-control" id="new-search-input-{{table_name}}" name="search" type="text" placeholder="Search {{title|lower}}" value="{%if request.GET.search %}{{request.GET.search}}{%endif%}"/> + <span class="remove-search-btn-{{table_name}} glyphicon glyphicon-remove-circle" tabindex="-1"></a> + </div> + </div> + <button class="btn btn-default search-submit-{{table_name}}"> Search </button> <button class="btn btn-link show-all-{{table_name}} remove-search-btn-{{table_name}}"> @@ -44,62 +50,76 @@ <div id="table-container-{{table_name}}" style="visibility: hidden"> <!-- control header --> - <div class="navbar" id="table-chrome-{{table_name}}"> - <div class="navbar-inner"> - <div class="navbar-search input-append pull-left span6"> - - <input id="search-input-{{table_name}}" name="search" type="text" placeholder="Search {{title|lower}}" value="{%if request.GET.search%}{{request.GET.search}}{%endif%}"/> - <a href="#" style="display:none" class="add-on btn remove-search-btn-{{table_name}}" tabindex="-1"> - <i class="icon-remove"></i> - </a> - <button class="btn" id="search-submit-{{table_name}}" >Search</button> + <div class="navbar navbar-default" id="table-chrome-{{table_name}}"> + <div class="container-fluid"> + <div class="navbar-header"> + <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#table-chrome-collapse-{{table_name}}" aria-expanded="false"> + <span class="sr-only">Toggle navigation</span> + <span class="icon-bar"></span> + <span class="icon-bar"></span> + <span class="icon-bar"></span> + </button> </div> - - <div class="pull-right"> - <div class="btn-group"> - <button id="edit-columns-button" class="btn dropdown-toggle" data-toggle="dropdown">Edit columns + <div class="collapse navbar-collapse" id="table-chrome-collapse-{{table_name}}"> + <form class="navbar-form navbar-left"> + <div class="form-group"> + <div class="btn-group"> + <input id="search-input-{{table_name}}" class="form-control" name="search" type="text" placeholder="Search {{title|lower}}" value="{%if request.GET.search%}{{request.GET.search}}{%endif%}"/> + <span class="remove-search-btn-{{table_name}} glyphicon glyphicon-remove-circle" tabindex="-1" style="display:none;"> + </div> + </div> + <button class="btn btn-default" id="search-submit-{{table_name}}" >Search</button> + </form> + <form class="navbar-form navbar-right"> + <div clas="form-group"> + <label>Show rows:</label> + <select class="form-control pagesize-{{table_name}}"> + {% with "10 25 50 100 150" as list%} + {% for i in list.split %} + <option value="{{i}}">{{i}}</option> + {% endfor %} + {% endwith %} + </select> + </div> + </form> + <div class="btn-group navbar-right"> + <button id="edit-columns-button" class="btn btn-default navbar-btn dropdown-toggle" data-toggle="dropdown">Edit columns <span class="caret"></span> </button> <ul class="dropdown-menu editcol"> </ul> </div> - <div style="display:inline"> - <span class="divider-vertical"></span> - <span class="help-inline" style="padding-top:5px;">Show rows:</span> - <select style="margin-top:5px;margin-bottom:0px;" class="pagesize-{{table_name}}"> - {% with "10 25 50 100 150" as list%} - {% for i in list.split %} - <option value="{{i}}">{{i}}</option> - {% endfor %} - {% endwith %} - </select> - </div> </div> </div> </div> <!-- The actual table --> - <table class="table table-bordered table-hover tablesorter" id="{{table_name}}"> - <thead> - <tr><th></th></tr> - </thead> - <tbody></tbody> - </table> + <div class="table-responsive"> + <table class="table table-bordered table-hover" id="{{table_name}}"> + <thead> + <tr><th></th></tr> + </thead> + <tbody></tbody> + </table> + </div> <!-- Pagination controls --> - <div class="pagination pagination-centered" id="pagination-{{table_name}}"> - <ul class="pagination" style="display: block-inline"> + <div id="pagination-{{table_name}}"> + <ul class="pagination"> </ul> - <div class="pull-right"> - <span class="help-inline" style="padding-top:5px;">Show rows:</span> - <select style="margin-top:5px;margin-bottom:0px;" class="pagesize-{{table_name}}"> - {% with "10 25 50 100 150" as list%} - {% for i in list.split %} - <option value="{{i}}">{{i}}</option> - {% endfor %} - {% endwith %} - </select> - </div> + <form class="navbar-form navbar-right"> + <div class="form-group"> + <label>Show rows:</label> + <select class="form-control pagesize-{{table_name}}"> + {% with "10 25 50 100 150" as list%} + {% for i in list.split %} + <option value="{{i}}">{{i}}</option> + {% endfor %} + {% endwith %} + </select> + </div> + </form> </div> -</div> + +</div> <!--end table container --> diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/unavailable_artifact.html b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/unavailable_artifact.html index 2d3d02c2e..fc77e405f 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/unavailable_artifact.html +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/unavailable_artifact.html @@ -6,10 +6,12 @@ {% block title %} Build artifact does not exist - Toaster {% endblock %} {% block pagecontent %} - <div class="row-fluid air"> - <div class="alert alert-info span8 lead"> - <p>The build artifact you are trying to download does not exist.</p> - <p><a href="javascript:window.history.back()">Back to previous page</a></p> + <div class="row"> + <div class="col-md-8"> + <div class="alert alert-info lead top-air"> + <p>The build artifact you are trying to download does not exist.</p> + <p><a href="javascript:window.history.back()">Back to previous page</a></p> + </div> </div> </div> {% endblock %} diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templatetags/field_values_filter.py b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templatetags/field_values_filter.py new file mode 100644 index 000000000..5a73af797 --- /dev/null +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templatetags/field_values_filter.py @@ -0,0 +1,18 @@ +from django import template + +register = template.Library() + +def field_values(iterable, field): + """ + Convert an iterable of models into a list of strings, one for each model, + where the string for each model is the value of the field "field". + """ + objects = [] + + if field: + for item in iterable: + objects.append(getattr(item, field)) + + return objects + +register.filter('field_values', field_values)
\ No newline at end of file diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templatetags/projecttags.py b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templatetags/projecttags.py index 75f2261be..b170a1616 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templatetags/projecttags.py +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templatetags/projecttags.py @@ -90,7 +90,7 @@ def whitespace_space_filter(value, arg): def divide(value, arg): if int(arg) == 0: return -1 - return int(value) / int(arg) + return int(value) // int(arg) @register.filter def multiply(value, arg): @@ -112,11 +112,11 @@ def task_color(task_object, show_green=False): show_green argument should be True to get green color. """ if not task_object.task_executed: - return 'class=muted' + return 'class=text-muted' elif task_object.outcome == task_object.OUTCOME_FAILED: - return 'class=error' + return 'class=text-danger' elif task_object.outcome == task_object.OUTCOME_SUCCESS and show_green: - return 'class=green' + return 'class=text-success' else: return '' @@ -250,15 +250,15 @@ from django.utils.safestring import mark_safe @register.filter def format_vpackage_rowclass(size): if size == -1: - return mark_safe('class="muted"') + return mark_safe('class="text-muted"') return '' @register.filter def format_vpackage_namehelp(name): r = name + ' ' - r += '<i class="icon-question-sign get-help hover-help"' + r += '<span class="glyphicon glyphicon-question-sign get-help hover-help"' r += ' title = "' + name + ' has not been built">' - r += '</i>' + r += '</span>' return mark_safe(r) @register.filter @@ -271,14 +271,6 @@ def get_dict_value(dictionary, key): return '' @register.filter -def format_build_date(completed_on): - now = timezone.now() - delta = now - completed_on - - if delta.days >= 1: - return True - -@register.filter def is_shaid(text): """ return True if text length is 40 characters and all hex-digits """ @@ -297,3 +289,11 @@ def cut_path_prefix(fullpath, prefixes): if fullpath.startswith(prefix): return relpath(fullpath, prefix) return fullpath + + +@register.filter +def for_target(package_dependencies, target): + """ filter the dependencies to be displayed by the supplied target + if no dependences are found for the target then return the predicted + dependences""" + return package_dependencies.for_target_or_none(target) diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/tests.py b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/tests.py index a4cab5848..2b5894f74 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/tests.py +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/tests.py @@ -25,12 +25,13 @@ from django.test import TestCase from django.test.client import RequestFactory from django.core.urlresolvers import reverse from django.utils import timezone +from django.db.models import Q from orm.models import Project, Release, BitbakeVersion, Package, LogMessage -from orm.models import ReleaseLayerSourcePriority, LayerSource, Layer, Build +from orm.models import LayerSource, Layer, Build from orm.models import Layer_Version, Recipe, Machine, ProjectLayer, Target from orm.models import CustomImageRecipe, ProjectVariable -from orm.models import Branch, CustomImagePackage +from orm.models import CustomImagePackage import toastermain import inspect @@ -57,7 +58,6 @@ class ViewTests(TestCase): self.project = Project.objects.first() self.recipe1 = Recipe.objects.get(pk=2) - self.recipe2 = Recipe.objects.last() self.customr = CustomImageRecipe.objects.first() self.cust_package = CustomImagePackage.objects.first() self.package = Package.objects.first() @@ -77,14 +77,18 @@ class ViewTests(TestCase): self.assertEqual(response.status_code, 200) self.assertTrue(response['Content-Type'].startswith('application/json')) - data = json.loads(response.content) + data = json.loads(response.content.decode('utf-8')) self.assertTrue("error" in data) self.assertEqual(data["error"], "ok") self.assertTrue("rows" in data) - self.assertTrue(self.project.name in [x["name"] for x in data["rows"]]) - self.assertTrue("id" in data["rows"][0]) + name_found = False + for row in data["rows"]: + name_found = row['name'].find(self.project.name) + + self.assertTrue(name_found, + "project name not found in projects table") def test_typeaheads(self): """Test typeahead ReST API""" @@ -102,7 +106,7 @@ class ViewTests(TestCase): self.assertEqual(response.status_code, 200) self.assertTrue(response['Content-Type'].startswith('application/json')) - data = json.loads(response.content) + data = json.loads(response.content.decode('utf-8')) self.assertTrue("error" in data) self.assertEqual(data["error"], "ok") @@ -145,34 +149,34 @@ class ViewTests(TestCase): def test_xhr_import_layer(self): """Test xhr_importlayer API""" - LayerSource.objects.create(sourcetype=LayerSource.TYPE_IMPORTED) #Test for importing an already existing layer args = {'vcs_url' : "git://git.example.com/test", 'name' : "base-layer", 'git_ref': "c12b9596afd236116b25ce26dbe0d793de9dc7ce", 'project_id': self.project.id, + 'local_source_dir': "", 'dir_path' : "/path/in/repository"} response = self.client.post(reverse('xhr_importlayer'), args) - data = json.loads(response.content) + data = json.loads(response.content.decode('utf-8')) self.assertEqual(response.status_code, 200) self.assertEqual(data["error"], "ok") #Test to verify import of a layer successful args['name'] = "meta-oe" response = self.client.post(reverse('xhr_importlayer'), args) - data = json.loads(response.content) + data = json.loads(response.content.decode('utf-8')) self.assertTrue(data["error"], "ok") #Test for html tag in the data args['<'] = "testing html tag" response = self.client.post(reverse('xhr_importlayer'), args) - data = json.loads(response.content) + data = json.loads(response.content.decode('utf-8')) self.assertNotEqual(data["error"], "ok") #Empty data passed args = {} response = self.client.post(reverse('xhr_importlayer'), args) - data = json.loads(response.content) + data = json.loads(response.content.decode('utf-8')) self.assertNotEqual(data["error"], "ok") def test_custom_ok(self): @@ -182,7 +186,7 @@ class ViewTests(TestCase): 'base': self.recipe1.id} response = self.client.post(url, params) self.assertEqual(response.status_code, 200) - data = json.loads(response.content) + data = json.loads(response.content.decode('utf-8')) self.assertEqual(data['error'], 'ok') self.assertTrue('url' in data) # get recipe from the database @@ -198,7 +202,7 @@ class ViewTests(TestCase): {'name': 'custom', 'project': self.project.id}]: response = self.client.post(url, params) self.assertEqual(response.status_code, 200) - data = json.loads(response.content) + data = json.loads(response.content.decode('utf-8')) self.assertNotEqual(data["error"], "ok") def test_xhr_custom_wrong_project(self): @@ -207,7 +211,7 @@ class ViewTests(TestCase): params = {'name': 'custom', 'project': 0, "base": self.recipe1.id} response = self.client.post(url, params) self.assertEqual(response.status_code, 200) - data = json.loads(response.content) + data = json.loads(response.content.decode('utf-8')) self.assertNotEqual(data["error"], "ok") def test_xhr_custom_wrong_base(self): @@ -216,7 +220,7 @@ class ViewTests(TestCase): params = {'name': 'custom', 'project': self.project.id, "base": 0} response = self.client.post(url, params) self.assertEqual(response.status_code, 200) - data = json.loads(response.content) + data = json.loads(response.content.decode('utf-8')) self.assertNotEqual(data["error"], "ok") def test_xhr_custom_details(self): @@ -231,7 +235,7 @@ class ViewTests(TestCase): 'project_id': self.project.id, } } - self.assertEqual(json.loads(response.content), expected) + self.assertEqual(json.loads(response.content.decode('utf-8')), expected) def test_xhr_custom_del(self): """Test deleting custom recipe""" @@ -244,12 +248,18 @@ class ViewTests(TestCase): url = reverse('xhr_customrecipe_id', args=(recipe.id,)) response = self.client.delete(url) self.assertEqual(response.status_code, 200) - self.assertEqual(json.loads(response.content), {"error": "ok"}) + + gotoUrl = reverse('projectcustomimages', args=(self.project.pk,)) + + self.assertEqual(json.loads(response.content.decode('utf-8')), + {"error": "ok", + "gotoUrl": gotoUrl}) + # try to delete not-existent recipe url = reverse('xhr_customrecipe_id', args=(recipe.id,)) response = self.client.delete(url) self.assertEqual(response.status_code, 200) - self.assertNotEqual(json.loads(response.content)["error"], "ok") + self.assertNotEqual(json.loads(response.content.decode('utf-8'))["error"], "ok") def test_xhr_custom_packages(self): """Test adding and deleting package to a custom recipe""" @@ -259,7 +269,7 @@ class ViewTests(TestCase): self.cust_package.id))) self.assertEqual(response.status_code, 200) - self.assertEqual(json.loads(response.content), + self.assertEqual(json.loads(response.content.decode('utf-8')), {"error": "ok"}) self.assertEqual(self.customr.appends_set.first().name, self.cust_package.name) @@ -270,7 +280,7 @@ class ViewTests(TestCase): response = self.client.delete(del_url) self.assertEqual(response.status_code, 200) - self.assertEqual(json.loads(response.content), {"error": "ok"}) + self.assertEqual(json.loads(response.content.decode('utf-8')), {"error": "ok"}) all_packages = self.customr.get_all_packages().values_list('pk', flat=True) @@ -282,7 +292,7 @@ class ViewTests(TestCase): response = self.client.delete(del_url) self.assertEqual(response.status_code, 200) - self.assertNotEqual(json.loads(response.content)["error"], "ok") + self.assertNotEqual(json.loads(response.content.decode('utf-8'))["error"], "ok") def test_xhr_custom_packages_err(self): """Test error conditions of xhr_customrecipe_packages""" @@ -293,51 +303,60 @@ class ViewTests(TestCase): for method in (self.client.put, self.client.delete): response = method(url) self.assertEqual(response.status_code, 200) - self.assertNotEqual(json.loads(response.content), + self.assertNotEqual(json.loads(response.content.decode('utf-8')), {"error": "ok"}) def test_download_custom_recipe(self): """Download the recipe file generated for the custom image""" # Create a dummy recipe file for the custom image generation to read - open("/tmp/a_recipe.bb", 'wa').close() + open("/tmp/a_recipe.bb", 'a').close() response = self.client.get(reverse('customrecipedownload', args=(self.project.id, self.customr.id))) self.assertEqual(response.status_code, 200) - def test_software_recipes_table(self): """Test structure returned for Software RecipesTable""" table = SoftwareRecipesTable() request = RequestFactory().get('/foo/', {'format': 'json'}) response = table.get(request, pid=self.project.id) - data = json.loads(response.content) + data = json.loads(response.content.decode('utf-8')) + + recipes = Recipe.objects.filter(Q(is_image=False)) + self.assertTrue(len(recipes) > 1, + "Need more than one software recipe to test " + "SoftwareRecipesTable") + + recipe1 = recipes[0] + recipe2 = recipes[1] rows = data['rows'] - row1 = next(x for x in rows if x['name'] == self.recipe1.name) - row2 = next(x for x in rows if x['name'] == self.recipe2.name) + row1 = next(x for x in rows if x['name'] == recipe1.name) + row2 = next(x for x in rows if x['name'] == recipe2.name) self.assertEqual(response.status_code, 200, 'should be 200 OK status') # check other columns have been populated correctly - self.assertEqual(row1['name'], self.recipe1.name) - self.assertEqual(row1['version'], self.recipe1.version) - self.assertEqual(row1['get_description_or_summary'], - self.recipe1.description) - self.assertEqual(row1['layer_version__layer__name'], - self.recipe1.layer_version.layer.name) - self.assertEqual(row2['name'], self.recipe2.name) - self.assertEqual(row2['version'], self.recipe2.version) - self.assertEqual(row2['get_description_or_summary'], - self.recipe2.description) - self.assertEqual(row2['layer_version__layer__name'], - self.recipe2.layer_version.layer.name) + self.assertTrue(recipe1.name in row1['name']) + self.assertTrue(recipe1.version in row1['version']) + self.assertTrue(recipe1.description in + row1['get_description_or_summary']) + + self.assertTrue(recipe1.layer_version.layer.name in + row1['layer_version__layer__name']) + + self.assertTrue(recipe2.name in row2['name']) + self.assertTrue(recipe2.version in row2['version']) + self.assertTrue(recipe2.description in + row2['get_description_or_summary']) + + self.assertTrue(recipe2.layer_version.layer.name in + row2['layer_version__layer__name']) def test_toaster_tables(self): """Test all ToasterTables instances""" - current_recipes = self.project.get_available_recipes() def get_data(table, options={}): """Send a request and parse the json response""" @@ -354,19 +373,36 @@ class ViewTests(TestCase): 'layerid': self.lver.pk, 'recipeid': self.recipe1.pk, 'recipe_id': image_recipe.pk, - 'custrecipeid': self.customr.pk - } + 'custrecipeid': self.customr.pk, + 'build_id': 1, + 'target_id': 1} response = table.get(request, **args) - return json.loads(response.content) + return json.loads(response.content.decode('utf-8')) + + def get_text_from_td(td): + """If we have html in the td then extract the text portion""" + # just so we don't waste time parsing non html + if "<" not in td: + ret = td + else: + ret = BeautifulSoup(td, "html.parser").text + + if len(ret): + return "0" + else: + return ret # Get a list of classes in tables module tables = inspect.getmembers(toastergui.tables, inspect.isclass) + tables.extend(inspect.getmembers(toastergui.buildtables, + inspect.isclass)) for name, table_cls in tables: # Filter out the non ToasterTables from the tables module if not issubclass(table_cls, toastergui.widgets.ToasterTable) or \ - table_cls == toastergui.widgets.ToasterTable: + table_cls == toastergui.widgets.ToasterTable or \ + 'Mixin' in name: continue # Get the table data without any options, this also does the @@ -379,8 +415,10 @@ class ViewTests(TestCase): "Cannot test on a %s table with < 1 row" % name) if table.default_orderby: - row_one = all_data['rows'][0][table.default_orderby.strip("-")] - row_two = all_data['rows'][1][table.default_orderby.strip("-")] + row_one = get_text_from_td( + all_data['rows'][0][table.default_orderby.strip("-")]) + row_two = get_text_from_td( + all_data['rows'][1][table.default_orderby.strip("-")]) if '-' in table.default_orderby: self.assertTrue(row_one >= row_two, @@ -399,28 +437,36 @@ class ViewTests(TestCase): # If a column is orderable test it in both order # directions ordering on the columns field_name ascending = get_data(table_cls(), - {"orderby" : column['field_name']}) + {"orderby": column['field_name']}) - row_one = ascending['rows'][0][column['field_name']] - row_two = ascending['rows'][1][column['field_name']] + row_one = get_text_from_td( + ascending['rows'][0][column['field_name']]) + row_two = get_text_from_td( + ascending['rows'][1][column['field_name']]) self.assertTrue(row_one <= row_two, - "Ascending sort applied but row 0 is less " - "than row 1 %s %s " % - (column['field_name'], name)) - + "Ascending sort applied but row 0: \"%s\"" + " is less than row 1: \"%s\" " + "%s %s " % + (row_one, row_two, + column['field_name'], name)) descending = get_data(table_cls(), - {"orderby" : + {"orderby": '-'+column['field_name']}) - row_one = descending['rows'][0][column['field_name']] - row_two = descending['rows'][1][column['field_name']] + row_one = get_text_from_td( + descending['rows'][0][column['field_name']]) + row_two = get_text_from_td( + descending['rows'][1][column['field_name']]) self.assertTrue(row_one >= row_two, - "Descending sort applied but row 0 is " - "greater than row 1 %s %s" % - (column['field_name'], name)) + "Descending sort applied but row 0: %s" + "is greater than row 1: %s" + "field %s table %s" % + (row_one, + row_two, + column['field_name'], name)) # If the two start rows are the same we haven't actually # changed the order diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/typeaheads.py b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/typeaheads.py index dd4b7f505..4ded9ac2e 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/typeaheads.py +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/typeaheads.py @@ -55,6 +55,7 @@ class LayersTypeAhead(ToasterTypeAhead): 'vcs_url' : layer_version.layer.vcs_url, 'vcs_reference' : vcs_reference, 'detail' : detail, + 'local_source_dir' : layer_version.layer.local_source_dir, } results.append(needed_fields) diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/urls.py b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/urls.py index 27b0baabf..ece9ac169 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/urls.py +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/urls.py @@ -21,8 +21,10 @@ from django.views.generic import RedirectView, TemplateView from django.http import HttpResponseBadRequest from toastergui import tables +from toastergui import buildtables from toastergui import typeaheads from toastergui import api +from toastergui import widgets urlpatterns = patterns('toastergui.views', # landing page @@ -34,17 +36,28 @@ urlpatterns = patterns('toastergui.views', # build info navigation url(r'^build/(?P<build_id>\d+)$', 'builddashboard', name="builddashboard"), + url(r'^build/(?P<build_id>\d+)/tasks/$', + buildtables.BuildTasksTable.as_view( + template_name="buildinfo-toastertable.html"), + name='tasks'), - url(r'^build/(?P<build_id>\d+)/tasks/$', 'tasks', name='tasks'), - url(r'^build/(?P<build_id>\d+)/tasks/(?P<task_id>\d+)/$', 'tasks_task', name='tasks_task'), url(r'^build/(?P<build_id>\d+)/task/(?P<task_id>\d+)$', 'task', name='task'), - url(r'^build/(?P<build_id>\d+)/recipes/$', 'recipes', name='recipes'), + url(r'^build/(?P<build_id>\d+)/recipes/$', + buildtables.BuiltRecipesTable.as_view( + template_name="buildinfo-toastertable.html"), + name='recipes'), + url(r'^build/(?P<build_id>\d+)/recipe/(?P<recipe_id>\d+)/active_tab/(?P<active_tab>\d{1})$', 'recipe', name='recipe'), + url(r'^build/(?P<build_id>\d+)/recipe/(?P<recipe_id>\d+)$', 'recipe', name='recipe'), url(r'^build/(?P<build_id>\d+)/recipe_packages/(?P<recipe_id>\d+)$', 'recipe_packages', name='recipe_packages'), - url(r'^build/(?P<build_id>\d+)/packages/$', 'bpackage', name='packages'), + url(r'^build/(?P<build_id>\d+)/packages/$', + buildtables.BuiltPackagesTable.as_view( + template_name="buildinfo-toastertable.html"), + name='packages'), + url(r'^build/(?P<build_id>\d+)/package/(?P<package_id>\d+)$', 'package_built_detail', name='package_built_detail'), url(r'^build/(?P<build_id>\d+)/package_built_dependencies/(?P<package_id>\d+)$', @@ -56,17 +69,31 @@ urlpatterns = patterns('toastergui.views', url(r'^build/(?P<build_id>\d+)/package_included_reverse_dependencies/(?P<target_id>\d+)/(?P<package_id>\d+)$', 'package_included_reverse_dependencies', name='package_included_reverse_dependencies'), - # images are known as targets in the internal model - url(r'^build/(?P<build_id>\d+)/target/(?P<target_id>\d+)$', 'target', name='target'), - url(r'^build/(?P<build_id>\d+)/target/(?P<target_id>\d+)/targetpkg$', 'targetpkg', name='targetpkg'), + url(r'^build/(?P<build_id>\d+)/target/(?P<target_id>\d+)$', + buildtables.InstalledPackagesTable.as_view( + template_name="target.html"), + name='target'), + + url(r'^dentries/build/(?P<build_id>\d+)/target/(?P<target_id>\d+)$', 'xhr_dirinfo', name='dirinfo_ajax'), url(r'^build/(?P<build_id>\d+)/target/(?P<target_id>\d+)/dirinfo$', 'dirinfo', name='dirinfo'), url(r'^build/(?P<build_id>\d+)/target/(?P<target_id>\d+)/dirinfo_filepath/_(?P<file_path>(?:/[^/\n]+)*)$', 'dirinfo', name='dirinfo_filepath'), url(r'^build/(?P<build_id>\d+)/configuration$', 'configuration', name='configuration'), url(r'^build/(?P<build_id>\d+)/configvars$', 'configvars', name='configvars'), - url(r'^build/(?P<build_id>\d+)/buildtime$', 'buildtime', name='buildtime'), - url(r'^build/(?P<build_id>\d+)/cputime$', 'cputime', name='cputime'), - url(r'^build/(?P<build_id>\d+)/diskio$', 'diskio', name='diskio'), + url(r'^build/(?P<build_id>\d+)/buildtime$', + buildtables.BuildTimeTable.as_view( + template_name="buildinfo-toastertable.html"), + name='buildtime'), + + url(r'^build/(?P<build_id>\d+)/cputime$', + buildtables.BuildCPUTimeTable.as_view( + template_name="buildinfo-toastertable.html"), + name='cputime'), + + url(r'^build/(?P<build_id>\d+)/diskio$', + buildtables.BuildIOTable.as_view( + template_name="buildinfo-toastertable.html"), + name='diskio'), # image information dir url(r'^build/(?P<build_id>\d+)/target/(?P<target_id>\d+)/packagefile/(?P<packagefile_id>\d+)$', @@ -164,27 +191,47 @@ urlpatterns = patterns('toastergui.views', name='xhr_configvaredit'), url(r'^xhr_importlayer/$', 'xhr_importlayer', name='xhr_importlayer'), - url(r'^xhr_updatelayer/$', 'xhr_updatelayer', name='xhr_updatelayer'), + + url(r'^xhr_layer/(?P<pid>\d+)/(?P<layerversion_id>\d+)$', + api.XhrLayer.as_view(), + name='xhr_layer'), # JS Unit tests url(r'^js-unit-tests/$', 'jsunittests', name='js-unit-tests'), # image customisation functionality - url(r'^xhr_customrecipe/(?P<recipe_id>\d+)/packages/(?P<package_id>\d+|)$', - 'xhr_customrecipe_packages', name='xhr_customrecipe_packages'), + url(r'^xhr_customrecipe/(?P<recipe_id>\d+)' + '/packages/(?P<package_id>\d+|)$', + api.XhrCustomRecipePackages.as_view(), + name='xhr_customrecipe_packages'), url(r'^xhr_customrecipe/(?P<recipe_id>\d+)/packages/$', - 'xhr_customrecipe_packages', name='xhr_customrecipe_packages'), + api.XhrCustomRecipePackages.as_view(), + name='xhr_customrecipe_packages'), - url(r'^xhr_customrecipe/(?P<recipe_id>\d+)$', 'xhr_customrecipe_id', + url(r'^xhr_customrecipe/(?P<recipe_id>\d+)$', + api.XhrCustomRecipeId.as_view(), name='xhr_customrecipe_id'), - url(r'^xhr_customrecipe/', 'xhr_customrecipe', + + url(r'^xhr_customrecipe/', + api.XhrCustomRecipe.as_view(), name='xhr_customrecipe'), url(r'^xhr_buildrequest/project/(?P<pid>\d+)$', - api.XhrBuildRequest.as_view(), + api.XhrBuildRequest.as_view(), name='xhr_buildrequest'), + url(r'xhr_project/(?P<project_id>\d+)$', + api.XhrProject.as_view(), + name='xhr_project'), + + url(r'xhr_build/(?P<build_id>\d+)$', + api.XhrBuild.as_view(), + name='xhr_build'), + + url(r'^mostrecentbuilds$', widgets.MostRecentBuildsView.as_view(), + name='most_recent_builds'), + # default redirection url(r'^$', RedirectView.as_view(url='landing', permanent=True)), ) diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/views.py b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/views.py index bd5bf6334..2efb0fd56 100755 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/views.py +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/views.py @@ -19,42 +19,37 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -# pylint: disable=method-hidden -# Gives E:848, 4: An attribute defined in json.encoder line 162 hides this method (method-hidden) -# which is an invalid warning -import operator,re +import re -from django.db.models import F, Q, Sum, Count, Max -from django.db import IntegrityError, Error +from django.db.models import F, Q, Sum +from django.db import IntegrityError from django.shortcuts import render, redirect, get_object_or_404 -from orm.models import Build, Target, Task, Layer, Layer_Version, Recipe, LogMessage, Variable -from orm.models import Task_Dependency, Recipe_Dependency, Package, Package_File, Package_Dependency -from orm.models import Target_Installed_Package, Target_File, Target_Image_File, BuildArtifact, CustomImagePackage +from orm.models import Build, Target, Task, Layer, Layer_Version, Recipe +from orm.models import LogMessage, Variable, Package_Dependency, Package +from orm.models import Task_Dependency, Package_File +from orm.models import Target_Installed_Package, Target_File +from orm.models import TargetKernelFile, TargetSDKFile, Target_Image_File from orm.models import BitbakeVersion, CustomImageRecipe -from bldcontrol import bbcontroller -from django.views.decorators.cache import cache_control + from django.core.urlresolvers import reverse, resolve from django.core.exceptions import MultipleObjectsReturned, ObjectDoesNotExist from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger -from django.http import HttpResponseBadRequest, HttpResponseNotFound +from django.http import HttpResponseNotFound from django.utils import timezone -from django.utils.html import escape from datetime import timedelta, datetime -from django.utils import formats from toastergui.templatetags.projecttags import json as jsonfilter from decimal import Decimal import json import os from os.path import dirname -from functools import wraps -import itertools import mimetypes import logging logger = logging.getLogger("toaster") + class MimeTypeFinder(object): # setting this to False enables additional non-standard mimetypes # to be included in the guess @@ -160,7 +155,7 @@ def _lv_to_dict(prj, x = None): return {"id": x.pk, "name": x.layer.name, "tooltip": "%s | %s" % (x.layer.vcs_url,x.get_vcs_reference()), - "detail": "(%s" % x.layer.vcs_url + (")" if x.up_branch == None else " | "+x.get_vcs_reference()+")"), + "detail": "(%s" % x.layer.vcs_url + (")" if x.release == None else " | "+x.get_vcs_reference()+")"), "giturl": x.layer.vcs_url, "layerdetailurl" : reverse('layerdetails', args=(prj.id,x.pk)), "revision" : x.get_vcs_reference(), @@ -200,16 +195,19 @@ def _verify_parameters(g, mandatory_parameters): return None def _redirect_parameters(view, g, mandatory_parameters, *args, **kwargs): - import urllib + try: + from urllib import unquote, urlencode + except ImportError: + from urllib.parse import unquote, urlencode url = reverse(view, kwargs=kwargs) params = {} for i in g: params[i] = g[i] for i in mandatory_parameters: if not i in params: - params[i] = urllib.unquote(str(mandatory_parameters[i])) + params[i] = unquote(str(mandatory_parameters[i])) - return redirect(url + "?%s" % urllib.urlencode(params), permanent = False, **kwargs) + return redirect(url + "?%s" % urlencode(params), permanent = False, **kwargs) class RedirectException(Exception): def __init__(self, view, g, mandatory_parameters, *args, **kwargs): @@ -229,10 +227,18 @@ OR_VALUE_SEPARATOR = "|" DESCENDING = "-" def __get_q_for_val(name, value): - if "OR" in value: - return reduce(operator.or_, map(lambda x: __get_q_for_val(name, x), [ x for x in value.split("OR") ])) + if "OR" in value or "AND" in value: + result = None + for x in value.split("OR"): + x = __get_q_for_val(name, x) + result = result | x if result else x + return result if "AND" in value: - return reduce(operator.and_, map(lambda x: __get_q_for_val(name, x), [ x for x in value.split("AND") ])) + result = None + for x in value.split("AND"): + x = __get_q_for_val(name, x) + result = result & x if result else x + return result if value.startswith("NOT"): value = value[3:] if value == 'None': @@ -251,14 +257,18 @@ def _get_filtering_query(filter_string): and_keys = search_terms[0].split(AND_VALUE_SEPARATOR) and_values = search_terms[1].split(AND_VALUE_SEPARATOR) - and_query = [] + and_query = None for kv in zip(and_keys, and_values): or_keys = kv[0].split(OR_VALUE_SEPARATOR) or_values = kv[1].split(OR_VALUE_SEPARATOR) - querydict = dict(zip(or_keys, or_values)) - and_query.append(reduce(operator.or_, map(lambda x: __get_q_for_val(x, querydict[x]), [k for k in querydict]))) + query = None + for key, val in zip(or_keys, or_values): + x = __get_q_for_val(key, val) + query = query | x if query else x + + and_query = and_query & query if and_query else query - return reduce(operator.and_, [k for k in and_query]) + return and_query def _get_toggle_order(request, orderkey, toggle_reverse = False): if toggle_reverse: @@ -295,21 +305,24 @@ def _validate_input(field_input, model): # Check we are looking for a valid field valid_fields = model._meta.get_all_field_names() for field in field_input_list[0].split(AND_VALUE_SEPARATOR): - if not reduce(lambda x, y: x or y, [ field.startswith(x) for x in valid_fields ]): - return None, (field, [ x for x in valid_fields ]) + if True in [field.startswith(x) for x in valid_fields]: + break + else: + return None, (field, valid_fields) return field_input, invalid # uses search_allowed_fields in orm/models.py to create a search query # for these fields with the supplied input text def _get_search_results(search_term, queryset, model): - search_objects = [] + search_object = None for st in search_term.split(" "): - q_map = map(lambda x: Q(**{x+'__icontains': st}), - model.search_allowed_fields) + queries = None + for field in model.search_allowed_fields: + query = Q(**{field + '__icontains': st}) + queries = queries | query if queries else query - search_objects.append(reduce(operator.or_, q_map)) - search_object = reduce(operator.and_, search_objects) + search_object = search_object & queries if search_object else queries queryset = queryset.filter(search_object) return queryset @@ -452,46 +465,58 @@ def builddashboard( request, build_id ): recipeCount = Recipe.objects.filter( layer_version__id__in = layerVersionId ).count( ); tgts = Target.objects.filter( build_id = build_id ).order_by( 'target' ); - ## # set up custom target list with computed package and image data - # - - targets = [ ] + targets = [] ntargets = 0 - hasImages = False - targetHasNoImages = False + + # True if at least one target for this build has an SDK artifact + # or image file + has_artifacts = False + for t in tgts: - elem = { } - elem[ 'target' ] = t - if t.is_image: - hasImages = True + elem = {} + elem['target'] = t + + target_has_images = False + image_files = [] + npkg = 0 pkgsz = 0 package = None for package in Package.objects.filter(id__in = [x.package_id for x in t.target_installed_package_set.all()]): pkgsz = pkgsz + package.size - if ( package.installed_name ): + if package.installed_name: npkg = npkg + 1 - elem[ 'npkg' ] = npkg - elem[ 'pkgsz' ] = pkgsz - ti = Target_Image_File.objects.filter( target_id = t.id ) - imageFiles = [ ] + elem['npkg'] = npkg + elem['pkgsz'] = pkgsz + ti = Target_Image_File.objects.filter(target_id = t.id) for i in ti: - ndx = i.file_name.rfind( '/' ) - if ( ndx < 0 ): + ndx = i.file_name.rfind('/') + if ndx < 0: ndx = 0; - f = i.file_name[ ndx + 1: ] - imageFiles.append({ + f = i.file_name[ndx + 1:] + image_files.append({ 'id': i.id, 'path': f, 'size': i.file_size, 'suffix': i.suffix }) - if t.is_image and (len(imageFiles) <= 0 or len(t.license_manifest_path) <= 0): - targetHasNoImages = True - elem[ 'imageFiles' ] = imageFiles - elem[ 'targetHasNoImages' ] = targetHasNoImages - targets.append( elem ) + if len(image_files) > 0: + target_has_images = True + elem['targetHasImages'] = target_has_images + + elem['imageFiles'] = image_files + elem['target_kernel_artifacts'] = t.targetkernelfile_set.all() + + target_sdk_files = t.targetsdkfile_set.all() + target_sdk_artifacts_count = target_sdk_files.count() + elem['target_sdk_artifacts_count'] = target_sdk_artifacts_count + elem['target_sdk_artifacts'] = target_sdk_files + + if target_has_images or target_sdk_artifacts_count > 0: + has_artifacts = True + + targets.append(elem) ## # how many packages in this build - ignore anonymous ones @@ -508,7 +533,7 @@ def builddashboard( request, build_id ): context = { 'build' : build, 'project' : build.project, - 'hasImages' : hasImages, + 'hasArtifacts' : has_artifacts, 'ntargets' : ntargets, 'targets' : targets, 'recipecount' : recipeCount, @@ -578,6 +603,7 @@ def task( request, build_id, task_id ): 'log_body' : log_body, 'showing_matches' : False, 'uri_list' : uri_list, + 'task_in_tasks_table_pg': int(task_object.order / 25) + 1 } if request.GET.get( 'show_matches', "" ): context[ 'showing_matches' ] = True @@ -662,175 +688,6 @@ def recipe_packages(request, build_id, recipe_id): _set_parameters_values(pagesize, orderby, request) return response -def target_common( request, build_id, target_id, variant ): - template = "target.html" - default_orderby = 'name:+' - - (pagesize, orderby) = _get_parameters_values(request, 25, default_orderby) - mandatory_parameters = { 'count': pagesize, 'page' : 1, 'orderby': orderby } - retval = _verify_parameters( request.GET, mandatory_parameters ) - if retval: - return _redirect_parameters( - variant, request.GET, mandatory_parameters, - build_id = build_id, target_id = target_id ) - ( filter_string, search_term, ordering_string ) = _search_tuple( request, Package ) - - # FUTURE: get rid of nested sub-queries replacing with ManyToMany field - queryset = Package.objects.filter( - size__gte = 0, - id__in = Target_Installed_Package.objects.filter( - target_id=target_id ).values( 'package_id' )) - packages_sum = queryset.aggregate( Sum( 'installed_size' )) - queryset = _get_queryset( - Package, queryset, filter_string, search_term, ordering_string, 'name' ) - queryset = queryset.select_related("recipe", "recipe__layer_version", "recipe__layer_version__layer") - packages = _build_page_range( Paginator(queryset, pagesize), request.GET.get( 'page', 1 )) - - build = Build.objects.get( pk = build_id ) - - # bring in package dependencies - for p in packages.object_list: - p.runtime_dependencies = p.package_dependencies_source.filter( - target_id = target_id, dep_type=Package_Dependency.TYPE_TRDEPENDS ).select_related("depends_on") - p.reverse_runtime_dependencies = p.package_dependencies_target.filter( - target_id = target_id, dep_type=Package_Dependency.TYPE_TRDEPENDS ).select_related("package") - tc_package = { - 'name' : 'Package', - 'qhelp' : 'Packaged output resulting from building a recipe included in this image', - 'orderfield' : _get_toggle_order( request, "name" ), - 'ordericon' : _get_toggle_order_icon( request, "name" ), - } - tc_packageVersion = { - 'name' : 'Package version', - 'qhelp' : 'The package version and revision', - } - tc_size = { - 'name' : 'Size', - 'qhelp' : 'The size of the package', - 'orderfield' : _get_toggle_order( request, "size", True ), - 'ordericon' : _get_toggle_order_icon( request, "size" ), - 'orderkey' : 'size', - 'clclass' : 'size', - 'dclass' : 'span2', - } - if ( variant == 'target' ): - tc_size[ "hidden" ] = 0 - else: - tc_size[ "hidden" ] = 1 - tc_sizePercentage = { - 'name' : 'Size over total (%)', - 'qhelp' : 'Proportion of the overall size represented by this package', - 'clclass' : 'size_over_total', - 'hidden' : 1, - } - tc_license = { - 'name' : 'License', - 'qhelp' : 'The license under which the package is distributed. Separate license names u\ -sing | (pipe) means there is a choice between licenses. Separate license names using & (ampersand) m\ -eans multiple licenses exist that cover different parts of the source', - 'orderfield' : _get_toggle_order( request, "license" ), - 'ordericon' : _get_toggle_order_icon( request, "license" ), - 'orderkey' : 'license', - 'clclass' : 'license', - } - if ( variant == 'target' ): - tc_license[ "hidden" ] = 1 - else: - tc_license[ "hidden" ] = 0 - tc_dependencies = { - 'name' : 'Dependencies', - 'qhelp' : "Package runtime dependencies (other packages)", - 'clclass' : 'depends', - } - if ( variant == 'target' ): - tc_dependencies[ "hidden" ] = 0 - else: - tc_dependencies[ "hidden" ] = 1 - tc_rdependencies = { - 'name' : 'Reverse dependencies', - 'qhelp' : 'Package run-time reverse dependencies (i.e. which other packages depend on this package', - 'clclass' : 'brought_in_by', - } - if ( variant == 'target' ): - tc_rdependencies[ "hidden" ] = 0 - else: - tc_rdependencies[ "hidden" ] = 1 - tc_recipe = { - 'name' : 'Recipe', - 'qhelp' : 'The name of the recipe building the package', - 'orderfield' : _get_toggle_order( request, "recipe__name" ), - 'ordericon' : _get_toggle_order_icon( request, "recipe__name" ), - 'orderkey' : "recipe__name", - 'clclass' : 'recipe_name', - 'hidden' : 0, - } - tc_recipeVersion = { - 'name' : 'Recipe version', - 'qhelp' : 'Version and revision of the recipe building the package', - 'clclass' : 'recipe_version', - 'hidden' : 1, - } - tc_layer = { - 'name' : 'Layer', - 'qhelp' : 'The name of the layer providing the recipe that builds the package', - 'orderfield' : _get_toggle_order( request, "recipe__layer_version__layer__name" ), - 'ordericon' : _get_toggle_order_icon( request, "recipe__layer_version__layer__name" ), - 'orderkey' : "recipe__layer_version__layer__name", - 'clclass' : 'layer_name', - 'hidden' : 1, - } - tc_layerBranch = { - 'name' : 'Layer branch', - 'qhelp' : 'The Git branch of the layer providing the recipe that builds the package', - 'orderfield' : _get_toggle_order( request, "recipe__layer_version__branch" ), - 'ordericon' : _get_toggle_order_icon( request, "recipe__layer_version__branch" ), - 'orderkey' : "recipe__layer_version__branch", - 'clclass' : 'layer_branch', - 'hidden' : 1, - } - tc_layerCommit = { - 'name' : 'Layer commit', - 'qhelp' : 'The Git commit of the layer providing the recipe that builds the package', - 'clclass' : 'layer_commit', - 'hidden' : 1, - } - - context = { - 'objectname': variant, - 'build' : build, - 'project' : build.project, - 'target' : Target.objects.filter( pk = target_id )[ 0 ], - 'objects' : packages, - 'packages_sum' : packages_sum[ 'installed_size__sum' ], - 'object_search_display': "packages included", - 'default_orderby' : default_orderby, - 'tablecols' : [ - tc_package, - tc_packageVersion, - tc_license, - tc_size, - tc_sizePercentage, - tc_dependencies, - tc_rdependencies, - tc_recipe, - tc_recipeVersion, - tc_layer, - tc_layerBranch, - tc_layerCommit, - ] - } - - - response = render(request, template, context) - _set_parameters_values(pagesize, orderby, request) - return response - -def target( request, build_id, target_id ): - return( target_common( request, build_id, target_id, "target" )) - -def targetpkg( request, build_id, target_id ): - return( target_common( request, build_id, target_id, "targetpkg" )) - from django.core.serializers.json import DjangoJSONEncoder from django.http import HttpResponse def xhr_dirinfo(request, build_id, target_id): @@ -910,8 +767,8 @@ def _get_dir_entries(build_id, target_id, start): response.append(entry) except Exception as e: - print "Exception ", e - traceback.print_exc(e) + print("Exception ", e) + traceback.print_exc() # sort by directories first, then by name rsorted = sorted(response, key=lambda entry : entry['name']) @@ -952,18 +809,29 @@ def dirinfo(request, build_id, target_id, file_path=None): return render(request, template, context) def _find_task_dep(task_object): - return map(lambda x: x.depends_on, Task_Dependency.objects.filter(task=task_object).filter(depends_on__order__gt = 0).exclude(depends_on__outcome = Task.OUTCOME_NA).select_related("depends_on")) - + tdeps = Task_Dependency.objects.filter(task=task_object).filter(depends_on__order__gt=0) + tdeps = tdeps.exclude(depends_on__outcome=Task.OUTCOME_NA).select_related("depends_on") + return [x.depends_on for x in tdeps] def _find_task_revdep(task_object): - tp = [] - tp = map(lambda t: t.task, Task_Dependency.objects.filter(depends_on=task_object).filter(task__order__gt=0).exclude(task__outcome = Task.OUTCOME_NA).select_related("task", "task__recipe", "task__build")) - return tp + tdeps = Task_Dependency.objects.filter(depends_on=task_object).filter(task__order__gt=0) + tdeps = tdeps.exclude(task__outcome = Task.OUTCOME_NA).select_related("task", "task__recipe", "task__build") + + # exclude self-dependencies to prevent infinite dependency loop + # in generateCoveredList2() + tdeps = tdeps.exclude(task=task_object) + + return [tdep.task for tdep in tdeps] def _find_task_revdep_list(tasklist): - tp = [] - tp = map(lambda t: t.task, Task_Dependency.objects.filter(depends_on__in=tasklist).filter(task__order__gt=0).exclude(task__outcome = Task.OUTCOME_NA).select_related("task", "task__recipe", "task__build")) - return tp + tdeps = Task_Dependency.objects.filter(depends_on__in=tasklist).filter(task__order__gt=0) + tdeps = tdeps.exclude(task__outcome=Task.OUTCOME_NA).select_related("task", "task__recipe", "task__build") + + # exclude self-dependencies to prevent infinite dependency loop + # in generateCoveredList2() + tdeps = tdeps.exclude(task=F('depends_on')) + + return [tdep.task for tdep in tdeps] def _find_task_provider(task_object): task_revdeps = _find_task_revdep(task_object) @@ -976,396 +844,6 @@ def _find_task_provider(task_object): return trc return None -def tasks_common(request, build_id, variant, task_anchor): -# This class is shared between these pages -# -# Column tasks buildtime diskio cpuusage -# --------- ------ ---------- ------- --------- -# Cache def -# CPU min - -# Disk min - -# Executed def def def def -# Log -# Order def + -# Outcome def def def def -# Recipe min min min min -# Version -# Task min min min min -# Time min - -# -# 'min':on always, 'def':on by default, else hidden -# '+' default column sort up, '-' default column sort down - - anchor = request.GET.get('anchor', '') - if not anchor: - anchor=task_anchor - - # default ordering depends on variant - default_orderby = None - filter_search_display = 'tasks' - - if 'buildtime' == variant: - default_orderby = 'elapsed_time:-' - title_variant = 'Time' - object_search_display = 'time data' - elif 'diskio' == variant: - default_orderby = 'disk_io:-' - title_variant = 'Disk I/O' - object_search_display = 'disk I/O data' - elif 'cputime' == variant: - default_orderby = 'cpu_time_system:-' - title_variant='CPU time' - object_search_display = 'CPU time data' - else: - default_orderby = 'order:+' - title_variant = 'Tasks' - object_search_display = 'tasks' - - (pagesize, orderby) = _get_parameters_values(request, 25, default_orderby) - - mandatory_parameters = {'count': pagesize, 'page' : 1, 'orderby': orderby} - - template = 'tasks.html' - retval = _verify_parameters( request.GET, mandatory_parameters ) - if retval: - if task_anchor: - mandatory_parameters['anchor']=task_anchor - return _redirect_parameters( variant, request.GET, mandatory_parameters, build_id = build_id) - (filter_string, search_term, ordering_string) = _search_tuple(request, Task) - queryset_all = Task.objects.filter(build=build_id).exclude(order__isnull=True).exclude(outcome=Task.OUTCOME_NA) - queryset_all = queryset_all.select_related("recipe", "build") - - queryset_with_search = _get_queryset(Task, queryset_all, None , search_term, ordering_string, 'order') - - if ordering_string.startswith('outcome'): - queryset = _get_queryset(Task, queryset_all, filter_string, search_term, 'order:+', 'order') - queryset = sorted(queryset, key=lambda ur: (ur.outcome_text), reverse=ordering_string.endswith('-')) - elif ordering_string.startswith('sstate_result'): - queryset = _get_queryset(Task, queryset_all, filter_string, search_term, 'order:+', 'order') - queryset = sorted(queryset, key=lambda ur: (ur.sstate_text), reverse=ordering_string.endswith('-')) - else: - queryset = _get_queryset(Task, queryset_all, filter_string, search_term, ordering_string, 'order') - - - # compute the anchor's page - if anchor: - request.GET = request.GET.copy() - del request.GET['anchor'] - i=0 - a=int(anchor) - count_per_page=int(pagesize) - for task_object in queryset.iterator(): - if a == task_object.order: - new_page= (i / count_per_page ) + 1 - request.GET.__setitem__('page', new_page) - mandatory_parameters['page']=new_page - return _redirect_parameters( variant, request.GET, mandatory_parameters, build_id = build_id) - i += 1 - - task_objects = _build_page_range(Paginator(queryset, pagesize),request.GET.get('page', 1)) - - # define (and modify by variants) the 'tablecols' members - tc_order={ - 'name':'Order', - 'qhelp':'The running sequence of each task in the build', - 'clclass': 'order', 'hidden' : 1, - 'orderkey' : 'order', - 'orderfield':_get_toggle_order(request, "order"), - 'ordericon':_get_toggle_order_icon(request, "order")} - if 'tasks' == variant: - tc_order['hidden']='0' - del tc_order['clclass'] - - tc_recipe={ - 'name':'Recipe', - 'qhelp':'The name of the recipe to which each task applies', - 'orderkey' : 'recipe__name', - 'orderfield': _get_toggle_order(request, "recipe__name"), - 'ordericon':_get_toggle_order_icon(request, "recipe__name"), - } - tc_recipe_version={ - 'name':'Recipe version', - 'qhelp':'The version of the recipe to which each task applies', - 'clclass': 'recipe_version', 'hidden' : 1, - } - tc_task={ - 'name':'Task', - 'qhelp':'The name of the task', - 'orderfield': _get_toggle_order(request, "task_name"), - 'ordericon':_get_toggle_order_icon(request, "task_name"), - 'orderkey' : 'task_name', - } - tc_executed={ - 'name':'Executed', - 'qhelp':"This value tells you if a task had to run (executed) in order to generate the task output, or if the output was provided by another task and therefore the task didn't need to run (not executed)", - 'clclass': 'executed', 'hidden' : 0, - 'orderfield': _get_toggle_order(request, "task_executed"), - 'ordericon':_get_toggle_order_icon(request, "task_executed"), - 'orderkey' : 'task_executed', - 'filter' : { - 'class' : 'executed', - 'label': 'Show:', - 'options' : [ - ('Executed Tasks', 'task_executed:1', queryset_with_search.filter(task_executed=1).count()), - ('Not Executed Tasks', 'task_executed:0', queryset_with_search.filter(task_executed=0).count()), - ] - } - - } - tc_outcome={ - 'name':'Outcome', - 'qhelp':"This column tells you if 'executed' tasks succeeded or failed. The column also tells you why 'not executed' tasks did not need to run", - 'clclass': 'outcome', 'hidden' : 0, - 'orderfield': _get_toggle_order(request, "outcome"), - 'ordericon':_get_toggle_order_icon(request, "outcome"), - 'orderkey' : 'outcome', - 'filter' : { - 'class' : 'outcome', - 'label': 'Show:', - 'options' : [ - ('Succeeded Tasks', 'outcome:%d'%Task.OUTCOME_SUCCESS, queryset_with_search.filter(outcome=Task.OUTCOME_SUCCESS).count(), "'Succeeded' tasks are those that ran and completed during the build" ), - ('Failed Tasks', 'outcome:%d'%Task.OUTCOME_FAILED, queryset_with_search.filter(outcome=Task.OUTCOME_FAILED).count(), "'Failed' tasks are those that ran but did not complete during the build"), - ('Cached Tasks', 'outcome:%d'%Task.OUTCOME_CACHED, queryset_with_search.filter(outcome=Task.OUTCOME_CACHED).count(), 'Cached tasks restore output from the <code>sstate-cache</code> directory or mirrors'), - ('Prebuilt Tasks', 'outcome:%d'%Task.OUTCOME_PREBUILT, queryset_with_search.filter(outcome=Task.OUTCOME_PREBUILT).count(),'Prebuilt tasks didn\'t need to run because their output was reused from a previous build'), - ('Covered Tasks', 'outcome:%d'%Task.OUTCOME_COVERED, queryset_with_search.filter(outcome=Task.OUTCOME_COVERED).count(), 'Covered tasks didn\'t need to run because their output is provided by another task in this build'), - ('Empty Tasks', 'outcome:%d'%Task.OUTCOME_EMPTY, queryset_with_search.filter(outcome=Task.OUTCOME_EMPTY).count(), 'Empty tasks have no executable content'), - ] - } - - } - - tc_cache={ - 'name':'Cache attempt', - 'qhelp':'This column tells you if a task tried to restore output from the <code>sstate-cache</code> directory or mirrors, and reports the result: Succeeded, Failed or File not in cache', - 'clclass': 'cache_attempt', 'hidden' : 0, - 'orderfield': _get_toggle_order(request, "sstate_result"), - 'ordericon':_get_toggle_order_icon(request, "sstate_result"), - 'orderkey' : 'sstate_result', - 'filter' : { - 'class' : 'cache_attempt', - 'label': 'Show:', - 'options' : [ - ('Tasks with cache attempts', 'sstate_result__gt:%d'%Task.SSTATE_NA, queryset_with_search.filter(sstate_result__gt=Task.SSTATE_NA).count(), 'Show all tasks that tried to restore ouput from the <code>sstate-cache</code> directory or mirrors'), - ("Tasks with 'File not in cache' attempts", 'sstate_result:%d'%Task.SSTATE_MISS, queryset_with_search.filter(sstate_result=Task.SSTATE_MISS).count(), 'Show tasks that tried to restore output, but did not find it in the <code>sstate-cache</code> directory or mirrors'), - ("Tasks with 'Failed' cache attempts", 'sstate_result:%d'%Task.SSTATE_FAILED, queryset_with_search.filter(sstate_result=Task.SSTATE_FAILED).count(), 'Show tasks that found the required output in the <code>sstate-cache</code> directory or mirrors, but could not restore it'), - ("Tasks with 'Succeeded' cache attempts", 'sstate_result:%d'%Task.SSTATE_RESTORED, queryset_with_search.filter(sstate_result=Task.SSTATE_RESTORED).count(), 'Show tasks that successfully restored the required output from the <code>sstate-cache</code> directory or mirrors'), - ] - } - - } - #if 'tasks' == variant: tc_cache['hidden']='0'; - tc_time={ - 'name':'Time (secs)', - 'qhelp':'How long it took the task to finish in seconds', - 'orderfield': _get_toggle_order(request, "elapsed_time", True), - 'ordericon':_get_toggle_order_icon(request, "elapsed_time"), - 'orderkey' : 'elapsed_time', - 'clclass': 'time_taken', 'hidden' : 1, - } - if 'buildtime' == variant: - tc_time['hidden']='0' - del tc_time['clclass'] - tc_cache['hidden']='1' - - tc_cpu_time_system={ - 'name':'System CPU time (secs)', - 'qhelp':'Total amount of time spent executing in kernel mode, in ' + - 'seconds. Note that this time can be greater than the task ' + - 'time due to parallel execution.', - 'orderfield': _get_toggle_order(request, "cpu_time_system", True), - 'ordericon':_get_toggle_order_icon(request, "cpu_time_system"), - 'orderkey' : 'cpu_time_system', - 'clclass': 'cpu_time_system', 'hidden' : 1, - } - - tc_cpu_time_user={ - 'name':'User CPU time (secs)', - 'qhelp':'Total amount of time spent executing in user mode, in seconds. ' + - 'Note that this time can be greater than the task time due to ' + - 'parallel execution.', - 'orderfield': _get_toggle_order(request, "cpu_time_user", True), - 'ordericon':_get_toggle_order_icon(request, "cpu_time_user"), - 'orderkey' : 'cpu_time_user', - 'clclass': 'cpu_time_user', 'hidden' : 1, - } - - if 'cputime' == variant: - tc_cpu_time_system['hidden']='0' - tc_cpu_time_user['hidden']='0' - del tc_cpu_time_system['clclass'] - del tc_cpu_time_user['clclass'] - tc_cache['hidden']='1' - - tc_diskio={ - 'name':'Disk I/O (bytes)', - 'qhelp':'Number of bytes written to and read from the disk during the task', - 'orderfield': _get_toggle_order(request, "disk_io", True), - 'ordericon':_get_toggle_order_icon(request, "disk_io"), - 'orderkey' : 'disk_io', - 'clclass': 'disk_io', 'hidden' : 1, - } - if 'diskio' == variant: - tc_diskio['hidden']='0' - del tc_diskio['clclass'] - tc_cache['hidden']='1' - - build = Build.objects.get(pk=build_id) - - context = { 'objectname': variant, - 'object_search_display': object_search_display, - 'filter_search_display': filter_search_display, - 'mainheading': title_variant, - 'build': build, - 'project': build.project, - 'objects': task_objects, - 'default_orderby' : default_orderby, - 'search_term': search_term, - 'total_count': queryset_with_search.count(), - 'tablecols':[ - tc_order, - tc_recipe, - tc_recipe_version, - tc_task, - tc_executed, - tc_outcome, - tc_cache, - tc_time, - tc_cpu_time_system, - tc_cpu_time_user, - tc_diskio, - ]} - - - response = render(request, template, context) - _set_parameters_values(pagesize, orderby, request) - return response - -def tasks(request, build_id): - return tasks_common(request, build_id, 'tasks', '') - -def tasks_task(request, build_id, task_id): - return tasks_common(request, build_id, 'tasks', task_id) - -def buildtime(request, build_id): - return tasks_common(request, build_id, 'buildtime', '') - -def diskio(request, build_id): - return tasks_common(request, build_id, 'diskio', '') - -def cputime(request, build_id): - return tasks_common(request, build_id, 'cputime', '') - -def recipes(request, build_id): - template = 'recipes.html' - (pagesize, orderby) = _get_parameters_values(request, 100, 'name:+') - mandatory_parameters = { 'count': pagesize, 'page' : 1, 'orderby' : orderby } - retval = _verify_parameters( request.GET, mandatory_parameters ) - if retval: - return _redirect_parameters( 'recipes', request.GET, mandatory_parameters, build_id = build_id) - (filter_string, search_term, ordering_string) = _search_tuple(request, Recipe) - - build = Build.objects.get(pk=build_id) - - queryset = build.get_recipes() - queryset = _get_queryset(Recipe, queryset, filter_string, search_term, ordering_string, 'name') - - recipes = _build_page_range(Paginator(queryset, pagesize),request.GET.get('page', 1)) - - # prefetch the forward and reverse recipe dependencies - deps = { } - revs = { } - queryset_dependency=Recipe_Dependency.objects.filter(recipe__layer_version__build_id = build_id).select_related("depends_on", "recipe") - for recipe in recipes: - deplist = [ ] - for recipe_dep in [x for x in queryset_dependency if x.recipe_id == recipe.id]: - deplist.append(recipe_dep) - deps[recipe.id] = deplist - revlist = [ ] - for recipe_dep in [x for x in queryset_dependency if x.depends_on_id == recipe.id]: - revlist.append(recipe_dep) - revs[recipe.id] = revlist - - context = { - 'objectname': 'recipes', - 'build': build, - 'project': build.project, - 'objects': recipes, - 'default_orderby' : 'name:+', - 'recipe_deps' : deps, - 'recipe_revs' : revs, - 'tablecols':[ - { - 'name':'Recipe', - 'qhelp':'Information about a single piece of software, including where to download the source, configuration options, how to compile the source files and how to package the compiled output', - 'orderfield': _get_toggle_order(request, "name"), - 'ordericon':_get_toggle_order_icon(request, "name"), - }, - { - 'name':'Recipe version', - 'qhelp':'The recipe version and revision', - }, - { - 'name':'Dependencies', - 'qhelp':'Recipe build-time dependencies (i.e. other recipes)', - 'clclass': 'depends_on', 'hidden': 1, - }, - { - 'name':'Reverse dependencies', - 'qhelp':'Recipe build-time reverse dependencies (i.e. the recipes that depend on this recipe)', - 'clclass': 'depends_by', 'hidden': 1, - }, - { - 'name':'Recipe file', - 'qhelp':'Path to the recipe .bb file', - 'orderfield': _get_toggle_order(request, "file_path"), - 'ordericon':_get_toggle_order_icon(request, "file_path"), - 'orderkey' : 'file_path', - 'clclass': 'recipe_file', 'hidden': 0, - }, - { - 'name':'Section', - 'qhelp':'The section in which recipes should be categorized', - 'orderfield': _get_toggle_order(request, "section"), - 'ordericon':_get_toggle_order_icon(request, "section"), - 'orderkey' : 'section', - 'clclass': 'recipe_section', 'hidden': 0, - }, - { - 'name':'License', - 'qhelp':'The list of source licenses for the recipe. Multiple license names separated by the pipe character indicates a choice between licenses. Multiple license names separated by the ampersand character indicates multiple licenses exist that cover different parts of the source', - 'orderfield': _get_toggle_order(request, "license"), - 'ordericon':_get_toggle_order_icon(request, "license"), - 'orderkey' : 'license', - 'clclass': 'recipe_license', 'hidden': 0, - }, - { - 'name':'Layer', - 'qhelp':'The name of the layer providing the recipe', - 'orderfield': _get_toggle_order(request, "layer_version__layer__name"), - 'ordericon':_get_toggle_order_icon(request, "layer_version__layer__name"), - 'orderkey' : 'layer_version__layer__name', - 'clclass': 'layer_version__layer__name', 'hidden': 0, - }, - { - 'name':'Layer branch', - 'qhelp':'The Git branch of the layer providing the recipe', - 'orderfield': _get_toggle_order(request, "layer_version__branch"), - 'ordericon':_get_toggle_order_icon(request, "layer_version__branch"), - 'orderkey' : 'layer_version__branch', - 'clclass': 'layer_version__branch', 'hidden': 1, - }, - { - 'name':'Layer commit', - 'qhelp':'The Git commit of the layer providing the recipe', - 'clclass': 'layer_version__layer__commit', 'hidden': 1, - }, - ] - } - - response = render(request, template, context) - _set_parameters_values(pagesize, orderby, request) - return response - def configuration(request, build_id): template = 'configuration.html' @@ -1437,7 +915,6 @@ def configvars(request, build_id): }, {'name': 'Value', 'qhelp': "The value assigned to the variable", - 'dclass': "span4", }, {'name': 'Set in file', 'qhelp': "The last configuration file that touched the variable value", @@ -1474,96 +951,6 @@ def configvars(request, build_id): _set_parameters_values(pagesize, orderby, request) return response -def bpackage(request, build_id): - template = 'bpackage.html' - (pagesize, orderby) = _get_parameters_values(request, 100, 'name:+') - mandatory_parameters = { 'count' : pagesize, 'page' : 1, 'orderby' : orderby } - retval = _verify_parameters( request.GET, mandatory_parameters ) - if retval: - return _redirect_parameters( 'packages', request.GET, mandatory_parameters, build_id = build_id) - (filter_string, search_term, ordering_string) = _search_tuple(request, Package) - queryset = Package.objects.filter(build = build_id).filter(size__gte=0) - queryset = _get_queryset(Package, queryset, filter_string, search_term, ordering_string, 'name') - - packages = _build_page_range(Paginator(queryset, pagesize),request.GET.get('page', 1)) - - build = Build.objects.get( pk = build_id ) - - context = { - 'objectname': 'packages built', - 'build': build, - 'project': build.project, - 'objects' : packages, - 'default_orderby' : 'name:+', - 'tablecols':[ - { - 'name':'Package', - 'qhelp':'Packaged output resulting from building a recipe', - 'orderfield': _get_toggle_order(request, "name"), - 'ordericon':_get_toggle_order_icon(request, "name"), - }, - { - 'name':'Package version', - 'qhelp':'The package version and revision', - }, - { - 'name':'Size', - 'qhelp':'The size of the package', - 'orderfield': _get_toggle_order(request, "size", True), - 'ordericon':_get_toggle_order_icon(request, "size"), - 'orderkey' : 'size', - 'clclass': 'size', 'hidden': 0, - 'dclass' : 'span2', - }, - { - 'name':'License', - 'qhelp':'The license under which the package is distributed. Multiple license names separated by the pipe character indicates a choice between licenses. Multiple license names separated by the ampersand character indicates multiple licenses exist that cover different parts of the source', - 'orderfield': _get_toggle_order(request, "license"), - 'ordericon':_get_toggle_order_icon(request, "license"), - 'orderkey' : 'license', - 'clclass': 'license', 'hidden': 1, - }, - { - 'name':'Recipe', - 'qhelp':'The name of the recipe building the package', - 'orderfield': _get_toggle_order(request, "recipe__name"), - 'ordericon':_get_toggle_order_icon(request, "recipe__name"), - 'orderkey' : 'recipe__name', - 'clclass': 'recipe__name', 'hidden': 0, - }, - { - 'name':'Recipe version', - 'qhelp':'Version and revision of the recipe building the package', - 'clclass': 'recipe__version', 'hidden': 1, - }, - { - 'name':'Layer', - 'qhelp':'The name of the layer providing the recipe that builds the package', - 'orderfield': _get_toggle_order(request, "recipe__layer_version__layer__name"), - 'ordericon':_get_toggle_order_icon(request, "recipe__layer_version__layer__name"), - 'orderkey' : 'recipe__layer_version__layer__name', - 'clclass': 'recipe__layer_version__layer__name', 'hidden': 1, - }, - { - 'name':'Layer branch', - 'qhelp':'The Git branch of the layer providing the recipe that builds the package', - 'orderfield': _get_toggle_order(request, "recipe__layer_version__branch"), - 'ordericon':_get_toggle_order_icon(request, "recipe__layer_version__branch"), - 'orderkey' : 'recipe__layer_version__branch', - 'clclass': 'recipe__layer_version__branch', 'hidden': 1, - }, - { - 'name':'Layer commit', - 'qhelp':'The Git commit of the layer providing the recipe that builds the package', - 'clclass': 'recipe__layer_version__layer__commit', 'hidden': 1, - }, - ] - } - - response = render(request, template, context) - _set_parameters_values(pagesize, orderby, request) - return response - def bfile(request, build_id, package_id): template = 'bfile.html' files = Package_File.objects.filter(package = package_id) @@ -1905,7 +1292,7 @@ if True: from django.contrib.auth import authenticate, login from django.contrib.auth.decorators import login_required - from orm.models import Branch, LayerSource, ToasterSetting, Release, Machine, LayerVersionDependency + from orm.models import LayerSource, ToasterSetting, Release, Machine, LayerVersionDependency from bldcontrol.models import BuildRequest import traceback @@ -1938,10 +1325,10 @@ if True: if ptype == "build": mandatory_fields.append('projectversion') # make sure we have values for all mandatory_fields - if reduce( lambda x, y: x or y, map(lambda x: len(request.POST.get(x, '')) == 0, mandatory_fields)): - # set alert for missing fields - raise BadParameterException("Fields missing: " + - ", ".join([x for x in mandatory_fields if len(request.POST.get(x, '')) == 0 ])) + missing = [field for field in mandatory_fields if len(request.POST.get(field, '')) == 0] + if missing: + # set alert for missing fields + raise BadParameterException("Fields missing: %s" % ", ".join(missing)) if not request.user.is_authenticated(): user = authenticate(username = request.POST.get('username', '_anonuser'), password = 'nopass') @@ -1964,7 +1351,8 @@ if True: except (IntegrityError, BadParameterException) as e: # fill in page with previously submitted values - map(lambda x: context.__setitem__(x, request.POST.get(x, "-- missing")), mandatory_fields) + for field in mandatory_fields: + context.__setitem__(field, request.POST.get(field, "-- missing")) if isinstance(e, IntegrityError) and "username" in str(e): context['alert'] = "Your chosen username is already used" else: @@ -1973,128 +1361,11 @@ if True: raise Exception("Invalid HTTP method for this page") - - # Shows the edit project page - @_template_renderer('project.html') def project(request, pid): - prj = Project.objects.get(id = pid) - - try: - puser = User.objects.get(id = prj.user_id) - except User.DoesNotExist: - puser = None - - # execute POST requests - if request.method == "POST": - # add layers - if 'layerAdd' in request.POST and len(request.POST['layerAdd']) > 0: - for lc in Layer_Version.objects.filter(pk__in=[i for i in request.POST['layerAdd'].split(",") if len(i) > 0]): - ProjectLayer.objects.get_or_create(project = prj, layercommit = lc) - - # remove layers - if 'layerDel' in request.POST and len(request.POST['layerDel']) > 0: - for t in request.POST['layerDel'].strip().split(" "): - pt = ProjectLayer.objects.filter(project = prj, layercommit_id = int(t)).delete() - - if 'projectName' in request.POST: - prj.name = request.POST['projectName'] - prj.save(); - - if 'projectVersion' in request.POST: - # If the release is the current project then return now - if prj.release.pk == int(request.POST.get('projectVersion',-1)): - return {} - - prj.release = Release.objects.get(pk = request.POST['projectVersion']) - # we need to change the bitbake version - prj.bitbake_version = prj.release.bitbake_version - prj.save() - # we need to change the layers - for project in prj.projectlayer_set.all(): - # find and add a similarly-named layer on the new branch - try: - layer_versions = prj.get_all_compatible_layer_versions() - layer_versions = layer_versions.filter(layer__name = project.layercommit.layer.name) - ProjectLayer.objects.get_or_create(project = prj, layercommit = layer_versions.first()) - except IndexError: - pass - finally: - # get rid of the old entry - project.delete() - - if 'machineName' in request.POST: - machinevar = prj.projectvariable_set.get(name="MACHINE") - machinevar.value=request.POST['machineName'] - machinevar.save() - - - # we use implicit knowledge of the current user's project to filter layer information, e.g. - pid = prj.id - - from collections import Counter - freqtargets = [] - try: - freqtargets += map(lambda x: x.target, reduce(lambda x, y: x + y, map(lambda x: list(x.target_set.all()), Build.objects.filter(project = prj, outcome__lt = Build.IN_PROGRESS)))) - freqtargets += map(lambda x: x.target, reduce(lambda x, y: x + y, map(lambda x: list(x.brtarget_set.all()), BuildRequest.objects.filter(project = prj, state = BuildRequest.REQ_FAILED)))) - except TypeError: - pass - freqtargets = Counter(freqtargets) - freqtargets = sorted(freqtargets, key = lambda x: freqtargets[x], reverse=True) - - context = { - "project" : prj, - "lvs_nos" : Layer_Version.objects.all().count(), - "completedbuilds": Build.objects.exclude(outcome = Build.IN_PROGRESS).filter(project_id = pid), - "prj" : {"name": prj.name, }, - "buildrequests" : prj.build_set.filter(outcome=Build.IN_PROGRESS), - "builds" : Build.get_recent(prj), - "layers" : map(lambda x: { - "id": x.layercommit.pk, - "orderid": x.pk, - "name" : x.layercommit.layer.name, - "vcs_url": x.layercommit.layer.vcs_url, - "vcs_reference" : x.layercommit.get_vcs_reference(), - "url": x.layercommit.layer.layer_index_url, - "layerdetailurl": x.layercommit.get_detailspage_url(prj.pk), - # This branch name is actually the release - "branch" : { "name" : x.layercommit.get_vcs_reference(), "layersource" : x.layercommit.up_branch.layer_source.name if x.layercommit.up_branch != None else None}}, - prj.projectlayer_set.all().order_by("id")), - "targets" : map(lambda x: {"target" : x.target, "task" : x.task, "pk": x.pk}, prj.projecttarget_set.all()), - "variables": map(lambda x: (x.name, x.value), prj.projectvariable_set.all()), - "freqtargets": freqtargets[:5], - "releases": map(lambda x: {"id": x.pk, "name": x.name, "description":x.description}, Release.objects.all()), - "project_html": 1, - "recipesTypeAheadUrl": reverse('xhr_recipestypeahead', args=(prj.pk,)), - "projectBuildsUrl": reverse('projectbuilds', args=(prj.pk,)), - } - - if prj.release is not None: - context['release'] = { "id": prj.release.pk, "name": prj.release.name, "description": prj.release.description} - - - try: - context["machine"] = {"name": prj.projectvariable_set.get(name="MACHINE").value} - except ProjectVariable.DoesNotExist: - context["machine"] = None - try: - context["distro"] = prj.projectvariable_set.get(name="DISTRO").value - except ProjectVariable.DoesNotExist: - context["distro"] = "-- not set yet" - - return context - - def xhr_response(fun): - """ - Decorator for REST methods. - calls jsonfilter on the returned dictionary and returns result - as HttpResponse object of content_type application/json - """ - @wraps(fun) - def wrapper(*args, **kwds): - return HttpResponse(jsonfilter(fun(*args, **kwds)), - content_type="application/json") - return wrapper + project = Project.objects.get(pk=pid) + context = {"project": project} + return render(request, "project.html", context) def jsunittests(request): """ Provides a page for the js unit tests """ @@ -2154,8 +1425,7 @@ if True: retval.append(project) return response({"error":"ok", - "rows" : map( _lv_to_dict(prj), - map(lambda x: x.layercommit, retval )) + "rows": [_lv_to_dict(prj) for y in [x.layercommit for x in retval]] }) except Exception as e: @@ -2164,11 +1434,16 @@ if True: def xhr_configvaredit(request, pid): try: prj = Project.objects.get(id = pid) + # There are cases where user can add variables which hold values + # like http://, file:/// etc. In such case a simple split(":") + # would fail. One example is SSTATE_MIRRORS variable. So we use + # max_split var to handle them. + max_split = 1 # add conf variables if 'configvarAdd' in request.POST: t=request.POST['configvarAdd'].strip() if ":" in t: - variable, value = t.split(":") + variable, value = t.split(":", max_split) else: variable = t value = "" @@ -2178,7 +1453,7 @@ if True: if 'configvarChange' in request.POST: t=request.POST['configvarChange'].strip() if ":" in t: - variable, value = t.split(":") + variable, value = t.split(":", max_split) else: variable = t value = "" @@ -2201,7 +1476,7 @@ if True: return_data = { "error": "ok", - 'configvars' : map(lambda x: (x.name, x.value, x.pk), configvars_query), + 'configvars': [(x.name, x.value, x.pk) for x in configvars_query] } try: return_data['distro'] = ProjectVariable.objects.get(project = prj, name = "DISTRO").value, @@ -2235,10 +1510,10 @@ if True: def xhr_importlayer(request): - if (not request.POST.has_key('vcs_url') or - not request.POST.has_key('name') or - not request.POST.has_key('git_ref') or - not request.POST.has_key('project_id')): + if ('vcs_url' not in request.POST or + 'name' not in request.POST or + 'git_ref' not in request.POST or + 'project_id' not in request.POST): return HttpResponse(jsonfilter({"error": "Missing parameters; requires vcs_url, name, git_ref and project_id"}), content_type = "application/json") layers_added = []; @@ -2253,18 +1528,12 @@ if True: prj = Project.objects.get(pk=request.POST['project_id']) # Strip trailing/leading whitespace from all values - # put into a new dict because POST one is immutable + # put into a new dict because POST one is immutable. post_data = dict() - for key,val in request.POST.iteritems(): + for key,val in request.POST.items(): post_data[key] = val.strip() - # We need to know what release the current project is so that we - # can set the imported layer's up_branch_id - prj_branch_name = Release.objects.get(pk=prj.release_id).branch_name - up_branch, branch_created = Branch.objects.get_or_create(name=prj_branch_name, layer_source_id=LayerSource.TYPE_IMPORTED) - - layer_source = LayerSource.objects.get(sourcetype=LayerSource.TYPE_IMPORTED) try: layer, layer_created = Layer.objects.get_or_create(name=post_data['name']) except MultipleObjectsReturned: @@ -2272,8 +1541,8 @@ if True: if layer: if layer_created: - layer.layer_source = layer_source - layer.vcs_url = post_data['vcs_url'] + layer.vcs_url = post_data.get('vcs_url') + layer.local_source_dir = post_data.get('local_source_dir') layer.up_date = timezone.now() layer.save() else: @@ -2283,18 +1552,30 @@ if True: if layer.vcs_url != post_data['vcs_url']: return HttpResponse(jsonfilter({"error": "hint-layer-exists-with-different-url" , "current_url" : layer.vcs_url, "current_id": layer.id }), content_type = "application/json") - - layer_version, version_created = Layer_Version.objects.get_or_create(layer_source=layer_source, layer=layer, project=prj, up_branch_id=up_branch.id,branch=post_data['git_ref'], commit=post_data['git_ref'], dirpath=post_data['dir_path']) + layer_version, version_created = \ + Layer_Version.objects.get_or_create( + layer_source=LayerSource.TYPE_IMPORTED, + layer=layer, project=prj, + release=prj.release, + branch=post_data['git_ref'], + commit=post_data['git_ref'], + dirpath=post_data['dir_path']) if layer_version: if not version_created: - return HttpResponse(jsonfilter({"error": "hint-layer-version-exists", "existing_layer_version": layer_version.id }), content_type = "application/json") + return HttpResponse(jsonfilter({"error": + "hint-layer-version-exists", + "existing_layer_version": + layer_version.id }), + content_type = "application/json") + + layer_version.layer_source = LayerSource.TYPE_IMPORTED layer_version.up_date = timezone.now() layer_version.save() # Add the dependencies specified for this new layer - if (post_data.has_key("layer_deps") and + if ('layer_deps' in post_data and version_created and len(post_data["layer_deps"]) > 0): for layer_dep_id in post_data["layer_deps"].split(","): @@ -2343,231 +1624,6 @@ if True: return HttpResponse(jsonfilter(json_response), content_type = "application/json") - def xhr_updatelayer(request): - - def error_response(error): - return HttpResponse(jsonfilter({"error": error}), content_type = "application/json") - - if not request.POST.has_key("layer_version_id"): - return error_response("Please specify a layer version id") - try: - layer_version_id = request.POST["layer_version_id"] - layer_version = Layer_Version.objects.get(id=layer_version_id) - except Layer_Version.DoesNotExist: - return error_response("Cannot find layer to update") - - - if request.POST.has_key("vcs_url"): - layer_version.layer.vcs_url = request.POST["vcs_url"] - if request.POST.has_key("dirpath"): - layer_version.dirpath = request.POST["dirpath"] - if request.POST.has_key("commit"): - layer_version.commit = request.POST["commit"] - if request.POST.has_key("up_branch"): - layer_version.up_branch_id = int(request.POST["up_branch"]) - - if request.POST.has_key("add_dep"): - lvd = LayerVersionDependency(layer_version=layer_version, depends_on_id=request.POST["add_dep"]) - lvd.save() - - if request.POST.has_key("rm_dep"): - rm_dep = LayerVersionDependency.objects.get(layer_version=layer_version, depends_on_id=request.POST["rm_dep"]) - rm_dep.delete() - - if request.POST.has_key("summary"): - layer_version.layer.summary = request.POST["summary"] - if request.POST.has_key("description"): - layer_version.layer.description = request.POST["description"] - - try: - layer_version.layer.save() - layer_version.save() - except Exception as e: - return error_response("Could not update layer version entry: %s" % e) - - return HttpResponse(jsonfilter({"error": "ok",}), content_type = "application/json") - - @xhr_response - def xhr_customrecipe(request): - """ - Custom image recipe REST API - - Entry point: /xhr_customrecipe/ - Method: POST - - Args: - name: name of custom recipe to create - project: target project id of orm.models.Project - base: base recipe id of orm.models.Recipe - - Returns: - {"error": "ok", - "url": <url of the created recipe>} - or - {"error": <error message>} - """ - # check if request has all required parameters - for param in ('name', 'project', 'base'): - if param not in request.POST: - return {"error": "Missing parameter '%s'" % param} - - # get project and baserecipe objects - params = {} - for name, model in [("project", Project), - ("base", Recipe)]: - value = request.POST[name] - try: - params[name] = model.objects.get(id=value) - except model.DoesNotExist: - return {"error": "Invalid %s id %s" % (name, value)} - - # create custom recipe - try: - - # Only allowed chars in name are a-z, 0-9 and - - if re.search(r'[^a-z|0-9|-]', request.POST["name"]): - return {"error": "invalid-name"} - - custom_images = CustomImageRecipe.objects.all() - - # Are there any recipes with this name already in our project? - existing_image_recipes_in_project = custom_images.filter( - name=request.POST["name"], project=params["project"]) - - if existing_image_recipes_in_project.count() > 0: - return {"error": "image-already-exists"} - - # Are there any recipes with this name which aren't custom - # image recipes? - custom_image_ids = custom_images.values_list('id', flat=True) - existing_non_image_recipes = Recipe.objects.filter( - Q(name=request.POST["name"]) & ~Q(pk__in=custom_image_ids) - ) - - if existing_non_image_recipes.count() > 0: - return {"error": "recipe-already-exists"} - - # create layer 'Custom layer' and verion if needed - layer = Layer.objects.get_or_create( - name=CustomImageRecipe.LAYER_NAME, - summary="Layer for custom recipes", - vcs_url="file:///toaster_created_layer")[0] - - # Check if we have a layer version already - # We don't use get_or_create here because the dirpath will change - # and is a required field - lver = Layer_Version.objects.filter(Q(project=params['project']) & - Q(layer=layer) & - Q(build=None)).last() - if lver == None: - lver, created = Layer_Version.objects.get_or_create( - project=params['project'], - layer=layer, - dirpath="toaster_created_layer") - - # Add a dependency on our layer to the base recipe's layer - LayerVersionDependency.objects.get_or_create( - layer_version=lver, - depends_on=params["base"].layer_version) - - # Add it to our current project if needed - ProjectLayer.objects.get_or_create(project=params['project'], - layercommit=lver, - optional=False) - - # Create the actual recipe - recipe, created = CustomImageRecipe.objects.get_or_create( - name=request.POST["name"], - base_recipe=params["base"], - project=params["project"], - layer_version=lver, - is_image=True) - - # If we created the object then setup these fields. They may get - # overwritten later on and cause the get_or_create to create a - # duplicate if they've changed. - if created: - recipe.file_path = request.POST["name"] - recipe.license = "MIT" - recipe.version = "0.1" - recipe.save() - - except Error as err: - return {"error": "Can't create custom recipe: %s" % err} - - # Find the package list from the last build of this recipe/target - target = Target.objects.filter(Q(build__outcome=Build.SUCCEEDED) & - Q(build__project=params['project']) & - (Q(target=params['base'].name) | - Q(target=recipe.name))).last() - if target: - # Copy in every package - # We don't want these packages to be linked to anything because - # that underlying data may change e.g. delete a build - for tpackage in target.target_installed_package_set.all(): - try: - built_package = tpackage.package - # The package had no recipe information so is a ghost - # package skip it - if built_package.recipe == None: - continue; - - config_package = CustomImagePackage.objects.get( - name=built_package.name) - - recipe.includes_set.add(config_package) - except Exception as e: - logger.warning("Error adding package %s %s" % - (tpackage.package.name, e)) - pass - - return {"error": "ok", - "packages" : recipe.get_all_packages().count(), - "url": reverse('customrecipe', args=(params['project'].pk, - recipe.id))} - - @xhr_response - def xhr_customrecipe_id(request, recipe_id): - """ - Set of ReST API processors working with recipe id. - - Entry point: /xhr_customrecipe/<recipe_id> - - Methods: - GET - Get details of custom image recipe - DELETE - Delete custom image recipe - - Returns: - GET: - {"error": "ok", - "info": dictionary of field name -> value pairs - of the CustomImageRecipe model} - DELETE: - {"error": "ok"} - or - {"error": <error message>} - """ - try: - custom_recipe = CustomImageRecipe.objects.get(id=recipe_id) - except CustomImageRecipe.DoesNotExist: - return {"error": "Custom recipe with id=%s " - "not found" % recipe_id} - - if request.method == 'GET': - info = {"id" : custom_recipe.id, - "name" : custom_recipe.name, - "base_recipe_id": custom_recipe.base_recipe.id, - "project_id": custom_recipe.project.id, - } - - return {"error": "ok", "info": info} - - elif request.method == 'DELETE': - custom_recipe.delete() - return {"error": "ok"} - else: - return {"error": "Method %s is not supported" % request.method} - def customrecipe_download(request, pid, recipe_id): recipe = get_object_or_404(CustomImageRecipe, pk=recipe_id) @@ -2580,230 +1636,6 @@ if True: return response - def _traverse_dependents(next_package_id, rev_deps, all_current_packages, tree_level=0): - """ - Recurse through reverse dependency tree for next_package_id. - Limit the reverse dependency search to packages not already scanned, - that is, not already in rev_deps. - Limit the scan to a depth (tree_level) not exceeding the count of - all packages in the custom image, and if that depth is exceeded - return False, pop out of the recursion, and write a warning - to the log, but this is unlikely, suggesting a dependency loop - not caught by bitbake. - On return, the input/output arg rev_deps is appended with queryset - dictionary elements, annotated for use in the customimage template. - The list has unsorted, but unique elements. - """ - max_dependency_tree_depth = all_current_packages.count() - if tree_level >= max_dependency_tree_depth: - logger.warning( - "The number of reverse dependencies " - "for this package exceeds " + max_dependency_tree_depth + - " and the remaining reverse dependencies will not be removed") - return True - - package = CustomImagePackage.objects.get(id=next_package_id) - dependents = \ - package.package_dependencies_target.annotate( - name=F('package__name'), - pk=F('package__pk'), - size=F('package__size'), - ).values("name", "pk", "size").exclude( - ~Q(pk__in=all_current_packages) - ) - - for pkg in dependents: - if pkg in rev_deps: - # already seen, skip dependent search - continue - - rev_deps.append(pkg) - if (_traverse_dependents( - pkg["pk"], rev_deps, all_current_packages, tree_level+1)): - return True - - return False - - def _get_all_dependents(package_id, all_current_packages): - """ - Returns sorted list of recursive reverse dependencies for package_id, - as a list of dictionary items, by recursing through dependency - relationships. - """ - rev_deps = [] - _traverse_dependents(package_id, rev_deps, all_current_packages) - rev_deps = sorted(rev_deps, key=lambda x: x["name"]) - return rev_deps - - @xhr_response - def xhr_customrecipe_packages(request, recipe_id, package_id): - """ - ReST API to add/remove packages to/from custom recipe. - - Entry point: /xhr_customrecipe/<recipe_id>/packages/<package_id> - - Methods: - PUT - Add package to the recipe - DELETE - Delete package from the recipe - GET - Get package information - - Returns: - {"error": "ok"} - or - {"error": <error message>} - """ - try: - recipe = CustomImageRecipe.objects.get(id=recipe_id) - except CustomImageRecipe.DoesNotExist: - return {"error": "Custom recipe with id=%s " - "not found" % recipe_id} - - if package_id: - try: - package = CustomImagePackage.objects.get(id=package_id) - except Package.DoesNotExist: - return {"error": "Package with id=%s " - "not found" % package_id} - - if request.method == 'GET': - # If no package_id then list the current packages - if not package_id: - total_size = 0 - packages = recipe.get_all_packages().values("id", - "name", - "version", - "size") - for package in packages: - package['size_formatted'] = \ - filtered_filesizeformat(package['size']) - total_size += package['size'] - - return {"error": "ok", - "packages" : list(packages), - "total" : len(packages), - "total_size" : total_size, - "total_size_formatted" : - filtered_filesizeformat(total_size) - } - else: - all_current_packages = recipe.get_all_packages() - - # Dependencies for package which aren't satisfied by the - # current packages in the custom image recipe - deps = package.package_dependencies_source.annotate( - name=F('depends_on__name'), - pk=F('depends_on__pk'), - size=F('depends_on__size'), - ).values("name", "pk", "size").filter( - # There are two depends types we don't know why - (Q(dep_type=Package_Dependency.TYPE_TRDEPENDS) | - Q(dep_type=Package_Dependency.TYPE_RDEPENDS)) & - ~Q(pk__in=all_current_packages) - ) - - # Reverse dependencies which are needed by packages that are - # in the image. Recursive search providing all dependents, - # not just immediate dependents. - reverse_deps = _get_all_dependents(package_id, all_current_packages) - total_size_deps = 0 - total_size_reverse_deps = 0 - - for dep in deps: - dep['size_formatted'] = \ - filtered_filesizeformat(dep['size']) - total_size_deps += dep['size'] - - for dep in reverse_deps: - dep['size_formatted'] = \ - filtered_filesizeformat(dep['size']) - total_size_reverse_deps += dep['size'] - - - return {"error": "ok", - "id": package.pk, - "name": package.name, - "version": package.version, - "unsatisfied_dependencies": list(deps), - "unsatisfied_dependencies_size": total_size_deps, - "unsatisfied_dependencies_size_formatted": - filtered_filesizeformat(total_size_deps), - "reverse_dependencies": list(reverse_deps), - "reverse_dependencies_size": total_size_reverse_deps, - "reverse_dependencies_size_formatted": - filtered_filesizeformat(total_size_reverse_deps)} - - included_packages = recipe.includes_set.values_list('pk', flat=True) - - if request.method == 'PUT': - # If we're adding back a package which used to be included in this - # image all we need to do is remove it from the excludes - if package.pk in included_packages: - try: - recipe.excludes_set.remove(package) - return {"error": "ok"} - except Package.DoesNotExist: - return {"error": - "Package %s not found in excludes but was in " - "included list" % package.name} - - else: - recipe.appends_set.add(package) - # Make sure that package is not in the excludes set - try: - recipe.excludes_set.remove(package) - except: - pass - # Add the dependencies we think will be added to the recipe - # as a result of appending this package. - # TODO this should recurse down the entire deps tree - for dep in package.package_dependencies_source.all_depends(): - try: - cust_package = CustomImagePackage.objects.get( - name=dep.depends_on.name) - - recipe.includes_set.add(cust_package) - try: - # When adding the pre-requisite package, make - # sure it's not in the excluded list from a - # prior removal. - recipe.excludes_set.remove(cust_package) - except Package.DoesNotExist: - # Don't care if the package had never been excluded - pass - except: - logger.warning("Could not add package's suggested" - "dependencies to the list") - - return {"error": "ok"} - - elif request.method == 'DELETE': - try: - # If we're deleting a package which is included we need to - # Add it to the excludes list. - if package.pk in included_packages: - recipe.excludes_set.add(package) - else: - recipe.appends_set.remove(package) - all_current_packages = recipe.get_all_packages() - reverse_deps_dictlist = _get_all_dependents(package.pk, all_current_packages) - ids = [entry['pk'] for entry in reverse_deps_dictlist] - reverse_deps = CustomImagePackage.objects.filter(id__in=ids) - for r in reverse_deps: - try: - if r.id in included_packages: - recipe.excludes_set.add(r) - else: - recipe.appends_set.remove(r) - except: - pass - - return {"error": "ok"} - except CustomImageRecipe.DoesNotExist: - return {"error": "Tried to remove package that wasn't present"} - - else: - return {"error": "Method %s is not supported" % request.method} - def importlayer(request, pid): template = "importlayer.html" context = { @@ -2811,20 +1643,33 @@ if True: } return render(request, template, context) + # TODO merge with api pseudo api here is used for deps modal @_template_renderer('layerdetails.html') def layerdetails(request, pid, layerid): project = Project.objects.get(pk=pid) layer_version = Layer_Version.objects.get(pk=layerid) - context = {'project' : project, - 'layerversion' : layer_version, - 'layerdeps' : {"list": [{"id": dep.id, - "name": dep.layer.name, - "layerdetailurl": reverse('layerdetails', args=(pid, dep.pk)), - "vcs_url": dep.layer.vcs_url, - "vcs_reference": dep.get_vcs_reference()} \ - for dep in layer_version.get_alldeps(project.id)]}, - 'projectlayers': map(lambda prjlayer: prjlayer.layercommit.id, ProjectLayer.objects.filter(project=project)) + project_layers = ProjectLayer.objects.filter( + project=project).values_list("layercommit_id", + flat=True) + + context = { + 'project': project, + 'layer_source': LayerSource.types_dict(), + 'layerversion': layer_version, + 'layerdeps': { + "list": [ + { + "id": dep.id, + "name": dep.layer.name, + "layerdetailurl": reverse('layerdetails', + args=(pid, dep.pk)), + "vcs_url": dep.layer.vcs_url, + "vcs_reference": dep.get_vcs_reference() + } + for dep in layer_version.get_alldeps(project.id)] + }, + 'projectlayers': list(project_layers) } return context @@ -2839,7 +1684,7 @@ if True: vars_blacklist = { 'PARALLEL_MAKE','BB_NUMBER_THREADS', 'BB_DISKMON_DIRS','BB_NUMBER_THREADS','CVS_PROXY_HOST','CVS_PROXY_PORT', - 'PARALLEL_MAKE','SSTATE_MIRRORS','TMPDIR', + 'PARALLEL_MAKE','TMPDIR', 'all_proxy','ftp_proxy','http_proxy ','https_proxy' } @@ -2887,7 +1732,7 @@ if True: else: context['dl_dir'] = ProjectVariable.objects.get(project = prj, name = "DL_DIR").value context['dl_dir_defined'] = "1" - except ProjectVariable.DoesNotExist,BuildEnvironment.DoesNotExist: + except (ProjectVariable.DoesNotExist, BuildEnvironment.DoesNotExist): pass try: context['fstypes'] = ProjectVariable.objects.get(project = prj, name = "IMAGE_FSTYPES").value @@ -2915,7 +1760,7 @@ if True: else: context['sstate_dir'] = ProjectVariable.objects.get(project = prj, name = "SSTATE_DIR").value context['sstate_dir_defined'] = "1" - except ProjectVariable.DoesNotExist, BuildEnvironment.DoesNotExist: + except (ProjectVariable.DoesNotExist, BuildEnvironment.DoesNotExist): pass return context @@ -2936,12 +1781,20 @@ if True: elif artifact_type == "imagefile": file_name = Target_Image_File.objects.get(target__build = build, pk = artifact_id).file_name - elif artifact_type == "buildartifact": - file_name = BuildArtifact.objects.get(build = build, pk = artifact_id).file_name + elif artifact_type == "targetkernelartifact": + target = TargetKernelFile.objects.get(pk=artifact_id) + file_name = target.file_name + + elif artifact_type == "targetsdkartifact": + target = TargetSDKFile.objects.get(pk=artifact_id) + file_name = target.file_name elif artifact_type == "licensemanifest": file_name = Target.objects.get(build = build, pk = artifact_id).license_manifest_path + elif artifact_type == "packagemanifest": + file_name = Target.objects.get(build = build, pk = artifact_id).package_manifest_path + elif artifact_type == "tasklogfile": file_name = Task.objects.get(build = build, pk = artifact_id).logfile @@ -2967,7 +1820,7 @@ if True: ) if file_name and response_file_name: - fsock = open(file_name, "r") + fsock = open(file_name, "rb") content_type = MimeTypeFinder.get_mimetype(file_name) response = HttpResponse(fsock, content_type = content_type) @@ -2978,5 +1831,5 @@ if True: return response else: return render(request, "unavailable_artifact.html") - except ObjectDoesNotExist, IOError: + except (ObjectDoesNotExist, IOError): return render(request, "unavailable_artifact.html") diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/widgets.py b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/widgets.py index d2ef5d3db..026903d35 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/widgets.py +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/widgets.py @@ -22,30 +22,41 @@ from django.views.generic import View, TemplateView from django.views.decorators.cache import cache_control from django.shortcuts import HttpResponse -from django.http import HttpResponseBadRequest -from django.core import serializers from django.core.cache import cache from django.core.paginator import Paginator, EmptyPage from django.db.models import Q -from orm.models import Project, ProjectLayer, Layer_Version +from orm.models import Project, Build from django.template import Context, Template +from django.template import VariableDoesNotExist +from django.template import TemplateSyntaxError from django.core.serializers.json import DjangoJSONEncoder from django.core.exceptions import FieldError -from django.conf.urls import url, patterns +from django.utils import timezone +from toastergui.templatetags.projecttags import sectohms, get_tasks +from toastergui.templatetags.projecttags import json as template_json +from django.http import JsonResponse +from django.core.urlresolvers import reverse import types import json import collections -import operator import re -import urllib + +try: + from urllib import unquote_plus +except ImportError: + from urllib.parse import unquote_plus import logging logger = logging.getLogger("toaster") -from toastergui.views import objtojson from toastergui.tablefilter import TableFilterMap + +class NoFieldOrDataName(Exception): + pass + + class ToasterTable(TemplateView): def __init__(self, *args, **kwargs): super(ToasterTable, self).__init__() @@ -63,25 +74,20 @@ class ToasterTable(TemplateView): self.empty_state = "Sorry - no data found" self.default_orderby = "" - # add the "id" column, undisplayable, by default - self.add_column(title="Id", - displayable=False, - orderable=True, - field_name="id") - # prevent HTTP caching of table data - @cache_control(must_revalidate=True, max_age=0, no_store=True, no_cache=True) + @cache_control(must_revalidate=True, + max_age=0, no_store=True, no_cache=True) def dispatch(self, *args, **kwargs): return super(ToasterTable, self).dispatch(*args, **kwargs) def get_context_data(self, **kwargs): context = super(ToasterTable, self).get_context_data(**kwargs) context['title'] = self.title - context['table_name'] = type(self).__name__.lower() + context['table_name'] = type(self).__name__.lower() + context['empty_state'] = self.empty_state return context - def get(self, request, *args, **kwargs): if request.GET.get('format', None) == 'json': @@ -102,8 +108,6 @@ class ToasterTable(TemplateView): return super(ToasterTable, self).get(request, *args, **kwargs) def get_filter_info(self, request, **kwargs): - data = None - self.setup_filters(**kwargs) search = request.GET.get("search", None) @@ -117,13 +121,18 @@ class ToasterTable(TemplateView): cls=DjangoJSONEncoder) def setup_columns(self, *args, **kwargs): - """ function to implement in the subclass which sets up the columns """ + """ function to implement in the subclass which sets up + the columns """ pass + def setup_filters(self, *args, **kwargs): - """ function to implement in the subclass which sets up the filters """ + """ function to implement in the subclass which sets up the + filters """ pass + def setup_queryset(self, *args, **kwargs): - """ function to implement in the subclass which sets up the queryset""" + """ function to implement in the subclass which sets up the + queryset""" pass def add_filter(self, table_filter): @@ -137,7 +146,6 @@ class ToasterTable(TemplateView): def add_column(self, title="", help_text="", orderable=False, hideable=True, hidden=False, field_name="", filter_name=None, static_data_name=None, - displayable=True, computation=None, static_data_template=None): """Add a column to the table. @@ -155,18 +163,15 @@ class ToasterTable(TemplateView): as data """ - self.columns.append({'title' : title, - 'help_text' : help_text, - 'orderable' : orderable, - 'hideable' : hideable, - 'hidden' : hidden, - 'field_name' : field_name, - 'filter_name' : filter_name, + self.columns.append({'title': title, + 'help_text': help_text, + 'orderable': orderable, + 'hideable': hideable, + 'hidden': hidden, + 'field_name': field_name, + 'filter_name': filter_name, 'static_data_name': static_data_name, - 'static_data_template': static_data_template, - 'displayable': displayable, - 'computation': computation, - }) + 'static_data_template': static_data_template}) def set_column_hidden(self, title, hidden): """ @@ -190,8 +195,8 @@ class ToasterTable(TemplateView): """Utility function to render the static data template""" context = { - 'extra' : self.static_context_extra, - 'data' : row, + 'extra': self.static_context_extra, + 'data': row, } context = Context(context) @@ -216,7 +221,7 @@ class ToasterTable(TemplateView): try: filter_name, action_name = filters.split(':') - action_params = urllib.unquote_plus(filter_value) + action_params = unquote_plus(filter_value) except ValueError: return @@ -241,20 +246,25 @@ class ToasterTable(TemplateView): if not hasattr(self.queryset.model, 'search_allowed_fields'): raise Exception("Search fields aren't defined in the model %s" - % self.queryset.model) + % self.queryset.model) - search_queries = [] + search_queries = None for st in search_term.split(" "): - q_map = [Q(**{field + '__icontains': st}) - for field in self.queryset.model.search_allowed_fields] - - search_queries.append(reduce(operator.or_, q_map)) - - search_queries = reduce(operator.and_, search_queries) + queries = None + for field in self.queryset.model.search_allowed_fields: + query = Q(**{field + '__icontains': st}) + if queries: + queries |= query + else: + queries = query + + if search_queries: + search_queries &= queries + else: + search_queries = queries self.queryset = self.queryset.filter(search_queries) - def get_data(self, request, **kwargs): """ Returns the data for the page requested with the specified @@ -262,7 +272,8 @@ class ToasterTable(TemplateView): filters: filter and action name, e.g. "outcome:build_succeeded" filter_value: value to pass to the named filter+action, e.g. "on" - (for a toggle filter) or "2015-12-11,2015-12-12" (for a date range filter) + (for a toggle filter) or "2015-12-11,2015-12-12" + (for a date range filter) """ page_num = request.GET.get("page", 1) @@ -276,12 +287,12 @@ class ToasterTable(TemplateView): # Make a unique cache name cache_name = self.__class__.__name__ - for key, val in request.GET.iteritems(): + for key, val in request.GET.items(): if key == 'nocache': continue cache_name = cache_name + str(key) + str(val) - for key, val in kwargs.iteritems(): + for key, val in kwargs.items(): cache_name = cache_name + str(key) + str(val) # No special chars allowed in the cache name apart from dash @@ -313,16 +324,16 @@ class ToasterTable(TemplateView): page = paginator.page(1) data = { - 'total' : self.queryset.count(), - 'default_orderby' : self.default_orderby, - 'columns' : self.columns, - 'rows' : [], - 'error' : "ok", + 'total': self.queryset.count(), + 'default_orderby': self.default_orderby, + 'columns': self.columns, + 'rows': [], + 'error': "ok", } try: - for row in page.object_list: - #Use collection to maintain the order + for model_obj in page.object_list: + # Use collection to maintain the order required_data = collections.OrderedDict() for col in self.columns: @@ -330,44 +341,73 @@ class ToasterTable(TemplateView): if not field: field = col['static_data_name'] if not field: - raise Exception("Must supply a field_name or static_data_name for column %s.%s" % (self.__class__.__name__,col)) + raise NoFieldOrDataName("Must supply a field_name or" + "static_data_name for column" + "%s.%s" % + (self.__class__.__name__, col) + ) + # Check if we need to process some static data if "static_data_name" in col and col['static_data_name']: - required_data["static:%s" % col['static_data_name']] = self.render_static_data(col['static_data_template'], row) - # Overwrite the field_name with static_data_name # so that this can be used as the html class name - col['field_name'] = col['static_data_name'] - # compute the computation on the raw data if needed - model_data = row - if col['computation']: - model_data = col['computation'](row) + try: + # Render the template given + required_data[col['static_data_name']] = \ + self.render_static_data( + col['static_data_template'], model_obj) + except (TemplateSyntaxError, + VariableDoesNotExist) as e: + logger.error("could not render template code" + "%s %s %s", + col['static_data_template'], + e, self.__class__.__name__) + required_data[col['static_data_name']] =\ + '<!--error-->' + else: - # Traverse to any foriegn key in the object hierachy - for subfield in field.split("__"): - if hasattr(model_data, subfield): - model_data = getattr(model_data, subfield) - # The field could be a function on the model so check - # If it is then call it + # Traverse to any foriegn key in the field + # e.g. recipe__layer_version__name + model_data = None + + if "__" in field: + for subfield in field.split("__"): + if not model_data: + # The first iteration is always going to + # be on the actual model object instance. + # Subsequent ones are on the result of + # that. e.g. forieng key objects + model_data = getattr(model_obj, + subfield) + else: + model_data = getattr(model_data, + subfield) + + else: + model_data = getattr(model_obj, + col['field_name']) + + # We might have a model function as the field so + # call it to return the data needed if isinstance(model_data, types.MethodType): - model_data = model_data() + model_data = model_data() - required_data[col['field_name']] = model_data + required_data[col['field_name']] = model_data data['rows'].append(required_data) except FieldError: # pass it to the user - programming-error here raise - data = json.dumps(data, indent=2, default=objtojson) + + data = json.dumps(data, indent=2, cls=DjangoJSONEncoder) cache.set(cache_name, data, 60*30) return data - class ToasterTypeAhead(View): """ A typeahead mechanism to support the front end typeahead widgets """ MAX_RESULTS = 6 @@ -388,34 +428,142 @@ class ToasterTypeAhead(View): error = "ok" search_term = request.GET.get("search", None) - if search_term == None: + if search_term is None: # We got no search value so return empty reponse - return response({'error' : error , 'results': []}) + return response({'error': error, 'results': []}) try: prj = Project.objects.get(pk=kwargs['pid']) except KeyError: prj = None - results = self.apply_search(search_term, prj, request)[:ToasterTypeAhead.MAX_RESULTS] + results = self.apply_search(search_term, + prj, + request)[:ToasterTypeAhead.MAX_RESULTS] if len(results) > 0: try: self.validate_fields(results[0]) - except MissingFieldsException as e: + except self.MissingFieldsException as e: error = e - data = { 'results' : results, - 'error' : error, - } + data = {'results': results, + 'error': error} return response(data) def validate_fields(self, result): - if 'name' in result == False or 'detail' in result == False: - raise MissingFieldsException("name and detail are required fields") + if 'name' in result is False or 'detail' in result is False: + raise self.MissingFieldsException( + "name and detail are required fields") def apply_search(self, search_term, prj): """ Override this function to implement search. Return an array of dictionaries with a minium of a name and detail field""" pass + + +class MostRecentBuildsView(View): + def _was_yesterday_or_earlier(self, completed_on): + now = timezone.now() + delta = now - completed_on + + if delta.days >= 1: + return True + + return False + + def get(self, request, *args, **kwargs): + """ + Returns a list of builds in JSON format. + """ + project = None + + project_id = request.GET.get('project_id', None) + if project_id: + try: + project = Project.objects.get(pk=project_id) + except: + # if project lookup fails, assume no project + pass + + recent_build_objs = Build.get_recent(project) + recent_builds = [] + + for build_obj in recent_build_objs: + dashboard_url = reverse('builddashboard', args=(build_obj.pk,)) + buildtime_url = reverse('buildtime', args=(build_obj.pk,)) + rebuild_url = \ + reverse('xhr_buildrequest', args=(build_obj.project.pk,)) + cancel_url = \ + reverse('xhr_buildrequest', args=(build_obj.project.pk,)) + + build = {} + build['id'] = build_obj.pk + build['dashboard_url'] = dashboard_url + + buildrequest_id = None + if hasattr(build_obj, 'buildrequest'): + buildrequest_id = build_obj.buildrequest.pk + build['buildrequest_id'] = buildrequest_id + + build['recipes_parsed_percentage'] = \ + int((build_obj.recipes_parsed / + build_obj.recipes_to_parse) * 100) + + tasks_complete_percentage = 0 + if build_obj.outcome in (Build.SUCCEEDED, Build.FAILED): + tasks_complete_percentage = 100 + elif build_obj.outcome == Build.IN_PROGRESS: + tasks_complete_percentage = build_obj.completeper() + build['tasks_complete_percentage'] = tasks_complete_percentage + + build['state'] = build_obj.get_state() + + build['errors'] = build_obj.errors.count() + build['dashboard_errors_url'] = dashboard_url + '#errors' + + build['warnings'] = build_obj.warnings.count() + build['dashboard_warnings_url'] = dashboard_url + '#warnings' + + build['buildtime'] = sectohms(build_obj.timespent_seconds) + build['buildtime_url'] = buildtime_url + + build['rebuild_url'] = rebuild_url + build['cancel_url'] = cancel_url + + build['is_default_project_build'] = build_obj.project.is_default + + build['build_targets_json'] = \ + template_json(get_tasks(build_obj.target_set.all())) + + # convert completed_on time to user's timezone + completed_on = timezone.localtime(build_obj.completed_on) + + completed_on_template = '%H:%M' + if self._was_yesterday_or_earlier(completed_on): + completed_on_template = '%d/%m/%Y ' + completed_on_template + build['completed_on'] = completed_on.strftime( + completed_on_template) + + targets = [] + target_objs = build_obj.get_sorted_target_list() + for target_obj in target_objs: + if target_obj.task: + targets.append(target_obj.target + ':' + target_obj.task) + else: + targets.append(target_obj.target) + build['targets'] = ' '.join(targets) + + # abbreviated form of the full target list + abbreviated_targets = '' + num_targets = len(targets) + if num_targets > 0: + abbreviated_targets = targets[0] + if num_targets > 1: + abbreviated_targets += (' +%s' % (num_targets - 1)) + build['targets_abbreviated'] = abbreviated_targets + + recent_builds.append(build) + + return JsonResponse(recent_builds, safe=False) diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastermain/management/commands/builddelete.py b/import-layers/yocto-poky/bitbake/lib/toaster/toastermain/management/commands/builddelete.py index ff93e549d..0bef8d410 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastermain/management/commands/builddelete.py +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastermain/management/commands/builddelete.py @@ -15,7 +15,7 @@ class Command(BaseCommand): try: b = Build.objects.get(pk = bid) except ObjectDoesNotExist: - print 'build %s does not exist, skipping...' %(bid) + print('build %s does not exist, skipping...' %(bid)) continue # theoretically, just b.delete() would suffice # however SQLite runs into problems when you try to diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastermain/management/commands/buildslist.py b/import-layers/yocto-poky/bitbake/lib/toaster/toastermain/management/commands/buildslist.py index cad987fd9..8dfef0aa0 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastermain/management/commands/buildslist.py +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastermain/management/commands/buildslist.py @@ -10,4 +10,4 @@ class Command(NoArgsCommand): def handle_noargs(self,**options): for b in Build.objects.all(): - print "%d: %s %s %s" % (b.pk, b.machine, b.distro, ",".join([x.target for x in b.target_set.all()])) + print("%d: %s %s %s" % (b.pk, b.machine, b.distro, ",".join([x.target for x in b.target_set.all()]))) diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastermain/management/commands/get-dburl.py b/import-layers/yocto-poky/bitbake/lib/toaster/toastermain/management/commands/get-dburl.py deleted file mode 100644 index 22b3eb79e..000000000 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastermain/management/commands/get-dburl.py +++ /dev/null @@ -1,9 +0,0 @@ -from toastermain.settings import getDATABASE_URL -from django.core.management.base import NoArgsCommand - -class Command(NoArgsCommand): - args = "" - help = "get database url" - - def handle_noargs(self,**options): - print getDATABASE_URL() diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastermain/management/commands/perf.py b/import-layers/yocto-poky/bitbake/lib/toaster/toastermain/management/commands/perf.py index 71a48e95d..6b450bbdf 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastermain/management/commands/perf.py +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastermain/management/commands/perf.py @@ -25,7 +25,7 @@ class Command(BaseCommand): info = self.url_info(full_url) status_code = info[0] load_time = info[1] - print 'Trying \'' + full_url + '\', ' + str(status_code) + ', ' + str(load_time) + print('Trying \'' + full_url + '\', ' + str(status_code) + ', ' + str(load_time)) def get_full_url(self, url_patt, url_root_res): full_url = str(url_patt).split('^')[1].replace('$>', '').replace('(?P<file_path>(?:/[', '/bin/busybox').replace('.*', '') @@ -54,5 +54,5 @@ class Command(BaseCommand): def error(self, *args): for arg in args: - print >>sys.stderr, arg, - print >>sys.stderr + print(arg, end=' ', file=sys.stderr) + print(file=sys.stderr) diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastermain/settings.py b/import-layers/yocto-poky/bitbake/lib/toaster/toastermain/settings.py index 74ab60462..3dfa2b223 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastermain/settings.py +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastermain/settings.py @@ -21,7 +21,7 @@ # Django settings for Toaster project. -import os, re +import os DEBUG = True TEMPLATE_DEBUG = DEBUG @@ -38,14 +38,19 @@ ADMINS = ( MANAGERS = ADMINS +TOASTER_SQLITE_DEFAULT_DIR = os.path.join(os.environ.get('TOASTER_DIR', ''), + 'build') + DATABASES = { 'default': { - 'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'. - 'NAME': 'toaster.sqlite', # Or path to database file if using sqlite3. + # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'. + 'ENGINE': 'django.db.backends.sqlite3', + # DB name or full path to database file if using sqlite3. + 'NAME': "%s/toaster.sqlite" % TOASTER_SQLITE_DEFAULT_DIR, 'USER': '', 'PASSWORD': '', - 'HOST': '127.0.0.1', # Empty for localhost through domain sockets or '127.0.0.1' for localhost through TCP. - 'PORT': '3306', # Set to empty string for default. + #'HOST': '127.0.0.1', # e.g. mysql server + #'PORT': '3306', # e.g. mysql port } } @@ -55,58 +60,6 @@ DATABASES = { if 'sqlite' in DATABASES['default']['ENGINE']: DATABASES['default']['OPTIONS'] = { 'timeout': 20 } -# Reinterpret database settings if we have DATABASE_URL environment variable defined - -if 'DATABASE_URL' in os.environ: - dburl = os.environ['DATABASE_URL'] - - if dburl.startswith('sqlite3://'): - result = re.match('sqlite3://(.*)', dburl) - if result is None: - raise Exception("ERROR: Could not read sqlite database url: %s" % dburl) - DATABASES['default'] = { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': result.group(1), - 'USER': '', - 'PASSWORD': '', - 'HOST': '', - 'PORT': '', - } - elif dburl.startswith('mysql://'): - # URL must be in this form: mysql://user:pass@host:port/name - result = re.match(r"mysql://([^:]*):([^@]*)@([^:]*):(\d*)/([^/]*)", dburl) - if result is None: - raise Exception("ERROR: Could not read mysql database url: %s" % dburl) - DATABASES['default'] = { - 'ENGINE': 'django.db.backends.mysql', - 'NAME': result.group(5), - 'USER': result.group(1), - 'PASSWORD': result.group(2), - 'HOST': result.group(3), - 'PORT': result.group(4), - } - else: - raise Exception("FIXME: Please implement missing database url schema for url: %s" % dburl) - - -# Allows current database settings to be exported as a DATABASE_URL environment variable value - -def getDATABASE_URL(): - d = DATABASES['default'] - if d['ENGINE'] == 'django.db.backends.sqlite3': - if d['NAME'] == ':memory:': - return 'sqlite3://:memory:' - elif d['NAME'].startswith("/"): - return 'sqlite3://' + d['NAME'] - return "sqlite3://" + os.path.join(os.getcwd(), d['NAME']) - - elif d['ENGINE'] == 'django.db.backends.mysql': - return "mysql://" + d['USER'] + ":" + d['PASSWORD'] + "@" + d['HOST'] + ":" + d['PORT'] + "/" + d['NAME'] - - raise Exception("FIXME: Please implement missing database url schema for engine: %s" % d['ENGINE']) - - - # Hosts/domain names that are valid for this site; required if DEBUG is False # See https://docs.djangoproject.com/en/1.5/ref/settings/#allowed-hosts ALLOWED_HOSTS = [] @@ -134,17 +87,16 @@ else: try: import pytz from pytz.exceptions import UnknownTimeZoneError - pass try: if pytz.timezone(zonename) is not None: - zonefilelist[hashlib.md5(open(filepath).read()).hexdigest()] = zonename - except UnknownTimeZoneError, ValueError: + zonefilelist[hashlib.md5(open(filepath, 'rb').read()).hexdigest()] = zonename + except UnknownTimeZoneError as ValueError: # we expect timezone failures here, just move over pass except ImportError: - zonefilelist[hashlib.md5(open(filepath).read()).hexdigest()] = zonename + zonefilelist[hashlib.md5(open(filepath, 'rb').read()).hexdigest()] = zonename - TIME_ZONE = zonefilelist[hashlib.md5(open('/etc/localtime').read()).hexdigest()] + TIME_ZONE = zonefilelist[hashlib.md5(open('/etc/localtime', 'rb').read()).hexdigest()] # Language code for this installation. All choices can be found here: # http://www.i18nguy.com/unicode/language-identifiers.html @@ -321,7 +273,7 @@ currentdir = os.path.dirname(__file__) for t in os.walk(os.path.dirname(currentdir)): modulename = os.path.basename(t[0]) #if we have a virtualenv skip it to avoid incorrect imports - if os.environ.has_key('VIRTUAL_ENV') and os.environ['VIRTUAL_ENV'] in t[0]: + if 'VIRTUAL_ENV' in os.environ and os.environ['VIRTUAL_ENV'] in t[0]: continue if ("views.py" in t[2] or "models.py" in t[2]) and not modulename in INSTALLED_APPS: diff --git a/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/hobcolor.py b/import-layers/yocto-poky/bitbake/lib/toaster/toastermain/settings_test.py index 3316542a2..a32271157 100644 --- a/import-layers/yocto-poky/bitbake/lib/bb/ui/crumbs/hobcolor.py +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastermain/settings_test.py @@ -1,9 +1,10 @@ # -# BitBake Graphical GTK User Interface +# ex:ts=4:sw=4:sts=4:et +# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- # -# Copyright (C) 2012 Intel Corporation +# BitBake Toaster Implementation # -# Authored by Shane Wang <shane.wang@intel.com> +# Copyright (C) 2016 Intel Corporation # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 as @@ -18,21 +19,23 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -class HobColors: - WHITE = "#ffffff" - PALE_GREEN = "#aaffaa" - ORANGE = "#eb8e68" - PALE_RED = "#ffaaaa" - GRAY = "#aaaaaa" - LIGHT_GRAY = "#dddddd" - SLIGHT_DARK = "#5f5f5f" - DARK = "#3c3b37" - BLACK = "#000000" - PALE_BLUE = "#53b8ff" - DEEP_RED = "#aa3e3e" - KHAKI = "#fff68f" +# Django settings for Toaster project. - OK = WHITE - RUNNING = PALE_GREEN - WARNING = ORANGE - ERROR = PALE_RED +# Settings overlay to use for running tests +# DJANGO_SETTINGS_MODULE=toastermain.settings-test + +from toastermain.settings import * + +DEBUG = True +TEMPLATE_DEBUG = DEBUG + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': '/tmp/toaster-test-db.sqlite', + 'TEST': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': '/tmp/toaster-test-db.sqlite', + } + } +} diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastermain/urls.py b/import-layers/yocto-poky/bitbake/lib/toaster/toastermain/urls.py index 534679dc5..1f8599edc 100644 --- a/import-layers/yocto-poky/bitbake/lib/toaster/toastermain/urls.py +++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastermain/urls.py @@ -71,7 +71,7 @@ import os currentdir = os.path.dirname(__file__) for t in os.walk(os.path.dirname(currentdir)): #if we have a virtualenv skip it to avoid incorrect imports - if os.environ.has_key('VIRTUAL_ENV') and os.environ['VIRTUAL_ENV'] in t[0]: + if 'VIRTUAL_ENV' in os.environ and os.environ['VIRTUAL_ENV'] in t[0]: continue if "urls.py" in t[2] and t[0] != currentdir: @@ -84,7 +84,7 @@ for t in os.walk(os.path.dirname(currentdir)): if not conflict: urlpatterns.insert(0, url(r'^' + modulename + '/', include ( modulename + '.urls'))) else: - logger.warn("Module \'%s\' has a regexp conflict, was not added to the urlpatterns" % modulename) + logger.warning("Module \'%s\' has a regexp conflict, was not added to the urlpatterns" % modulename) from pprint import pformat #logger.debug("urlpatterns list %s", pformat(urlpatterns)) diff --git a/import-layers/yocto-poky/bitbake/toaster-requirements.txt b/import-layers/yocto-poky/bitbake/toaster-requirements.txt index 0e8c742cb..e61c8e2aa 100644 --- a/import-layers/yocto-poky/bitbake/toaster-requirements.txt +++ b/import-layers/yocto-poky/bitbake/toaster-requirements.txt @@ -1,4 +1,3 @@ Django>1.8,<1.9 -argparse==1.2.1 -wsgiref==0.1.2 beautifulsoup4>=4.4.0 +pytz |