Skip to content

Commit

Permalink
Added some options to the @include command to enable quoting and mini…
Browse files Browse the repository at this point in the history
…fication. NOTE: embedded content is currently NOT escaped.
  • Loading branch information
nathan-osman committed Mar 20, 2012
1 parent 764b6b1 commit 97abc19
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 17 deletions.
6 changes: 4 additions & 2 deletions juice.json
@@ -1,9 +1,11 @@
{
"options": {
"debug": { "value": false, "help": "prints debug information while running" },
"use-math": { "value": true, "help": "performs some math calculations" }
"use-math": { "value": true, "help": "performs some math calculations" },
"embed-css": { "value": false, "help": "embeds the CSS in the JavaScript source" }
},
"files": [
{ "filename": "src/sample.js", "output": "sample.min.js" }
{ "filename": "src/sample.js", "output": "sample.min.js" },
{ "filename": "src/style.css", "output": "sample.min.css", "condition": "!embed-css" }
]
}
72 changes: 57 additions & 15 deletions juice.py
Expand Up @@ -32,8 +32,9 @@ class FileStream:
TokenCommand = 1
TokenContent = 2

# Precompiled regex that helps find the preprocessor definitions
# Precompiled regexs that helps find the preprocessor definitions and manipulate them
command_regex = compile(r'/\*\s+@(\w+)(?:\s+(.+?))?\s+\*/')
split_regex = compile(r'\s+')

# Initializes the stream
def __init__(self, filename):
Expand All @@ -57,7 +58,10 @@ def next(self):
return token
elif m:
self.pos += m.end()
return { 'type': self.TokenCommand, 'command': m.group(1), 'parameter': m.group(2), }
p = m.group(2)
if p:
p = self.split_regex.split(p)
return { 'type': self.TokenCommand, 'command': m.group(1), 'parameter': p, }
else:
token = { 'type': self.TokenContent, 'content': self.contents[self.pos:], }
self.pos = len(self.contents)
Expand All @@ -69,8 +73,10 @@ class JuiceBuilder:
# Valid commands
commands = ['if', 'else', 'endif', 'include',]

# Precompiled regex for matching command line arguments
arg_regex = compile(r'^--(enable|disable)-(.*)$')
# Precompiled regular expressions
arg_regex = compile(r'^--(enable|disable)-(.*)$')
comment_regex = compile(r'\s*/\*.*?\*/\s*', DOTALL)
ws_regex = compile(r'\s+')

# Initializes the builder
def __init__(self):
Expand Down Expand Up @@ -117,13 +123,34 @@ def _minify_js_file(self, input):
data=params).read())
return json_response['compiledCode']

# Performs basic RegEx based minification of CSS files
def _minify_css_file(self, input):
# Remove comments and unnecessary spaces
output = self.comment_regex.sub('', input)
return self.ws_regex.sub(' ', output)

# Minifies the specified source file
def _minify_file(self, input, filename):
if filename.endswith('.js'):
return self._minify_js_file(input)
elif filename.endswith('.css'):
return self._minify_css_file(input)
else:
return input # Passthru since we don't know the type

# This method combines content tokens in the specified list into a single token
def _combine_tokens(self, tokens):
output = ''
for t in tokens:
output += t['content']
return output

# Utility method that pops tokens off the stack until the specified command token is found
# This method also combines anything it finds along the way
def _pop_until(self, stack, commands):
output = ''
tokens = []
while not (stack[-1]['type'] == FileStream.TokenCommand and stack[-1]['command'] in commands):
output = stack.pop()['content'] + output
stack.append({ 'type': FileStream.TokenContent, 'content': output, })
tokens.insert(0, stack.pop())
stack.append({ 'type': FileStream.TokenContent, 'content': self._combine_tokens(tokens) })

# Tokenizes and parses the specified file using the specified stack
def _parse_file(self, src_file, stack, nest_level):
Expand All @@ -137,7 +164,7 @@ def _parse_file(self, src_file, stack, nest_level):
raise Exception('Command "%s" not recognized.' % cmd)
if cmd == 'endif':
stack.pop() # discard the endif
self._pop_until(stack, ('if', 'else'))
self._pop_until(stack, ('if', 'else',))
true_output = stack.pop()
false_output = None
if stack[-1]['type'] == FileStream.TokenCommand and stack[-1]['command'] == 'else':
Expand All @@ -146,13 +173,27 @@ def _parse_file(self, src_file, stack, nest_level):
false_output = true_output
true_output = stack.pop()
c = stack.pop()
if self._is_option_set(c['parameter']):
if self._is_option_set(c['parameter'][0]):
stack.append(true_output)
elif not false_output == None:
stack.append(false_output)
elif cmd == 'include':
c = stack.pop()
self._parse_file(c['parameter'], stack, nest_level + 1)
inc = stack.pop()
fn = inc['parameter'][0]
minify = 'minify' in inc['parameter'][1:]
quote = 'quote' in inc['parameter'][1:]
# Create a new stack for the included file
new_stack = []
self._parse_file(fn, new_stack, nest_level + 1)
if minify or quote:
c = self._combine_tokens(new_stack)
if minify:
c = self._minify_file(c, fn)
if quote:
c = '"' + c + '"'
stack.append({ 'type': FileStream.TokenContent, 'content': c })
else:
stack.extend(new_stack)

# Builds the specified file
def _build_file(self, src_file):
Expand All @@ -168,7 +209,7 @@ def _build_file(self, src_file):
output += t['content']
print 'Producing "%s".' % output_fn
f = open(output_fn, 'w')
f.write(self._minify_js_file(output))
f.write(self._minify_file(output, output_fn))
f.close()

# Builds the project
Expand All @@ -179,9 +220,10 @@ def build(self):
# Check to see if building this file depends on a condition
if 'condition' in src_file:
c = src_file['condition']
value = self._is_option_set(c)
if c.startswith('!'):
value = not value
value = not self._is_option_set(c[1:])
else:
value = self._is_option_set(c)
if value:
self._build_file(src_file)
else:
Expand Down
7 changes: 7 additions & 0 deletions src/sample.js
Expand Up @@ -10,4 +10,11 @@

/* @endif */

// Juice's @include command is very flexible - it can quote and minify
// the file that is being included prior to actually embedding it:

/* @if embed-css */
var stylesheet = /* @include src/style.css quote minify */;
/* @endif */

alert("All done!");
6 changes: 6 additions & 0 deletions src/style.css
@@ -0,0 +1,6 @@
/* Sample CSS File */

body {
font-family: arial, helvetica, sans;
padding: 10px;
}

0 comments on commit 97abc19

Please sign in to comment.