Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DO NOT MERGE] Preprocessor #19

Open
wants to merge 13 commits into
base: new-feline
Choose a base branch
from
Empty file added src/__init__.py
Empty file.
236 changes: 236 additions & 0 deletions src/csstree/BlockSplitter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
from __future__ import print_function
import re
import logging
import logging_config #to configure logging, no calls needed
from CssTree.CssElement import CssElement
from collections import namedtuple
from Util import StringWithSource


Block = namedtuple("Block", "name attrs")


MIN_ZOOM = 1
MAX_ZOOM = 19


BLOCK_SPLITTER = re.compile(r'([^@{]*)\s*\{(.*?)\}', re.DOTALL | re.MULTILINE)
ZOOM = re.compile(r'(.*?)(\|z[\d\-]*?)?(\[.*)?') #deprecated
ONE_ZOOM = re.compile(r'(\d{1,2})$')
ZOOM_RANGE = re.compile(r'(\d{1,2})-(\d{1,2})$')
ZOOM_TO_MAX = re.compile(r'(\d{1,2})-$')


TAG_RE = re.compile(r'(^.*?)[\|\[$:]', re.MULTILINE)
ZOOM_RE = re.compile(r'.*?\|z([\d\-]*?)[\[$:]')
SELECTORS_RE = re.compile(r'(\[.*?\])')
SUB_RE = re.compile(r'.*:(.*)$')
ONE_SELECTOR_RE = re.compile(r'\[(.*?)\]')


class BlockSplitter:
"""
Should also be initializeable by a preprocessed file
Copy link

@mgsergio mgsergio May 6, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

В докстрингах хорошо писать документацию к классу, а это больше похоже на коммент.

"""
def __init__(self, preprocessed_blocks, write_to_file=False):
self.blocks = preprocessed_blocks
self.write_to_file = write_to_file
self.blocks_by_zoom_level = dict(map(lambda i: (i, {}), range(MIN_ZOOM, MAX_ZOOM +1)))


def process(self):
self.split_commas()
self.process_blocks_by_zoom_level()

if self.write_to_file:
self.write()


def process_blocks_by_zoom_level(self):
for zoom in self.blocks_by_zoom_level:
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for zoom, block in self.blocks_by_zoom_level.iter_items():
   self.process_zoom_level(zoom, block)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Вроде поправлял. Если где-то не поправил, то, когда будут попадаться, буду заменять.

self.process_zoom_level(zoom, self.blocks_by_zoom_level[zoom])


def process_zoom_level(self, zoom, block):
clean_block = {}
block_keys = sorted(block.keys())
old_block = Block("", [])
for tag in block_keys:
attrs = block[tag]
if tag == old_block.name:
old_block.attrs.extend(attrs)
else:
if old_block.name:
clean_block[old_block.name] = self.parse_attributes(old_block.attrs, tag, zoom)
old_block = Block(tag, attrs)
self.blocks_by_zoom_level[zoom] = clean_block


def parse_attributes(self, list_of_attrs, tag, zoom):
ret = {} #attribute : StringWithSource(value, imported_from)
for attr, source in list_of_attrs.iteritems():
key, val = map(str.strip, attr.split(":", 1))
if key in ret:
logging.warning("Duplicate value for attribute {} ({}) for tag {} on zoom {}. First declared in {}".format(key, val, tag, zoom, ret[key].source))

ret[key] = StringWithSource(val, source)
return ret


def clean_split_by(self, string, separator):
return filter(lambda x: x != "", map(lambda x: x.strip(), string.split(separator)))


def all_zooms_in_css_range(self, str_range):
min_zoom = -1
max_zoom = -1

if not str_range:
min_zoom = MIN_ZOOM
max_zoom = MAX_ZOOM

elif ONE_ZOOM.match(str_range):
min_zoom = int(str_range)
max_zoom = min_zoom

elif ZOOM_TO_MAX.match(str_range):
min_zoom = int(str_range[:-1])
max_zoom = MAX_ZOOM

elif ZOOM_RANGE.match(str_range):
found = ZOOM_RANGE.findall(str_range)[0]
(min_zoom, max_zoom) = map(lambda x: int(x), found)

if max_zoom < 0 or min_zoom < 0 or max_zoom < min_zoom:
raise Exception("Failed to parse the zoom levels")

max_zoom = MAX_ZOOM if max_zoom > MAX_ZOOM else max_zoom
min_zoom = MIN_ZOOM if min_zoom < MIN_ZOOM else min_zoom

return range(min_zoom, max_zoom + 1)


def split_keys_by_zoom(self, keys):
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4 отступа! Жуть как много

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Промахнулся, это должно быть на строке выше.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Отступы со временем тоже будут уходить.

ret = []
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

В "длинных" функциях лучше давать более говорящие имена локальным переменным,
чтобы было ясно что в них хранится.

for key in keys:
parts_list = ZOOM.findall(key)
if not parts_list:
logging.error("Unparseable key {}".format(key))
continue
parts = parts_list[0]
if parts:
selector, zoom = parts[0], parts[1][2:]
print(">>>> {} : {}".format(selector, zoom))
all_zooms = self.all_zooms_in_css_range(zoom)
ret.append(map(lambda x: (selector, x), all_zooms))
else:
logging.error("Got an unparseable node and zoom level: {}".format(key))
logging.warning("NOTE THAT THIS TAG HAS BEEN IGNORED AND MUST BE PROCESSED IN THE FUTURE!")
return ret


def clean_up_attribute_block(self, attributes, key, imported_from):
last_attr = ""
clean_attributes = []
for a in attributes:
if a == last_attr:
logging.warning("Duplicate attribute {} for tag/zoom {} imported from {}".format(a, key, imported_from))
continue
clean_attributes.append(a)
return clean_attributes


def filter_attributes_against_processed(self, clean_attributes, element, imported_from):
filtered_attributes = []
for a in clean_attributes:
if a in self.blocks_by_zoom_level[element.zoom][element]:
logging.warning("Duplicate attribute {} for tag {} imported from {}".format(a, element, imported_from))
else:
filtered_attributes.append(a)
return filtered_attributes


def split_commas(self):
for block, imported_from in self.blocks:
found = BLOCK_SPLITTER.findall(block)
for entry in found:
keys = self.clean_split_by(entry[0], ",")
attributes = sorted(self.clean_split_by(entry[1], ";")) #TODO attributes should also be a dictionary. Or should they?

clean_attributes = self.clean_up_attribute_block(attributes, entry[0], imported_from)

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Опять куча отступов.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Буду убирать со временем. По-моему, кстати, везде поправил

for key in keys:
elements = self.css_key_factory(key)
self.add_elements_to_zoom_level(elements, clean_attributes, imported_from)


def add_elements_to_zoom_level(self, elements, clean_attributes, imported_from):
for element in elements:
if element in self.blocks_by_zoom_level[element.zoom]:
filtered_attributes = self.filter_attributes_against_processed(clean_attributes, element, imported_from)
self.blocks_by_zoom_level[element.zoom][element].update(self.map_attrs_to_import_source(filtered_attributes, imported_from))
else:
self.blocks_by_zoom_level[element.zoom][element] = self.map_attrs_to_import_source(clean_attributes, imported_from)


def map_attrs_to_import_source(self, attributes, imported_from):
return dict(map(lambda x: (x, imported_from), attributes))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return {x: imported_from for x in attributes}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Пока не трогал.



def write(self):
print("Writing split blocks by zoom, num blocks {}".format(len(self.blocks_by_zoom_level)))
with open("../../out/split_by_commas.mapcss", "w") as out_file:
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use os.path.join

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Да, подобные штуки будут заменяться с хардкода на параметры.

for zoom in sorted(self.blocks_by_zoom_level.keys()):
blocks = self.blocks_by_zoom_level[zoom]
# for zoom, blocks in self.blocks_by_zoom_level:
out_file.write(" /* ===== ZOOM {} ===== */\n\n".format(zoom))
keys = blocks.keys()
keys.sort(key=lambda x : x.tag)
for tag in keys:
attrs = blocks[tag]
out_file.write("{} {{\n".format(tag))
for attr in attrs:
out_file.write(" {}: {}; /* == {} == */\n".format(attr, attrs[attr].string, attrs[attr].source))
out_file.write("}\n\n")


def css_key_factory(self, str_key):
# type: (str) -> [CssElement]
tag_list = TAG_RE.findall(str_key)
tag = tag_list[0] if tag_list else str_key

zoom_list = ZOOM_RE.findall(str_key)
zoom = zoom_list[0] if zoom_list else ""

selectors_list = SELECTORS_RE.findall(str_key)

str_key = TAG_RE.sub("", str_key)
str_key = ZOOM_RE.sub("", str_key)
str_key = SELECTORS_RE.sub("", str_key)

subclass_list = SUB_RE.findall(str_key)
subclass = subclass_list[0] if subclass_list else ""
all_zooms = self.all_zooms_in_css_range(zoom)
ret = map(lambda z: CssElement(tag, z, selectors_list, subclass), all_zooms)
return ret



if __name__ == "__main__":


blockSplitter = BlockSplitter([])
# print(blockSplitter.all_zooms_in_css_range("10"))
# print(blockSplitter.all_zooms_in_css_range("10-"))
# print(blockSplitter.all_zooms_in_css_range("10-12"))
# print(blockSplitter.all_zooms_in_css_range("10-25"))

# print(blockSplitter.split_key_by_components("*::*"))
# print(blockSplitter.split_key_by_components("*"))
# print(blockSplitter.split_key_by_components("*|z12"))
# print(blockSplitter.split_key_by_components("*::int_name "))
# print(blockSplitter.split_key_by_components("line|z5[highway=world_level]"))
# print(blockSplitter.css_key_factory("line|z17-18[highway=footway][tunnel?]::tunnelBackground"))

pass

67 changes: 67 additions & 0 deletions src/csstree/CssTree/CssElement.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
WILDCARD = "*"

class CssElement:
def __init__(self, tag, zoom, selectors, subclass):
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/subclass/css_subclass/

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Подумаю

self.tag = tag
self.zoom = zoom
self.selectors = selectors #[]
self.subclass = subclass


def __repr__(self):
return self._my_str_repr()


def _my_str_repr(self, must_sort=False):
selectors = self.selectors
if must_sort:
selectors = sorted(selectors)

str_selectors = "".join(selectors) if selectors else ""
str_subclass = "::{}".format(self.subclass) if self.subclass else ""
return "{}|z{}{}{}".format(self.tag, self.zoom, str_selectors, str_subclass)


def __eq__(self, other):
return (isinstance(other, self.__class__)
and self.__dict__ == other.__dict__)


def __ne__(self, other):
return not self.__eq__(other)


def __hash__(self):
return hash(self._my_str_repr(must_sort=True))


def can_adopt(self, css_element):
if self.zoom != css_element.zoom:
return False
#my tag must be * or the same as theirs, my subclass must be * or the same as theirs, and my selectors must count less than theirs and be a subset of theirs.
if self.tag != WILDCARD and self.tag != css_element.tag:
return False
if self.subclass != WILDCARD and self.subclass != css_element.subclass:
return False
if len(self.selectors) >= len(css_element.selectors):
return False
if not set(self.selectors).issubset(css_element.selectors):
return False

return True





if __name__ == "__main__":
e1 = CssElement("line", 14, ["[piste:type=downhill]", "[piste:difficulty=intermediate]"], "")
e2 = CssElement("line", 14, ["[piste:type=downhill]"], "")

print(e1.can_adopt(e1))
print(e1.can_adopt(e2))
print(e2.can_adopt(e1))
# class CssBlock:
# def __init__(self, element, styles):
# self.element = element
# self.styles = styles
Loading