vitae 0.1.3 → 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -2,4 +2,7 @@
2
2
  pkg/*
3
3
  *.gem
4
4
  .bundle
5
- tmp
5
+ tmp
6
+ .autotest
7
+ Gemfile.lock
8
+ .yardoc
@@ -5,7 +5,7 @@ Vitae is to CVs what rubygems is to ruby code.
5
5
  It's also very under development right now, and not very useful yet. Come back soon!
6
6
 
7
7
 
8
- === Usage
8
+ == Usage
9
9
  First install the vitae gem:
10
10
  $: sudo gem i vitae
11
11
 
@@ -20,19 +20,18 @@ Now lets create a new project called my_cv_project, with some named CVs:
20
20
  Great, it's created our two named CVs in the my_cv_project/cvs directory from a basic yaml template. Edit these yaml files with your own info (note: currently data is not actually read from these, that will change soon).
21
21
 
22
22
  Once your done editing, start up the server:
23
- $: cd my_cv_project
24
- $: vitae server
23
+ $: vitae server my_cv_project
25
24
  Serving 2 CVs at http://0.0.0.0:3141/ from my_cv_project
26
25
 
27
26
  Enjoy!
28
27
 
29
- === Future plans
28
+ == Future plans
30
29
  1. All data read from the yaml files should be displayed sensibly.
31
30
  2. Vitae should com bundled with better default templates - CSS, JS and YAML.
32
31
  3. Support for multiple themes.
33
32
  4. The clever stuff
34
33
 
35
- === The clever stuff
34
+ == The clever stuff
36
35
  Much like rubygems or git
37
36
 
38
37
  Push a CV:
@@ -50,13 +49,13 @@ Pull a CV:
50
49
  Theme pulled to themes/darkness
51
50
  Theme pulled to themes/espresso
52
51
 
53
- === Want to help?
52
+ == Want to help?
54
53
  Just the github usual for development - fork, commit, pull request, bonus points for tests.
55
54
  Have a play and report bugs, request features via the issue tracker.
56
55
 
57
56
  Feel free to email me at arthur@gunn.co.nz
58
57
 
59
- === Credits
58
+ == Credits
60
59
  Development by Arthur Gunn.
61
60
 
62
61
  Thanks to Maxim Chernyak and Juriy Zaytsev for CSS used in the default theme.
@@ -1,6 +1,4 @@
1
1
  $:.unshift File.dirname(File.expand_path(__FILE__))
2
- require "vitae/cv"
3
- require "vitae/ordered_hash"
4
2
 
5
3
  module Vitae
6
4
  @@project_root = nil
@@ -13,7 +11,7 @@ module Vitae
13
11
  @@project_root = root
14
12
  end
15
13
 
16
- module OrderedHashExtensions
14
+ module HashExtensions
17
15
  def except(exceptions=[])
18
16
  reject do |k, v|
19
17
  exceptions.include? k
@@ -23,4 +21,7 @@ module Vitae
23
21
 
24
22
  end
25
23
 
26
- Hash.send :include, Vitae::OrderedHashExtensions
24
+ require "vitae/cv"
25
+ require "vitae/ordered_hash"
26
+
27
+ Hash.send :include, Vitae::HashExtensions
@@ -1,4 +1,3 @@
1
- require "rubygems"
2
1
  require "thor"
3
2
 
4
3
  module Vitae
@@ -17,6 +16,8 @@ module Vitae
17
16
  self.destination_root = name
18
17
  puts "creating #{name}..."
19
18
 
19
+ copy_file "config.ru"
20
+
20
21
  if cv_names.size > 0
21
22
  cv_names.each do |cv_name|
22
23
  @name = cv_name
@@ -48,13 +49,15 @@ module Vitae
48
49
 
49
50
  port_set_by_user = ARGV.any? { |a| %w[-p --port].include?( a ) }
50
51
 
52
+
53
+ Vitae::project_root = File.expand_path(project_path, Dir.pwd)
54
+
51
55
  server = Rack::Server.new.tap do |s|
52
- s.options[:config] = File.join(File.dirname(__FILE__), "..", "..", "config.ru")
56
+ s.options[:config] = File.join(Vitae::project_root, "config.ru")
53
57
  s.options[:Port] = 3141 unless port_set_by_user
54
58
  end
55
59
 
56
- require "vitae/server/server"
57
- Vitae::project_root = File.expand_path(project_path, Dir.pwd)
60
+
58
61
 
59
62
  opts = server.options
60
63
  cv_count = CV.all.size
@@ -1,6 +1,8 @@
1
+ require "vitae/delegators"
1
2
  require "yaml"
2
3
 
3
4
  class CV
5
+ extend Vitae::Delegators
4
6
  attr_reader :file_name
5
7
 
6
8
  class << self
@@ -17,16 +19,12 @@ class CV
17
19
  cv.file_name == file_name
18
20
  end
19
21
  end
20
-
21
- def first
22
- all.first
23
- end
24
-
25
- def last
26
- all.last
27
- end
28
22
  end
29
23
 
24
+ class_delegate :first, :last, :size, :to => :all
25
+ delegate :[], :except, :each, :to => :data_hash
26
+
27
+
30
28
  def initialize yaml_file
31
29
  @yaml_file = yaml_file
32
30
  @file_name = File.basename(yaml_file, '.yaml')
@@ -45,20 +43,6 @@ class CV
45
43
  @data_hash ||= YAML::load_file(@yaml_file)
46
44
  end
47
45
 
48
- def [] key
49
- data_hash[key]
50
- end
51
-
52
- def except exceptions=[]
53
- data_hash.except( exceptions )
54
- end
55
-
56
- def each &block
57
- data_hash.each do |k, v|
58
- yield k, v
59
- end
60
- end
61
-
62
46
 
63
47
  def link
64
48
  "/#{file_name}"
@@ -0,0 +1,26 @@
1
+ module Vitae::Delegators
2
+
3
+ def class_delegate *args
4
+ target = args.pop[:to] rescue raise(ArgumentError, "Delegation needs a target. Supply an options hash with a :to key as the last argument (e.g. delegate :hello, :to => :greeter).")
5
+ actions = args
6
+ metaclass = (class << self; self; end)
7
+
8
+ actions.each do |action|
9
+ metaclass.send(:define_method, action) do |*args|
10
+ send(target).send(action, *args)
11
+ end
12
+ end
13
+ end
14
+
15
+ def delegate *args
16
+ target = args.pop[:to] rescue raise(ArgumentError, "Delegation needs a target. Supply an options hash with a :to key as the last argument (e.g. delegate :hello, :to => :greeter).")
17
+ actions = args
18
+
19
+ actions.each do |action|
20
+ define_method action do |*args|
21
+ send(target).send(action, *args)
22
+ end
23
+ end
24
+ end
25
+
26
+ end
@@ -1,20 +1,21 @@
1
1
  require 'yaml'
2
2
 
3
3
  YAML.add_builtin_type("omap") do |type, val|
4
- ActiveSupport::OrderedHash[val.map(&:to_a).map(&:first)]
4
+ Vitae::OrderedHash[val.map(&:to_a).map(&:first)]
5
5
  end
6
6
 
7
- module ActiveSupport
7
+ module Vitae
8
+ # This OrderedHash class was stolen from ActiveSupport.
8
9
  # The order of iteration over hashes in Ruby 1.8 is undefined. For example, you do not know the
9
- # order in which +keys+ will return keys, or +each+ yield pairs. <tt>ActiveSupport::OrderedHash</tt>
10
+ # order in which +keys+ will return keys, or +each+ yield pairs. <tt>Vitae::OrderedHash</tt>
10
11
  # implements a hash that preserves insertion order, as in Ruby 1.9:
11
12
  #
12
- # oh = ActiveSupport::OrderedHash.new
13
+ # oh = Vitae::OrderedHash.new
13
14
  # oh[:a] = 1
14
15
  # oh[:b] = 2
15
16
  # oh.keys # => [:a, :b], this order is guaranteed
16
17
  #
17
- # <tt>ActiveSupport::OrderedHash</tt> is namespaced to prevent conflicts with other implementations.
18
+ # <tt>Vitae::OrderedHash</tt> is namespaced to prevent conflicts with other implementations.
18
19
  class OrderedHash < ::Hash #:nodoc:
19
20
  def to_yaml_type
20
21
  "!tag:yaml.org,2002:omap"
@@ -3,10 +3,12 @@ module Helpers
3
3
  @cv && @cv.theme || "default"
4
4
  end
5
5
 
6
- def include_theme_assets
7
- html = content_tag(:script, nil, :type => "text/javascript", :src => "/#{current_theme}/application.js")
8
- html << "\n"+tag(:link, nil, :type => "text/css", :media => "screen", :rel => "stylesheet", :href => "/#{current_theme}/application.css")
9
- html
6
+ def include_theme_css
7
+ tag(:link, nil, :type => "text/css", :media => "screen", :rel => "stylesheet", :href => "/#{current_theme}/application.css")
8
+ end
9
+
10
+ def include_theme_js
11
+ content_tag(:script, nil, :type => "text/javascript", :src => "/#{current_theme}/application.js")
10
12
  end
11
13
 
12
14
  def link_to(text, *args)
@@ -0,0 +1,185 @@
1
+ module Vitae::Nodes
2
+
3
+ class Node
4
+ include Haml::Helpers
5
+ include Helpers
6
+
7
+ attr_reader :level, :name, :data, :path
8
+
9
+ class << self
10
+ def set(option, value=self, &block)
11
+ value = block if block
12
+ if value.kind_of?(Proc)
13
+ define_method option, &value
14
+ elsif value == self && option.respond_to?(:each)
15
+ option.each { |k,v| set(k, v) }
16
+ else
17
+ set option, Proc.new{value}
18
+ end
19
+ self
20
+ end
21
+
22
+ def types
23
+ {
24
+ "simple_tag_cloud" => SimpleTagCloudNode,
25
+ "tag_cloud" => TagCloudNode,
26
+ "project_list" => ProjectListNode,
27
+ "project" => ProjectNode,
28
+ "base" => BaseNode,
29
+ "standard" => Node
30
+ }
31
+ end
32
+ end
33
+
34
+ def initialize(data, name=nil, level=1, parent_path=[])
35
+ @data = data
36
+ @name = name
37
+ @level = level
38
+ @path = parent_path << name
39
+
40
+ init_haml_helpers
41
+ end
42
+
43
+ def to_s
44
+ capture_haml do
45
+ html
46
+ end
47
+ end
48
+
49
+ def html
50
+ haml_tag "h#{level}", name if name
51
+ show_data
52
+ end
53
+
54
+ def output_data
55
+ data.except(%w[node_type intro extro])
56
+ end
57
+
58
+ def show_data
59
+ case data
60
+ when Hash
61
+ # haml_tag "p", self.class.name, :style => "color:red"
62
+ haml_tag ".intro", data["intro"] if data["intro"]
63
+ show_hash
64
+ haml_tag ".extro", data["extro"] if data["extro"]
65
+ when Array
66
+ haml_concat "Array!"
67
+ else
68
+ haml_concat data
69
+ end
70
+ end
71
+
72
+ def collection_wrapper &block
73
+ return block.call unless collection_tag
74
+ haml_tag collection_tag, &block
75
+ end
76
+
77
+ def node_wrapper &block
78
+ return block.call unless node_tag
79
+ haml_tag node_tag, &block
80
+ end
81
+
82
+ set :collection_tag => "ul", :node_tag => "li"
83
+
84
+ def show_hash
85
+ collection_wrapper do
86
+ output_data.each do |key, value|
87
+ node_class = child_node_class(key, value)
88
+ node_wrapper do
89
+ haml_concat node_class.new(value, key, level+1, path)
90
+ end
91
+ end
92
+ end
93
+ end
94
+
95
+ def child_node_class_from_yaml(value)
96
+ Node.types[value["node_type"]] if value.respond_to? :[]
97
+ end
98
+
99
+ def child_node_class_from_name(name)
100
+ case name
101
+ when /expertise/i then TagCloudNode
102
+ when /projects/i then ProjectListNode
103
+ else Node
104
+ end
105
+ end
106
+
107
+ def child_node_class(key, value)
108
+ child_node_class_from_yaml(value) || default_child_node_class || child_node_class_from_name(key)
109
+ end
110
+ set :default_child_node_class, nil
111
+
112
+ end
113
+
114
+ # Define all the classes upfront so that they can rely on one another.
115
+ %w[ProjectNode TagCloudNode ProjectListNode BaseNode SimpleTagCloudNode].each do |class_name|
116
+ Vitae::Nodes.const_set(class_name, Class.new(Node))
117
+ end
118
+
119
+ class BaseNode < Node
120
+ set :collection_tag, nil
121
+ set :node_tag, nil
122
+ end
123
+
124
+ class TagCloudNode < Node
125
+ set :collection_tag, "ul#expertise.tags"
126
+
127
+ def show_hash
128
+ collection_wrapper do
129
+ output_data.each do |key, value|
130
+ haml_tag "li.skill", key, :class => value
131
+ end
132
+ end
133
+ end
134
+ end
135
+
136
+ class SimpleTagCloudNode < Node
137
+ set :collection_tag, "ul#expertise.tags"
138
+
139
+ def show_hash
140
+ collection_wrapper do
141
+ output_data.each_with_index do |(key, value), index|
142
+ percent = ((output_data.size-index)/output_data.size.to_f)*70 + 75
143
+ haml_tag "li.skill", key, :style => "font-size:#{percent}%;"
144
+ end
145
+ end
146
+ end
147
+ end
148
+
149
+ class ProjectListNode < Node
150
+ set :collection_tag, "ul.jobs-list.vcalendar"
151
+ set :node_tag, "li.vevent.experience"
152
+ set :default_child_node_class, ProjectNode
153
+ end
154
+
155
+ class ProjectNode < Node
156
+ def html
157
+ link = link_to(name, data["url"]) if data["url"]
158
+ haml_tag "h#{level}" do
159
+ haml_tag "span.location", (link||name)
160
+ if data["role"]
161
+ haml_concat " - "
162
+ haml_tag "span.summary", data["role"]
163
+ end
164
+ show_date_range
165
+ end
166
+
167
+ haml_tag "p.job-description", data["description"]
168
+ # show_data
169
+ end
170
+
171
+ def show_date_range
172
+ return unless data["start"] || data["end"]
173
+
174
+ start_text = data["start"] ? data["start"].strftime("%B %Y") : nil
175
+ end_text = data["end"] ? data["end"].strftime("%B %Y") : "Present"
176
+
177
+ haml_tag "span.dtrange" do
178
+ haml_tag "span.dtstart", start_text, :title => data["start"] if start_text
179
+ haml_concat "-"
180
+ haml_tag "span.dtend", end_text, :title => data["end"] if end_text
181
+ end
182
+ end
183
+ end
184
+
185
+ end
@@ -1,12 +1,17 @@
1
1
  require 'sinatra/base'
2
2
  require 'haml'
3
3
  require "vitae/server/helpers"
4
+ require "vitae/server/node"
4
5
 
5
6
  class Server < Sinatra::Base
6
7
  helpers Helpers
7
8
  set :views, File.dirname(__FILE__) + '/views'
8
9
  set :public, File.join(Vitae::project_root, "themes") rescue ''
9
10
 
11
+ before do
12
+ request.path_info = CV.first.link if CV.size==1
13
+ end
14
+
10
15
  get '/' do
11
16
  @cvs = CV.all
12
17
  haml :index
@@ -17,8 +22,16 @@ class Server < Sinatra::Base
17
22
  end
18
23
 
19
24
  get '/:name' do
25
+ reload_nodes_module if ENV["RACK_ENV"]=="development"
26
+
20
27
  @cv = CV.find( params[:name] )
21
28
  haml :show
22
29
  end
23
30
 
31
+ def reload_nodes_module
32
+ warn "Reloading Nodes module"
33
+ Vitae.send :remove_const, :Nodes if defined? Vitae::Nodes
34
+ load File.join(File.dirname(__FILE__), "node.rb")
35
+ end
36
+
24
37
  end
@@ -2,7 +2,10 @@
2
2
  %html
3
3
  %head
4
4
  %title Vitae #{ @cv && ": "+ @cv.name }
5
- = include_theme_assets
5
+ = include_theme_css
6
6
  %body
7
+ %div
8
+ %button#summarise{:style => "display:none"} Summarise!
7
9
  #content
8
- = yield
10
+ = yield
11
+ = include_theme_js
@@ -1,21 +1,8 @@
1
- = haml :vcard
2
- - @cv.except(%w[vitae_config contact]).each do |category, value|
3
- %h2= category
4
- .category{ :id => category }
5
- - case value
6
- - when String
7
- = value
8
- - when Hash
9
- %ul.jobs-list
10
- - value.each do |subcategory, subvalue|
11
- %li.vevent.experience
12
- %h3= subcategory
13
- %p.job-description= subvalue
14
-
15
- / - value.each do |subcategory, subvalue|
16
- / %h3= subcategory
17
- / .subcategory{ :id => subcategory }
18
- / = subvalue
1
+ = haml :vcard, :locals => { :name => @cv.name, :contact_details => @cv["contact"] } if @cv["contact"]
2
+
3
+ = Vitae::Nodes::BaseNode.new(@cv.except(%w[vitae_config contact]))
4
+
19
5
  %br
20
6
  %br
21
- =link_to "Vitae Home", "/"
7
+ - unless CV.size==1
8
+ = link_to "Vitae Home", "/"
@@ -1,8 +1,8 @@
1
1
  .vcard
2
- %h1#title.fn= @cv.name
2
+ %h1#title.fn= name
3
3
 
4
4
  %ul#quick-links
5
- - @cv["contact"].each do |key, value|
5
+ - contact_details.each do |key, value|
6
6
  - case value
7
7
  - when /^https?:\/\//
8
8
  %li
@@ -0,0 +1,6 @@
1
+ require "vitae"
2
+ require "vitae/server/server"
3
+
4
+ Vitae::project_root = File.expand_path(File.dirname(__FILE__))
5
+
6
+ run Server
@@ -19,4 +19,49 @@
19
19
  - postal-code: 9016
20
20
  - phones: !omap
21
21
  - home: (03) 477 99 44
22
- - mobile: 027 477 7723
22
+ - mobile: 027 477 7723
23
+
24
+ - In Short:
25
+ Live in Dunedin, NZ. Program a bit.
26
+
27
+ - Projects: !omap
28
+ - Vitae:
29
+ start: 2010-12-01
30
+ description: A structured CV publishing system, specifically the one used to generate and very likely serve this CV.
31
+ role: Designer and programmer.
32
+ - Mewzet.com:
33
+ start: 2009-01-01
34
+ url: http://mewzet.com
35
+ description: A web application to teach music theory online.
36
+ role: Co-founder, designer and programmer.
37
+ - GunnMap:
38
+ start: 2007-06-01
39
+ end: 2008-01-01
40
+ url: http://gunn.co.nz/map
41
+ description: A system to help wikipedia contributors very easily create maps of country level demographics. Wikimedia has a <a href="http://commons.wikimedia.org/wiki/Category:Maps_generated_with_GunnMap">list</a> of some of the generated maps.
42
+ role: Designer and programmer
43
+ - AstroTour:
44
+ start: 2006-01-01
45
+ end: 2007-12-30
46
+ url: http://gunn.co.nz/astrotour
47
+ description: An interactive, customisble, scriptable, 3D, in browser simulation of the solar system.
48
+ role: Designer and programmer
49
+
50
+ - Expertise:
51
+ Ruby: prefer strong
52
+ Rails: prefer strong
53
+ Sinatra: prefer strong
54
+ Flash: prefer strong
55
+ Flex: prefer strong
56
+ ActionScript: prefer strong
57
+ Javascript: prefer
58
+ Jquery: prefer
59
+ CSS: weak
60
+ rspec: weak
61
+ html:
62
+ haml: prefer strong
63
+ sass: prefer weak
64
+ mysql: prefer
65
+ RR: prefer
66
+ Test::Unit: prefer strong
67
+ <em>shoulda</em>: prefer
@@ -58,12 +58,15 @@ ul {
58
58
  font-family: "Courier New", Courier, monospace;
59
59
  margin-top: 0;
60
60
  margin-bottom: 2em;
61
+ padding-right: 0.5em;
62
+ padding-left: 1em;
61
63
  }
62
64
 
63
65
  .tags li {
64
66
  display: inline;
65
67
  margin-right: 0.5em;
66
68
  vertical-align: middle;
69
+ white-space: nowrap;
67
70
  }
68
71
 
69
72
  .tags li {
@@ -110,12 +113,17 @@ ul {
110
113
  #quick-links {
111
114
  list-style-type: none;
112
115
  padding-left: 0.5em;
113
- width: 25em;
116
+ width: 21em;
114
117
  margin: 2em auto;
115
118
  color: #666;
116
119
  font-size: 0.9em;
117
120
  }
118
121
 
122
+ ul#quick-links li {
123
+ padding: 0;
124
+ margin-bottom: 0;
125
+ }
126
+
119
127
  #quick-links li a {
120
128
  text-decoration: none;
121
129
  font-size: 1.1em;
@@ -132,7 +140,7 @@ ul {
132
140
 
133
141
  #quick-links li span.adr {
134
142
  display: inline-block;
135
- width: 15em;
143
+ width: 12em;
136
144
  }
137
145
 
138
146
  #quick-links li a:hover, #quick-links li a:focus {
@@ -185,7 +193,7 @@ em {
185
193
  margin-bottom: 2em;
186
194
  }
187
195
 
188
- .jobs-list li {
196
+ li {
189
197
  padding: 0.75em 0.5em 0.75em 1em;
190
198
  margin-bottom: 1em;
191
199
  }
@@ -207,7 +215,7 @@ em {
207
215
 
208
216
 
209
217
 
210
- #summarize { color: #555; position: absolute; top: 1em; right: 1em; width: 10em; }
218
+ #summarise { color: #555; position: absolute; top: 1em; right: 1em; width: 10em; }
211
219
 
212
220
  #projects-contributed-to { margin-left: 1em; }
213
221
  #projects-contributed-to li { display: inline; margin-right: 0.5em; line-height: 1.5; }
@@ -1 +1,148 @@
1
- // I'm javascript!
1
+ // emile.js (c) 2009 Thomas Fuchs
2
+ // Licensed under the terms of the MIT license.
3
+ (function(k,b){var o=document.createElement("div"),l=("backgroundColor borderBottomColor borderBottomWidth borderLeftColor borderLeftWidth borderRightColor borderRightWidth borderSpacing borderTopColor borderTopWidth bottom color fontSize fontWeight height left letterSpacing lineHeight marginBottom marginLeft marginRight marginTop maxHeight maxWidth minHeight minWidth opacity outlineColor outlineOffset outlineWidth paddingBottom paddingLeft paddingRight paddingTop right textIndent top width wordSpacing zIndex").split(" "),m=typeof o.style.opacity=="string",f=typeof o.style.filter=="string",n=document.defaultView,a=n&&typeof n.getComputedStyle!=="undefined",h=/alpha\s*\(\s*opacity\s*=\s*([^\)]+)\)/,c=function(){},i=function(){return"1"};if(m){c=function(q,r){q.style.opacity=r};i=function(q){return q.opacity}}else{if(f){c=function(q,r){var s=q.style;if(q.currentStyle&&!q.currentStyle.hasLayout){s.zoom=1}if(h.test(s.filter)){r=r>=0.9999?"":("alpha(opacity="+(r*100)+")");s.filter=s.filter.replace(h,r)}else{s.filter+=" alpha(opacity="+(r*100)+")"}};i=function(r){var q=r.filter.match(h);return(q?(q[1]/100):1)+""}}}function j(q,r,s){return(q+(r-q)*s).toFixed(3)}function p(r,q,s){return r.substr(q,s||1)}function e(s,x,z){var u=2,t,y,w,A=[],q=[];while(t=3,y=arguments[u-1],u--){if(p(y,0)=="r"){y=y.match(/\d+/g);while(t--){A.push(~~y[t])}}else{if(y.length==4){y="#"+p(y,1)+p(y,1)+p(y,2)+p(y,2)+p(y,3)+p(y,3)}while(t--){A.push(parseInt(p(y,1+t*2,2),16))}}}while(t--){w=~~(A[t+3]+(A[t]-A[t+3])*z);q.push(w<0?0:w>255?255:w)}return"rgb("+q.join(",")+")"}function d(t){var s=parseFloat(t),r=t.replace(/^[\-\d\.]+/,"");return isNaN(s)?{v:r,f:e,u:""}:{v:s,f:j,u:r}}function g(t){var s,u={},r=l.length,q;o.innerHTML='<div style="'+t+'"></div>';s=o.childNodes[0].style;while(r--){if(q=s[l[r]]){u[l[r]]=d(q)}}return u}b[k]=function(w,t,q){w=typeof w=="string"?document.getElementById(w):w;q=q||{};var z=g(t),y=a?n.getComputedStyle(w,null):w.currentStyle,s,A={},u=+new Date,r=q.duration||200,C=u+r,v,B=q.easing||function(D){return(-Math.cos(D*Math.PI)/2)+0.5},x;for(s in z){A[s]=d(s==="opacity"?i(y):y[s])}v=setInterval(function(){var D=+new Date,E=D>C?1:(D-u)/r;for(s in z){x=z[s].f(A[s].v,z[s].v,B(E))+z[s].u;if(s==="opacity"){c(w,x)}else{w.style[s]=x}}if(D>C){clearInterval(v);q.after&&q.after()}},10)}})("emile",this);
4
+
5
+ /* Following JS by Maxim Chernyak and Juriy Zaytsev - https://github.com/maxim/maxim.github.com */
6
+
7
+ (function(){
8
+ function byId(id) {
9
+ return (typeof id === 'string' ? document.getElementById(id) : id);
10
+ }
11
+ function removeEl(el) {
12
+ el.parentNode.removeChild(el);
13
+ }
14
+ function setElStyles(el, styles) {
15
+ for (var name in styles) {
16
+ el.style[name] = styles[name];
17
+ }
18
+ }
19
+ var getComputedStyle = function(el) { return el.style; },
20
+ view = document.defaultView;
21
+ if (view && view.getComputedStyle) {
22
+ getComputedStyle = function(el){ return view.getComputedStyle(el, ''); };
23
+ }
24
+ else if (document.documentElement.currentStyle) {
25
+ getComputedStyle = function(el){ return el.currentStyle; };
26
+ }
27
+ function getOffset(el, direction) {
28
+ var offsetProp = 'offset' + (direction.charAt(0).toUpperCase() + direction.slice(1)),
29
+ offsetValue = el[offsetProp],
30
+ cs;
31
+ while ((el = el.offsetParent) && (cs = getComputedStyle(el))) {
32
+ offsetValue += el[offsetProp];
33
+ }
34
+ return offsetValue;
35
+ }
36
+
37
+ var summaryEls = [ ];
38
+ function absolutizeSummaryEls() {
39
+ var emEls = document.getElementsByTagName('em');
40
+ for (var i = 0, len = emEls.length; i < len; i++) {
41
+ var left = getOffset(emEls[i], 'left'),
42
+ top = getOffset(emEls[i], 'top'),
43
+ clone = emEls[i].cloneNode(true);
44
+ setElStyles(clone, {
45
+ position: 'absolute',
46
+ left: left + 'px',
47
+ top: top + 'px'
48
+ });
49
+ document.body.appendChild(clone);
50
+ summaryEls.push(clone);
51
+ }
52
+ var titleEl = byId('title');
53
+ var titleClone = titleEl.cloneNode(true);
54
+ setElStyles(titleClone, {
55
+ position: 'absolute',
56
+ left: getOffset(titleEl, 'left') + 'px',
57
+ top: getOffset(titleEl, 'top') + 'px',
58
+ width: titleEl.offsetWidth + 'px',
59
+ margin: 0
60
+ });
61
+ titleClone.id = 'title-clone';
62
+ document.body.appendChild(titleClone);
63
+ }
64
+
65
+ function centerElementAt(element, top) {
66
+ element.style.left = '50%';
67
+ element.style.marginLeft = -(element.offsetWidth / 2) + 'px';
68
+ emile(element, 'top:' + top + 'px', { duration: 500 });
69
+ }
70
+
71
+ function showSummaryEls() {
72
+ var top = 130, animationInterval = 100;
73
+ emile('content', 'opacity:0', {
74
+ duration: 500,
75
+ after: function(){
76
+ setTimeout(function() {
77
+ if (summaryEls.length) {
78
+ top += 30;
79
+ centerElementAt(summaryEls.shift(), top);
80
+ setTimeout(arguments.callee, animationInterval);
81
+ }
82
+ else {
83
+ summarizeBtn.disabled = false;
84
+ }
85
+ }, animationInterval);
86
+ }
87
+ });
88
+ }
89
+
90
+ function removeSummaryEls() {
91
+ var ems = [ ];
92
+ for (var i = 0, els = document.body.childNodes, len = els.length; i < len; i++) {
93
+ if (els[i].tagName === 'EM') {
94
+ ems.push(els[i]);
95
+ }
96
+ }
97
+ (function(){
98
+ if (ems.length) {
99
+ var el = ems.shift();
100
+ emile(el, 'left:-500px', {
101
+ after: (function(el){
102
+ return function() {
103
+ removeEl(el);
104
+ };
105
+ })(el)
106
+ });
107
+ setTimeout(arguments.callee, 50);
108
+ }
109
+ })();
110
+ }
111
+
112
+ var summarizeBtn = byId('summarise');
113
+ var isSummarized = false;
114
+
115
+ function hideSummary() {
116
+ summarizeBtn.disabled = true;
117
+ isSummarized = false;
118
+ summarizeBtn.innerHTML = 'Summarise!';
119
+ removeSummaryEls();
120
+ removeEl(byId('title-clone'));
121
+ setTimeout(function(){
122
+ emile('content', 'opacity:1', {
123
+ duration: 500,
124
+ after: function(){
125
+ summarizeBtn.disabled = false
126
+ }
127
+ });
128
+ }, 700);
129
+ }
130
+
131
+ function showSummary() {
132
+ summarizeBtn.disabled = true;
133
+ isSummarized = true;
134
+ summarizeBtn.innerHTML = 'Show content';
135
+ absolutizeSummaryEls();
136
+ showSummaryEls();
137
+ }
138
+
139
+ summarizeBtn.style.display = '';
140
+ summarizeBtn.onclick = function() {
141
+ if (isSummarized) {
142
+ hideSummary();
143
+ }
144
+ else {
145
+ showSummary();
146
+ }
147
+ };
148
+ })();
@@ -1,3 +1,3 @@
1
1
  module Vitae
2
- VERSION = "0.1.3"
2
+ VERSION = "0.1.4"
3
3
  end
@@ -9,6 +9,7 @@ class ActiveServerTest < VitaeServerTestCase
9
9
  assert last_response.ok?
10
10
  assert_matches %w[Katya Derek Arthur Zeena]
11
11
  assert_select "a[href='/katya']", "Katya"
12
+ assert_select "a[href='/zeena']", "Zeena"
12
13
 
13
14
  check_includes_standard_assets
14
15
  end
@@ -20,7 +21,6 @@ class ActiveServerTest < VitaeServerTestCase
20
21
  assert last_response.ok?
21
22
 
22
23
  assert_select "h1", "Sajal Shah"
23
- assert_select "a[href='/']"
24
24
 
25
25
  check_includes_standard_assets
26
26
  end
@@ -35,6 +35,18 @@ class ActiveServerTest < VitaeServerTestCase
35
35
  end
36
36
  end
37
37
 
38
+ test "if there's only one CV it should be at the root" do
39
+ with_project :sals do
40
+ get '/'
41
+ assert last_response.ok?
42
+
43
+ assert_select "h1", "Sajal Shah"
44
+ assert_no_select "a[href='/']"
45
+
46
+ check_includes_standard_assets
47
+ end
48
+ end
49
+
38
50
  def check_includes_standard_assets theme="default"
39
51
  assert_select "script[src='/#{theme}/application.js'][type='text/javascript']"
40
52
  assert_select "link[href='/#{theme}/application.css'][rel='stylesheet'][type='text/css']"
@@ -30,6 +30,7 @@ class VitaeTestCase < Test::Unit::TestCase
30
30
 
31
31
  def vitae_test_dir
32
32
  FileUtils.mkdir_p @@vitae_test_dir
33
+ @@vitae_test_dir
33
34
  end
34
35
 
35
36
  def vitae_executable
@@ -6,9 +6,12 @@ class VitaeExecutableTest < VitaeTestCase
6
6
  clear_test_dir
7
7
  output = vitae_create("my_cvs")
8
8
 
9
- files = %w[my_cvs/cvs/arthur_gunn.yaml
9
+ files = %w[
10
+ my_cvs/config.ru
11
+ my_cvs/cvs/arthur_gunn.yaml
10
12
  my_cvs/themes/default/application.js
11
- my_cvs/themes/default/application.css]
13
+ my_cvs/themes/default/application.css
14
+ ]
12
15
 
13
16
  files.each do |file|
14
17
  assert(vitae_file?( file ), "#{file} should exist in #{vitae_test_dir}")
@@ -10,7 +10,7 @@ Gem::Specification.new do |s|
10
10
  s.email = ["arthur@gunn.co.nz"]
11
11
  s.homepage = "https://github.com/gunn/vitae"
12
12
  s.summary = %q{A structured CV publishing system.}
13
- s.description = %q{Vitae is to CVs what rubygems is to ruby code. Still very under development.}
13
+ s.description = %q{Vitae is to CVs what rubygems is to ruby code. Still very under development. Semi-usable at this point.}
14
14
 
15
15
  s.add_dependency "sinatra", "~>1.1.0"
16
16
  s.add_dependency "haml", "~>3.0.0"
metadata CHANGED
@@ -1,13 +1,12 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: vitae
3
3
  version: !ruby/object:Gem::Version
4
- hash: 29
5
4
  prerelease: false
6
5
  segments:
7
6
  - 0
8
7
  - 1
9
- - 3
10
- version: 0.1.3
8
+ - 4
9
+ version: 0.1.4
11
10
  platform: ruby
12
11
  authors:
13
12
  - Arthur Gunn
@@ -15,7 +14,7 @@ autorequire:
15
14
  bindir: bin
16
15
  cert_chain: []
17
16
 
18
- date: 2010-12-08 00:00:00 +13:00
17
+ date: 2010-12-20 00:00:00 +13:00
19
18
  default_executable:
20
19
  dependencies:
21
20
  - !ruby/object:Gem::Dependency
@@ -26,7 +25,6 @@ dependencies:
26
25
  requirements:
27
26
  - - ~>
28
27
  - !ruby/object:Gem::Version
29
- hash: 19
30
28
  segments:
31
29
  - 1
32
30
  - 1
@@ -42,7 +40,6 @@ dependencies:
42
40
  requirements:
43
41
  - - ~>
44
42
  - !ruby/object:Gem::Version
45
- hash: 7
46
43
  segments:
47
44
  - 3
48
45
  - 0
@@ -58,7 +55,6 @@ dependencies:
58
55
  requirements:
59
56
  - - ~>
60
57
  - !ruby/object:Gem::Version
61
- hash: 39
62
58
  segments:
63
59
  - 0
64
60
  - 14
@@ -74,7 +70,6 @@ dependencies:
74
70
  requirements:
75
71
  - - ~>
76
72
  - !ruby/object:Gem::Version
77
- hash: 7
78
73
  segments:
79
74
  - 1
80
75
  - 4
@@ -82,7 +77,7 @@ dependencies:
82
77
  version: 1.4.0
83
78
  type: :development
84
79
  version_requirements: *id004
85
- description: Vitae is to CVs what rubygems is to ruby code. Still very under development.
80
+ description: Vitae is to CVs what rubygems is to ruby code. Still very under development. Semi-usable at this point.
86
81
  email:
87
82
  - arthur@gunn.co.nz
88
83
  executables:
@@ -92,26 +87,27 @@ extensions: []
92
87
  extra_rdoc_files: []
93
88
 
94
89
  files:
95
- - .autotest
96
90
  - .gitignore
97
91
  - Gemfile
98
- - Gemfile.lock
99
92
  - README.rdoc
100
93
  - Rakefile
101
94
  - bin/vitae
102
- - config.ru
103
95
  - lib/vitae.rb
104
96
  - lib/vitae/cli.rb
105
97
  - lib/vitae/cv.rb
98
+ - lib/vitae/delegators.rb
106
99
  - lib/vitae/ordered_hash.rb
107
100
  - lib/vitae/server/.gitignore
108
101
  - lib/vitae/server/assets/favicon.ico
102
+ - lib/vitae/server/assets/favicon.psd
109
103
  - lib/vitae/server/helpers.rb
104
+ - lib/vitae/server/node.rb
110
105
  - lib/vitae/server/server.rb
111
106
  - lib/vitae/server/views/index.haml
112
107
  - lib/vitae/server/views/layout.haml
113
108
  - lib/vitae/server/views/show.haml
114
109
  - lib/vitae/server/views/vcard.haml
110
+ - lib/vitae/templates/config.ru
115
111
  - lib/vitae/templates/cvs/arthur_gunn.yaml
116
112
  - lib/vitae/templates/cvs/default.yaml.tt
117
113
  - lib/vitae/templates/themes/default/application.css
@@ -139,7 +135,6 @@ required_ruby_version: !ruby/object:Gem::Requirement
139
135
  requirements:
140
136
  - - ">="
141
137
  - !ruby/object:Gem::Version
142
- hash: 3
143
138
  segments:
144
139
  - 0
145
140
  version: "0"
@@ -148,7 +143,6 @@ required_rubygems_version: !ruby/object:Gem::Requirement
148
143
  requirements:
149
144
  - - ">="
150
145
  - !ruby/object:Gem::Version
151
- hash: 3
152
146
  segments:
153
147
  - 0
154
148
  version: "0"
data/.autotest DELETED
@@ -1,10 +0,0 @@
1
- require 'autotest/restart'
2
- require 'autotest/fsevent'
3
- require 'redgreen/autotest'
4
-
5
- Autotest.add_hook(:initialize) do |at|
6
- at.clear_mappings
7
- at.add_mapping(/.*/) do |filename, _|
8
- Dir.glob('test/*.rb')
9
- end
10
- end
@@ -1,29 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- vitae (0.0.2)
5
- haml
6
- sinatra
7
- thor
8
-
9
- GEM
10
- remote: http://rubygems.org/
11
- specs:
12
- haml (3.0.24)
13
- nokogiri (1.4.4)
14
- rack (1.2.1)
15
- sinatra (1.1.0)
16
- rack (~> 1.1)
17
- tilt (~> 1.1)
18
- thor (0.14.6)
19
- tilt (1.1)
20
-
21
- PLATFORMS
22
- ruby
23
-
24
- DEPENDENCIES
25
- haml
26
- nokogiri
27
- sinatra
28
- thor
29
- vitae!
data/config.ru DELETED
@@ -1,4 +0,0 @@
1
- require File.expand_path('../lib/vitae', __FILE__)
2
- require "vitae/server/server"
3
-
4
- run Server