From 659693be3da21072f32698c67566dff538ed44bc Mon Sep 17 00:00:00 2001 From: drscannellrh Date: Sat, 12 Apr 2014 14:48:34 -0400 Subject: [PATCH 1/4] create CssStructure for common methods --- src/css_parser/css_structure.py | 16 +++++++++ src/css_parser/rule.py | 34 ++++++++++++++----- src/css_parser/stylesheet.py | 16 ++------- .../test_rule/test_append_declaration.py | 19 +++++++++-- 4 files changed, 60 insertions(+), 25 deletions(-) create mode 100644 src/css_parser/css_structure.py diff --git a/src/css_parser/css_structure.py b/src/css_parser/css_structure.py new file mode 100644 index 0000000..f2c9d3b --- /dev/null +++ b/src/css_parser/css_structure.py @@ -0,0 +1,16 @@ + +class CssStructure: + + def _insert_tokens_before(self, tokens, token): + i = self.tokens.index(token) + for t in reversed(tokens): + self.tokens.insert(i, t) + + def _insert_tokens_after(self, tokens, token): + if token == self.tokens[-1]: + self.tokens += tokens + else: + i = self.tokens.index(token) + 1 + for t in reversed(tokens): + self.tokens.insert(i, t) + diff --git a/src/css_parser/rule.py b/src/css_parser/rule.py index af44351..dcf9341 100644 --- a/src/css_parser/rule.py +++ b/src/css_parser/rule.py @@ -1,7 +1,8 @@ +import css_structure import re import stylesheet_reader -class Rule: +class Rule(css_structure.CssStructure): def __init__(self, tokens=[]): self.tokens = tokens self.selector_tokens = [] @@ -37,13 +38,6 @@ def get_declarations(self, query=None): return self._get_declarations_by_query(query) return self.declarations - def _get_declarations_by_query(self, query): - matches = [] - for declaration in self.declarations: - if declaration.get_property().strip() == query.strip(): - matches.append(declaration) - return matches - def remove_declaration(self, decl): i = self.declarations.index(decl) self.declarations.pop(i) @@ -51,7 +45,7 @@ def remove_declaration(self, decl): def append_declaration(self, newdecl, existingdecl=None): if existingdecl: - raise Exception('not implemented') + self._insert_decl_after(newdecl, existingdecl) else: lastdecl = self.declarations[-1] lasttoken = lastdecl.get_tokens()[-1] @@ -77,3 +71,25 @@ def to_string(self): def __str__(self): return ''.join([str(t) for t in self.tokens]) + + + # ------------- private ------------- + + def _get_declarations_by_query(self, query): + matches = [] + for declaration in self.declarations: + if declaration.get_property().strip() == query.strip(): + matches.append(declaration) + return matches + + def _insert_decl_after(self, new, existing): + existingtokens = existing.get_tokens() + lasttoken = existingtokens[len(existingtokens) - 1] + self._insert_tokens_after(new.get_tokens(), lasttoken) + i = self.declarations.index(existing) + self.declarations.insert(i, new) + + + + + diff --git a/src/css_parser/stylesheet.py b/src/css_parser/stylesheet.py index 3449966..3583a6c 100644 --- a/src/css_parser/stylesheet.py +++ b/src/css_parser/stylesheet.py @@ -1,8 +1,9 @@ +import css_structure import re import stylesheet_reader import stylesheet_writer -class StyleSheet: +class StyleSheet(css_structure.CssStructure): def __init__(self): self.mediaqueries = [] @@ -95,19 +96,6 @@ def _insert_rule_after(self, newrule, existingrule): i = self.rules.index(existingrule) self.rules.insert(i, newrule) - def _insert_tokens_before(self, tokens, token): - i = self.tokens.index(token) - for t in reversed(tokens): - self.tokens.insert(i, t) - - def _insert_tokens_after(self, tokens, token): - if token == self.tokens[-1]: - self.tokens += tokens - else: - i = self.tokens.index(token) + 1 - for t in reversed(tokens): - self.tokens.insert(i, t) - def _get_rules_by_query(self, query): query = re.sub(r'\s+', ' ', query) matches = [] diff --git a/src/tests/test_rule/test_append_declaration.py b/src/tests/test_rule/test_append_declaration.py index 00a0b87..c0af074 100644 --- a/src/tests/test_rule/test_append_declaration.py +++ b/src/tests/test_rule/test_append_declaration.py @@ -13,12 +13,23 @@ def test_append_declaration(self): ' padding:0;' \ '}', 'to_add': 'color:blue;', + 'existing_index': None, 'expected': 'body{' \ ' margin:0;' \ ' padding:0;color:blue;' \ '}' + }, + {'input': 'body{' \ + ' margin:0;' \ + ' padding:0;' \ + '}', + 'to_add': 'color:blue;', + 'existing_index': 0, + 'expected': 'body{' \ + ' margin:0;color:blue;' \ + ' padding:0;' \ + '}' } - ] for test in tests: @@ -27,7 +38,10 @@ def test_append_declaration(self): def check_append_declaration(self, test): rule = Rule.from_string(test['input']) decl = Declaration.from_string(test['to_add']) - rule.append_declaration(decl) + existing_rule = None + if test['existing_index'] != None: + existing_rule = rule.declarations[test['existing_index']] + rule.append_declaration(decl, existing_rule) observed = rule.to_string() for k in test: print '%s: %s' % (k, test[k]) @@ -38,3 +52,4 @@ def check_append_declaration(self, test): + From 4e7dcf7258606db2218346df48fa4d22d5c37c5d Mon Sep 17 00:00:00 2001 From: drscannellrh Date: Sat, 12 Apr 2014 15:32:42 -0400 Subject: [PATCH 2/4] refactor: push responsibility to superclass --- src/css_parser/css_structure.py | 18 +++++++++++++----- src/css_parser/mediaquery.py | 8 ++++++++ src/css_parser/rule.py | 30 ++++++++++-------------------- src/css_parser/stylesheet.py | 30 ++++++++++-------------------- 4 files changed, 41 insertions(+), 45 deletions(-) diff --git a/src/css_parser/css_structure.py b/src/css_parser/css_structure.py index f2c9d3b..dcf2eef 100644 --- a/src/css_parser/css_structure.py +++ b/src/css_parser/css_structure.py @@ -1,16 +1,24 @@ class CssStructure: - def _insert_tokens_before(self, tokens, token): - i = self.tokens.index(token) + def _insert_tokens_before(self, new, existing): + tokens = new.get_tokens() + firsttoken = existing.get_tokens()[0] + i = self.tokens.index(firsttoken) for t in reversed(tokens): self.tokens.insert(i, t) - def _insert_tokens_after(self, tokens, token): - if token == self.tokens[-1]: + def _insert_tokens_after(self, new, existing): + tokens = new.get_tokens() + existingtokens = existing.get_tokens() + lasttoken = existingtokens[-1] + if lasttoken == self.tokens[-1]: self.tokens += tokens else: - i = self.tokens.index(token) + 1 + i = self.tokens.index(lasttoken) + 1 for t in reversed(tokens): self.tokens.insert(i, t) + def __str__(self): + return ''.join([str(t) for t in self.tokens]) + diff --git a/src/css_parser/mediaquery.py b/src/css_parser/mediaquery.py index 32db0b4..26b767c 100644 --- a/src/css_parser/mediaquery.py +++ b/src/css_parser/mediaquery.py @@ -5,6 +5,14 @@ def __init__(self, starttokens=[], endtokens=[], querytext=''): self.endtokens = endtokens self.querytext = querytext + def get_tokens(self): + """ get all tokens directly associated with media-query + + TODO: This approach leaves me uneasy, as it does not + convey the gap in the middle where rules would be. + """ + return self.get_starttokens() + self.get_endtokens() + def get_starttokens(self): return self.starttokens diff --git a/src/css_parser/rule.py b/src/css_parser/rule.py index dcf9341..5602d92 100644 --- a/src/css_parser/rule.py +++ b/src/css_parser/rule.py @@ -31,7 +31,8 @@ def get_tokens(self): return self.tokens def get_selector(self): - return self.stringify_tokens(self.selector_tokens) + txt = ''.join([str(t) for t in self.selector_tokens]) + return re.sub('\s+', ' ', txt).strip() def get_declarations(self, query=None): if query: @@ -43,25 +44,20 @@ def remove_declaration(self, decl): self.declarations.pop(i) decl.remove() - def append_declaration(self, newdecl, existingdecl=None): - if existingdecl: - self._insert_decl_after(newdecl, existingdecl) + def append_declaration(self, new, existing=None): + if existing: + self._insert_decl_after(new, existing) else: - lastdecl = self.declarations[-1] - lasttoken = lastdecl.get_tokens()[-1] + last = self.declarations[-1] + lasttoken = last.get_tokens()[-1] i = self.tokens.index(lasttoken) + 1 - for t in reversed(newdecl.get_tokens()): + for t in reversed(new.get_tokens()): self.tokens.insert(i, t) - self.declarations.append(newdecl) - + self.declarations.append(new) def get_mediaquery(self): return self.mediaquery - def stringify_tokens(self, tokens): - txt = ''.join([str(t) for t in tokens]) - return re.sub('\s+', ' ', txt).strip() - def to_string(self): tokens = [t for t in self.tokens] if self.mediaquery: @@ -69,10 +65,6 @@ def to_string(self): tokens = m.get_starttokens() + tokens + m.get_endtokens() return ''.join([str(t) for t in tokens]) - def __str__(self): - return ''.join([str(t) for t in self.tokens]) - - # ------------- private ------------- def _get_declarations_by_query(self, query): @@ -83,9 +75,7 @@ def _get_declarations_by_query(self, query): return matches def _insert_decl_after(self, new, existing): - existingtokens = existing.get_tokens() - lasttoken = existingtokens[len(existingtokens) - 1] - self._insert_tokens_after(new.get_tokens(), lasttoken) + self._insert_tokens_after(new, existing) i = self.declarations.index(existing) self.declarations.insert(i, new) diff --git a/src/css_parser/stylesheet.py b/src/css_parser/stylesheet.py index 3583a6c..1589f18 100644 --- a/src/css_parser/stylesheet.py +++ b/src/css_parser/stylesheet.py @@ -68,31 +68,21 @@ def to_string(self): def to_file(self, filepath): stylesheet_writer.StyleSheetWriter.write_filepath(self, filepath) - def __str__(self): - return ''.join([str(t) for t in self.tokens]) - # ------------- private ------------- - def _insert_rule_before(self, newrule, existingrule): - firsttoken = None - if newrule.get_mediaquery() != existingrule.get_mediaquery(): - mediaquery = existingrule.get_mediaquery() - firsttoken = mediaquery.get_starttokens()[0] - else: - firsttoken = existingrule.get_tokens()[0] - self._insert_tokens_before(newrule.get_tokens(), firsttoken) - i = self.rules.index(existingrule) - self.rules.insert(i, newrule) + def _insert_rule_before(self, new, existing): + insertbefore = existing + if new.get_mediaquery() != existing.get_mediaquery(): + insertbefore = existing.get_mediaquery() + self._insert_tokens_before(new, insertbefore) + i = self.rules.index(existing) + self.rules.insert(i, new) def _insert_rule_after(self, newrule, existingrule): - lasttoken = None + insertafter = existingrule if newrule.get_mediaquery() != existingrule.get_mediaquery(): - mediaquery = existingrule.get_mediaquery() - lasttoken = mediaquery.get_endtokens()[-1] - else: - existingruletokens = existingrule.get_tokens() - lasttoken = existingruletokens[len(existingruletokens) - 1] - self._insert_tokens_after(newrule.get_tokens(), lasttoken) + insertafter = existingrule.get_mediaquery() + self._insert_tokens_after(newrule, insertafter) i = self.rules.index(existingrule) self.rules.insert(i, newrule) From bde5d9178e60519b6f5f7b006cd5dd2e6b9b79f6 Mon Sep 17 00:00:00 2001 From: drscannellrh Date: Sat, 12 Apr 2014 15:57:18 -0400 Subject: [PATCH 3/4] add prepend_declaration, fix append_declaration --- src/css_parser/css_structure.py | 12 +++- src/css_parser/rule.py | 33 +++++++++-- .../test_rule/test_append_declaration.py | 11 ++++ .../test_rule/test_prepend_declaration.py | 56 +++++++++++++++++++ 4 files changed, 104 insertions(+), 8 deletions(-) create mode 100644 src/tests/test_rule/test_prepend_declaration.py diff --git a/src/css_parser/css_structure.py b/src/css_parser/css_structure.py index dcf2eef..c061a87 100644 --- a/src/css_parser/css_structure.py +++ b/src/css_parser/css_structure.py @@ -4,7 +4,10 @@ class CssStructure: def _insert_tokens_before(self, new, existing): tokens = new.get_tokens() firsttoken = existing.get_tokens()[0] - i = self.tokens.index(firsttoken) + self._insert_tokens_before_token(tokens, firsttoken) + + def _insert_tokens_before_token(self, tokens, token): + i = self.tokens.index(token) for t in reversed(tokens): self.tokens.insert(i, t) @@ -12,10 +15,13 @@ def _insert_tokens_after(self, new, existing): tokens = new.get_tokens() existingtokens = existing.get_tokens() lasttoken = existingtokens[-1] - if lasttoken == self.tokens[-1]: + self._insert_tokens_after_token(tokens, lasttoken) + + def _insert_tokens_after_token(self, tokens, token): + if token == self.tokens[-1]: self.tokens += tokens else: - i = self.tokens.index(lasttoken) + 1 + i = self.tokens.index(token) + 1 for t in reversed(tokens): self.tokens.insert(i, t) diff --git a/src/css_parser/rule.py b/src/css_parser/rule.py index 5602d92..423f784 100644 --- a/src/css_parser/rule.py +++ b/src/css_parser/rule.py @@ -1,6 +1,7 @@ import css_structure import re import stylesheet_reader +import token class Rule(css_structure.CssStructure): def __init__(self, tokens=[]): @@ -44,16 +45,33 @@ def remove_declaration(self, decl): self.declarations.pop(i) decl.remove() + def prepend_declaration(self, new, existing=None): + if existing: + self._insert_decl_before(new, existing) + else: + blockstart = self._get_block_start() + self._insert_tokens_after_token(new.get_tokens(), blockstart) + self.declarations.insert(0, new) + def append_declaration(self, new, existing=None): if existing: self._insert_decl_after(new, existing) else: - last = self.declarations[-1] - lasttoken = last.get_tokens()[-1] - i = self.tokens.index(lasttoken) + 1 - for t in reversed(new.get_tokens()): - self.tokens.insert(i, t) + blockend = self._get_block_end() + self._insert_tokens_before_token(new.get_tokens(), blockend) self.declarations.append(new) + + def _get_block_start(self): + for t in self.get_tokens(): + if t.get_type() == token.Token.BLOCK_START: + return t + return None + + def _get_block_end(self): + for t in self.get_tokens(): + if t.get_type() == token.Token.BLOCK_END: + return t + return None def get_mediaquery(self): return self.mediaquery @@ -74,6 +92,11 @@ def _get_declarations_by_query(self, query): matches.append(declaration) return matches + def _insert_decl_before(self, new, existing): + self._insert_tokens_before(new, existing) + i = self.declarations.index(existing) + self.declarations.insert(i, new) + def _insert_decl_after(self, new, existing): self._insert_tokens_after(new, existing) i = self.declarations.index(existing) diff --git a/src/tests/test_rule/test_append_declaration.py b/src/tests/test_rule/test_append_declaration.py index c0af074..2e4a150 100644 --- a/src/tests/test_rule/test_append_declaration.py +++ b/src/tests/test_rule/test_append_declaration.py @@ -19,6 +19,17 @@ def test_append_declaration(self): ' padding:0;color:blue;' \ '}' }, + {'input': 'body{\n' \ + ' margin:0;\n' \ + ' padding:0;\n' \ + '}', + 'to_add': 'color:blue;', + 'existing_index': None, + 'expected': 'body{\n' \ + ' margin:0;\n' \ + ' padding:0;\n' \ + 'color:blue;}' + }, {'input': 'body{' \ ' margin:0;' \ ' padding:0;' \ diff --git a/src/tests/test_rule/test_prepend_declaration.py b/src/tests/test_rule/test_prepend_declaration.py new file mode 100644 index 0000000..5d6e606 --- /dev/null +++ b/src/tests/test_rule/test_prepend_declaration.py @@ -0,0 +1,56 @@ +from css_parser.rule import Rule +from css_parser.declaration import Declaration + +class TestCases: + + # ---------------------------------------- + + def test_prepend_declaration(self): + tests = [ + # two declarations, add one + {'input': 'body{' \ + ' margin:0;' \ + ' padding:0;' \ + '}', + 'to_add': 'color:blue;', + 'existing_index': None, + 'expected': 'body{color:blue;' \ + ' margin:0;' \ + ' padding:0;' \ + '}' + }, + {'input': 'body{' \ + ' margin:0;' \ + ' padding:0;' \ + '}', + 'to_add': 'color:blue;', + 'existing_index': 0, + 'expected': 'body{' \ + ' color:blue;margin:0;' \ + ' padding:0;' \ + '}' + } + ] + + for test in tests: + yield self.check_prepend_declaration, test + + def check_prepend_declaration(self, test): + rule = Rule.from_string(test['input']) + decl = Declaration.from_string(test['to_add']) + existing_rule = None + if test['existing_index'] != None: + existing_rule = rule.declarations[test['existing_index']] + rule.prepend_declaration(decl, existing_rule) + observed = rule.to_string() + for k in test: + print '%s: %s' % (k, test[k]) + print 'observed: %s' % (observed) + assert observed == test['expected'] + + # ---------------------------------------- + + + + + From a58c7210d4659b1281d00f7c68a2fd31ba67ac30 Mon Sep 17 00:00:00 2001 From: drscannellrh Date: Sat, 12 Apr 2014 16:09:50 -0400 Subject: [PATCH 4/4] update readme --- readme.markdown | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/readme.markdown b/readme.markdown index c5007bc..aeb20c1 100644 --- a/readme.markdown +++ b/readme.markdown @@ -14,11 +14,11 @@ This is a a CSS modeling utility written in pure Python. 6. [✓] Remove rule 7. [✓] Prepend rule 8. [✓] Append rule -9. [✓] Add rule before existing rule -10. [✓] Add rule after existing rule -11. [ ] Comment out rule -12. [✓] Add rule to media-query -13. [ ] Add rule with new media-query +9. [✓] Add rule before/after existing rule +10. [ ] Comment out rule +11. [✓] Add rule to media-query +12. [ ] Add rule with new media-query +13. [ ] Pretty-Printing ### Rule @@ -28,9 +28,10 @@ This is a a CSS modeling utility written in pure Python. 1. [✓] Get declarations by property 1. [✓] Remove declaration 2. [✓] Append declaration -3. [ ] Prepend declaration -4. [ ] Add declaration before/after existing declaration +3. [✓] Prepend declaration +4. [✓] Add declaration before/after existing declaration 5. [ ] Comment out declaration +6. [ ] Pretty-Printing @@ -145,6 +146,15 @@ rule.remove_declaration(declaration) # append declaration rule.append_declaration(declaration) + +# insert declaration after existing declaration +rule.append_declaration(declaration, existingdeclaration) + +# prepend declaration +rule.append_declaration(declaration) + +# insert declaration before existing declaration +rule.prepend_declaration(declaration, existingdeclaration) ``` ### Declaration ### @@ -155,4 +165,10 @@ from css_parser.rule import Rule # create declaration from string text = 'margin:1em 5% 1em 5%;' declaration = Declaration.from_string(text) + +# get property +prop_string = declaration.get_property() + +# get value +val_string = declaration.get_value() ``` \ No newline at end of file