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 +4 -1
- data/README.rdoc +6 -7
- data/lib/vitae.rb +5 -4
- data/lib/vitae/cli.rb +7 -4
- data/lib/vitae/cv.rb +6 -22
- data/lib/vitae/delegators.rb +26 -0
- data/lib/vitae/ordered_hash.rb +6 -5
- data/lib/vitae/server/assets/favicon.psd +0 -0
- data/lib/vitae/server/helpers.rb +6 -4
- data/lib/vitae/server/node.rb +185 -0
- data/lib/vitae/server/server.rb +13 -0
- data/lib/vitae/server/views/layout.haml +5 -2
- data/lib/vitae/server/views/show.haml +6 -19
- data/lib/vitae/server/views/vcard.haml +2 -2
- data/lib/vitae/templates/config.ru +6 -0
- data/lib/vitae/templates/cvs/arthur_gunn.yaml +46 -1
- data/lib/vitae/templates/themes/default/application.css +12 -4
- data/lib/vitae/templates/themes/default/application.js +148 -1
- data/lib/vitae/version.rb +1 -1
- data/test/active_server_test.rb +13 -1
- data/test/test_helper.rb +1 -0
- data/test/vitae_executable_test.rb +5 -2
- data/vitae.gemspec +1 -1
- metadata +8 -14
- data/.autotest +0 -10
- data/Gemfile.lock +0 -29
- data/config.ru +0 -4
data/.gitignore
CHANGED
data/README.rdoc
CHANGED
@@ -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
|
-
|
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
|
-
$:
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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.
|
data/lib/vitae.rb
CHANGED
@@ -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
|
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
|
-
|
24
|
+
require "vitae/cv"
|
25
|
+
require "vitae/ordered_hash"
|
26
|
+
|
27
|
+
Hash.send :include, Vitae::HashExtensions
|
data/lib/vitae/cli.rb
CHANGED
@@ -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(
|
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
|
-
|
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
|
data/lib/vitae/cv.rb
CHANGED
@@ -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
|
data/lib/vitae/ordered_hash.rb
CHANGED
@@ -1,20 +1,21 @@
|
|
1
1
|
require 'yaml'
|
2
2
|
|
3
3
|
YAML.add_builtin_type("omap") do |type, val|
|
4
|
-
|
4
|
+
Vitae::OrderedHash[val.map(&:to_a).map(&:first)]
|
5
5
|
end
|
6
6
|
|
7
|
-
module
|
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>
|
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 =
|
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>
|
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"
|
Binary file
|
data/lib/vitae/server/helpers.rb
CHANGED
@@ -3,10 +3,12 @@ module Helpers
|
|
3
3
|
@cv && @cv.theme || "default"
|
4
4
|
end
|
5
5
|
|
6
|
-
def
|
7
|
-
|
8
|
-
|
9
|
-
|
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
|
data/lib/vitae/server/server.rb
CHANGED
@@ -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
|
@@ -1,21 +1,8 @@
|
|
1
|
-
= haml :vcard
|
2
|
-
|
3
|
-
|
4
|
-
|
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
|
-
|
7
|
+
- unless CV.size==1
|
8
|
+
= link_to "Vitae Home", "/"
|
@@ -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:
|
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:
|
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
|
-
|
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
|
-
#
|
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
|
-
//
|
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
|
+
})();
|
data/lib/vitae/version.rb
CHANGED
data/test/active_server_test.rb
CHANGED
@@ -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']"
|
data/test/test_helper.rb
CHANGED
@@ -6,9 +6,12 @@ class VitaeExecutableTest < VitaeTestCase
|
|
6
6
|
clear_test_dir
|
7
7
|
output = vitae_create("my_cvs")
|
8
8
|
|
9
|
-
files = %w[
|
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}")
|
data/vitae.gemspec
CHANGED
@@ -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
|
-
-
|
10
|
-
version: 0.1.
|
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-
|
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
data/Gemfile.lock
DELETED
@@ -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