themekit 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md ADDED
@@ -0,0 +1,112 @@
1
+ # Yardstick ThemeKit
2
+
3
+ A tiny app that makes designing Yardstick Measure themes a whole lot more enjoyable.
4
+
5
+ ## Setup
6
+
7
+ After cloning the repo, just install the gem:
8
+
9
+ $ sudo gem install themekit-[version].gem
10
+
11
+ Then define the environment variable `YS_SYSTEM_CSS`. This tells ThemeKit where on your computer the Yardstick system.css file is located:
12
+
13
+ export YS_SYSTEM_CSS="/absolute/path/to/yardstick/screen.css"
14
+
15
+ _Tip: Add this line to your ~/.profile_
16
+
17
+ ## Creating a Theme
18
+
19
+ Create a new Theme by passing the name (alphanumeric) to ThemeKit:
20
+
21
+ $ themekit my_awesome_theme
22
+
23
+ This will create a new directory, `my_awesome_theme`, which is a fully-functioning skeleton Theme that can be customized.
24
+
25
+ The first thing you should do is edit your theme's name in `info.yml`.
26
+
27
+ The following files are required:
28
+
29
+ * `info.yml` - Defines your Theme's name & available variations.
30
+ * `theme.html` - Theme's markup & template tags.
31
+ * `styles.css` - Theme's stylesheet. This should contain the styles for all variations.
32
+
33
+ ## Running the server
34
+
35
+ To start ThemeKit, just change into your theme directory and run it:
36
+
37
+ $ cd ~/yardstick/themes/my_awesome_theme
38
+ $ themekit
39
+
40
+ Then head to http://localhost:4567/
41
+
42
+ ## Theme Variations
43
+
44
+ To test different **theme variations**, pass in the variations(s) as a space-separated list in the querystring:
45
+
46
+ http://localhost:4567/?green people
47
+
48
+ or if you've got OCD:
49
+
50
+ http://localhost:4567/?green%20people
51
+
52
+ These variations will be tacked onto the `<body>` tag:
53
+
54
+ <body class="green people">
55
+
56
+ If no variation is specified, the value `"default"` will be used. You can use the `body.[variation] [selector]` technique to style elements differently depending on the active variation:
57
+
58
+ body.people { background: url(people.jpg); }
59
+ body.medical { background: url(medical.jpg); }
60
+
61
+ body.green h1 { color: green; }
62
+ body.blue h1 { color: blue; }
63
+
64
+ ## Supported Tags
65
+
66
+ * {{ theme_variation }}
67
+ * {{ title }}
68
+ * {{ page.title }}
69
+ * {% image image\_data\_id [default image path] %}
70
+ * {% text text\_data\_id [default text] %}
71
+ * {% page_meta %}
72
+ * {% menu main %}
73
+ * {% menu account %}
74
+ * {{ page.content }}
75
+ * {{ system_stylesheet_path }}
76
+ * {{ stylesheet_path }}
77
+
78
+
79
+ ## Automatic Screenshots
80
+
81
+ **Requires [PyYaML](http://pyyaml.org/wiki/PyYAML)**
82
+
83
+ ThemeKit can automatically take screenshots of your Theme, in all its variations. For example, if `info.yml` contains:
84
+
85
+ variations:
86
+ colors:
87
+ red: Red
88
+ green: Green
89
+ grey: Grey
90
+ blue: Blue
91
+ industries:
92
+ industrial: Industrial
93
+ medical: Medical
94
+
95
+ ThemeKit will take 8 screenshots (4 colors x 2 industries).
96
+
97
+ ### Setup
98
+
99
+ You must symlink two executables from ThemeKit into your path:
100
+
101
+ $ sudo ln -s ~/yardstick/themekit/bin/webkit2png /usr/bin/webkit2png
102
+ $ sudo ln -s ~/yardstick/themekit/bin/screenshots /usr/bin/screenshots
103
+
104
+ ### Taking Screenshots
105
+
106
+ From within your Theme directory, just type:
107
+
108
+ $ screenshots
109
+
110
+ This will read `info.yml` and determine all the possible variations your theme has and save a screenshot of each. Screenshots are saved to a subdirectory of your theme, aptly named `screenshots`.
111
+
112
+ **Each time you generate screenshots, existing screenshots will be deleted.**
data/Rakefile ADDED
@@ -0,0 +1,17 @@
1
+ begin
2
+ require 'jeweler'
3
+ Jeweler::Tasks.new do |gemspec|
4
+ gemspec.name = "themekit"
5
+ gemspec.summary = "Enjoyable theming for Yardstick Measure."
6
+ gemspec.description = "A tiny app that makes designing Yardstick Measure themes a whole lot more enjoyable."
7
+ gemspec.email = "kyle@yardsticksoftware.com"
8
+ gemspec.homepage = "http://github.com/yardstick/themekit"
9
+ gemspec.authors = ["Yardstick Software"]
10
+ gemspec.has_rdoc = false
11
+ gemspec.executables = ['themekit']
12
+ gemspec.add_dependency('sinatra', '>= 0.9.4')
13
+ end
14
+ rescue LoadError
15
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
16
+ end
17
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.8
data/bin/screenshots ADDED
@@ -0,0 +1,95 @@
1
+ #!/usr/bin/env python
2
+
3
+ import os
4
+ import sys
5
+ import urllib2
6
+ import Image
7
+ import yaml
8
+
9
+ BASE_URL = "http://localhost:4567/"
10
+ YAML_FILE = 'info.yml'
11
+
12
+ class Variation(object):
13
+
14
+ def __init__(self, name, labels):
15
+ self.name = name
16
+ self.labels = list(labels)
17
+
18
+ def __unicode__(self):
19
+ return self.filename
20
+ __repr__ = __unicode__
21
+
22
+ @property
23
+ def filename(self):
24
+ return '_'.join(self.labels)
25
+
26
+ @property
27
+ def url(self):
28
+ return "%s?%s" % (BASE_URL, "%20".join(self.labels))
29
+
30
+
31
+ def product(*args, **kwds):
32
+ "Given a list of iterables, calculate the possible combinations."
33
+ # Based on itertools.product from Python 2.6
34
+ pools = map(tuple, args) * kwds.get('repeat', 1)
35
+ result = [[]]
36
+ for pool in pools:
37
+ result = [x+[y] for x in result for y in pool]
38
+ for prod in result:
39
+ yield tuple(prod)
40
+
41
+
42
+ def load_variations(yaml_path):
43
+ "Loads a list of Variations from the specified yaml file."
44
+ data = open(yaml_path).read()
45
+ parsed = yaml.load(data)
46
+ variation_name = parsed['name'].lower().replace(r' ', '_')
47
+ variation_categories = []
48
+ variations_dict = parsed.get('variations', {})
49
+ for category_name in sorted(variations_dict.keys()):
50
+ variation_categories.append(variations_dict[category_name])
51
+ variations = []
52
+ for combo in product(*[category.keys() for category in variation_categories]):
53
+ variations.append(Variation(variation_name, combo))
54
+ return variations
55
+
56
+
57
+ if __name__ == '__main__':
58
+ if not os.path.exists(YAML_FILE):
59
+ print "\n** Can't find %s. This script must be run from the Theme's root directory.\n" % YAML_FILE
60
+ sys.exit(0)
61
+
62
+ try:
63
+ urllib2.urlopen(BASE_URL)
64
+ except urllib2.URLError, e:
65
+ print "\n** Couldn't connect to %s -- are you sure ThemeKit is running?\n" % BASE_URL
66
+ sys.exit(0)
67
+
68
+ # Determine the directory in which to save the screenshots.
69
+ # If it already exists, clear it's files and remove the directory.
70
+ dest = os.path.join(os.getcwd(), 'screenshots')
71
+ if os.path.isdir(dest):
72
+ print "** Removing existing screenshots..."
73
+ for path in os.listdir(dest):
74
+ os.remove(os.path.join(dest, path))
75
+ os.rmdir(dest)
76
+ os.mkdir(dest)
77
+
78
+ from subprocess import call, PIPE
79
+ variations = load_variations(YAML_FILE)
80
+ print '** Grabbing screenshots for %s variations...' % len(variations)
81
+ count = 1
82
+ for v in variations:
83
+ print "** %s) %s \t(%s)" % (count, " ".join(v.labels), v.url)
84
+ count += 1
85
+ call(['webkit2png', '--filename=%s' % v.filename, '--dir=%s' % dest, '--width=1024', '--height=768', '-F', v.url], stdout=open(os.devnull, 'w'))
86
+
87
+ for img_path in os.listdir(dest):
88
+ full_path = os.path.join(dest, img_path)
89
+ img = Image.open(full_path)
90
+ img = img.crop((0,0,1024,768))
91
+ img.thumbnail((300,300), Image.ANTIALIAS)
92
+ img.save(full_path.replace("-full", ""), "JPEG", quality=95)
93
+ os.remove(full_path)
94
+
95
+ print '** Screenshots have been saved in %s' % dest
data/bin/themekit ADDED
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "rubygems"
4
+ require "themekit"
5
+
6
+ def load_system_stylesheet
7
+ system_stylesheet = ENV["YS_SYSTEM_CSS"]
8
+ if system_stylesheet.nil?
9
+ puts "\n** WHUPS! You need to set the 'YS_SYSTEM_CSS' environment variable."
10
+ puts "This tells ThemeKit where the Yardstick 'system.css' file is located."
11
+ puts "Example: export YS_SYSTEM_CSS=\"~/path/to/system.css\"\n\n"
12
+ Kernel.exit(0)
13
+ end
14
+ system_stylesheet
15
+ end
16
+
17
+ if ARGV.empty?
18
+ # Run the ThemeKit Sinatra app.
19
+ theme_directory = Dir.pwd
20
+ puts "** Starting ThemeKit in #{theme_directory}"
21
+ ThemeKit::Server.run!({
22
+ :host => 'localhost',
23
+ :port => 4567,
24
+ :system_stylesheet => load_system_stylesheet,
25
+ :theme_directory => theme_directory
26
+ })
27
+ else
28
+ # Create a new theme
29
+ theme_name = ARGV.first
30
+ theme = ThemeKit::Theme.new(theme_name, Dir.pwd)
31
+ theme.create
32
+ puts "Created #{theme.name} in #{theme.path}"
33
+ end
data/bin/webkit2png ADDED
@@ -0,0 +1,278 @@
1
+ #!/usr/bin/env python
2
+
3
+ # webkit2png - makes screenshots of webpages
4
+ # http://www.paulhammond.org/webkit2png
5
+
6
+ __version__ = "0.5"
7
+
8
+ # Copyright (c) 2009 Paul Hammond
9
+ #
10
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ # of this software and associated documentation files (the "Software"), to deal
12
+ # in the Software without restriction, including without limitation the rights
13
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ # copies of the Software, and to permit persons to whom the Software is
15
+ # furnished to do so, subject to the following conditions:
16
+ #
17
+ # The above copyright notice and this permission notice shall be included in
18
+ # all copies or substantial portions of the Software.
19
+ #
20
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
26
+ # THE SOFTWARE.
27
+ #
28
+
29
+ import sys
30
+ import optparse
31
+
32
+ try:
33
+ import Foundation
34
+ import WebKit
35
+ import AppKit
36
+ import objc
37
+ except ImportError:
38
+ print "Cannot find pyobjc library files. Are you sure it is installed?"
39
+ sys.exit()
40
+
41
+
42
+
43
+ class AppDelegate (Foundation.NSObject):
44
+ # what happens when the app starts up
45
+ def applicationDidFinishLaunching_(self, aNotification):
46
+ webview = aNotification.object().windows()[0].contentView()
47
+ webview.frameLoadDelegate().getURL(webview)
48
+
49
+ class WebkitLoad (Foundation.NSObject, WebKit.protocols.WebFrameLoadDelegate):
50
+ # what happens if something goes wrong while loading
51
+ def webView_didFailLoadWithError_forFrame_(self,webview,error,frame):
52
+ print " ... something went wrong"
53
+ self.getURL(webview)
54
+ def webView_didFailProvisionalLoadWithError_forFrame_(self,webview,error,frame):
55
+ print " ... something went wrong"
56
+ self.getURL(webview)
57
+
58
+ def makeFilename(self,URL,options):
59
+ # make the filename
60
+ if options.filename:
61
+ filename = options.filename
62
+ elif options.md5:
63
+ try:
64
+ import md5
65
+ except ImportError:
66
+ print "--md5 requires python md5 library"
67
+ AppKit.NSApplication.sharedApplication().terminate_(None)
68
+ filename = md5.new(URL).hexdigest()
69
+ else:
70
+ import re
71
+ filename = re.sub('\W','',URL);
72
+ filename = re.sub('^http','',filename);
73
+ if options.datestamp:
74
+ import time
75
+ now = time.strftime("%Y%m%d")
76
+ filename = now + "-" + filename
77
+ import os
78
+ dir = os.path.abspath(os.path.expanduser(options.dir))
79
+ return os.path.join(dir,filename)
80
+
81
+ def saveImages(self,bitmapdata,filename,options):
82
+ # save the fullsize png
83
+ if options.fullsize:
84
+ bitmapdata.representationUsingType_properties_(AppKit.NSPNGFileType,None).writeToFile_atomically_(filename + "-full.png",objc.YES)
85
+
86
+ if options.thumb or options.clipped:
87
+ # work out how big the thumbnail is
88
+ width = bitmapdata.pixelsWide()
89
+ height = bitmapdata.pixelsHigh()
90
+ thumbWidth = (width * options.scale)
91
+ thumbHeight = (height * options.scale)
92
+
93
+ # make the thumbnails in a scratch image
94
+ scratch = AppKit.NSImage.alloc().initWithSize_(
95
+ Foundation.NSMakeSize(thumbWidth,thumbHeight))
96
+ scratch.lockFocus()
97
+ AppKit.NSGraphicsContext.currentContext().setImageInterpolation_(
98
+ AppKit.NSImageInterpolationHigh)
99
+ thumbRect = Foundation.NSMakeRect(0.0, 0.0, thumbWidth, thumbHeight)
100
+ clipRect = Foundation.NSMakeRect(0.0,
101
+ thumbHeight-options.clipheight,
102
+ options.clipwidth, options.clipheight)
103
+ bitmapdata.drawInRect_(thumbRect)
104
+ thumbOutput = AppKit.NSBitmapImageRep.alloc().initWithFocusedViewRect_(thumbRect)
105
+ clipOutput = AppKit.NSBitmapImageRep.alloc().initWithFocusedViewRect_(clipRect)
106
+ scratch.unlockFocus()
107
+
108
+ # save the thumbnails as pngs
109
+ if options.thumb:
110
+ thumbOutput.representationUsingType_properties_(
111
+ AppKit.NSPNGFileType,None
112
+ ).writeToFile_atomically_(filename + "-thumb.png",objc.YES)
113
+ if options.clipped:
114
+ clipOutput.representationUsingType_properties_(
115
+ AppKit.NSPNGFileType,None
116
+ ).writeToFile_atomically_(filename + "-clipped.png",objc.YES)
117
+
118
+ def getURL(self,webview):
119
+ if self.urls:
120
+ if self.urls[0] == '-':
121
+ url = sys.stdin.readline().rstrip()
122
+ if not url: AppKit.NSApplication.sharedApplication().terminate_(None)
123
+ else:
124
+ url = self.urls.pop(0)
125
+ else:
126
+ AppKit.NSApplication.sharedApplication().terminate_(None)
127
+ print "Fetching", url, "..."
128
+ self.resetWebview(webview)
129
+ webview.mainFrame().loadRequest_(Foundation.NSURLRequest.requestWithURL_(Foundation.NSURL.URLWithString_(url)))
130
+ if not webview.mainFrame().provisionalDataSource():
131
+ print " ... not a proper url?"
132
+ self.getURL(webview)
133
+
134
+ def resetWebview(self,webview):
135
+ rect = Foundation.NSMakeRect(0,0,self.options.initWidth,self.options.initHeight)
136
+ webview.window().setContentSize_((self.options.initWidth,self.options.initHeight))
137
+ webview.setFrame_(rect)
138
+
139
+ def resizeWebview(self,view):
140
+ view.window().display()
141
+ view.window().setContentSize_(view.bounds().size)
142
+ view.setFrame_(view.bounds())
143
+
144
+ def captureView(self,view):
145
+ view.lockFocus()
146
+ bitmapdata = AppKit.NSBitmapImageRep.alloc()
147
+ bitmapdata.initWithFocusedViewRect_(view.bounds())
148
+ view.unlockFocus()
149
+ return bitmapdata
150
+
151
+ # what happens when the page has finished loading
152
+ def webView_didFinishLoadForFrame_(self,webview,frame):
153
+ # don't care about subframes
154
+ if (frame == webview.mainFrame()):
155
+ Foundation.NSTimer.scheduledTimerWithTimeInterval_target_selector_userInfo_repeats_( self.options.delay, self, self.doGrab, webview, False)
156
+
157
+ def doGrab(self,timer):
158
+ webview = timer.userInfo()
159
+ view = webview.mainFrame().frameView().documentView()
160
+
161
+ self.resizeWebview(view)
162
+
163
+ URL = webview.mainFrame().dataSource().initialRequest().URL().absoluteString()
164
+ filename = self.makeFilename(URL, self.options)
165
+
166
+ bitmapdata = self.captureView(view)
167
+ self.saveImages(bitmapdata,filename,self.options)
168
+
169
+ print " ... done"
170
+ self.getURL(webview)
171
+
172
+
173
+ def main():
174
+
175
+ # parse the command line
176
+ usage = """%prog [options] [http://example.net/ ...]
177
+
178
+ examples:
179
+ %prog http://google.com/ # screengrab google
180
+ %prog -W 1000 -H 1000 http://google.com/ # bigger screengrab of google
181
+ %prog -T http://google.com/ # just the thumbnail screengrab
182
+ %prog -TF http://google.com/ # just thumbnail and fullsize grab
183
+ %prog -o foo http://google.com/ # save images as "foo-thumb.png" etc
184
+ %prog - # screengrab urls from stdin
185
+ %prog -h | less # full documentation"""
186
+
187
+ cmdparser = optparse.OptionParser(usage,version=("webkit2png "+__version__))
188
+ # TODO: add quiet/verbose options
189
+ cmdparser.add_option("-W", "--width",type="float",default=800.0,
190
+ help="initial (and minimum) width of browser (default: 800)")
191
+ cmdparser.add_option("-H", "--height",type="float",default=600.0,
192
+ help="initial (and minimum) height of browser (default: 600)")
193
+ cmdparser.add_option("--clipwidth",type="float",default=200.0,
194
+ help="width of clipped thumbnail (default: 200)",
195
+ metavar="WIDTH")
196
+ cmdparser.add_option("--clipheight",type="float",default=150.0,
197
+ help="height of clipped thumbnail (default: 150)",
198
+ metavar="HEIGHT")
199
+ cmdparser.add_option("-s", "--scale",type="float",default=0.25,
200
+ help="scale factor for thumbnails (default: 0.25)")
201
+ cmdparser.add_option("-m", "--md5", action="store_true",
202
+ help="use md5 hash for filename (like del.icio.us)")
203
+ cmdparser.add_option("-o", "--filename", type="string",default="",
204
+ metavar="NAME", help="save images as NAME-full.png,NAME-thumb.png etc")
205
+ cmdparser.add_option("-F", "--fullsize", action="store_true",
206
+ help="only create fullsize screenshot")
207
+ cmdparser.add_option("-T", "--thumb", action="store_true",
208
+ help="only create thumbnail sreenshot")
209
+ cmdparser.add_option("-C", "--clipped", action="store_true",
210
+ help="only create clipped thumbnail screenshot")
211
+ cmdparser.add_option("-d", "--datestamp", action="store_true",
212
+ help="include date in filename")
213
+ cmdparser.add_option("-D", "--dir",type="string",default="./",
214
+ help="directory to place images into")
215
+ cmdparser.add_option("--delay",type="float",default=0,
216
+ help="delay between page load finishing and screenshot")
217
+ cmdparser.add_option("--noimages", action="store_true",
218
+ help="don't load images")
219
+ cmdparser.add_option("--debug", action="store_true",
220
+ help=optparse.SUPPRESS_HELP)
221
+ (options, args) = cmdparser.parse_args()
222
+ if len(args) == 0:
223
+ cmdparser.print_usage()
224
+ return
225
+ if options.filename:
226
+ if len(args) != 1 or args[0] == "-":
227
+ print "--filename option requires exactly one url"
228
+ return
229
+ if options.scale == 0:
230
+ cmdparser.error("scale cannot be zero")
231
+ # make sure we're outputing something
232
+ if not (options.fullsize or options.thumb or options.clipped):
233
+ options.fullsize = True
234
+ options.thumb = True
235
+ options.clipped = True
236
+ # work out the initial size of the browser window
237
+ # (this might need to be larger so clipped image is right size)
238
+ options.initWidth = (options.clipwidth / options.scale)
239
+ options.initHeight = (options.clipheight / options.scale)
240
+ if options.width>options.initWidth:
241
+ options.initWidth = options.width
242
+ if options.height>options.initHeight:
243
+ options.initHeight = options.height
244
+
245
+ app = AppKit.NSApplication.sharedApplication()
246
+
247
+ # create an app delegate
248
+ delegate = AppDelegate.alloc().init()
249
+ AppKit.NSApp().setDelegate_(delegate)
250
+
251
+ # create a window
252
+ rect = Foundation.NSMakeRect(0,0,100,100)
253
+ win = AppKit.NSWindow.alloc()
254
+ win.initWithContentRect_styleMask_backing_defer_ (rect,
255
+ AppKit.NSBorderlessWindowMask, 2, 0)
256
+ if options.debug:
257
+ win.orderFrontRegardless()
258
+ # create a webview object
259
+ webview = WebKit.WebView.alloc()
260
+ webview.initWithFrame_(rect)
261
+ # turn off scrolling so the content is actually x wide and not x-15
262
+ webview.mainFrame().frameView().setAllowsScrolling_(objc.NO)
263
+
264
+ webview.setPreferencesIdentifier_('webkit2png')
265
+ webview.preferences().setLoadsImagesAutomatically_(not options.noimages)
266
+
267
+ # add the webview to the window
268
+ win.setContentView_(webview)
269
+
270
+ # create a LoadDelegate
271
+ loaddelegate = WebkitLoad.alloc().init()
272
+ loaddelegate.options = options
273
+ loaddelegate.urls = args
274
+ webview.setFrameLoadDelegate_(loaddelegate)
275
+
276
+ app.run()
277
+
278
+ if __name__ == '__main__' : main()
@@ -0,0 +1,12 @@
1
+ # Give this theme a unique name
2
+ name: Example Theme
3
+
4
+ # Replace these with the variations actually supported by this theme.
5
+ variations:
6
+ colors:
7
+ red: Red
8
+ green: Green
9
+ blue: Blue
10
+ industries:
11
+ it: IT
12
+ people: People
@@ -0,0 +1,32 @@
1
+ body, .themeContent { font-size: 13px; }
2
+
3
+ .container { width: 800px; }
4
+
5
+ #header { background: #222; padding: 30px 0;}
6
+ #header h2 { float: right; font-size: 15px; color: #fff; font-weight: normal; }
7
+
8
+ #nav { background: #444; }
9
+ #nav a { color: #999; font-size: 13px; padding: 0px 15px; line-height: 40px; }
10
+ #nav a:hover,
11
+ #nav li.active a { background: #555; color: #fff; }
12
+ #mainNav { float: left; }
13
+ #loginNav { float: right; }
14
+ #loginNav a { font-size: 11px; }
15
+
16
+ .themeContentContainer { padding-top: 30px; }
17
+
18
+ .themeContent { color: #777; }
19
+
20
+ .themeContent h1,
21
+ .themeContent h2,
22
+ .themeContent h3,
23
+ .themeContent h4,
24
+ .themeContent h5,
25
+ .themeContent th { color: #444; }
26
+ .themeContent th { background: #f4f4f4;}
27
+
28
+ .themeContent h1 { letter-spacing: -1px; margin-bottom: 30px; }
29
+
30
+ .themeContent img { border: 1px solid #ccc; padding: 1px; }
31
+
32
+ #footer .container { font-size: 11px; color: #aaa; text-align: center; border-top: 1px solid #eee; padding: 5px 0 15px 0;}
@@ -0,0 +1,40 @@
1
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
2
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
3
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
4
+ <head>
5
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
6
+ <link href="{{ system_stylesheet_path }}" media="screen" rel="stylesheet" type="text/css" />
7
+ <link rel="stylesheet" type="text/css" href="{{ stylesheet_path }}">
8
+ <title>{{ title }}</title>
9
+ {% page_meta %}
10
+ </head>
11
+ <body class="themeBody {{ theme_variation }}">
12
+
13
+ <div id="header"><div class="container">
14
+ <div id="logo">
15
+ {% image image_data_id, common/default_image.png %}
16
+ </div>
17
+ <h2 id="subheading">{% text text_data_id, Making things more better! %}</h2>
18
+ </div></div>
19
+
20
+ <div id="nav"><div class="container">
21
+ <div id="mainNav" class="horizontal menu">{% menu main %}</div>
22
+ <div id="loginNav" class="horizontal menu">{% menu account %}</div>
23
+ </div></div>
24
+
25
+ <div class="themeContentContainer container">
26
+ <div class="themeContent">
27
+ {% if notice %}
28
+ <div class="messages notice">{{ notice }}</div>
29
+ {% endif %}
30
+ <h1 class="pageTitle">{{ page.title }}</h1>
31
+ {{ page.content }}
32
+ </div>
33
+ </div></div>
34
+
35
+ <div id="footer"><div class="container">
36
+ {% text text_data_id, Copyright 2009 Yardstick Software %}
37
+ </div></div>
38
+
39
+ </body>
40
+ </html>
data/github-test.rb ADDED
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env ruby
2
+ require 'yaml'
3
+
4
+ if ARGV.size < 1
5
+ puts "Usage: github-test.rb my-project.gemspec"
6
+ exit
7
+ end
8
+
9
+ require 'rubygems/specification'
10
+ data = File.read(ARGV[0])
11
+ spec = nil
12
+
13
+ if data !~ %r{!ruby/object:Gem::Specification}
14
+ Thread.new { spec = eval("$SAFE = 3\n#{data}") }.join
15
+ else
16
+ spec = YAML.load(data)
17
+ end
18
+
19
+ spec.validate
20
+
21
+ puts spec
22
+ puts "OK"
@@ -0,0 +1,121 @@
1
+ module ThemeKit
2
+ module Data
3
+
4
+ META_TAGS = <<-EOS
5
+ <meta name="keywords" content="themes, yardstick, server, design" />
6
+ <meta name="description" content="This is the Yardstick Measure Theme Server." />
7
+ EOS
8
+
9
+ MAIN_NAV = <<-EOS
10
+ <ul id="ys-menu_main" class="ys-menu">
11
+ <li class="active ys-menuItem"><a href="/tincidunt-orci-quis-lectus-nullam">Home</a></li>
12
+ <li class="ys-menuItem"><a href="#">Products</a></li>
13
+ <li class="ys-menuItem"><a href="#">Services</a></li>
14
+ <li class="ys-menuItem"><a href="#">Our Company</a></li>
15
+ <li class="ys-menuItem"><a href="#">Contact Us</a></li>
16
+ </ul>
17
+ EOS
18
+
19
+ USER_NAV = <<-EOS
20
+ <ul id="ys-menu_account" class="ys-menu">
21
+ <li class="ys-menuItem"><a href="/login">Login</a></li>
22
+ <li class="ys-menuItem"><a href="/login">Sign up</a></li>
23
+ </ul>
24
+ EOS
25
+
26
+ DUMMY_HTML = <<-EOS
27
+ <!-- START DUMMY CONTENT -->
28
+ <p>Lorem ipsum dolor sit amet, <a href="#">claritas ut aliquam</a> consectetuer magna. Quis per me placerat humanitatis est sed. Usus claritatem habent aliquip te veniam tincidunt adipiscing eu. Blandit minim cum facer nisl in feugiat nihil eleifend. Eua nunc consuetudium non molestie iusto claram eleifend claritas eorum, id.</p>
29
+ <h2>Heading Level 2</h2>
30
+ <p>It's time for some <strong>strong</strong> text and some <em>emphasized</em> text.</p>
31
+ <p>Enim autem littera erat elit ii nihil sequitur molestie dignissim magna. Ad, et, facilisi ex aliquip fiant decima hendrerit habent. Praesent sollemnes tincidunt at. Parum quod, formas imperdiet suscipit te velit. Nunc delenit me est demonstraverunt formas minim non. Eros legunt iriure at lorem notare.</p>
32
+ <p>Here's a table:</p>
33
+ <table>
34
+ <caption>The awesome people of Yardstick</caption>
35
+ <thead>
36
+ <tr>
37
+ <th>Name</th>
38
+ <th>Languages</th>
39
+ <th>Awesome Factor</th>
40
+ </tr>
41
+ </thead>
42
+ <tbody>
43
+ <tr>
44
+ <th>Kyle</th>
45
+ <td>Python</td>
46
+ <td>11</td>
47
+ </tr>
48
+ <tr>
49
+ <th>Paul</th>
50
+ <td>Ruby</td>
51
+ <td>10.5</td>
52
+ </tr>
53
+ <tr>
54
+ <th>Nolan</th>
55
+ <td>Photoshop</td>
56
+ <td>11</td>
57
+ </tr>
58
+ <tr>
59
+ <th>Ben</th>
60
+ <td>ColdFusion</td>
61
+ <td>11</td>
62
+ </tr>
63
+ <tr>
64
+ <th>Don</th>
65
+ <td>ColdFusion</td>
66
+ <td>11</td>
67
+ </tr>
68
+ </tbody>
69
+ </table>
70
+ <p>Here's a quote:</p>
71
+ <blockquote>
72
+ <p>Enim autem littera erat elit ii nihil sequitur molestie dignissim magna. Ad, et, facilisi ex aliquip fiant decima hendrerit habent. Praesent sollemnes tincidunt at. Parum quod, formas imperdiet suscipit te velit. Nunc delenit me est demonstraverunt formas minim non. Eros legunt iriure at lorem notare.</p>
73
+ </blockquote>
74
+ <h3>Heading Level 3</h3>
75
+ <p>Lectores, doming quarta, ad possim cum eua esse. Et quod eu quis id aliquip ii liber. In ex te, sequitur sed me feugiat ipsum molestie elit legentis. Lectorum praesent est eros non lorem. At possim soluta illum per ad. Amet tincidunt cum eua nibh mutationem.</p>
76
+ <h4>Heading Level 4</h4>
77
+ <p>Et, investigationes nostrud, parum eros assum, claritas lius. Facit facer euismod eu id, nulla. Adipiscing, littera ii consectetuer typi in praesent, aliquam. Mirum erat minim eum duis. Nisl ex delenit te, me velit est typi. Non at hendrerit ad soluta nibh qui cum, eua.</p>
78
+ <h5>Heading Level 5</h5>
79
+ <p>Et, investigationes nostrud, parum eros assum, claritas lius. Facit facer euismod eu id, nulla. Adipiscing, littera ii consectetuer typi in praesent, aliquam. Mirum erat minim eum duis. Nisl ex delenit te, me velit est typi. Non at hendrerit ad soluta nibh qui cum, eua.</p>
80
+ <h6>Heading Level 6</h6>
81
+ <p>Et, investigationes nostrud, parum eros assum, claritas lius. Facit facer euismod eu id, nulla. Adipiscing, littera ii consectetuer typi in praesent, aliquam. Mirum erat minim eum duis. Nisl ex delenit te, me velit est typi. Non at hendrerit ad soluta nibh qui cum, eua.</p>
82
+ <ul>
83
+ <li>Milk</li>
84
+ <li>Bread</li>
85
+ <li>Eggs</li>
86
+ <li>Veggies
87
+ <ul>
88
+ <li>Peppers</li>
89
+ <li>Cucumbers</li>
90
+ <li>Radishes</li>
91
+ </ul>
92
+ </li>
93
+ <li>Cookies</li>
94
+ </ul>
95
+ <p>Lectores, doming quarta, ad possim cum eua esse. Et quod eu quis id aliquip ii liber. In ex te, sequitur sed me feugiat ipsum molestie elit legentis. Lectorum praesent est eros non lorem. At possim soluta illum per ad. Amet tincidunt cum eua nibh mutationem.</p>
96
+ <ol>
97
+ <li>Milk</li>
98
+ <li>Bread</li>
99
+ <li>Eggs</li>
100
+ <li>Veggies
101
+ <ul>
102
+ <li>Peppers</li>
103
+ <li>Cucumbers</li>
104
+ <li>Radishes</li>
105
+ </ul>
106
+ </li>
107
+ <li>Cookies</li>
108
+ </ol>
109
+ <p>Lectores, doming quarta, ad possim cum eua esse. Et quod eu quis id aliquip ii liber. In ex te, sequitur sed me feugiat ipsum molestie elit legentis. Lectorum praesent est eros non lorem. At possim soluta illum per ad. Amet tincidunt cum eua nibh mutationem.</p>
110
+ <h2>A block image followed by an inline image</h2>
111
+ <p>Lectores, doming quarta, ad possim cum eua esse. Et quod eu quis id aliquip ii liber. In ex te, sequitur sed me feugiat ipsum molestie elit legentis. Lectorum praesent est eros non lorem. At possim soluta illum per ad. Amet tincidunt cum eua nibh mutationem.</p>
112
+ <p><img alt="none" src="http://farm4.static.flickr.com/3253/2496907243_5efbbab5cd.jpg" /></p>
113
+ <p>Lectores, doming quarta, ad possim cum eua esse. Et quod eu quis id aliquip ii liber. In ex te, sequitur sed me feugiat ipsum molestie elit legentis. Lectorum praesent est eros non lorem. At possim soluta illum per ad. Amet tincidunt cum eua nibh mutationem.</p>
114
+ <p>Lectores, doming quarta, ad possim cum eua esse. Et quod eu quis id aliquip ii liber. In ex te, sequitur sed me feugiat ipsum molestie elit legentis. Lectorum praesent est eros non lorem. At possim soluta illum per ad. Amet tincidunt cum eua nibh mutationem.</p>
115
+ <p><img alt="none" src="http://farm4.static.flickr.com/3253/2496907243_5efbbab5cd_t.jpg" /> This image should be floating, but that's something the user can specify in the WYSIWYG editor, or the theme can define the float. Lectores, doming quarta, ad possim cum eua esse. Et quod eu quis id aliquip ii liber. In ex te, sequitur sed me feugiat ipsum molestie elit legentis. Lectorum praesent est eros non lorem. At possim soluta illum per ad. Amet tincidunt cum eua nibh mutationem.</p>
116
+ <p>Lectores, doming quarta, ad possim cum eua esse. Et quod eu quis id aliquip ii liber. In ex te, sequitur sed me feugiat ipsum molestie elit legentis. Lectorum praesent est eros non lorem. At possim soluta illum per ad. Amet tincidunt cum eua nibh mutationem.</p>
117
+ <!-- END DUMMY CONTENT -->
118
+ EOS
119
+
120
+ end
121
+ end
@@ -0,0 +1,50 @@
1
+ require 'rubygems'
2
+ require 'sinatra/base'
3
+
4
+ module ThemeKit
5
+
6
+ class Server < Sinatra::Base
7
+
8
+ get '/*' do
9
+
10
+ system_stylesheet = options.system_stylesheet
11
+ theme_directory = options.theme_directory
12
+
13
+ # Get the path they're requesting.
14
+ # If the path is empty, ie: "/", default to theme.html
15
+ path = params[:splat][0]
16
+ path = "theme.html" if path.length == 0
17
+
18
+ if path == "system.css"
19
+ file_path = system_stylesheet
20
+ puts "** Rendering system stylesheet: #{file_path}"
21
+ else
22
+ file_path = File.join(theme_directory, path)
23
+ end
24
+
25
+ # Everything *except* theme.html can be statically served.
26
+ return send_file(file_path) unless path == "theme.html"
27
+
28
+ # Because sinatra is lame and doesn't keep GET variables when using
29
+ # a splat in the route.
30
+ begin
31
+ variations = request.url.split("?")[1].gsub(/%20/, ' ')
32
+ rescue
33
+ variations = 'default'
34
+ end
35
+
36
+ puts "** Rendering theme with variations: #{variations}"
37
+
38
+ begin
39
+ html = File.open(file_path).read()
40
+ return ThemeKit::Theme.render(html, {:variations => variations})
41
+ rescue
42
+ missing = File.join(theme_directory, 'theme.html')
43
+ return "<strong>Theme file doesn't exist:</strong> #{missing}"
44
+ end
45
+
46
+ end
47
+
48
+ end
49
+
50
+ end
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/env ruby -wKU
2
+ require 'fileutils'
3
+
4
+ module ThemeKit
5
+
6
+ class Theme
7
+
8
+ attr_reader :name, :path, :example_path
9
+
10
+ def initialize(name, path)
11
+ @name = name
12
+ @path = path
13
+ @example_path = File.join(File.dirname(__FILE__), '../../example_theme')
14
+ end
15
+
16
+ def create
17
+ FileUtils.cp_r @example_path, File.join(@path, @name)
18
+ end
19
+
20
+ def self.render(html, options={})
21
+ data = {
22
+ :variations => 'default',
23
+ :title => "Welcome | CompuGlobal Exams",
24
+ :meta_tags => ThemeKit::Data::META_TAGS,
25
+ :main_nav => ThemeKit::Data::MAIN_NAV,
26
+ :user_nav => ThemeKit::Data::USER_NAV,
27
+ :page_title => "Welcome to my website",
28
+ :page_content => "#{ThemeKit::Data::DUMMY_HTML}<p>Yardstick ThemeKit: <strong>#{Time.now}</strong></p>",
29
+ :system_stylesheet_path => "system.css",
30
+ :stylesheet_path => "styles.css",
31
+ :media_path => "",
32
+ :notice => "This is an example notice.",
33
+ }.merge(options)
34
+ html.gsub!(/\{\{\s*theme_variation\s*\}\}/, data[:variations]) # {{ theme_variation }}
35
+ html.gsub!(/\{\{\s*title\s*\}\}/, data[:title]) # {{ title }}
36
+ html.gsub!(/\{\{\s*page.title\s*\}\}/, data[:page_title]) # {{ page.title }}
37
+ html.gsub!(/\{%\s*image \w.*, (\w.*)\s*%\}/, "<img src=\"#{data[:media_path]}\\1\" />") # {% image ... %}
38
+ html.gsub!(/\{%\s*text \w.*, (\w.*)\s*%\}/, "\\1") # {% text ... %}
39
+ html.gsub!(/\{%\s*page_meta\s*%\}/, data[:meta_tags]) # {% page_meta %}
40
+ html.gsub!(/\{%\s*menu main\s*%\}/, data[:main_nav]) # {% menu main %}
41
+ html.gsub!(/\{%\s*menu account\s*%\}/, data[:user_nav]) # {% menu account %}
42
+ html.gsub!(/\{\{\s*page.content\s*\}\}/, data[:page_content]) # {{ page.content }}
43
+ html.gsub!(/\{\{\s*system_stylesheet_path\s*\}\}/, data[:system_stylesheet_path]) # {{ system_stylesheet_path }}
44
+ html.gsub!(/\{\{\s*stylesheet_path\s*\}\}/, data[:stylesheet_path]) # {{ stylesheet_path }}
45
+
46
+ # Notices
47
+ html.gsub!(/\{%\s*if notice\s*%\}/, '')
48
+ html.gsub!(/\{%\s*endif\s*%\}/, '')
49
+ html.gsub!(/\{\{\s*notice\s*\}\}/, data[:notice])
50
+
51
+ html
52
+ end
53
+
54
+ end
55
+
56
+ end
data/lib/themekit.rb ADDED
@@ -0,0 +1,6 @@
1
+ require 'themekit/server'
2
+ require 'themekit/data'
3
+ require 'themekit/theme'
4
+
5
+ module ThemeKit
6
+ end
data/themekit.gemspec ADDED
@@ -0,0 +1,14 @@
1
+ spec = Gem::Specification.new do |s|
2
+ s.name = "themekit"
3
+ s.summary = "A tiny app that makes designing Yardstick Measure themes a whole lot more enjoyable."
4
+ s.version = "0.1.0"
5
+ s.author = "Yardstick Software"
6
+ s.email = "kyle@yardsticksoftware.com"
7
+ s.homepage = "https://github.com/yardstick/themekit/tree/master"
8
+ s.platform = Gem::Platform::RUBY
9
+ s.files = Dir['**/**']
10
+ s.executables = ['themekit']
11
+ s.has_rdoc = false
12
+ s.add_dependency "sinatra"
13
+ end
14
+
metadata ADDED
@@ -0,0 +1,88 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: themekit
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ - 0
9
+ version: 0.1.0
10
+ platform: ruby
11
+ authors:
12
+ - Yardstick Software
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-02-25 00:00:00 -07:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: sinatra
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ segments:
28
+ - 0
29
+ version: "0"
30
+ type: :runtime
31
+ version_requirements: *id001
32
+ description:
33
+ email: kyle@yardsticksoftware.com
34
+ executables:
35
+ - themekit
36
+ extensions: []
37
+
38
+ extra_rdoc_files: []
39
+
40
+ files:
41
+ - bin/screenshots
42
+ - bin/themekit
43
+ - bin/webkit2png
44
+ - example_theme/info.yml
45
+ - example_theme/styles.css
46
+ - example_theme/theme.html
47
+ - github-test.rb
48
+ - lib/themekit/data.rb
49
+ - lib/themekit/server.rb
50
+ - lib/themekit/theme.rb
51
+ - lib/themekit.rb
52
+ - Rakefile
53
+ - README.md
54
+ - themekit-0.1.0.gem
55
+ - themekit.gemspec
56
+ - VERSION
57
+ has_rdoc: true
58
+ homepage: https://github.com/yardstick/themekit/tree/master
59
+ licenses: []
60
+
61
+ post_install_message:
62
+ rdoc_options: []
63
+
64
+ require_paths:
65
+ - lib
66
+ required_ruby_version: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ segments:
71
+ - 0
72
+ version: "0"
73
+ required_rubygems_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ segments:
78
+ - 0
79
+ version: "0"
80
+ requirements: []
81
+
82
+ rubyforge_project:
83
+ rubygems_version: 1.3.6
84
+ signing_key:
85
+ specification_version: 3
86
+ summary: A tiny app that makes designing Yardstick Measure themes a whole lot more enjoyable.
87
+ test_files: []
88
+